#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 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