#define MTTESTER_DLL // Автоматически запускает правильно настроенный Тестер (нужно разрешение DLL). #resource "\\Indicators\\Spy.ex5" // https://www.mql5.com/ru/forum/170952/page68#comment_6406566 input string inPortfolioFolder = "Portfolio"; // Папка с одиночными проходами для объединения #include // https://www.mql5.com/ru/code/16006 #include // https://www.mql5.com/ru/code/27611 #include // https://www.mql5.com/ru/code/20225 #ifdef MTTESTER_DLL #include // https://www.mql5.com/ru/code/19003 #include // https://www.mql5.com/ru/code/26132 bool RunMe( const string Symb ) { MqlParam Params[]; string Names[]; bool Res = EXPERT::Parameters(0, Params, Names, false); if (Res) { string Str = "[Tester]" + "\nExpert=" + StringSubstr(Params[0].string_value, StringLen("Experts\\")) + "\nSymbol=" + Symb + "\nOptimization=0" + "\nModel=4" + "\n[TesterInputs]"; const int Size = ArraySize(Names); for (int i = 0; i < Size; i++) Str += "\n" + Names[i] + "=" + Params[i + 1].string_value; Res = MTTESTER::SetSettings2(Str) && MTTESTER::ClickStart(); } return(Res); } #define REPORT_BROWSER // Создание отчета с запуском браузера - требует разрешения DLL. #endif // MTTESTER_DLL #define REPORT_TESTER // В тестере будут автоматически записываться отчеты #define REPORT_INTERACTIVE_CHARTS // Добавляет в отчет интерактивные графики. #include // https://www.mql5.com/ru/code/16006 int GetPortfolio( const string &FileNames[], SINGLETESTERCACHE &Portfolio[], const int Common_Flag = 0 ) { int Amount = 0; const int Size = ArrayResize(Portfolio, ArraySize(FileNames)); for (int i = 0; i < Size; i++) if (SingleTesterCache[Amount].Load(FileNames[i], Common_Flag)) Amount++; return(ArrayResize(Portfolio, Amount)); } int GetPortfolioSymbols( const SINGLETESTERCACHE &Portfolio[], string &Symbols[] ) { int Size = 0; ::ArrayFree(Symbols); for (int i = ::ArraySize(Portfolio) - 1; i >= 0; i--) { string Symbols2[]; for (int j = Portfolio[i].GetTradeSymbols(Symbols2) - 1; j >= 0; j--) { const string Symb = Symbols2[j]; int k = 0; for (; (k < Size) && (Symbols[k] != Symb); k++) ; if (k == Size) { Size = ArrayResize(Symbols, Size + 1); Symbols[Size - 1] = Symb; } } } return(Size); } string PortfolioToString( const SINGLETESTERCACHE &Portfolio[], const string &FileNames[], const string &Symbols[] ) { const int Size = ArraySize(Portfolio); const int Amount = ArraySize(Symbols); string Str = "TesterPortfolio:"; for (int i = 0; i < Size; i++) Str += "\n" + (string)(i + 1) + ": "+ FileNames[i] + "\n" + Portfolio[i].ToString() + "\n"; Str += "\nSymbols:"; for (int i = 0; i < Amount; i++) Str += "\n" + (string)(i + 1) + ": "+ Symbols[i]; return(Str); } string PrevTesterSettings = NULL; SINGLETESTERCACHE SingleTesterCache[]; int PosDeal[]; string PortfolioString = NULL; int OnInit() { string FileNames[]; string Symbols[]; bool Res = !MQLInfoInteger(MQL_OPTIMIZATION) && FILE::GetFileNames(FileNames, inPortfolioFolder + "\\*.tst", FILE_COMMON) && ArrayResize(PosDeal, GetPortfolio(FileNames, SingleTesterCache, FILE_COMMON)) && GetPortfolioSymbols(SingleTesterCache, Symbols) && EventSetTimer(1); if (Res) { PortfolioString = PortfolioToString(SingleTesterCache, FileNames, Symbols); if (MQLInfoInteger(MQL_TESTER)) { for (int i = ArraySize(Symbols) - 1; i >= 0; i--) if (Symbols[i] != _Symbol) iCustom(Symbols[i], PERIOD_W1, "::Indicators\\Spy.ex5", ChartID(), i); ArrayInitialize(PosDeal, 1); PortfolioTrade(); } #ifdef MTTESTER_DLL else if (Res = MQLInfoInteger(MQL_DLLS_ALLOWED) && MTTESTER::GetSettings(PrevTesterSettings) && (MessageBox(PortfolioString, __FILE__, MB_YESNO | MB_ICONQUESTION) == IDYES)) { ArrayFree(SingleTesterCache); ArrayFree(PosDeal); if (!(Res = RunMe(Symbols[0]))) MTTESTER::SetSettings2(PrevTesterSettings); } #endif // MTTESTER_DLL } return(Res ? INIT_SUCCEEDED : INIT_FAILED); } void OnDeinit( const int ) { if (MQLInfoInteger(MQL_TESTER)) Print(PortfolioString); } void OnTimer() { static const bool IsTester = MQLInfoInteger(MQL_TESTER); if (IsTester) PortfolioTrade(); #ifdef MTTESTER_DLL else if (MTTESTER::IsReady()) { MTTESTER::SetSettings2(PrevTesterSettings); ExpertRemove(); } #endif // MTTESTER_DLL } void OnTick() { PortfolioTrade(); } void OnChartEvent( const int id, const long &lparam, const double&, const string& ) { if (id == CHARTEVENT_CUSTOM) PortfolioTrade(); } bool OrderScan( const int Type, const string Symb, const MAGIC_TYPE Magic, const double Lot = 0 ) { double LotsMax = Lot; TICKET_TYPE TicketMax = 0; for (int i = OrdersTotal() - 1; i >= 0; i--) if (OrderSelect(i, SELECT_BY_POS) && (OrderType() == Type) && (OrderMagicNumber() == Magic) && (OrderSymbol() == Symb)) { if (OrderLots() == Lot) return(true); else if (OrderLots() > LotsMax) { LotsMax = OrderLots(); TicketMax = OrderTicket(); } } return(TicketMax && OrderSelect(TicketMax, SELECT_BY_TICKET)); } bool CloseOrder( const string Symb, const int Type, double Lots, const double Price, const MAGIC_TYPE Magic, const string comment ) { bool Res = Lots; while (Res && Lots && (Res = OrderScan(Type, Symb, Magic, Lots))) { Res = (OrderClosePrice() == Price) && OrderClose(OrderTicket(), Lots, Price, 0, clrNONE, comment); Lots = NormalizeDouble(Lots - OrderLots(), 8); } return(Res); } #define MACROS(A) \ { \ if (!(A)) \ { \ Print(#A); \ \ TesterStop(); \ } \ } void PortfolioTrade() { static const bool IsTester = MQLInfoInteger(MQL_TESTER); static const bool IsNetting = ((ENUM_ACCOUNT_MARGIN_MODE)::AccountInfoInteger(ACCOUNT_MARGIN_MODE) != ACCOUNT_MARGIN_MODE_RETAIL_HEDGING); if (IsTester) { const int Size = ArraySize(SingleTesterCache); const datetime time = TimeTradeServer(); for (int i = 0; i < Size; i++) { bool Flag = true; while (Flag && (PosDeal[i] < ArraySize(SingleTesterCache[i].Deals)) && (SingleTesterCache[i].Deals[PosDeal[i]].GetProperty(DEAL_TIME) <= time)) { const TradeDeal Deal = SingleTesterCache[i].Deals[PosDeal[i]]; const datetime DealTime = (datetime)Deal.GetProperty(DEAL_TIME); if (Deal.GetProperty(DEAL_TIME) == time) { const int Type = (int)Deal.GetProperty(DEAL_TYPE); if (Type == OP_BALANCE) { const double Profit = Deal.GetProperty(DEAL_PROFIT); if (Profit > 0) MACROS(TesterDeposit(Profit)) else if (Profit < 0) MACROS(TesterWithdrawal(-Profit)) } else { const string Symb = Deal.GetProperty(DEAL_SYMBOL); const double Price = SymbolInfoDouble(Symb, Type ? SYMBOL_BID : SYMBOL_ASK); if (Flag = !NormalizeDouble(Deal.GetProperty(DEAL_PRICE) - Price, 8)) // if (Flag = (Deal.GetProperty(DEAL_PRICE) == Price)) { if (IsNetting || (Deal.GetProperty(DEAL_ENTRY) == DEAL_ENTRY_IN)) MACROS(OrderSend(Symb, Type, Deal.GetProperty(DEAL_VOLUME), Price, 0, 0, 0, Deal.GetProperty(DEAL_COMMENT), i) != -1) else MACROS(CloseOrder(Symb, 1 - Type, Deal.GetProperty(DEAL_VOLUME), Price, i, Deal.GetProperty(DEAL_COMMENT))) } } if (Flag) PosDeal[i]++; } else PosDeal[i]++; } } } }