//+------------------------------------------------------------------+ //| Article-16991-MQL5-Post-Factum-Trade-Analysis-Trailing-Stop.mq5 | //| Copyright 2026, MetaQuotes Ltd. | //| https://www.mql5.com | //+------------------------------------------------------------------+ //+------------------------------------------------------------------+ //| TradingByHistoryDeals_Ext.mq5 | //| Copyright 2024, MetaQuotes Ltd. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #include "SymbolTradeExt.mqh" #include "Trailings.mqh" enum ENUM_TESTING_MODE { TESTING_MODE_ORIGIN, /* Original trading */ // Оригинальная торговля TESTING_MODE_SLTP, /* Specified StopLoss and TakeProfit values */ // Указанные значения StopLoss и TakeProfit TESTING_MODE_TRAIL_SIMPLE, /* Simple Trailing */ // Простой трал TESTING_MODE_TRAIL_SAR, /* Trailing by Parabolic SAR indicator */ // Трал по Parabolic SAR TESTING_MODE_TRAIL_AMA, /* Trailing by AMA indicator */ // Трал по адаптивной скользящей средней TESTING_MODE_TRAIL_DEMA, /* Trailing by DEMA indicator */ // Трал по двойной экспоненциальной скользящей средней TESTING_MODE_TRAIL_FRAMA, /* Trailing by FRAMA indicator */ // Трал по фрактальной адаптивной скользящей средней TESTING_MODE_TRAIL_MA, /* Trailing by MA indicator */ // Трал по простой скользящей средней TESTING_MODE_TRAIL_TEMA, /* Trailing by TEMA indicator */ // Трал по тройной экспоненциальной скользящей средней TESTING_MODE_TRAIL_VIDYA, /* Trailing by VIDYA indicator */ // Трал по скользящей средней с динамическим периодом усреднения }; //+------------------------------------------------------------------+ //| Expert | //+------------------------------------------------------------------+ //--- input parameters input group " - Strategy parameters - " input string InpTestedSymbol = ""; /* The symbol being tested in the tester */ // Тестируемый символ input long InpTestedMagic = -1; /* The magic number being tested in the tester */ // Тестируемый магик sinput bool InpShowDataInLog = false; /* Show collected data in the log */ // Показать собранные данные сделок в журнале input group " - Stops parameters - " input ENUM_TESTING_MODE InpTestingMode = TESTING_MODE_ORIGIN; /* Testing Mode */ // Режим тестирования input int InpStopLoss = 300; /* StopLoss in points */ // Отступ StopLoss в пунктах input int InpTakeProfit = 500; /* TakeProfit in points */ // Отступ TakeProfit в пунктах input group " - Trailing Parameters -" input bool InpSetStopLoss = true; /* Set Initial StopLoss */ // Устанавливать начальный StopLoss input bool InpSetTakeProfit = true; /* Set Initial TakeProfit */ // Устанавливать начальный TakeProfit input int InpTrailingStart = 150; /* Trailing start */ // Прибыль в пунктах для старта трейлинга input int InpTrailingStep = 50; /* Trailing step in points */ // Шаг трала в пунктах цены input int InpTrailingOffset = 0; /* Trailing offset in points */ // Отступ трала от цены в пунктах input group " - Indicator Parameters -" input ENUM_TIMEFRAMES InpIndTimeframe = PERIOD_CURRENT; /* Indicator's timeframe */ // Таймфрейм индикатора, используемого в расчёте трала input int InpMAPeriod = 0; /* MA Period */ // Период расчёта скользящей средней input int InpMAShift = 0; /* MA Shift */ // Горизонтальный сдвиг скользящей средней input int InpFastEMAPeriod = 2; /* AMA Fast EMA Period */ // Период расчёта быстрой EMA адаптивной скользящей средней input int InpSlowEMAPeriod = 30; /* AMA Slow EMA Period */ // Период расчёта медленной EMA адаптивной скользящей средней input int InpCMOPeriod = 9; /* VIDYA CMO Period */ // Период CMO скользящей средней с динамическим периодом усреднения input double InpSARStep = 0.02; /* Parabolic SAR Step */ // Шаг Parabolic SAR input double InpSARMax = 0.2; /* Parabolic SAR Max */ // Максимум Parabolic SAR input ENUM_APPLIED_PRICE InpAppliedPrice = PRICE_CLOSE; /* MA Applied Price */ // Цена для расчёта скользящей средней input ENUM_MA_METHOD InpMAMethod = MODE_SMA; /* MA Smoothing Method */ // Тип сглаживания скользящей средней input int InpDataIndex = 1; /* Indicator data index */ // Бар данных, получаемых от индикатора //--- global variables CSymbolTradeExt SymbTradeTmp; SDeal ExtArrayDeals[]={}; CArrayObj ExtListSymbols; int ExtStopLoss; int ExtTakeProfit; //+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- Корректируем размеры стопов ExtStopLoss =(InpStopLoss<1 ? 0 : InpStopLoss); ExtTakeProfit=(InpTakeProfit<1 ? 0 : InpTakeProfit); //--- Если советник запущен не в тестере if(!MQLInfoInteger(MQL_TESTER)) { //--- подготовим файл со всеми историческими сделками if(!PreparesDealsHistoryFile(ExtArrayDeals)) return(INIT_FAILED); //--- распечатаем в журнале все сделки после загрузки их из файла if(InpShowDataInLog) DealsArrayPrint(ExtArrayDeals); //--- получаем первую балансовую сделку, создаём текст сообщения в выводим его при помощи Alert SDeal deal=ExtArrayDeals[0]; long leverage=AccountInfoInteger(ACCOUNT_LEVERAGE); double start_money=deal.profit; datetime first_time=deal.time; string start_time=TimeToString(deal.time, TIME_DATE); string message=StringFormat("Now you can run testing\nInterval: %s - current date\nInitial deposit: %.2f, leverage 1:%I64u", start_time, start_money, leverage); //--- сообщим алертом рекомендуемые параметры тестера стратегий для запуска тестирования Alert(message); } //--- Советник запущен в тестере else { //--- прочитаем данные из файла в массив ulong file_size=0; ArrayResize(ExtArrayDeals, 0); if(!FileReadDealsToArray(ExtArrayDeals, file_size)) { PrintFormat("Failed to read file \"%s\". Error %d", FILE_NAME, GetLastError()); return(INIT_FAILED); } //--- сообщим в журнале о количестве прочитанных байт из файла и о записи массива сделок. PrintFormat("%I64u bytes were read from the file \"%s\" and written to the deals array. A total of %u deals were received", file_size, FILE_NAME, ExtArrayDeals.Size()); } //--- Из массива исторических сделок создаём список торговых объектов по символам if(!CreateListSymbolTrades(ExtArrayDeals, &ExtListSymbols)) { Print("Errors found while creating symbol list"); return(INIT_FAILED); } //--- Распечатаем в журнале созданный список сделок SymbolsArrayPrint(&ExtListSymbols); //--- Обратимся к каждому символу для начала закачки исторических данных //--- и открытия графиков проторгованных символов в тестере стратегий datetime array[]; int total=ExtListSymbols.Total(); for(int i=0; i0); if(!res) PrintFormat("%s: FileWriteArray() failed. Error ",__FUNCTION__, GetLastError()); else file_size=FileSize(handle); //--- закрываем файл FileClose(handle); return res; } //+------------------------------------------------------------------+ //| Подготавливает файл со сделками истории | //+------------------------------------------------------------------+ bool PreparesDealsHistoryFile(SDeal &deals_array[]) { //--- сохраним все сделки счёта в массив сделок int total=SaveDealsToArray(deals_array); if(total==0) return false; //--- запишем данные массива сделок в файл ulong file_size=0; if(!FileWriteDealsFromArray(deals_array, file_size)) return false; //--- распечатаем в журнале сколько сделок было прочитано и сохранено в файл, путь к файлу и его размер PrintFormat("%u deals were saved in an array and written to a \"%s\" file of %I64u bytes in size", deals_array.Size(), "TERMINAL_COMMONDATA_PATH\\Files\\"+ PATH, file_size); //--- теперь для проверки прочитаем данные из файла в массив ArrayResize(deals_array, 0, total); if(!FileReadDealsToArray(deals_array, file_size)) return false; //--- распечатаем в журнале сколько байт было прочитано из файла и количество полученных в массив сделок PrintFormat("%I64u bytes were read from the file \"%s\" and written to the deals array. A total of %u deals were received", file_size, FILE_NAME, deals_array.Size()); return true; } //+------------------------------------------------------------------+ //| Создаёт объект сделки из структуры | //+------------------------------------------------------------------+ CDeal *CreateDeal(SDeal &deal_str) { //--- Если объект сделки создать не удалось - сообщаем в журнале об ошибке и возвращаем NULL CDeal *deal=new CDeal(deal_str.ticket, deal_str.Symbol()); if(deal==NULL) { PrintFormat("%s: Error. Failed to create deal object"); return NULL; } //--- заполняем свойства сделки из полей структуры deal.SetOrder(deal_str.order); // Ордер, на основании которого была открыта сделка deal.SetPositionID(deal_str.pos_id); // Идентификатор позиции deal.SetTimeMsc(deal_str.time_msc); // Время в миллисекундах deal.SetTime(deal_str.time); // Время deal.SetVolume(deal_str.volume); // Объём deal.SetPrice(deal_str.price); // Цена deal.SetProfit(deal_str.profit); // Прибыль deal.SetCommission(deal_str.commission); // Комиссия по сделке deal.SetSwap(deal_str.swap); // Накопленный своп при закрытии deal.SetFee(deal_str.fee); // Оплата за проведение сделки, начисляется сразу после совершения сделки deal.SetSL(deal_str.sl); // Уровень Stop Loss deal.SetTP(deal_str.tp); // Уровень Take Profit deal.SetType(deal_str.type); // Тип deal.SetEntry(deal_str.entry); // Способ изменения позиции deal.SetReason(deal_str.reason); // Причина или источник проведения сделки deal.SetMagic(deal_str.magic); // Идентификатор эксперта deal.SetComment(deal_str.Comment()); // Комментарий к сделке deal.SetExternalID(deal_str.ExternalID()); // Идентификатор сделки во внешней торговой системе (на бирже) //--- Возвращаем указатель на созданный объект return deal; } //+------------------------------------------------------------------+ //| Создаёт массив используемых символов | //+------------------------------------------------------------------+ bool CreateListSymbolTrades(SDeal &array_deals[], CArrayObj *list_symbols) { bool res=true; // результат MqlParam param[7]={}; // параметры тралов int total=(int)array_deals.Size(); // общее количество сделок в массиве //--- если массив сделок пустой - возвращаем false if(total==0) { PrintFormat("%s: Error! Empty deals array passed",__FUNCTION__); return false; } //--- в цикле по массиву сделок CSymbolTradeExt *SymbolTrade=NULL; for(int i=0; i-1 && deal.Magic()!=magic) || (symbol!="" && deal.Symbol()!=symbol)) continue; //--- фильтруем сделку по типу (только сделки покупки/продажи) ENUM_DEAL_TYPE type=deal.TypeDeal(); if(type!=DEAL_TYPE_BUY && type!=DEAL_TYPE_SELL) continue; //--- если это уже обработанная в тестере сделка - идём к следующей if(deal.TicketTester()>0) continue; //--- если время сделки ещё не настало - идём к следующему торговому объекту следующего символа if(!obj.CheckTime(deal.Time())) continue; //--- если сделка входа в рынок ENUM_DEAL_ENTRY entry=deal.Entry(); if(entry==DEAL_ENTRY_IN) { //--- устанавливаем размеры стоп-приказов в зависимости от метода установки стопов //--- изначально стоп-приказы не используются (для оригинальной торговли) ENUM_ORDER_TYPE order_type=(deal.TypeDeal()==DEAL_TYPE_BUY ? ORDER_TYPE_BUY : ORDER_TYPE_SELL); double sl=0; double tp=0; //--- если режим установки указанных значений стоп-приказов if(InpTestingMode==TESTING_MODE_SLTP) { //--- получаем корректные значения для StopLoss и TakeProfit sl=CorrectStopLoss(deal.Symbol(), order_type, ExtStopLoss); tp=CorrectTakeProfit(deal.Symbol(), order_type, ExtTakeProfit); } //--- иначе, если тестирование с трейлингами else { if(InpTestingMode!=TESTING_MODE_ORIGIN) { //--- если разрешено в настройках, выставляем корректные стоп-приказы открываемым позициям if(InpSetStopLoss) sl=CorrectStopLoss(deal.Symbol(), order_type, ExtStopLoss); if(InpSetTakeProfit) tp=CorrectTakeProfit(deal.Symbol(), order_type, ExtTakeProfit); } } //--- открываем позицию по типу сделки ulong ticket=(type==DEAL_TYPE_BUY ? obj.Buy(deal.Volume(), deal.Magic(), sl, tp, deal.Comment()) : type==DEAL_TYPE_SELL ? obj.Sell(deal.Volume(),deal.Magic(), sl, tp, deal.Comment()) : 0); //--- если позиция открыта (получили её тикет) if(ticket>0) { //--- увеличиваем количество обработанных тестером сделок и записываем тикет сделки в тестере в свойства объекта сделки obj.SetNumProcessedDeals(obj.NumProcessedDeals()+1); deal.SetTicketTester(ticket); //--- получаем идентификатор позиции в тестере и записываем его в свойства объекта сделки long pos_id_tester=0; if(HistoryDealSelect(ticket)) { pos_id_tester=HistoryDealGetInteger(ticket, DEAL_POSITION_ID); deal.SetPosIDTester(pos_id_tester); } } } //--- если сделка выхода из рынка if(entry==DEAL_ENTRY_OUT || entry==DEAL_ENTRY_INOUT || entry==DEAL_ENTRY_OUT_BY) { //--- получаем сделку, на основании которой была открыта позиция CDeal *deal_in=obj.GetDealInByPosID(deal.PositionID()); if(deal_in==NULL) continue; //--- получаем тикет позиции в тестере из свойств открывающей сделки //--- если тикет равен нулю, значит скорее всего позиция в тестере уже закрыта ulong ticket_tester=deal_in.TicketTester(); if(ticket_tester==0) { PrintFormat("Could not get position ticket, apparently position #%I64d (#%I64d) is already closed \n", deal.PositionID(), deal_in.PosIDTester()); obj.SetNextDealIndex(); continue; } //--- если воспроизводим в тестере оригинальную торговую историю, if(InpTestingMode==TESTING_MODE_ORIGIN) { //--- если позиция закрыта по тикету if(obj.ClosePos(ticket_tester)) { //--- увеличиваем количество обработанных тестером сделок и записываем тикет сделки в тестере в свойства объекта сделки obj.SetNumProcessedDeals(obj.NumProcessedDeals()+1); deal.SetTicketTester(ticket_tester); } } //--- иначе - в тестере работаем со стоп-приказами, выставляемыми по различным алгоритмам, и сделки закрытия пропускаются; //--- соответственно, для закрывающей сделки просто увеличиваем количество обработанных тестером сделок и //--- записываем тикет сделки в тестере в свойства объекта сделки else { obj.SetNumProcessedDeals(obj.NumProcessedDeals()+1); deal.SetTicketTester(ticket_tester); } } //--- если теперь в объекте сделки записан тикет - значит сделка успешно обработана - //--- устанавливаем индекс сделки в списке на следующую сделку if(deal.TicketTester()>0) { obj.SetNextDealIndex(); } } } //+------------------------------------------------------------------+ //| Устанавливает параметры трала по выбранному его типу | //+------------------------------------------------------------------+ void SetTrailingParams(const ENUM_TRAILING_MODE mode, MqlParam ¶m[]) { //--- обнуляем все параметры ZeroMemory(param); //--- в зависимости от выбранного типа трейлинга устанавливаем параметры индикатора switch(mode) { case TRAILING_MODE_SAR : param[0].type=TYPE_DOUBLE; param[0].double_value=InpSARStep; param[1].type=TYPE_DOUBLE; param[1].double_value=InpSARMax; break; case TRAILING_MODE_AMA : param[0].type=TYPE_INT; param[0].integer_value=InpMAPeriod; param[1].type=TYPE_INT; param[1].integer_value=InpMAShift; param[2].type=TYPE_INT; param[2].integer_value=InpAppliedPrice; param[4].type=TYPE_INT; param[4].integer_value=InpFastEMAPeriod; param[5].type=TYPE_INT; param[5].integer_value=InpSlowEMAPeriod; break; case TRAILING_MODE_DEMA : param[0].type=TYPE_INT; param[0].integer_value=InpMAPeriod; param[1].type=TYPE_INT; param[1].integer_value=InpMAShift; param[2].type=TYPE_INT; param[2].integer_value=InpAppliedPrice; break; case TRAILING_MODE_FRAMA : param[0].type=TYPE_INT; param[0].integer_value=InpMAPeriod; param[1].type=TYPE_INT; param[1].integer_value=InpMAShift; param[2].type=TYPE_INT; param[2].integer_value=InpAppliedPrice; break; case TRAILING_MODE_MA : param[0].type=TYPE_INT; param[0].integer_value=InpMAPeriod; param[1].type=TYPE_INT; param[1].integer_value=InpMAShift; param[2].type=TYPE_INT; param[2].integer_value=InpAppliedPrice; param[3].type=TYPE_INT; param[3].integer_value=InpMAMethod; break; case TRAILING_MODE_TEMA : param[0].type=TYPE_INT; param[0].integer_value=InpMAPeriod; param[1].type=TYPE_INT; param[1].integer_value=InpMAShift; param[2].type=TYPE_INT; param[2].integer_value=InpAppliedPrice; break; case TRAILING_MODE_VIDYA : param[0].type=TYPE_INT; param[0].integer_value=InpMAPeriod; param[1].type=TYPE_INT; param[1].integer_value=InpMAShift; param[2].type=TYPE_INT; param[2].integer_value=InpAppliedPrice; param[6].type=TYPE_INT; param[6].integer_value=InpCMOPeriod; break; case TRAILING_MODE_SIMPLE : break; default: break; } } //+------------------------------------------------------------------+ //| Трейлинг позиций | //+------------------------------------------------------------------+ void Trailing(void) { //--- переменные для получения свойств позиции long magic=-1; string symbol=""; //--- в цикле по всем позициям int total=PositionsTotal(); for(int i=total-1; i>=0; i--) { //--- получаем тикет очередной позиции ulong ticket=PositionGetTicket(i); if(ticket==0) continue; //--- получаем магик и символ позиции ResetLastError(); if(!PositionGetInteger(POSITION_MAGIC, magic)) { Print("PositionGetInteger() failed. Error ", GetLastError()); continue; } if(!PositionGetString(POSITION_SYMBOL, symbol)) { Print("PositionGetString() failed. Error ", GetLastError()); continue; } //--- если позиция не проходит по заданным условиям магика и символа - идём к следующей if((InpTestedMagic>-1 && magic!=InpTestedMagic) || (InpTestedSymbol!="" && symbol!=InpTestedSymbol)) continue; //--- получаем торговый объект по имени символа и вызываем его метод трейлинга позиции по тикету CSymbolTradeExt *obj=GetSymbolTrade(symbol, &ExtListSymbols); if(obj!=NULL) obj.Trailing(ticket); } } //+------------------------------------------------------------------+ //| Возвращает корректный StopLoss относительно StopLevel | //+------------------------------------------------------------------+ double CorrectStopLoss(const string symbol_name, const ENUM_ORDER_TYPE order_type, const int stop_loss, const int spread_multiplier=2) { if(stop_loss==0 || (order_type!=ORDER_TYPE_BUY && order_type!=ORDER_TYPE_SELL)) return 0; int lv=StopLevel(symbol_name, spread_multiplier), dg=(int)SymbolInfoInteger(symbol_name, SYMBOL_DIGITS); double pt=SymbolInfoDouble(symbol_name, SYMBOL_POINT); double price=(order_type==ORDER_TYPE_BUY ? SymbolInfoDouble(symbol_name, SYMBOL_BID) : SymbolInfoDouble(symbol_name, SYMBOL_ASK)); return (order_type==ORDER_TYPE_BUY ? NormalizeDouble(fmin(price-lv*pt, price-stop_loss*pt), dg) : NormalizeDouble(fmax(price+lv*pt, price+stop_loss*pt), dg) ); } //+------------------------------------------------------------------+ //| Возвращает корректный TakeProfit относительно StopLevel | //+------------------------------------------------------------------+ double CorrectTakeProfit(const string symbol_name, const ENUM_ORDER_TYPE order_type, const int take_profit, const int spread_multiplier=2) { if(take_profit==0 || (order_type!=ORDER_TYPE_BUY && order_type!=ORDER_TYPE_SELL)) return 0; int lv=StopLevel(symbol_name, spread_multiplier), dg=(int)SymbolInfoInteger(symbol_name, SYMBOL_DIGITS); double pt=SymbolInfoDouble(symbol_name, SYMBOL_POINT); double price=(order_type==ORDER_TYPE_BUY ? SymbolInfoDouble(symbol_name, SYMBOL_BID) : SymbolInfoDouble(symbol_name, SYMBOL_ASK)); return (order_type==ORDER_TYPE_BUY ? NormalizeDouble(fmax(price+lv*pt, price+take_profit*pt), dg) : NormalizeDouble(fmin(price-lv*pt, price-take_profit*pt), dg) ); } //+------------------------------------------------------------------+ //| Возвращает размер StopLevel в пунктах | //+------------------------------------------------------------------+ int StopLevel(const string symbol_name, const int spread_multiplier) { int spread=(int)SymbolInfoInteger(symbol_name, SYMBOL_SPREAD); int stop_level=(int)SymbolInfoInteger(symbol_name, SYMBOL_TRADE_STOPS_LEVEL); return(stop_level==0 ? spread*spread_multiplier : stop_level); } //+------------------------------------------------------------------+