// https://www.mql5.com/ru/forum/1111/page2336#comment_9660624 // Нужно для BestInterval // #define TICKS_CORRECT_TIME // время тиков, кратное секунде, будет скорректировано на миллисекунду вперед #ifndef ORDERS_MQH #define ORDERS_MQH #include "Order.mqh" #ifdef __MQL5__ #ifndef SELECT_BY_POS #define SELECT_BY_POS 0 #endif // SELECT_BY_POS #ifndef SELECT_BY_TICKET #define SELECT_BY_TICKET 1 #endif // SELECT_BY_TICKET #ifndef MODE_TRADES #define MODE_TRADES 0 #endif // MODE_TRADES #ifndef MODE_HISTORY #define MODE_HISTORY 1 #endif // MODE_HISTORY #ifndef OP_SELL #define OP_SELL ORDER_TYPE_SELL #endif // OP_SELL #endif // __MQL5__ #define MAX_ORDERS 100 #define RESERVE_HISTORY_ORDERS 2000 //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ //class OrderCallBack //{ //public: // virtual void OnSuccess(TICKET_TYPE result, ORDER *order) // { // Print("Order success. Result: ",(string)result,order.ToString(TimeCurrent())); // } // virtual void OnFailure(TICKET_TYPE result, ORDER *order) // { // Print("Order failure. Result: ", (string)result, order.ToString(TimeCurrent())); // } // virtual void OnTimeout(TICKET_TYPE result, ORDER *order) // { // Print("Order timeout. Result: ", (string)result, order.ToString(TimeCurrent())); // } //}; //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ class ORDERS { private: bool Netting; double Balance; double Equity; ORDER Orders[MAX_ORDERS]; int AmountOrders; MqlTick CurrentTick; int SelectIndex; ORDER SelectOrder; ORDER HistoryOrders[]; int AmountHistoryOrders; int ReserveHistoryOrders; //OrderCallBack *pCallBack; static string GetTickFlag( uint tickflag ) { string flag = " " + (string)tickflag; #define TICKFLAG_MACRO(A) flag += ((bool)(tickflag & TICK_FLAG_##A)) ? " TICK_FLAG_" + #A : ""; \ tickflag -= tickflag & TICK_FLAG_##A; TICKFLAG_MACRO(BID) TICKFLAG_MACRO(ASK) TICKFLAG_MACRO(LAST) TICKFLAG_MACRO(VOLUME) TICKFLAG_MACRO(BUY) TICKFLAG_MACRO(SELL) #undef TICKFLAG_MACRO if (tickflag) flag += " FLAG_UNKNOWN (" + (string)tickflag + ")"; return(flag); } #define TOSTR(A) " " + #A + " = " + (string)Tick.A #define TOSTR2(A) " " + #A + " = " + ::DoubleToString(Tick.A, _Digits) static string TickToString( const MqlTick &Tick ) { return(TOSTR(time) + "." + ::IntegerToString(Tick.time_msc % 1000, 3, '0') + TOSTR2(bid) + TOSTR2(ask) + TOSTR2(last)+ TOSTR(volume) + ORDERS::GetTickFlag(Tick.flags)); } #undef TOSTR2 #undef TOSTR void AddHistoryOrder( const ORDER &Order ) { if (this.AmountHistoryOrders == this.ReserveHistoryOrders) this.ReserveHistoryOrders = ::ArrayResize(this.HistoryOrders, this.ReserveHistoryOrders + RESERVE_HISTORY_ORDERS); if (Order.IsNotNull()) this.HistoryOrders[this.AmountHistoryOrders++] = Order; return; } bool IsChange( void ) { bool Res = false; double Profit = 0; int j = 0; for (int i = 0; i < this.AmountOrders; i++) { Res |= this.Orders[i].IsChange(this.CurrentTick) && !this.Orders[i].IsClosed(); if (this.Orders[i].IsClosed()) { this.Balance += this.Orders[i].OrderProfit(); this.AddHistoryOrder(this.Orders[i]); } else { Profit += this.Orders[i].OrderProfit(); if (i != j) this.Orders[j] = this.Orders[i]; j++; } } this.AmountOrders = j; this.Equity = this.Balance + Profit; return(Res); } void CloseBy( void ) { int j = -1; for (int i = 0; i < this.AmountOrders; i++) if (!this.Orders[i].IsClosed() && this.Orders[i].IsPosition()) { if (j < 0) j = i; else if (this.Orders[j].CloseBy(this.Orders[i], this.CurrentTick, this.Netting) && this.Orders[j].IsClosed()) { i = j; j = -1; } } return; } void Check( void ) { while (this.IsChange()) if (this.Netting) this.CloseBy(); return; } int GetIndex( const long &Ticket ) const { int Res = -1; if (this.SelectIndex < this.AmountOrders) { if (this.Orders[this.SelectIndex].OrderTicket() == Ticket) Res = this.SelectIndex; else for (int i = 0; i < this.AmountOrders; i++) if (this.Orders[i].OrderTicket() == Ticket) { Res = i; break; } } return(Res); } bool SelectByPos( const int Index ) { const bool Res = (Index >= 0) && (Index < this.AmountOrders); if (Res) { this.SelectIndex = Index; this.SelectOrder = this.Orders[this.SelectIndex]; } return(Res); } bool SelectByTicket( const long Ticket, const long Pool ) { const int Index = this.GetIndex(Ticket); const bool Res = (Index >= 0); if (Res) { this.SelectIndex = Index; this.SelectOrder = this.Orders[this.SelectIndex]; } return(Res); } bool SelectByPosHistory( const int Index ) { const bool Res = (Index >= 0) && (Index < this.AmountHistoryOrders); if (Res) this.SelectOrder = this.HistoryOrders[Index]; return(Res); } public: const int Handle; ORDERS( const int iHandle, const datetime StartTime = 0 ) : Handle(iHandle), AmountOrders(0), SelectIndex(0), AmountHistoryOrders(0), ReserveHistoryOrders(0) { MqlTick Tick = {0}; Tick.time_msc = (long)StartTime * 1000; this.NewTick(Tick); //pCallBack = NULL; } void Set( const double dBalance = 0, const bool bNetting = false ) { this.Balance = (dBalance > 0) ? dBalance : ::AccountInfoDouble(ACCOUNT_BALANCE); this.Equity = this.Balance; this.Netting = (dBalance > 0) ? bNetting : #ifdef __MQL5__ !((ENUM_ACCOUNT_MARGIN_MODE)::AccountInfoInteger(ACCOUNT_MARGIN_MODE) == ACCOUNT_MARGIN_MODE_RETAIL_HEDGING) #else // __MQL5__ bNetting #endif //__MQL5__ ; this.AmountOrders = 0; this.SelectIndex = 0; this.AmountHistoryOrders = 0; this.ReserveHistoryOrders = 0; // ::ArrayResize(this.HistoryOrders, RESERVE_HISTORY_ORDERS); this.OrderDeposit(this.Balance); return; } bool IsNetting( void ) const { return(this.Netting); } void NewTick( const MqlTick &Tick ) { this.CurrentTick = Tick; #ifdef TICKS_CORRECT_TIME if (!(this.CurrentTick.time_msc % 1000)) this.CurrentTick.time_msc++; #endif // TICKS_CORRECT_TIME this.Check(); return; } void OrderDeposit( const double Deposit ) { if (Deposit) { ORDER Order; Order.Deposit(Deposit, this.CurrentTick); this.AddHistoryOrder(Order); } return; } TICKET_TYPE OrderSend( const string&, const int &Type, const double dVolume, const double Price, const int &SlipPage, const double SL, const double TP, const string &comment, const MAGIC_TYPE &magic, const datetime &dExpiration ) { TICKET_TYPE Res = -1; if (this.Orders[this.AmountOrders].Create((ENUM_ORDER_TYPE)Type, dVolume, Price, SL, TP, magic, comment, this.CurrentTick)) { Res = this.Orders[this.AmountOrders].OrderTicket(); if (this.Orders[this.AmountOrders++].IsPosition()) { if (Netting) this.CloseBy(); this.IsChange(); } else this.Check(); } //if (pCallBack != INVALID_HANDLE) // if (Res > 0) // pCallBack.OnSuccess(Res, this.Orders[this.AmountOrders]); //if (Res < 0) // pCallBack.OnFailure(Res, return(Res); } void Stop( void ) { for (int i = 0; i < this.AmountOrders; i++) this.Orders[i].Stop(this.CurrentTick); this.IsChange(); // Здесь можно ужать массив истории торгов для экономии памяти. return; } bool OrderSelect( const long Index, const int Select, const int Pool = MODE_TRADES ) { return((Select == SELECT_BY_POS) ? ((Pool == MODE_TRADES) ? this.SelectByPos((int)Index) : this.SelectByPosHistory((int)Index)) : this.SelectByTicket(Index, Pool)); } bool OrderClose( const long Ticket, const double dLots, const double Price ) { bool Res = false; const int Pos = this.GetIndex(Ticket); if (Pos >= 0) { const double LeftLots = this.Orders[Pos].Close(dLots, this.CurrentTick); Res = this.Orders[Pos].IsClosed(); if (Res) { if (LeftLots) { this.Orders[this.AmountOrders] = this.Orders[Pos]; this.Orders[this.AmountOrders++].SetLots(LeftLots); } this.IsChange(); } } //if (pCallBack != INVALID_HANDLE) // if (Res > 0) // pCallBack.OnSuccess(Res, this.Orders[this.AmountOrders]); return(Res); } bool OrderModify( const long Ticket, const double Price, const double SL, const double TP, const datetime Expiration = 0 ) { const int Pos = this.GetIndex(Ticket); const bool Res = (Pos >= 0) ? this.Orders[Pos].Modify(Price, SL, TP, this.CurrentTick) : false; if (Res) this.Check(); return(Res); } bool OrderCloseBy( const long Ticket, const long Opposite ) { bool Res = false; const int Pos1 = (Ticket != Opposite) ? this.GetIndex(Ticket) : -1; if (Pos1 >= 0) { const int Pos2 = this.GetIndex(Opposite); if (Pos2 >= 0) { Res = this.Orders[Pos1].CloseBy(this.Orders[Pos2], this.CurrentTick); if (Res) this.IsChange(); } } return(Res); } bool OrderDelete( const long Ticket ) { bool Res = false; const int Pos = this.GetIndex(Ticket); if (Pos >= 0) { Res = this.Orders[Pos].Delete(this.CurrentTick); if (Res) this.IsChange(); } return(Res); } int OrdersTotal2( void ) const // 2 - MT4Orders { return(this.AmountOrders); } int OrdersHistoryTotal( void ) const { return(this.AmountHistoryOrders); } #define ORDERFUNCTION(NAME,T) \ T Order##NAME( void ) const \ { \ return(this.SelectOrder.Order##NAME()); \ } ORDERFUNCTION(Ticket, TICKET_TYPE) ORDERFUNCTION(Type, int) ORDERFUNCTION(Lots, double) ORDERFUNCTION(OpenPrice, double) ORDERFUNCTION(OpenTimeMsc, long) ORDERFUNCTION(OpenTime, datetime) ORDERFUNCTION(StopLoss, double) ORDERFUNCTION(TakeProfit, double) ORDERFUNCTION(ClosePrice, double) ORDERFUNCTION(CloseTimeMsc, long) ORDERFUNCTION(CloseTime, datetime) ORDERFUNCTION(Expiration, datetime) ORDERFUNCTION(MagicNumber, MAGIC_TYPE) ORDERFUNCTION(Profit, double) ORDERFUNCTION(Commission, double) ORDERFUNCTION(Swap, double) ORDERFUNCTION(Symbol, string) ORDERFUNCTION(Comment, string) ORDERFUNCTION(OpenPriceRequest, double) ORDERFUNCTION(ClosePriceRequest, double) ORDERFUNCTION(OpenReason, ENUM_DEAL_REASON) ORDERFUNCTION(CloseReason, ENUM_DEAL_REASON) #undef ORDERFUNCTION void OrderPrint( void ) const { this.SelectOrder.OrderPrint(this.CurrentTick.time); return; } double AccountBalance( void ) const { return(::NormalizeDouble(this.Balance, 2)); } double AccountEquity( void ) const { return(::NormalizeDouble(this.Equity, 2)); } double AccountProfit( void ) const { return(::NormalizeDouble(this.Equity - this.Balance, 2)); } datetime TimeCurrent( void ) const { return(this.CurrentTick.time); } datetime TimeCurrent( MqlDateTime &StructTime ) const { ::TimeToStruct(this.CurrentTick.time, StructTime); return(this.CurrentTick.time); } bool SymbolInfoTick( const string, MqlTick &Tick ) const { Tick = this.CurrentTick; return(true); } double SymbolInfoDouble( const string Symb, const ENUM_SYMBOL_INFO_DOUBLE Property ) const { return((Property == SYMBOL_BID) ? this.CurrentTick.bid : ((Property == SYMBOL_ASK) ? this.CurrentTick.ask : ::SymbolInfoDouble(Symb, Property))); } bool SymbolInfoDouble( const string Symb, const ENUM_SYMBOL_INFO_DOUBLE Property, double &Value ) const { const bool Res = (Property == SYMBOL_BID) || (Property == SYMBOL_ASK); if (Res) { if (Property == SYMBOL_BID) Value = this.CurrentTick.bid; else Value = this.CurrentTick.ask; } return(Res ? true : ::SymbolInfoDouble(Symb, Property, Value)); } long SymbolInfoInteger( const string Symb, const ENUM_SYMBOL_INFO_INTEGER Property ) const { return((Property == SYMBOL_TIME) ? this.CurrentTick.time : ((Property == SYMBOL_SPREAD) ? (long)((this.CurrentTick.ask - this.CurrentTick.bid) / _Point + 0.1) : ::SymbolInfoInteger(Symb, Property))); } long SymbolInfoInteger( const string Symb, const ENUM_SYMBOL_INFO_INTEGER Property, long &Value ) const { const bool Res = (Property == SYMBOL_TIME) || (Property == SYMBOL_SPREAD); if (Res) { if (Property == SYMBOL_TIME) Value = this.CurrentTick.time; else Value = (long)((this.CurrentTick.ask - this.CurrentTick.bid) / _Point + 0.1); } return(Res ? true : ::SymbolInfoInteger(Symb, Property, Value)); } long AccountInfoInteger( const ENUM_ACCOUNT_INFO_INTEGER Property ) const { #ifdef __MQL5__ return((Property == ACCOUNT_MARGIN_MODE) ? (this.Netting ? ACCOUNT_MARGIN_MODE_RETAIL_NETTING : ACCOUNT_MARGIN_MODE_RETAIL_HEDGING) : ::AccountInfoInteger(Property)); #else // __MQL5__ return(::AccountInfoInteger(Property)); #endif // __MQL5__ } double AccountInfoDouble( const ENUM_ACCOUNT_INFO_DOUBLE Property ) const { double Res; switch (Property) { case ACCOUNT_BALANCE: Res = this.AccountBalance(); break; case ACCOUNT_PROFIT: Res = this.AccountProfit(); break; case ACCOUNT_EQUITY: Res = this.AccountEquity(); break; case ACCOUNT_MARGIN: Res = 0; break; default: Res = ::AccountInfoDouble(Property); } return(Res); } string ToString( int LastHistoryOrders = 0, const bool Pending = true ) const { string Str = ORDERS::TickToString(this.CurrentTick) + "\n\n"; for (int i = 0; i < this.AmountOrders; i++) Str += this.Orders[i].ToString(this.CurrentTick.time) + "\n"; Str += "\nBalance = " + ::DoubleToString(this.Balance, 2) + ", Equity = " + ::DoubleToString(this.Equity, 2) + (this.AmountHistoryOrders && (this.HistoryOrders[0].OrderType() == OP_BALANCE) ? ", Profit = " + ::DoubleToString(this.Equity - this.HistoryOrders[0].OrderProfit(), 2) : NULL) + (this.AmountHistoryOrders ? ", " + ::TimeToString(this.HistoryOrders[0].OrderOpenTime(), TIME_DATE) + " - " + (string)this.TimeCurrent() : NULL); if (LastHistoryOrders > 0) for (int i = this.AmountHistoryOrders - 1; (i >= 0) && LastHistoryOrders; i--) if (Pending || (this.HistoryOrders[i].OrderType() <= OP_SELL) || (this.HistoryOrders[i].OrderType() == OP_BALANCE)) { Str += "\n" + this.HistoryOrders[i].ToString(this.CurrentTick.time); LastHistoryOrders--; } return(Str); } int ToFileCSV(const string fName = "orders\\orders.csv") { int fHandle = FileOpen(fName,FILE_WRITE|FILE_SHARE_WRITE|FILE_SHARE_READ|FILE_CSV,'\t',CP_UTF8); if(fHandle == INVALID_HANDLE){ PrintFormat("Операция FileOpen неудачна, ошибка %s",GetLastError()); return -1; } for( int i = 0; i < this.AmountOrders; i++) { this.Orders[i].ToFileCSV(fHandle); } FileClose(fHandle); return this.AmountOrders; } int FromFileCSV(const string fName = "orders\\orders.csv") { int fHandle = FileOpen(fName, FILE_SHARE_READ|FILE_READ|FILE_CSV,'\t',CP_UTF8); if(fHandle == INVALID_HANDLE){ PrintFormat("Операция FileOpen неудачна, ошибка %s",GetLastError()); return -1; } this.AmountOrders = 0; while( FileIsEnding(fHandle) ) { if (this.Orders[this.AmountOrders].FromFileCSV(fHandle)) { PrintFormat("Операция From FileCSV неудачна, ошибка %s",GetLastError()); break; } this.AmountOrders++; } FileClose(fHandle); return this.AmountOrders; } }; #undef RESERVE_HISTORY_ORDERS #undef MAX_ORDERS #endif //ORDERS_MQH