Virtual_by_fxsaber/Include/fxsaber/Virtual/Order.mqh

661 lines
No EOL
34 KiB
MQL5

#property strict
#include "Order_Base.mqh"
// #define ORDER_COMMISSION 0 // Задание комиссии (OrderCommission() = OrderLots() * (ORDER_COMMISSION)), включая динамический вариант.
// #define ORDER_CURRENCY_DIGITS 2 // Задание Digits для вычисления профита/комиссии/свопа при помещении в историю торгов.
struct ORDER : public ORDER_BASE
{
#ifdef __MQL5__
using ORDER_BASE::operator=; // https://www.mql5.com/ru/forum/492248/page23#comment_57855997
#endif // #ifdef __MQL5__
private:
static int TicketCount;
int GetNewTicket( void ) const
{
return(++ORDER::TicketCount);
}
bool Reset( void )
{
this.StopLoss = -1; // Чтобы при создании ордера сработало условие (this.StopLoss != SL) в Check.
return(true);
}
bool Check( double &Price, double &SL, double &TP, const MqlTick &Tick ) const
{
_VC
bool Res = false;
if (SL && (this.StopLoss != SL))
SL = ::NormalizeDouble(SL, this.Digits);
if (TP && (this.TakeProfit != TP))
TP = ::NormalizeDouble(TP, this.Digits);
switch (this.Type)
{
case ORDER_TYPE_BUY:
Res = ((this.StopLoss != SL) || (this.TakeProfit != TP)) &&
(SL >= 0) && (SL <= Tick.bid) &&
((!TP) || (TP >= Tick.bid));
break;
case ORDER_TYPE_SELL:
Res = ((this.StopLoss != SL) || (this.TakeProfit != TP)) &&
(TP >= 0) && (TP <= Tick.ask) &&
(!SL || (SL >= Tick.ask));
break;
case ORDER_TYPE_BUY_LIMIT:
if (Price != this.OpenPrice)
Price = ::NormalizeDouble(Price, this.Digits);
/*
#ifdef BESTINTERVAL_ONTESTER
if (Price == Tick.ask) // Для применения BestInterval пока нельзя выставлять лимитник по текущей цене.
Price = Tick.ask - _Point;
#endif // BESTINTERVAL_ONTESTER
*/
Res = ((this.OpenPrice != Price) || (this.StopLoss != SL) || (this.TakeProfit != TP)) &&
(Price > 0) && (Price <= Tick.ask) &&
(SL >= 0) && (SL <= Price) &&
(!TP || (TP >= Price));
break;
case ORDER_TYPE_SELL_LIMIT:
if (Price != this.OpenPrice)
Price = ::NormalizeDouble(Price, this.Digits);
/*
#ifdef BESTINTERVAL_ONTESTER
if (Price == Tick.bid) // Для применения BestInterval пока нельзя выставлять лимитник по текущей цене.
Price = Tick.bid + _Point;
#endif // BESTINTERVAL_ONTESTER
*/
Res = ((this.OpenPrice != Price) || (this.StopLoss != SL) || (this.TakeProfit != TP)) &&
(Price >= Tick.bid) &&
(TP >= 0) && (TP <= Price) &&
(!SL || (SL >= Price));
break;
case ORDER_TYPE_BUY_STOP:
if (Price != this.OpenPrice)
Price = ::NormalizeDouble(Price, this.Digits);
Res = ((this.OpenPrice != Price) || (this.StopLoss != SL) || (this.TakeProfit != TP)) &&
(Price >= Tick.ask) &&
(SL >= 0) && (SL <= Price) &&
(!TP || (TP >= Price));
break;
case ORDER_TYPE_SELL_STOP:
if (Price != this.OpenPrice)
Price = ::NormalizeDouble(Price, this.Digits);
Res = ((this.OpenPrice != Price) || (this.StopLoss != SL) || (this.TakeProfit != TP)) &&
(Price > 0) && (Price <= Tick.bid) &&
(TP >= 0) && (TP <= Price) &&
(!SL || (SL >= Price));
;
}
return(Res);
}
void ToClose( const MqlTick &Tick )
{
_VC
if (!this.IsClosed())
{
this.Ticket = -this.Ticket;
this.CloseTimeMsc = Tick.time_msc;
if (this.IsPosition())
this.Profit = this.GetCalcProfit();
}
return;
}
void ToNull( void )
{
_VC
this.Ticket = 0;
// Обнуление нужно для ORDERS::IsChange: this.Balance += this.Orders[i].GetProfit() + this.Orders[i].GetCommission();
this.Profit = 0;
this.Commission = 0;
return;
}
double GetCalcClosePrice( const MqlTick &Tick ) const
{
_VC
double Price = this.ClosePrice;
if (!this.IsClosed())
// Price = !((this.Type ^ 2) % 3) ? Tick.ask : Tick.bid;
switch (this.Type)
{
case ORDER_TYPE_BUY:
case ORDER_TYPE_SELL_LIMIT:
case ORDER_TYPE_SELL_STOP:
Price = Tick.bid;
break;
case ORDER_TYPE_SELL:
case ORDER_TYPE_BUY_LIMIT:
case ORDER_TYPE_BUY_STOP:
Price = Tick.ask;
}
return(Price);
}
double GetCalcProfit( void ) const
{
_VC
// https://www.mql5.com/ru/forum/474452/page19#comment_55099014
return((this.Type <= ORDER_TYPE_SELL) ? (this.ClosePrice - this.OpenPrice) * this.TickValue : 0); // bool Create(...)
// return(this.IsPosition() ? (this.ClosePrice - this.OpenPrice) * this.TickValue : 0); // bool Create(...)
// return((this.Type == ORDER_TYPE_BUY) ? /*this.Lots * */(this.ClosePrice - this.OpenPrice) * this.TickValue
// : ((this.Type == ORDER_TYPE_SELL) ? /*this.Lots * */(this.OpenPrice - this.ClosePrice) * this.TickValue : 0));
}
static void CloseBy( ORDER &Order1, ORDER &Order2, const MqlTick &Tick )
{
Order2.ClosePrice = Order1.OpenPrice;
Order2.CloseReason = Order2.OpenReason;
Order2.ClosePriceRequest = Order1.OpenPriceRequest;
Order2.ToClose(Tick);
Order1.ChangeLots(::NormalizeDouble(Order1.Lots - Order2.Lots, 8));
Order1.Profit = Order1.GetCalcProfit();
#ifdef ORDER_COMMISSION
Order1.Commission = Order1.Lots * (ORDER_COMMISSION);
#endif // ORDER_COMMISSION
return;
}
public:
bool Modify( double Price, double SL, double TP, const MqlTick &Tick )
{
_VC
const bool Res = this.Check(Price, SL, TP, Tick);
if (Res)
{
this.TakeProfit = TP;
this.StopLoss = SL;
if (!this.IsPosition())
{
this.OpenPrice = Price;
this.OpenPriceRequest = this.OpenPrice;
}
}
return(Res);
}
bool IsClosed( void ) const
{
_VC
return(this.Ticket <= 0); // ClosTimeMsc нельзя, т.к. есть Null-ордера.
}
bool IsNotNull( void ) const
{
_VC
return((bool)this.Ticket);
}
bool IsChange( const MqlTick &Tick )
{
_VC
bool Res = false;
const ENUM_ORDER_TYPE PrevType = this.Type;
// if (!this.IsClosed())
if (this.Ticket > 0)
{
switch (this.Type)
{
case ORDER_TYPE_BUY:
if ((Tick.bid <= this.StopLoss) && Tick.bid) // https://www.mql5.com/ru/forum/360210/page41#comment_20741563
{
this.CloseReason = DEAL_REASON_SL;
this.ClosePriceRequest = this.StopLoss;
this.ClosePrice = Tick.bid;
Res = true;
}
else if (this.TakeProfit && (Tick.bid >= this.TakeProfit))
{
this.CloseReason = DEAL_REASON_TP;
this.ClosePriceRequest = this.TakeProfit;
#ifdef VIRTUAL_LIMITS_TP_SLIPPAGE
this.ClosePrice = Tick.bid;
#else // VIRTUAL_LIMITS_TP_SLIPPAGE
this.ClosePrice = this.TakeProfit;
#endif // VIRTUAL_LIMITS_TP_SLIPPAGE
Res = true;
}
break;
case ORDER_TYPE_SELL:
if (this.StopLoss && (Tick.ask >= this.StopLoss))
{
this.CloseReason = DEAL_REASON_SL;
this.ClosePriceRequest = this.StopLoss;
this.ClosePrice = Tick.ask;
Res = true;
}
else if ((Tick.ask <= this.TakeProfit) && Tick.ask) // https://www.mql5.com/ru/forum/360210/page41#comment_20741563
{
this.CloseReason = DEAL_REASON_TP;
this.ClosePriceRequest = this.TakeProfit;
#ifdef VIRTUAL_LIMITS_TP_SLIPPAGE
this.ClosePrice = Tick.ask;
#else // VIRTUAL_LIMITS_TP_SLIPPAGE
this.ClosePrice = this.TakeProfit;
#endif // VIRTUAL_LIMITS_TP_SLIPPAGE
Res = true;
}
break;
case ORDER_TYPE_BUY_LIMIT:
if (Res = (Tick.ask <= this.OpenPrice) && Tick.ask) // https://www.mql5.com/ru/forum/360210/page41#comment_20741563
// if (Res = (::NormalizeDouble(Tick.ask - this.OpenPrice, _Digits) <= 0)) // https://www.mql5.com/ru/forum/222141/page22#comment_14210601
{
this.Type = ORDER_TYPE_BUY;
#ifdef VIRTUAL_LIMITS_TP_SLIPPAGE
this.OpenPrice = Tick.ask;
#endif // VIRTUAL_LIMITS_TP_SLIPPAGE
}
break;
case ORDER_TYPE_SELL_LIMIT:
if (Res = (Tick.bid >= this.OpenPrice))
// if (Res = (::NormalizeDouble(Tick.bid - this.OpenPrice, _Digits) >= 0)) // https://www.mql5.com/ru/forum/222141/page22#comment_14210601
{
this.Type = ORDER_TYPE_SELL;
#ifdef VIRTUAL_LIMITS_TP_SLIPPAGE
this.OpenPrice = Tick.bid;
#endif // VIRTUAL_LIMITS_TP_SLIPPAGE
}
break;
case ORDER_TYPE_BUY_STOP:
if (Res = (Tick.ask >= this.OpenPrice))
{
this.Type = ORDER_TYPE_BUY;
this.OpenPrice = Tick.ask;
}
break;
case ORDER_TYPE_SELL_STOP:
if (Res = (Tick.bid <= this.OpenPrice) && Tick.bid) // https://www.mql5.com/ru/forum/360210/page41#comment_20741563
{
this.Type = ORDER_TYPE_SELL;
this.OpenPrice = Tick.bid;
}
}
if (Res)
{
if (this.Type == PrevType)
this.ToClose(Tick);
else
{
this.OpenTimeMsc = Tick.time_msc;
this.ClosePrice = this.GetCalcClosePrice(Tick);
this.Profit = this.GetCalcProfit();
#ifdef ORDER_COMMISSION
this.Commission = this.Lots * (ORDER_COMMISSION);
#endif // ORDER_COMMISSION
}
}
else
{
this.ClosePrice = this.GetCalcClosePrice(Tick);
this.Profit = this.GetCalcProfit();
}
}
return(Res);
}
bool Delete( const MqlTick &Tick )
{
_VC
const bool Res = !this.IsPosition();
if (Res)
{
this.ClosePrice = this.GetCalcClosePrice(Tick);
this.CloseReason = this.OpenReason;
this.ToClose(Tick);
}
return(Res);
}
bool Create( const SYMBOL_BASE &SymbolBase, const ENUM_ORDER_TYPE inType, const double &dLots,
double inPrice, double dSL, double dTP, const MAGIC_TYPE &iMagicNumber,
const string &sComment, const MqlTick &Tick )
{
this = SymbolBase;
this.Type = inType;
this.TickValue *= (this.Lots = ::NormalizeDouble(dLots, 8));
if ((bool)(this.Type & 1))
this.TickValue = -this.TickValue;
const bool Res = (this.Lots > 0) && this.Reset() && this.Check(inPrice, dSL, dTP, Tick);
if (Res)
{
this.Ticket = this.GetNewTicket();
this.OpenPrice = (this.Type == ORDER_TYPE_BUY) ? Tick.ask : ((this.Type == ORDER_TYPE_SELL) ? Tick.bid : inPrice);
this.OpenTimeMsc = Tick.time_msc;
this.StopLoss = dSL;
this.TakeProfit = dTP;
this.MagicNumber = iMagicNumber;
this.ClosePrice = this.GetCalcClosePrice(Tick);
this.Profit = this.GetCalcProfit();
this.CloseTimeMsc = 0;
this.comment = sComment;
this.OpenReason = DEAL_REASON_EXPERT;
this.CloseReason = DEAL_REASON_CLIENT;
this.OpenPriceRequest = this.OpenPrice;
// this.ClosePriceRequest = this.ClosePrice;
#ifdef ORDER_COMMISSION
if (this.IsPosition())
this.Commission = this.Lots * (ORDER_COMMISSION);
else
#endif // #ifdef ORDER_COMMISSION
this.Commission = 0;
}
return(Res);
}
void Deposit( const double dDeposit, const MqlTick &Tick )
{
this.Ticket = this.GetNewTicket();
this.Type = OP_BALANCE;
this.Lots = 0;
// this.Symbol = NULL;
const string Str = NULL;
this.comment = Str;
this.Symbol = Str;
this.OpenPrice = 0;
this.OpenTimeMsc = Tick.time_msc;
this.StopLoss = 0;
this.TakeProfit = 0;
this.ClosePrice = this.OpenPrice;
// this.Expiration = 0;
this.MagicNumber = 0;
this.Profit = dDeposit;
this.Commission = 0;
this.Swap = 0;
this.OpenReason = DEAL_REASON_CLIENT;
this.CloseReason = this.OpenReason;
this.OpenPriceRequest = this.OpenPrice;
this.ClosePriceRequest = this.OpenPriceRequest;
this.ToClose(Tick);
return;
}
static void CloseByNetting( ORDER &Order1, ORDER &Order2, const MqlTick &Tick )
{
Order1.ClosePrice = Order2.OpenPrice;
Order1.CloseReason = Order2.OpenReason;
Order1.ClosePriceRequest = Order2.OpenPriceRequest;
Order1.ToClose(Tick);
Order2.ToNull();
return;
}
bool CloseBy( ORDER &Order, const MqlTick &Tick, const bool Netting = false )
{
const bool Res = (this.Ticket != Order.Ticket) && (this.Type == ORDER_TYPE_SELL - Order.Type) && !this.IsClosed() && !Order.IsClosed();
if (Res)
{
if (this.Lots == Order.Lots)
{
if (!Netting)
{
this.ClosePrice = Order.OpenPrice;
this.CloseReason = this.OpenReason;
this.ClosePriceRequest = Order.OpenPriceRequest;
this.ToClose(Tick);
Order.ClosePrice = Order.OpenPrice;
Order.CloseReason = Order.OpenReason;
Order.OpenPriceRequest = Order.OpenPrice;
Order.ClosePriceRequest = Order.ClosePrice;
// Order.ChangeLots();
Order.Lots = 0; // Не должен влиять на подсчет оборота.
Order.Commission = 0;
Order.ToClose(Tick);
}
else if (this.OpenTimeMsc <= Order.OpenTimeMsc)
ORDER::CloseByNetting(this, Order, Tick);
else
ORDER::CloseByNetting(Order, this, Tick);
}
else if (this.Lots > Order.Lots)
ORDER::CloseBy(this, Order, Tick);
else
ORDER::CloseBy(Order, this, Tick);
}
return(Res);
}
double Close( double dLots, const MqlTick &Tick )
{
_VC
double Res = 0;
if (this.IsPosition() && !this.IsClosed())
{
dLots = ::NormalizeDouble(dLots, 8);
if ((dLots > 0) && (dLots <= this.Lots))
{
Res = this.Lots - dLots;
if (Res)
this.ChangeLots(dLots);
this.CloseReason = this.OpenReason;
this.ClosePriceRequest = this.ClosePrice;
#ifdef ORDER_COMMISSION
this.Commission = this.Lots * (ORDER_COMMISSION);
#endif // ORDER_COMMISSION
this.ToClose(Tick);
}
}
return(Res);
}
void ChangeLots( const double dLots )
{
this.TickValue *= dLots / this.Lots;
this.Lots = dLots;
return;
}
void SetLots( const double &dLots )
{
this.Ticket = this.GetNewTicket();
this.ChangeLots(::NormalizeDouble(dLots, 8));
#ifdef ORDER_COMMISSION
this.Commission = this.Lots * (ORDER_COMMISSION);
#else // ORDER_COMMISSION
this.Commission = 0;
#endif // ORDER_COMMISSION
this.CloseTimeMsc = 0;
this.Profit = this.GetCalcProfit();
return;
}
void ToHistory( void )
{
_VC
if (this.IsClosed())
{
this.Ticket = -this.Ticket;
this.TickValue = ::MathAbs(this.TickValue) * this.TickSize /
(this.Lots ? this.Lots : 1); // Для CloseBy TickValue учитывает Lots.
// https://www.mql5.com/ru/forum/282062/page70#comment_56002732
// Чтобы было совпадение с реальным окружением. Важно для BestInterval.
this.Profit = ::NormalizeDouble(this.Profit, SYMBOL_BASE::DigitsCurrency);
this.Commission = ::NormalizeDouble(this.Commission, SYMBOL_BASE::DigitsCurrency);
this.Swap = ::NormalizeDouble(this.Swap, SYMBOL_BASE::DigitsCurrency);
}
return;
}
void Stop( const MqlTick &Tick )
{
if (!this.Delete(Tick) && !this.Close(this.Lots, Tick))
{
const string Str = this.comment.Get() + "[end of test]";
this.comment = Str;
}
return;
}
static void SetTicketCount( const int NewValue )
{
if (NewValue > ORDER::TicketCount)
ORDER::TicketCount = NewValue;
return;
}
// Искусственная техническая конструкция по этой причине:
// https://www.mql5.com/ru/forum/464362/page3#comment_52856376
template <typename T>
void IsChange2( bool &Res, T &Orders2, const int &i, int &j, double &Profit2 )
{
Res |= this.IsChange(Orders2.CurrentTick) && !this.IsClosed();
if (this.IsClosed())
{
#ifdef ORDER_COMMISSION
Orders2.Balance += this.GetProfit() + this.GetCommission();
#else // ORDER_COMMISSION
Orders2.Balance += this.GetProfit();
#endif // ORDER_COMMISSION
Orders2.AddHistoryOrder(this);
}
else
{
#ifdef ORDER_COMMISSION
Profit2 += this.GetFullProfit();
#else // ORDER_COMMISSION
Profit2 += this.GetProfit();
#endif // ORDER_COMMISSION
if (i != j)
Orders2.Orders[j] = this;
j++;
}
return;
}
};
static int ORDER::TicketCount = 0; // https://www.mql5.com/ru/forum/1111/page2302#comment_8891928