Article-16991-MQL5-Post-Fac.../Article-16991-MQL5-Post-Factum-Trade-Analysis-Trailing-Stop.mq5
2026-03-24 14:01:24 +07:00

934 lines
No EOL
48 KiB
MQL5

//+------------------------------------------------------------------+
//| 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; i<total; i++)
{
CSymbolTradeExt *obj=ExtListSymbols.At(i);
if(obj==NULL)
continue;
CopyTime(obj.Symbol(), PERIOD_CURRENT, 0, 1, array);
}
//--- Всё успешно
return(INIT_SUCCEEDED);
}
//+------------------------------------------------------------------+
//| Expert deinitialization function |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
//--- очищаем созданные списки и массивы
ExtListSymbols.Clear();
ArrayFree(ExtArrayDeals);
}
//+------------------------------------------------------------------+
//| Expert tick function |
//+------------------------------------------------------------------+
void OnTick()
{
//--- работаем только в тестере стратегий
if(!MQLInfoInteger(MQL_TESTER))
return;
//--- Тралим открытые позиции
Trailing();
//--- Обрабатываем список сделок из файла
TradeByHistory(InpTestedSymbol, InpTestedMagic);
}
//+------------------------------------------------------------------+
//| Tester function |
//+------------------------------------------------------------------+
double OnTester(void)
{
//--- посчитаем и вернём общее количество обработанных в тестере сделок
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;
}
//--- общее количество сделок в списке
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;
}
//+------------------------------------------------------------------+
//| Открывает файл для чтения, возвращает хэндл |
//+------------------------------------------------------------------+
bool FileOpenToRead(int &handle)
{
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());
//--- запишем данные массива в конец файла
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;
}
//+------------------------------------------------------------------+
//| Загружает в массив данные сделок из файла |
//+------------------------------------------------------------------+
bool FileReadDealsToArray(SDeal &array[], ulong &file_size)
{
//--- откроем файл для чтения, получим его хэндл
int handle=INVALID_HANDLE;
if(!FileOpenToRead(handle))
return false;
//--- переместим файловый указатель на конец файла
bool res=true;
ResetLastError();
//--- прочитаем данные из файла в массив
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;
}
//+------------------------------------------------------------------+
//| Подготавливает файл со сделками истории |
//+------------------------------------------------------------------+
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<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);
}
//+------------------------------------------------------------------+