2026-03-24 14:01:24 +07:00
//+------------------------------------------------------------------+
2026-03-24 13:42:05 +07:00
//| Article-16991-MQL5-Post-Factum-Trade-Analysis-Trailing-Stop.mq5 |
//| Copyright 2026, MetaQuotes Ltd. |
//| https://www.mql5.com |
//+------------------------------------------------------------------+
2026-03-24 14:01:24 +07:00
//+------------------------------------------------------------------+
//| 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 ;
2026-03-24 13:42:05 +07:00
//+------------------------------------------------------------------+
//| Expert initialization function |
//+------------------------------------------------------------------+
int OnInit ( )
{
2026-03-24 14:01:24 +07:00
//--- Корректируем размеры стопов
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 \n Interval: %s - current date \n Initial 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 ) ;
2026-03-24 13:42:05 +07:00
2026-03-24 14:01:24 +07:00
//--- Обратимся к каждому символу для начала закачки исторических данных
//--- и открытия графиков проторгованных символов в тестере стратегий
datetime array [ ] ;
int total = ExtListSymbols . Total ( ) ;
for ( int i = 0 ; i < total ; i + + )
{
CSymbolTradeExt * obj = ExtListSymbols . At ( i ) ;
if ( obj = = NULL )
continue ;
CopyTime ( obj . Symbol ( ) , PERIOD_CURRENT , 0 , 1 , array ) ;
}
//--- Всё успешно
2026-03-24 13:42:05 +07:00
return ( INIT_SUCCEEDED ) ;
}
//+------------------------------------------------------------------+
//| Expert deinitialization function |
//+------------------------------------------------------------------+
void OnDeinit ( const int reason )
{
2026-03-24 14:01:24 +07:00
//--- очищаем созданные списки и массивы
ExtListSymbols . Clear ( ) ;
ArrayFree ( ExtArrayDeals ) ;
2026-03-24 13:42:05 +07:00
}
//+------------------------------------------------------------------+
//| Expert tick function |
//+------------------------------------------------------------------+
void OnTick ( )
{
2026-03-24 14:01:24 +07:00
//--- работаем только в тестере стратегий
if ( ! MQLInfoInteger ( MQL_TESTER ) )
return ;
//--- Тралим открытые позиции
Trailing ( ) ;
2026-03-24 13:42:05 +07:00
2026-03-24 14:01:24 +07:00
//--- Обрабатываем список сделок из файла
TradeByHistory ( InpTestedSymbol , InpTestedMagic ) ;
2026-03-24 13:42:05 +07:00
}
//+------------------------------------------------------------------+
2026-03-24 14:01:24 +07:00
//| Tester function |
2026-03-24 13:42:05 +07:00
//+------------------------------------------------------------------+
2026-03-24 14:01:24 +07:00
double OnTester ( void )
2026-03-24 13:42:05 +07:00
{
2026-03-24 14:01:24 +07:00
//--- посчитаем и вернём общее количество обработанных в тестере сделок
double ret = 0.0 ;
int total = ExtListSymbols . Total ( ) ;
for ( int i = 0 ; i < total ; i + + )
{
CSymbolTradeExt * obj = ExtListSymbols . At ( i ) ;
if ( obj ! = NULL )
ret + = obj . OnTester ( ) ;
}
return ( ret ) ;
}
//+------------------------------------------------------------------+
//| Возвращает указатель на торговый объект символа по имени |
//+------------------------------------------------------------------+
CSymbolTrade * GetSymbolTrade ( const string symbol , CArrayObj * list )
{
SymbTradeTmp . SetSymbol ( symbol ) ;
list .Sort ( ) ;
int index = list . Search ( & SymbTradeTmp ) ;
return list . At ( index ) ;
}
//+------------------------------------------------------------------+
//| Сохраняет сделки из истории в массив |
//+------------------------------------------------------------------+
int SaveDealsToArray ( SDeal & array [ ] , bool logs = false )
{
//--- структура сделки
SDeal deal = { } ;
//--- запросим историю сделок в интервале с самого начала по текущий момент
if ( ! HistorySelect ( 0 , TimeCurrent ( ) ) )
{
Print ( " HistorySelect() failed. Error " , GetLastError ( ) ) ;
return 0 ;
}
2026-03-24 13:42:05 +07:00
2026-03-24 14:01:24 +07:00
//--- общее количество сделок в списке
int total = HistoryDealsTotal ( ) ;
//--- обработаем каждую сделку
for ( int i = 0 ; i < total ; i + + )
{
//--- получаем тикет очередной сделки (сделка автоматически выбирается для получения её свойств)
ulong ticket = HistoryDealGetTicket ( i ) ;
if ( ticket = = 0 )
continue ;
//--- сохраняем только балансовые и торговые сделки
ENUM_DEAL_TYPE deal_type = ( ENUM_DEAL_TYPE ) HistoryDealGetInteger ( ticket , DEAL_TYPE ) ;
if ( deal_type ! = DEAL_TYPE_BUY & & deal_type ! = DEAL_TYPE_SELL & & deal_type ! = DEAL_TYPE_BALANCE )
continue ;
//--- сохраняем свойства сделки в структуре
deal . ticket = ticket ;
deal . type = deal_type ;
deal . order = HistoryDealGetInteger ( ticket , DEAL_ORDER ) ;
deal . entry = ( ENUM_DEAL_ENTRY ) HistoryDealGetInteger ( ticket , DEAL_ENTRY ) ;
deal . reason = ( ENUM_DEAL_REASON ) HistoryDealGetInteger ( ticket , DEAL_REASON ) ;
deal . time = ( datetime ) HistoryDealGetInteger ( ticket , DEAL_TIME ) ;
deal . time_msc = HistoryDealGetInteger ( ticket , DEAL_TIME_MSC ) ;
deal . pos_id = HistoryDealGetInteger ( ticket , DEAL_POSITION_ID ) ;
deal . volume = HistoryDealGetDouble ( ticket , DEAL_VOLUME ) ;
deal . price = HistoryDealGetDouble ( ticket , DEAL_PRICE ) ;
deal . profit = HistoryDealGetDouble ( ticket , DEAL_PROFIT ) ;
deal . commission = HistoryDealGetDouble ( ticket , DEAL_COMMISSION ) ;
deal . swap = HistoryDealGetDouble ( ticket , DEAL_SWAP ) ;
deal . fee = HistoryDealGetDouble ( ticket , DEAL_FEE ) ;
deal . sl = HistoryDealGetDouble ( ticket , DEAL_SL ) ;
deal . tp = HistoryDealGetDouble ( ticket , DEAL_TP ) ;
deal . magic = HistoryDealGetInteger ( ticket , DEAL_MAGIC ) ;
deal . SetSymbol ( HistoryDealGetString ( ticket , DEAL_SYMBOL ) ) ;
deal . SetComment ( HistoryDealGetString ( ticket , DEAL_COMMENT ) ) ;
deal . SetExternalID ( HistoryDealGetString ( ticket , DEAL_EXTERNAL_ID ) ) ;
deal . digits = ( int ) SymbolInfoInteger ( deal . Symbol ( ) , SYMBOL_DIGITS ) ;
//--- увеличиваем массив и
int size = ( int ) array .Size ( ) ;
ResetLastError ( ) ;
if ( ArrayResize ( array , size + 1 , total ) ! = size + 1 )
{
Print ( " ArrayResize() failed. Error " , GetLastError ( ) ) ;
continue ;
}
//--- сохраняем в массиве сделку
array [ size ] = deal ;
//--- если разрешено, выводим описание сохранённой сделки в журнал
if ( logs )
DealPrint ( deal , i ) ;
}
//--- возвращаем количество сохранённых в массиве сделок
return ( int ) array .Size ( ) ;
}
//+------------------------------------------------------------------+
//| Выводит сделки из массива в журнал |
//+------------------------------------------------------------------+
void DealsArrayPrint ( SDeal & array [ ] )
{
int total = ( int ) array .Size ( ) ;
//--- если передан пустой массив - сообщаем об этом и возвращаем false
if ( total = = 0 )
{
PrintFormat ( " %s: Error! Empty deals array passed " , __FUNCTION__ ) ;
return ;
}
//--- В цикле по массиву сделок распечатаем описание каждой сделки
for ( int i = 0 ; i < total ; i + + )
{
DealPrint ( array [ i ] , i ) ;
}
}
//+------------------------------------------------------------------+
//| Возвращает описание типа сделки |
//+------------------------------------------------------------------+
string DealTypeDescription ( const ENUM_DEAL_TYPE type )
{
switch ( type )
{
case DEAL_TYPE_BUY : return " Buy " ;
case DEAL_TYPE_SELL : return " Sell " ;
case DEAL_TYPE_BALANCE : return " Balance " ;
case DEAL_TYPE_CREDIT : return " Credit " ;
case DEAL_TYPE_CHARGE : return " Additional charge " ;
case DEAL_TYPE_CORRECTION : return " Correction " ;
case DEAL_TYPE_BONUS : return " Bonus " ;
case DEAL_TYPE_COMMISSION : return " Additional commission " ;
case DEAL_TYPE_COMMISSION_DAILY : return " Daily commission " ;
case DEAL_TYPE_COMMISSION_MONTHLY : return " Monthly commission " ;
case DEAL_TYPE_COMMISSION_AGENT_DAILY : return " Daily agent commission " ;
case DEAL_TYPE_COMMISSION_AGENT_MONTHLY : return " Monthly agent commission " ;
case DEAL_TYPE_INTEREST : return " Interest rate " ;
case DEAL_TYPE_BUY_CANCELED : return " Canceled buy deal " ;
case DEAL_TYPE_SELL_CANCELED : return " Canceled sell deal " ;
case DEAL_DIVIDEND : return " Dividend operations " ;
case DEAL_DIVIDEND_FRANKED : return " Franked (non-taxable) dividend operations " ;
case DEAL_TAX : return " Tax charges " ;
default : return " Unknown deal type: " + ( string ) type ;
}
}
//+------------------------------------------------------------------+
//| Возвращает описание способа изменения позиции |
//+------------------------------------------------------------------+
string DealEntryDescription ( const ENUM_DEAL_ENTRY entry )
{
switch ( entry )
{
case DEAL_ENTRY_IN : return " Entry In " ;
case DEAL_ENTRY_OUT : return " Entry Out " ;
case DEAL_ENTRY_INOUT : return " Entry InOut " ;
case DEAL_ENTRY_OUT_BY : return " Entry OutBy " ;
default : return " Unknown entry: " + ( string ) entry ;
}
}
//+------------------------------------------------------------------+
//| Возвращает описание сделки |
//+------------------------------------------------------------------+
string DealDescription ( SDeal & deal , const int index )
{
string indexs = StringFormat ( " % 5d " , index ) ;
if ( deal . type ! = DEAL_TYPE_BALANCE )
return ( StringFormat ( " %s: deal #%I64u %s, type %s, Position #%I64d %s (magic %I64d), Price %.*f at %s, sl %.*f, tp %.*f " ,
indexs , deal . ticket , DealEntryDescription ( deal . entry ) , DealTypeDescription ( deal . type ) ,
deal . pos_id , deal . Symbol ( ) , deal . magic , deal . digits , deal . price ,
TimeToString ( deal . time , TIME_DATE | TIME_MINUTES | TIME_SECONDS ) , deal . digits , deal . sl , deal . digits , deal . tp ) ) ;
else
return ( StringFormat ( " %s: deal #%I64u %s, type %s %.2f %s at %s " ,
indexs , deal . ticket , DealEntryDescription ( deal . entry ) , DealTypeDescription ( deal . type ) ,
deal . profit , AccountInfoString ( ACCOUNT_CURRENCY ) , TimeToString ( deal . time ) ) ) ;
}
//+------------------------------------------------------------------+
//| Возвращает описание сделки |
//+------------------------------------------------------------------+
string DealDescription ( CDeal * deal , const int index )
{
string indexs = StringFormat ( " % 5d " , index ) ;
if ( deal . TypeDeal ( ) ! = DEAL_TYPE_BALANCE )
return ( StringFormat ( " %s: deal #%I64u %s, type %s, Position #%I64d %s (magic %I64d), Price %.*f at %s, sl %.*f, tp %.*f " ,
indexs , deal . Ticket ( ) , DealEntryDescription ( deal . Entry ( ) ) , DealTypeDescription ( deal . TypeDeal ( ) ) ,
deal . PositionID ( ) , deal . Symbol ( ) , deal . Magic ( ) , deal . Digits ( ) , deal . Price ( ) ,
TimeToString ( deal . Time ( ) , TIME_DATE | TIME_MINUTES | TIME_SECONDS ) , deal . Digits ( ) , deal . SL ( ) , deal . Digits ( ) , deal . TP ( ) ) ) ;
else
return ( StringFormat ( " %s: deal #%I64u %s, type %s %.2f %s at %s " ,
indexs , deal . Ticket ( ) , DealEntryDescription ( deal . Entry ( ) ) , DealTypeDescription ( deal . TypeDeal ( ) ) ,
deal . Profit ( ) , AccountInfoString ( ACCOUNT_CURRENCY ) , TimeToString ( deal . Time ( ) ) ) ) ;
}
//+------------------------------------------------------------------+
//| Распечатывает в журнале данные сделки |
//+------------------------------------------------------------------+
void DealPrint ( SDeal & deal , const int index )
{
Print ( DealDescription ( deal , index ) ) ;
}
//+------------------------------------------------------------------+
//| Открывает файл для записи, возвращает хэндл |
//+------------------------------------------------------------------+
bool FileOpenToWrite ( int & handle )
{
ResetLastError ( ) ;
handle = FileOpen ( PATH , FILE_WRITE | FILE_BIN | FILE_COMMON ) ;
if ( handle = = INVALID_HANDLE )
{
PrintFormat ( " %s: FileOpen() failed. Error %d " , __FUNCTION__ , GetLastError ( ) ) ;
return false ;
}
//--- успешно
return true ;
2026-03-24 13:42:05 +07:00
}
//+------------------------------------------------------------------+
2026-03-24 14:01:24 +07:00
//| Открывает файл для чтения, возвращает хэндл |
2026-03-24 13:42:05 +07:00
//+------------------------------------------------------------------+
2026-03-24 14:01:24 +07:00
bool FileOpenToRead ( int & handle )
2026-03-24 13:42:05 +07:00
{
2026-03-24 14:01:24 +07:00
ResetLastError ( ) ;
handle = FileOpen ( PATH , FILE_READ | FILE_BIN | FILE_COMMON ) ;
if ( handle = = INVALID_HANDLE )
{
PrintFormat ( " %s: FileOpen() failed. Error %d " , __FUNCTION__ , GetLastError ( ) ) ;
return false ;
}
//--- успешно
return true ;
}
//+------------------------------------------------------------------+
//| Сохраняет в файл данные сделок из массива |
//+------------------------------------------------------------------+
bool FileWriteDealsFromArray ( SDeal & array [ ] , ulong & file_size )
{
//--- если передан пустой массив - сообщаем об этом и возвращаем false
if ( array .Size ( ) = = 0 )
{
PrintFormat ( " %s: Error! Empty deals array passed " , __FUNCTION__ ) ;
return false ;
}
//--- откроем файл для записи, получим его хэндл
int handle = INVALID_HANDLE ;
if ( ! FileOpenToWrite ( handle ) )
return false ;
//--- переместим файловый указатель на конец файла
bool res = true ;
ResetLastError ( ) ;
res & = FileSeek ( handle , 0 , SEEK_END ) ;
if ( ! res )
PrintFormat ( " %s: FileSeek(SEEK_END) failed. Error %d " , __FUNCTION__ , GetLastError ( ) ) ;
2026-03-24 13:42:05 +07:00
2026-03-24 14:01:24 +07:00
//--- запишем данные массива в конец файла
file_size = 0 ;
res & = ( FileWriteArray ( handle , array ) = = array .Size ( ) ) ;
if ( ! res )
PrintFormat ( " %s: FileWriteArray() failed. Error " , __FUNCTION__ , GetLastError ( ) ) ;
else
file_size = FileSize ( handle ) ;
//--- закрываем файл
FileClose ( handle ) ;
return res ;
2026-03-24 13:42:05 +07:00
}
//+------------------------------------------------------------------+
2026-03-24 14:01:24 +07:00
//| Загружает в массив данные сделок из файла |
2026-03-24 13:42:05 +07:00
//+------------------------------------------------------------------+
2026-03-24 14:01:24 +07:00
bool FileReadDealsToArray ( SDeal & array [ ] , ulong & file_size )
2026-03-24 13:42:05 +07:00
{
2026-03-24 14:01:24 +07:00
//--- откроем файл для чтения, получим его хэндл
int handle = INVALID_HANDLE ;
if ( ! FileOpenToRead ( handle ) )
return false ;
//--- переместим файловый указатель на конец файла
bool res = true ;
ResetLastError ( ) ;
2026-03-24 13:42:05 +07:00
2026-03-24 14:01:24 +07:00
//--- прочитаем данные из файла в массив
file_size = 0 ;
res = ( FileReadArray ( handle , array ) > 0 ) ;
if ( ! res )
PrintFormat ( " %s: FileWriteArray() failed. Error " , __FUNCTION__ , GetLastError ( ) ) ;
else
file_size = FileSize ( handle ) ;
//--- закрываем файл
FileClose ( handle ) ;
return res ;
2026-03-24 13:42:05 +07:00
}
//+------------------------------------------------------------------+
2026-03-24 14:01:24 +07:00
//| Подготавливает файл со сделками истории |
2026-03-24 13:42:05 +07:00
//+------------------------------------------------------------------+
2026-03-24 14:01:24 +07:00
bool PreparesDealsHistoryFile ( SDeal & deals_array [ ] )
2026-03-24 13:42:05 +07:00
{
2026-03-24 14:01:24 +07:00
//--- сохраним все сделки счёта в массив сделок
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 ) ;
2026-03-24 13:42:05 +07:00
2026-03-24 14:01:24 +07:00
//--- теперь для проверки прочитаем данные из файла в массив
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 ;
2026-03-24 13:42:05 +07:00
}
//+------------------------------------------------------------------+
2026-03-24 14:01:24 +07:00
//| Создаёт объект сделки из структуры |
//+------------------------------------------------------------------+
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 < total ; i + + )
{
//--- получаем очередную сделку и, если это не покупка и не продажа - идём к следующей
SDeal deal_str = array_deals [ i ] ;
if ( deal_str . type ! = DEAL_TYPE_BUY & & deal_str . type ! = DEAL_TYPE_SELL )
continue ;
//--- найдём торговый объект в списке, у которого символ равен символу сделки
string symbol = deal_str . Symbol ( ) ;
SymbTradeTmp . SetSymbol ( symbol ) ;
list_symbols .Sort ( ) ;
int index = list_symbols . Search ( & SymbTradeTmp ) ;
//--- если индекс искомого объекта в списке равен -1 - такого объекта в списке нет
if ( index = = WRONG_VALUE )
{
//--- создаём новый торговый объект символа и, если создать не получилось -
//--- добавляем к результату значение false и идём к следующей сделке
SymbolTrade = new CSymbolTradeExt ( symbol , InpIndTimeframe ) ;
if ( SymbolTrade = = NULL )
{
res & = false ;
continue ;
}
//--- если торговый объект символа не удалось добавить в список -
//--- удаляем вновь созданный объект, добавляем к результату значение false
//--- и идём к следующей сделке
if ( ! list_symbols . Add ( SymbolTrade ) )
{
delete SymbolTrade ;
res & = false ;
continue ;
}
//--- инициализируем в торговом объекте заданный в настройках трейлинг
ENUM_TRAILING_MODE mode = ( ENUM_TRAILING_MODE ) InpTestingMode ;
SetTrailingParams ( mode , param ) ;
SymbolTrade . SetTrailing ( mode , InpDataIndex , InpTestedMagic , InpTrailingStart , InpTrailingStep , InpTrailingOffset , param ) ;
}
//--- иначе, если торговый объект уже существует в списке - получаем его по индексу
else
{
SymbolTrade = list_symbols . At ( index ) ;
if ( SymbolTrade = = NULL )
continue ;
}
//--- если текущей сделки ещё нет в списке сделок торгового объекта символа
if ( SymbolTrade . GetDealByTime ( deal_str . time ) = = NULL )
{
//--- создаём объект сделки по её образцу-структуре
CDeal * deal = CreateDeal ( deal_str ) ;
if ( deal = = NULL )
{
res & = false ;
continue ;
}
//--- к значению результата добавляем результат добавления объекта сделки в список сделок торгового объекта символа
res & = SymbolTrade . AddDeal ( deal ) ;
}
}
//--- возвращаем итоговый результат создания торговых объектов и добавления сделок в их списки
return res ;
}
//+------------------------------------------------------------------+
//| Выводит в журнал список торговых объектов символов |
//+------------------------------------------------------------------+
void SymbolsArrayPrint ( CArrayObj * list_symbols )
{
int total = list_symbols . Total ( ) ;
if ( total = = 0 )
return ;
Print ( " Symbols used in trading: " ) ;
for ( int i = 0 ; i < total ; i + + )
{
string index = StringFormat ( " % 3d " , i + 1 ) ;
CSymbolTradeExt * obj = list_symbols . At ( i ) ;
if ( obj = = NULL )
continue ;
PrintFormat ( " %s. %s " , index , obj . Description ( ) ) ;
}
}
//+------------------------------------------------------------------+
//| Торговля по истории |
//+------------------------------------------------------------------+
void TradeByHistory ( const string symbol = " " , const long magic = -1 )
{
datetime time = 0 ;
int total = ExtListSymbols . Total ( ) ; // количество торговых объектов в списке
//--- в цикле по всем торговым объектам символов
for ( int i = 0 ; i < total ; i + + )
{
//--- получаем очередной торговый объект
CSymbolTradeExt * obj = ExtListSymbols . At ( i ) ;
if ( obj = = NULL )
continue ;
//--- получаем текущую сделку, на которую указывает индекс списка сделок
CDeal * deal = obj . GetDealCurrent ( ) ;
if ( deal = = NULL )
continue ;
//--- фильтруем сделку по магику и символу
if ( ( magic > -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 & param [ ] )
{
//--- обнуляем все параметры
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 ) ;
}
//+------------------------------------------------------------------+