#property strict #include "Symbol_Base.mqh" #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 // https://www.mql5.com/ru/forum/317893/page3#comment_55114508 struct ORDER_BASE : public SYMBOL_BASE { #ifdef __MQL5__ using SYMBOL_BASE::operator=; // https://www.mql5.com/ru/forum/492248/page23#comment_57855997 #endif // #ifdef __MQL5__ private: static int PriceToPips( const double Price, const double point ) { return(point ? ((int)(Price / point + 0.1)) : 0); } static int ProfitToPips( const double dPriceClose, const double dPriceOpen, const double point ) { return(ORDER_BASE::PriceToPips(dPriceClose, point) - ORDER_BASE::PriceToPips(dPriceOpen , point)); } static string LengthToString( const datetime Length ) { const int Days = (int)(Length / (24 * 3600)); return(((Days) ? (string)Days + "d ": NULL) + ::TimeToString(Length, TIME_SECONDS)); } template string NumToString( T Num, const bool CheckType = true, const int digits = 2 ) const // Report.mqh { if (CheckType && (this.GetType() == OP_SELL)) Num = -Num; return(((Num > 0) ? "+" : "") + ((typename(T) == "double") ? ::DoubleToString(Num, digits) : (string)Num)); } // https://www.mql5.com/ru/forum/170952/page208#comment_24667438 #define DAY (24 * 3600) #define WEEK (DAY * 7) // Возвращает количество "четвергов" между двумя датами. static int GetAmountWeekDay( const datetime &Begin, const datetime &End, const int DayWeek = -1 ) { const datetime OffsetTime = (DayWeek - WEDNESDAY) * DAY; return((DayWeek != -1) ? (int)((End - OffsetTime) / WEEK - (Begin - OffsetTime) / WEEK) : 0); } // Возвращает количество рабочих дней между двумя датами. static int GetAmountWorkingDays( const datetime &Begin, const datetime &End ) { const int Res = (int)(End / DAY - Begin / DAY); return(Res ? Res - ORDER_BASE::GetAmountWeekDay(Begin, End, SATURDAY) - ORDER_BASE::GetAmountWeekDay(Begin, End, SUNDAY) : 0); } // Возвращает количество ролловеров (включая тройные) между двумя датами. static int GetRolloverAmounts( datetime TimeOpen, datetime TimeClose, const datetime &RolloverTime, const int &Rollover3Days ) { TimeOpen -= RolloverTime; TimeClose -= RolloverTime; const int Res = ORDER_BASE::GetAmountWorkingDays(TimeOpen, TimeClose); return(Res ? Res + (ORDER_BASE::GetAmountWeekDay(TimeOpen, TimeClose, Rollover3Days) << 1) : 0); } #undef WEEK #undef DAY static string TimeToString( const long time ) { return((string)(datetime)(time / 1000) + "." + ::IntegerToString(time % 1000, 3, '0')); } static int GetDigits( double Price ) { int Res = 0; while ((bool)(Price = ::NormalizeDouble(Price - (int)Price, 8))) { Price *= 10; Res++; } return(Res); } static string DoubleToString( const double Num, const int digits ) { return(::DoubleToString(Num, ::MathMax(digits, ORDER_BASE::GetDigits(Num)))); } static string GetAddType( const int Type, const bool FlagOrder ) { #ifdef __MQL5__ ::ResetLastError(); string Str = FlagOrder ? ::EnumToString((ENUM_ORDER_TYPE)Type) : ::EnumToString((ENUM_DEAL_TYPE)Type); if (!::_LastError && ::StringToLower(Str)) { Str = FlagOrder ? ::StringSubstr(Str, 11) // "order_type_" : (!::StringFind(Str, "deal_type_") ? ::StringSubstr(Str, 10) // "deal_type_" : (!::StringFind(Str, "deal_") ? ::StringSubstr(Str, 5) // "deal_" : Str)); ::StringReplace(Str, "_", " "); } else #else // #ifdef __MQL5__ string #endif // #ifdef __MQL5__ #else Str = "unknown(" + (string)Type + ")"; return(Str); } public: long Ticket; ENUM_ORDER_TYPE Type; double Lots; 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; /* // Отсутствие конструктора позволяет использовать в union для VIRTUAL::Save/Load. // Зачем было обнуление Ticket? Возможно, для SelectOrder.Ticket в Orders.mqh. // Сделан обход (в ORDERS::Set( const double dBalance = DBL_MIN, const bool bNetting = false )) в виде this.SelectOrder.SetTicket(0); // Обнуление Ticket требовалось для такой конструкции: // ORDER_BASE Orders[4]; // OP_BUY, OP_SELL, OP_BUYLIMIT, OP_SELLLIMIT // Orders[OrderType()] = VIRTUAL::GetOrder(); // Обход - https://www.mql5.com/ru/forum/170952/page209#comment_25891642 ORDER_BASE( void ) : Ticket(0) { } */ #define ORDERFUNCTION(NAME,T) \ T Get##NAME( void ) const \ { \ _VC 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) ORDERFUNCTION(Commission, double) ORDERFUNCTION(Swap, double) ORDERFUNCTION(SymbolID, int) #undef ORDERFUNCTION datetime GetOpenTime( void ) const { return((datetime)(this.OpenTimeMsc / 1000)); } datetime GetCloseTime( void ) const { return((datetime)(this.CloseTimeMsc / 1000)); } datetime GetExpiration( void ) const { return(0); } string GetComment( void ) const { return(this.comment.Get()); } void GetPrint( const datetime &timeCurrent ) const { ::Print(this.ToString(timeCurrent)); } double GetClosePriceRequest( void ) const { return(this.CloseTimeMsc ? this.ClosePriceRequest : this.GetClosePrice()); } double GetFullProfit( void ) const { return(this.GetProfit() + this.GetCommission() + this.GetSwap()); } void SetComment( const string &NewComment ) { this.comment = NewComment; return; } double SetSwap( const double &SwapShort, const double &SwapLong, const datetime &RolloverTime, const int &Rollover3Days ) { const double PrevSwap = this.Swap; // См. ORDER::ToHistory(). this.Swap = this.IsPosition() ? ::NormalizeDouble((ORDER_BASE::GetRolloverAmounts(this.GetOpenTime(), this.GetCloseTime(), RolloverTime, Rollover3Days) * (this.Type ? SwapShort : SwapLong) * (this.Lots ? this.Lots : 1) * // Для CloseBy TickValue учитывает Lots. this.TickValue * (this.Point / this.TickSize)), SYMBOL_BASE::DigitsCurrency) : 0; return(this.Swap - PrevSwap); } string ToString( const datetime tTimeCurrent ) const { static const string Types[] = {"buy", "sell", "buy limit", "sell limit", "buy stop", "sell stop", "balance"}; const string Symb = (this.Type >= OP_BALANCE) ? NULL : this.GetSymbol(); const int digits = (this.Type >= OP_BALANCE) ? 0 : (this.CloseTimeMsc ? (int)::SymbolInfoInteger(Symb, SYMBOL_DIGITS) : this.Digits); const double point = (this.Type == OP_BALANCE) ? 0 : (this.CloseTimeMsc ? ::SymbolInfoDouble(Symb, SYMBOL_POINT) : this.Point); const string StrComm = this.GetComment(); return(("#" + (string)this.GetTicket() + " " + ORDER_BASE::TimeToString(this.GetOpenTimeMsc()) + " " + (((this.Type < ::ArraySize(Types)) && ((this.Type <= ORDER_TYPE_SELL_STOP) || !this.OpenPrice)) ? Types[this.Type] : ORDER_BASE::GetAddType(this.Type, this.OpenPrice)) + " " + // (string)this.GetLots() + " " + ORDER_BASE::DoubleToString(this.GetLots(), 2) + " " + ((this.Type >= OP_BALANCE) ? NULL : Symb + " ") + ORDER_BASE::DoubleToString(this.GetOpenPrice(), digits) + " " + ORDER_BASE::DoubleToString(this.GetStopLoss(), digits) + " " + ORDER_BASE::DoubleToString(this.GetTakeProfit(), digits) + " " + ((this.GetCloseTimeMsc() > 0) ? ORDER_BASE::TimeToString(this.GetCloseTimeMsc()) + " " : NULL) + ORDER_BASE::DoubleToString(this.GetClosePrice(), digits) + " " + ORDER_BASE::DoubleToString(this.GetCommission(), SYMBOL_BASE::DigitsCurrency) + " " + ORDER_BASE::DoubleToString(this.GetSwap(), SYMBOL_BASE::DigitsCurrency) + " " + ORDER_BASE::DoubleToString(this.GetProfit(), SYMBOL_BASE::DigitsCurrency) + " " + ((StrComm == "") ? NULL : (StrComm + " ")) + (string)this.GetMagicNumber() + (((this.GetExpiration() > 0) ? (" expiration " + (string)this.GetExpiration()): NULL))) + ((this.IsPosition()) ? ": " + this.NumToString(ORDER_BASE::ProfitToPips(this.GetClosePrice(), this.GetOpenPrice(), point)) + " (" + this.NumToString(ORDER_BASE::ProfitToPips(this.GetClosePriceRequest(), this.GetOpenPriceRequest(), point)) + ")" : NULL) + " - " + ORDER_BASE::LengthToString((this.GetCloseTimeMsc() ? this.GetCloseTime() : ((this.GetOpenTime() > tTimeCurrent) ? this.GetOpenTime() : tTimeCurrent)) - this.GetOpenTime())); } bool IsPosition( void ) const { _VC return(this.Type <= ORDER_TYPE_SELL); } bool IsPending( void ) const { _VC return((this.Type > OP_SELL) && (this.Type < OP_BALANCE)); } bool operator ==( const ENUM_ORDER_TYPE tType ) const { return(this.Type == tType); } bool operator !=( const ENUM_ORDER_TYPE tType ) const { return(this.Type != tType); } bool operator ==( const ORDER_BASE &Order ) const { static const bool IsTester = ::MQLInfoInteger(MQL_TESTER); return((this.GetType() == Order.GetType()) && (this.GetMagicNumber() == Order.GetMagicNumber()) && // (this.GetLots() <= Order.GetLots()) && (this.GetTakeProfit() == Order.GetTakeProfit()) && (this.GetStopLoss() == Order.GetStopLoss()) && (this.IsPosition() || (this.GetOpenPrice() == Order.GetOpenPrice())) && (IsTester || (this.GetSymbol() == Order.GetSymbol()))); } bool operator !=( const ORDER_BASE &Order ) const { return(!(this == Order)); } #define MACROS_OPERATOR(A) \ bool operator A( const long lTime ) const \ { \ return(this.OpenTimeMsc A lTime); \ } MACROS_OPERATOR(>=) MACROS_OPERATOR(<=) MACROS_OPERATOR(!=) MACROS_OPERATOR(<) MACROS_OPERATOR(>) #undef MACROS_OPERATOR double GetMargin(void) const { #ifdef __MQL5__ double result; if (::OrderCalcMargin(this.Type, this.GetSymbol(), this.Lots, this.OpenPrice, result)) return result; #endif // __MQL5__ return 0; } #define MACROS(A) this.##A = ::Order##A(); bool Copy( const bool WithoutHistory = false ) { #ifdef __MQL5__ #ifdef __MT4ORDERS__ static const bool IsTester = ::MQLInfoInteger(MQL_TESTER); if (IsTester) { static bool FirstRun = true; static ORDER_BASE TmpOrder; if (FirstRun) { this.Symbol = _Symbol; // const string Str = NULL; // this.comment = Str; this.comment.Array[0] = 0; FirstRun = false; } this = TmpOrder; } else { // MACROS(Symbol) const string Symb = ::OrderSymbol(); this.Symbol = Symb; } MACROS(CloseTimeMsc) MACROS(OpenPrice) if (WithoutHistory && !this.CloseTimeMsc) // Для исторических ордеров оставляем все без изменений. { if (!IsTester) { // const string Str = NULL; // this.comment = Str; this.comment.Array[0] = 0; // = NULL; } this.Commission = 0; this.OpenPriceRequest = this.OpenPrice; } else if (!IsTester || this.CloseTimeMsc)// В MT4Orders требуется обращение к истории. { const string Str = ::OrderComment(); this.comment = Str; MACROS(Commission) MACROS(OpenPriceRequest) if (IsTester) { // MACROS(Symbol) const string Symb = ::OrderSymbol(); this.Symbol = Symb; } } MACROS(Ticket) // MACROS(Type) this.Type = (ENUM_ORDER_TYPE)::OrderType(); MACROS(Lots) MACROS(OpenTimeMsc) MACROS(StopLoss) MACROS(TakeProfit) MACROS(ClosePrice) MACROS(MagicNumber) MACROS(Profit) MACROS(Swap) // MACROS(Expiration) MACROS(OpenReason) MACROS(CloseReason) MACROS(ClosePriceRequest) MACROS(Profit) #endif // #ifdef __MT4ORDERS__ #else // #ifdef __MQL5__ MACROS(Ticket) // MACROS(Type) this.Type = (ENUM_ORDER_TYPE)::OrderType(); // MACROS(Symbol) const string Symb = ::OrderSymbol(); this.Symbol = Symb; MACROS(Lots) // MACROS(Comment) const string Str = ::OrderComment(); this.comment = Str; MACROS(OpenPrice) this.OpenTimeMsc = ::OrderOpenTime() * 1000; MACROS(StopLoss) MACROS(TakeProfit) MACROS(ClosePrice) this.CloseTimeMsc = ::OrderCloseTime() * 1000; MACROS(MagicNumber) MACROS(Profit) MACROS(Commission) MACROS(Swap) // MACROS(Expiration) #ifdef __MQL5__ if (!WithoutHistory) { MACROS(OpenReason) MACROS(CloseReason) MACROS(OpenPriceRequest) MACROS(ClosePriceRequest) } else #endif // #ifdef __MQL5__ { this.OpenReason = DEAL_REASON_CLIENT; this.CloseReason = DEAL_REASON_CLIENT; this.OpenPriceRequest = this.OpenPrice; this.ClosePriceRequest = this.ClosePrice; } MACROS(Profit) #endif // #ifdef __MQL5__ #else return(true); } #undef MACROS // Если отрицательный - см. HISTORYORDERS::AddHistoryOrder -> ORDER::ToHistory void SetTicket( const long iTicket ) { this.Ticket = iTicket; return; } void Set( const long lTicket, const ENUM_ORDER_TYPE eType, const double dLots, const string sSymbol, const string sComment, const double dOpenPrice, const long lOpenTimeMsc, const double dStopLoss = 0, const double dTakeProfit = 0, const double dClosePrice = 0, const long lCloseTimeMsc = 0, const long lMagicNumber = 0, const double dProfit = 0, const double dCommission = 0, const double dSwap = 0, const ENUM_DEAL_REASON eOpenReason = DEAL_REASON_CLIENT, const ENUM_DEAL_REASON eCloseReason = DEAL_REASON_CLIENT, const double dOpenPriceRequest = 0, const double dClosePriceRequest = 0 ) { this.Ticket = lTicket; this.Type = eType; this.Lots = dLots; this.Symbol = sSymbol; this.comment = sComment; this.OpenPrice = dOpenPrice; this.OpenTimeMsc = lOpenTimeMsc; this.StopLoss = dStopLoss; this.TakeProfit = dTakeProfit; this.ClosePrice = dClosePrice; this.CloseTimeMsc = lCloseTimeMsc; this.MagicNumber = lMagicNumber; this.Profit = dProfit; this.Commission = dCommission; this.Swap = dSwap; this.OpenReason = eOpenReason; this.CloseReason = eCloseReason; this.OpenPriceRequest = dOpenPriceRequest ? dOpenPriceRequest : this.OpenPrice; this.ClosePriceRequest = dClosePriceRequest ? dClosePriceRequest : this.ClosePrice; return; } };