#ifndef ORDER_MQH #define ORDER_MQH #property strict #include "String.mqh" #define ORDER_COMMISSION (0) //#define ORDER_COMMISSION (-5) #ifdef __MQL5__ #ifndef TICKET_TYPE #define TICKET_TYPE long #endif // TICKET_TYPE #ifndef MAGIC_TYPE #define MAGIC_TYPE long #endif // MAGIC_TYPE #ifndef OP_SELL #define OP_SELL ORDER_TYPE_SELL #endif // OP_SELL #else // __MQL5__ #ifndef TICKET_TYPE #define TICKET_TYPE int #endif // TICKET_TYPE #ifndef MAGIC_TYPE #define MAGIC_TYPE int #endif // MAGIC_TYPE // https://www.mql5.com/ru/docs/constants/tradingconstants/dealproperties#enum_deal_reason enum ENUM_DEAL_REASON { DEAL_REASON_CLIENT, DEAL_REASON_MOBILE, DEAL_REASON_WEB, DEAL_REASON_EXPERT, DEAL_REASON_SL, DEAL_REASON_TP, DEAL_REASON_SO, DEAL_REASON_ROLLOVER, DEAL_REASON_VMARGIN, DEAL_REASON_SPLIT }; #endif // __MQL5__ #ifndef OP_BALANCE #define OP_BALANCE 6 #endif // OP_BALANCE struct ORDER { private: static TICKET_TYPE TicketCount; TICKET_TYPE Ticket; ENUM_ORDER_TYPE Type; double Lots; string oSymbol; string comment; double OpenPrice; long OpenTimeMsc; double StopLoss; double TakeProfit; double ClosePrice; long CloseTimeMsc; // datetime Expiration; long MagicNumber; double Profit; // double Commission; // double Swap; ENUM_DEAL_REASON OpenReason; ENUM_DEAL_REASON CloseReason; double OpenPriceRequest; double ClosePriceRequest; static int PriceToPips( const double Price, const double point ) { return(point ? ((int)(Price / point + 0.1)) : 0); } static int ProfitToPips( const double PriceClose, const double PriceOpen, const double point ) { return(ORDER::PriceToPips(PriceClose, point) - ORDER::PriceToPips(PriceOpen , point)); } static string LengthToString( const datetime Length ) { const int Days = (int)(Length / (24 * 3600)); return(((Days) ? (string)Days + "d ": "") + ::TimeToString(Length, TIME_SECONDS)); } template string NumToString( T Num, const bool CheckType = true, const int digits = 2 ) const // Report.mqh { if (CheckType && (this.OrderType() == OP_SELL)) Num = -Num; return(((Num > 0) ? "+" : "") + ((typename(T) == "double") ? ::DoubleToString(Num, digits) : (string)Num)); } TICKET_TYPE 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 { bool Res = false; if (SL) SL = ::NormalizeDouble(SL, _Digits); if (TP) TP = ::NormalizeDouble(TP, _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 (TimeCurrent() >= D'2019.01.11 11:50:58') DebugBreak(); */ Price = ::NormalizeDouble(Price, _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: Price = ::NormalizeDouble(Price, _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: Price = ::NormalizeDouble(Price, _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: Price = ::NormalizeDouble(Price, _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 ) { if (!this.IsClosed()) { this.Ticket = -this.Ticket; this.CloseTimeMsc = Tick.time_msc; if (this.IsPosition()) this.Profit = this.GetProfit(); } return; } void ToNull( void ) { this.Ticket = 0; this.Profit = 0; return; } double GetClosePrice( const MqlTick &Tick ) const { double Price = this.ClosePrice; if (!this.IsClosed()) 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 GetProfit( void ) const { return((this.Type == ORDER_TYPE_BUY) ? this.Lots * (this.ClosePrice - this.OpenPrice) / _Point : ((this.Type == ORDER_TYPE_SELL) ? this.Lots * (this.OpenPrice - this.ClosePrice) / _Point : 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.Lots = ::NormalizeDouble(Order1.Lots - Order2.Lots, 2); Order1.Profit = Order1.GetProfit(); return; } public: bool IsPosition( void ) const { return(this.Type <= ORDER_TYPE_SELL); } bool Modify( double Price, double SL, double TP, const MqlTick &Tick ) { 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 { return(this.Ticket <= 0); } bool IsNotNull( void ) const { return((bool)this.Ticket); } bool IsChange( const MqlTick &Tick ) { bool Res = false; const ENUM_ORDER_TYPE PrevType = this.Type; if (!this.IsClosed()) { switch (this.Type) { case ORDER_TYPE_BUY: if (Tick.bid <= this.StopLoss) { this.CloseReason = DEAL_REASON_SL; this.ClosePriceRequest = this.StopLoss; this.ClosePrice = Tick.bid; Res = true; } else if (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) { 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)) { 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)) { 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)) { 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.GetClosePrice(Tick); this.Profit = this.GetProfit(); } } else { this.ClosePrice = this.GetClosePrice(Tick); this.Profit = this.GetProfit(); } } return(Res); } bool Delete( const MqlTick &Tick ) { const bool Res = !this.IsPosition(); if (Res) { this.ClosePrice = this.GetClosePrice(Tick); this.CloseReason = this.OpenReason; this.ToClose(Tick); } return(Res); } bool Create( const ENUM_ORDER_TYPE inType, double dLots, double inPrice, double inSL, double inTP, const MAGIC_TYPE &iMagicNumber, const string &sComment, const MqlTick &Tick ) { this.Lots = ::NormalizeDouble(dLots, 2); this.Type = inType; const bool Res = (this.Lots > 0) && this.Reset() && this.Check(inPrice, inSL, inTP, 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 = inSL; this.TakeProfit = inTP; this.MagicNumber = iMagicNumber; this.ClosePrice = this.GetClosePrice(Tick); this.Profit = this.GetProfit(); this.CloseTimeMsc = 0; this.comment = sComment; this.OpenReason = DEAL_REASON_EXPERT; this.CloseReason = DEAL_REASON_CLIENT; this.OpenPriceRequest = this.OpenPrice; // this.ClosePriceRequest = this.ClosePrice; } 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.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.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 ) { double Res = 0; if (this.IsPosition() && !this.IsClosed()) { dLots = ::NormalizeDouble(dLots, 2); if ((dLots > 0) && (dLots <= this.Lots)) { Res = this.Lots - dLots; this.Lots = dLots; this.CloseReason = this.OpenReason; this.ClosePriceRequest = this.ClosePrice; this.ToClose(Tick); } } return(Res); } void SetLots( const double dLots ) { this.Ticket = this.GetNewTicket(); this.Lots = ::NormalizeDouble(dLots, _Digits); this.CloseTimeMsc = 0; this.Profit = this.GetProfit(); return; } #define ORDERFUNCTION(NAME,T) \ T Order##NAME( void ) const \ { \ return((T)this.NAME); \ } ORDERFUNCTION(Ticket, TICKET_TYPE) ORDERFUNCTION(Type, int) ORDERFUNCTION(Lots, double) ORDERFUNCTION(OpenPrice, double) ORDERFUNCTION(OpenTimeMsc, long) ORDERFUNCTION(StopLoss, double) ORDERFUNCTION(TakeProfit, double) ORDERFUNCTION(ClosePrice, double) ORDERFUNCTION(CloseTimeMsc, long) ORDERFUNCTION(MagicNumber, MAGIC_TYPE) ORDERFUNCTION(Profit, double) ORDERFUNCTION(OpenReason, ENUM_DEAL_REASON) ORDERFUNCTION(CloseReason, ENUM_DEAL_REASON) ORDERFUNCTION(OpenPriceRequest, double) #undef ORDERFUNCTION datetime OrderOpenTime( void ) const { return((datetime)(this.OpenTimeMsc / 1000)); } datetime OrderCloseTime( void ) const { return((datetime)(this.CloseTimeMsc / 1000)); } datetime OrderExpiration( void ) const { return(0); } double OrderCommission( void ) const { return(this.Lots * ORDER_COMMISSION); } double OrderSwap( void ) const { return(0); } string OrderSymbol( void ) const { return(_Symbol); } string OrderComment( void ) const { return(this.comment); } void OrderPrint( const datetime timeCurrent ) const { ::Print(this.ToString(timeCurrent)); } double OrderClosePriceRequest( void ) const { return(this.IsClosed() ? this.ClosePriceRequest : this.OrderClosePrice()); } string ToString( const datetime tTimeCurrent ) const { static const string Types[] = {"buy", "sell", "buy limit", "sell limit", "buy stop", "sell stop", "balance"}; return(("#" + (string)this.OrderTicket() + " " + (string)this.OrderOpenTime() + "." + ::IntegerToString(this.OrderOpenTimeMsc() % 1000, 3, '0') + " " + ((this.Type < ::ArraySize(Types)) ? Types[this.Type] : "unknown") + " " + ::DoubleToString(this.OrderLots(), 2) + " " + this.OrderSymbol() + " " + ::DoubleToString(this.OrderOpenPrice(), _Digits) + " " + ::DoubleToString(this.OrderStopLoss(), _Digits) + " " + ::DoubleToString(this.OrderTakeProfit(), _Digits) + " " + ((this.OrderCloseTime() > 0) ? ((string)this.OrderCloseTime() + "." + ::IntegerToString(this.OrderCloseTimeMsc() % 1000, 3, '0') + " ") : NULL) + ::DoubleToString(this.OrderClosePrice(), _Digits) + " " + ::DoubleToString(this.OrderCommission(), 2) + " " + ::DoubleToString(this.OrderSwap(), 2) + " " + ::DoubleToString(this.OrderProfit(), 2) + " " + ((this.OrderComment() == "") ? NULL : (this.OrderComment() + " ")) + (string)this.OrderMagicNumber() + (((this.OrderExpiration() > 0) ? (" expiration " + (string)this.OrderExpiration()): NULL))) + ((this.OrderType() <= OP_SELL) ? ": " + this.NumToString(ORDER::ProfitToPips(this.OrderClosePrice(), this.OrderOpenPrice(), _Point)) + " (" + this.NumToString(ORDER::ProfitToPips(this.OrderClosePriceRequest(), this.OrderOpenPriceRequest(), _Point)) + ")" : NULL) + " - " + ORDER::LengthToString((this.OrderCloseTime() ? this.OrderCloseTime() : tTimeCurrent) - this.OrderOpenTime())); } string ToStringCSV( const string delim = "\t") const { static const string Types[] = {"buy", "sell", "buy limit", "sell limit", "buy stop", "sell stop", "balance"}; return((string)this.OrderTicket() + delim + (string)this.OrderOpenTime() + delim + IntegerToString(this.Type) + delim + ::DoubleToString(this.OrderLots(), 2) + delim + this.OrderSymbol() + delim + ::DoubleToString(this.OrderOpenPrice(), _Digits) + delim + ::DoubleToString(this.OrderStopLoss(), _Digits) + delim + ::DoubleToString(this.OrderTakeProfit(), _Digits) + delim + ((this.OrderCloseTime() > 0) ? ((string)this.OrderCloseTime() + delim) : delim) + ::DoubleToString(this.OrderClosePrice(), _Digits) + delim + ::DoubleToString(this.OrderCommission(), 2) + delim + ::DoubleToString(this.OrderSwap(), 2) + delim + ::DoubleToString(this.OrderProfit(), 2) + delim + ((this.OrderComment() == "") ? delim : (this.OrderComment() + delim)) + (string)this.OrderMagicNumber() + delim + ((this.OrderExpiration() > 0) ? ((string)this.OrderExpiration() + delim): delim) + "\r\n"); } int ToFileCSV(const int fHandle) const { return((int)FileWrite(fHandle,this.OrderTicket(), this.OrderOpenTime(), this.Type, ::DoubleToString(this.OrderLots(), 2), this.OrderSymbol(), ::DoubleToString(this.OrderOpenPrice(), _Digits), ::DoubleToString(this.OrderStopLoss(), _Digits), ::DoubleToString(this.OrderTakeProfit(), _Digits), this.OrderCloseTime(), ::DoubleToString(this.OrderClosePrice(), _Digits), //::DoubleToString(this.OrderCommission(), 2), //::DoubleToString(this.OrderSwap(), 2), //::DoubleToString(this.OrderProfit(), 2), this.OrderComment(), this.OrderMagicNumber())); //this.OrderExpiration())); } bool FromFileCSV(const int fHandle) { this.Ticket = (TICKET_TYPE) FileReadNumber(fHandle); this.OpenTimeMsc = 1000 * FileReadDatetime(fHandle); this.Type = (ENUM_ORDER_TYPE)FileReadNumber(fHandle); this.Lots = FileReadNumber(fHandle); this.oSymbol = FileReadString(fHandle); this.OpenPrice = FileReadNumber(fHandle); this.StopLoss = FileReadNumber(fHandle); this.TakeProfit = FileReadNumber(fHandle); this.CloseTimeMsc = FileReadDatetime(fHandle); this.ClosePrice = FileReadNumber(fHandle); //this.Commission = FileReadNumber(fHandle); //this.Swap = FileReadNumber(fHandle); //this.OrderProfit = FileReadNumber(fHandle); this.comment = FileReadString(fHandle); this.MagicNumber = (MAGIC_TYPE)FileReadNumber(fHandle); //this.Expiration = FileReadDatetime(fHandle); if(!FileIsLineEnding(fHandle)) return false; if (this.TicketCount < this.Ticket) this.TicketCount = this.OrderTicket(); return true; } void Stop( const MqlTick &Tick ) { if (!this.Delete(Tick) && !this.Close(this.Lots, Tick)) { const string Str = "end of test"; this.comment = Str; } return; } }; static TICKET_TYPE ORDER::TicketCount = 0; // https://www.mql5.com/ru/forum/1111/page2302#comment_8891928 #endif //ORDER_MQH