У меня немного другой подход. Поскольку у меня каждый робот и так пишет лог своих сделок, достаточно сделать скрипт. Который, при запуске его на графике инструмента запрашивает номер (magic) робота и из файла с этим инструментом и этим номером в названии загружает на график сделки - все или только лонг/только шорт (бывает полезно разделить). Такой подход позволяет работать и с неттингом, что для меня принципиально важно. И даже с роботами, торгующими в тестовом режиме - не совершая сделки, а лишь имитируя, обсчитывая и записывая результаты в файл.
//+------------------------------------------------------------------+ //| Структура сделки. Используется для создания файла истории сделок| //+------------------------------------------------------------------+ struct SDeal { ulong ticket; // Тикет сделки long order; // Ордер, на основании которого была открыта сделка long pos_id; // Идентификатор позиции long time_msc; // Время в миллисекундах datetime time; // Время double volume; // Объём double price; // Цена double profit; // Прибыль double commission; // Комиссия по сделке double swap; // Накопленный своп при закрытии double fee; // Оплата за проведение сделки, начисляется сразу после совершения сделки double sl; // Уровень Stop Loss double tp; // Уровень Take Profit ENUM_DEAL_TYPE type; // Тип ENUM_DEAL_ENTRY entry; // Способ изменения позиции ENUM_DEAL_REASON reason; // Причина или источник проведения сделки long magic; // Идентификатор эксперта int digits; // Digits символа ushort symbol[16]; // Символ ushort comment[64]; // Комментарий к сделке ushort external_id[256]; // Идентификатор сделки во внешней торговой системе (на бирже) //--- Установка строковых свойств bool SetSymbol(const string deal_symbol) { return(::StringToShortArray(deal_symbol, symbol)==deal_symbol.Length()); } bool SetComment(const string deal_comment) { return(::StringToShortArray(deal_comment, comment)==deal_comment.Length()); } bool SetExternalID(const string deal_external_id) { return(::StringToShortArray(deal_external_id, external_id)==deal_external_id.Length()); } //--- Возврат строковых свойств string Symbol(void) { return(::ShortArrayToString(symbol)); } string Comment(void) { return(::ShortArrayToString(comment)); } string ExternalID(void) { return(::ShortArrayToString(external_id)); } }; //+------------------------------------------------------------------+ //| Сохраняет сделки из истории в массив | //+------------------------------------------------------------------+ 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(); }
Не понимаю, почему игнорируется логика архитектуры.
- Если массив заполняется поэлементно, то должна быть функция для заполнения элемента.
- Если элемент является структурой, то он должен сам заполняться.
- Если сразу выделяешь резервное место под массив, зачем проводить "ресайз" на каждом заполнении?
Почему не писать, например, так?
//+------------------------------------------------------------------+ //| Структура сделки. Используется для создания файла истории сделок| //+------------------------------------------------------------------+ struct SDeal { ulong ticket; // Тикет сделки long order; // Ордер, на основании которого была открыта сделка long pos_id; // Идентификатор позиции long time_msc; // Время в миллисекундах datetime time; // Время double volume; // Объём double price; // Цена double profit; // Прибыль double commission; // Комиссия по сделке double swap; // Накопленный своп при закрытии double fee; // Оплата за проведение сделки, начисляется сразу после совершения сделки double sl; // Уровень Stop Loss double tp; // Уровень Take Profit ENUM_DEAL_TYPE type; // Тип ENUM_DEAL_ENTRY entry; // Способ изменения позиции ENUM_DEAL_REASON reason; // Причина или источник проведения сделки long magic; // Идентификатор эксперта int digits; // Digits символа ushort symbol[16]; // Символ ushort comment[64]; // Комментарий к сделке ushort external_id[256]; // Идентификатор сделки во внешней торговой системе (на бирже) //--- Установка строковых свойств bool SetSymbol(const string deal_symbol) { return(::StringToShortArray(deal_symbol, symbol)==deal_symbol.Length()); } bool SetComment(const string deal_comment) { return(::StringToShortArray(deal_comment, comment)==deal_comment.Length()); } bool SetExternalID(const string deal_external_id) { return(::StringToShortArray(deal_external_id, external_id)==deal_external_id.Length()); } //--- Возврат строковых свойств string Symbol(void) { return(::ShortArrayToString(symbol)); } string Comment(void) { return(::ShortArrayToString(comment)); } string ExternalID(void) { return(::ShortArrayToString(external_id)); } bool Set( const ulong _ticket ) { this.ticket=_ticket; this.type=(ENUM_DEAL_TYPE)HistoryDealGetInteger(_ticket, DEAL_TYPE); this.order=HistoryDealGetInteger(_ticket, DEAL_ORDER); this.entry=(ENUM_DEAL_ENTRY)HistoryDealGetInteger(_ticket, DEAL_ENTRY); this.reason=(ENUM_DEAL_REASON)HistoryDealGetInteger(_ticket, DEAL_REASON); this.time=(datetime)HistoryDealGetInteger(_ticket, DEAL_TIME); this.time_msc=HistoryDealGetInteger(_ticket, DEAL_TIME_MSC); this.pos_id=HistoryDealGetInteger(_ticket, DEAL_POSITION_ID); this.volume=HistoryDealGetDouble(_ticket, DEAL_VOLUME); this.price=HistoryDealGetDouble(_ticket, DEAL_PRICE); this.profit=HistoryDealGetDouble(_ticket, DEAL_PROFIT); this.commission=HistoryDealGetDouble(_ticket, DEAL_COMMISSION); this.swap=HistoryDealGetDouble(_ticket, DEAL_SWAP); this.fee=HistoryDealGetDouble(_ticket, DEAL_FEE); this.sl=HistoryDealGetDouble(_ticket, DEAL_SL); this.tp=HistoryDealGetDouble(_ticket, DEAL_TP); this.magic=HistoryDealGetInteger(_ticket, DEAL_MAGIC); this.SetSymbol(HistoryDealGetString(_ticket, DEAL_SYMBOL)); this.SetComment(HistoryDealGetString(_ticket, DEAL_COMMENT)); this.SetExternalID(HistoryDealGetString(_ticket, DEAL_EXTERNAL_ID)); this.digits=(int)SymbolInfoInteger(this.Symbol(), SYMBOL_DIGITS); return((bool)this.time); } }; bool SetDeal( SDeal &deal, const ulong ticket ) { //--- сохраняем только балансовые и торговые сделки const ENUM_DEAL_TYPE deal_type = (ENUM_DEAL_TYPE)HistoryDealGetInteger(ticket, DEAL_TYPE); return((deal_type!=DEAL_TYPE_BUY && deal_type!=DEAL_TYPE_SELL && deal_type!=DEAL_TYPE_BALANCE) && deal.Set(ticket)); } //+------------------------------------------------------------------+ //| Сохраняет сделки из истории в массив | //+------------------------------------------------------------------+ int SaveDealsToArray(SDeal &array[], bool logs=false) { int Amount = 0; //--- запросим историю сделок в интервале с самого начала по текущий момент if(HistorySelect(0, INT_MAX)) { //--- общее количество сделок в списке const int total=ArrayResize(array, HistoryDealsTotal()); //--- обработаем каждую сделку for(int i=0; i<total; i++) if (SetDeal(array[Amount], HistoryDealGetTicket(i))) Amount++; } //--- возвращаем количество сохранённых в массиве сделок return(ArrayResize(array, Amount)); }
Сам я очень далек от образцового написания кода. Но давайте как-то более осмысленно подходить.
Вот снова.
//+------------------------------------------------------------------+ //| Возвращает описание сделки | //+------------------------------------------------------------------+ 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))); }
Почему структура себя не распечатывает?
Если элемент является структурой, то он должен сам заполняться.
Покажите стандартные самораспечатывающиеся и самозаполняющиеся структуры, пожалуйста.
Эти коды пишутся для упрощения понимания.
Нет цели всё завернуть в неудобочитаемую, упакованную в одну строчку монструозность, пусть и работающую.
Статьи пишутся, чтобы как можно понятнее донести суть, а не виртуозность написания кода. Для виртуозности есть специальные ветки, и Вы в них участвуете. И они полезны.
Статьи же несут иную цель.
Покажите стандартные самораспечатывающиеся и самозаполняющиеся структуры, пожалуйста.
Если речь идет про структуры от MQ, то таких нет в MQL5. Авторы не могли знать, что и кому понадобится. Поэтому там только база и возможность наследования от них, чтобы наделять стандартные структуры необходимым пользователю функционалом.
Эти коды пишутся для упрощения понимания.
Нет цели всё завернуть в неудобочитаемую, упакованную в одну строчку монструозность, пусть и работающую.
Такого не предлагаю.
Статьи пишутся, чтобы как можно понятнее донести суть, а не виртуозность написания кода. Для виртуозности есть специальные ветки, и Вы в них участвуете. И они полезны.
Статьи же несут иную цель.
Виртуозность - это автор Учебника. Выступаю за логичность.
Если объект может что-то делать с собой без посторонней помощи, то он должен это делать, а не за него. Должна же прослеживаться минимальная иерархия в архитектуре написания кода. Вы же в своем коде предлагаете лишь только несколько отдельных массивов свойств сделок заменить на один массив свойств сделок, используя структуру. Ну а дальше от структуры используете почти (чтение/запись и приравнивание) ничего. Все тот же процедурный стиль работы со свойствами сделок.
Если речь идет про структуры от MQ, то таких нет в MQL5. Авторы не могли знать, что и кому понадобится. Поэтому там только база и возможность наследования от них, чтобы наделять стандартные структуры необходимым пользователю функционалом.
Такого не предлагаю.
Виртуозность - это автор Учебника. Выступаю за логичность.
Если объект может что-то делать с собой без посторонней помощи, то он должен это делать, а не за него. Должна же прослеживаться минимальная иерархия в архитектуре написания кода. Вы же в своем коде предлагаете лишь только несколько отдельных массивов свойств сделок заменить на один массив свойств сделок, используя структуру. Ну а дальше от структуры используете почти (чтение/запись и приравнивание) ничего. Все тот же процедурный стиль работы со свойствами сделок.
Возможности, Вами предлагаемые, я использую в статьях по библиотеке. Здесь не нужно.
//+------------------------------------------------------------------+ //| Открывает файл для записи, возвращает хэндл | //+------------------------------------------------------------------+ 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); res=(ArrayResize(array, FileReadArray(handle, array))>0); if(!res) PrintFormat("%s: FileWriteArray() failed. Error ",__FUNCTION__, GetLastError()); else file_size=FileSize(handle); //--- закрываем файл FileClose(handle); return res; }
В выделении частая ошибка и ее исправление.
Но все же почему не написать иначе весь этот код?
//+------------------------------------------------------------------+ //| Сохраняет в файл данные сделок из массива | //+------------------------------------------------------------------+ bool FileWriteDealsFromArray(SDeal &array[], long &file_size) { return((file_size = FileSave(PATH, array, FILE_COMMON) * sizeof(SDeal)) > 0); } //+------------------------------------------------------------------+ //| Загружает в массив данные сделок из файла | //+------------------------------------------------------------------+ bool FileReadDealsToArray2(SDeal &array[], long &file_size) { return((file_size = ArrayResize(array, (int)FileLoad(PATH, array, FILE_COMMON)) * sizeof(SDeal)) > 0); }
Статья рассчитана на достаточно низкий уровень подготовки. Для охвата широкой части читающих.
Форум по трейдингу, автоматическим торговым системам и тестированию торговых стратегий
Тестер стратегий MetaTrader 5: ошибки, баги, предложения по улучшению работы
fxsaber, 2019.09.06 15:45
Хорошую функцию игнорируете
MqlTick tiks[]; if (FileLoad("deribit1.out.bin", ticks)) { // ....
- Бесплатные приложения для трейдинга
- 8 000+ сигналов для копирования
- Экономические новости для анализа финансовых рынков
Вы принимаете политику сайта и условия использования
Опубликована статья Визуальная оценка и корректировка торговли в MetaTrader 5:
В тестере стратегий можно не только оптимизировать параметры торгового робота. Мы покажем, как оценить постфактум проторгованную историю своего счёта и внести корректировки в торговлю в тестере, изменяя размеры стоп-приказов открываемых позиций.
Представим ситуацию: на каком-либо счёте достаточно долгое время ведётся более-менее активная торговля на разных инструментах разными советниками и где-то даже вручную. И вот, по истечении некоторого времени, мы хотим видеть результаты работы. Естественно, можно посмотреть стандартные отчёты по торговле в терминале, нажав сочетание клавиш Alt+E. Можно загрузить значки сделок на график и посмотреть входы и выходы позиций. Но что, если хочется в динамике посмотреть как велась торговля, где и как открывались и закрывались позиции? Посмотреть раздельно по каждому символу, либо сразу все вместе, открытие и закрытие позиций, на каких уровнях ставились стоп-приказы, и обоснован ли был их размер. А что если далее мы зададимся вопросом "что бы было, если бы ..." (и тут много вариантов — другие стопы, по иным алгоритмам и критериям, использование трейлинга позиций, либо перенос стопов в безубыток и т.д.); а потом ещё и протестировать все свои "если" с наглядным видимым результатом. Как бы могла измениться торговля, если бы...
Для решения таких вопросов, оказывается, всё уже есть. Нам достаточно лишь загрузить историю счёта в файл — все проведённые сделки, а затем в тестере стратегий прогнать советник, который читает сделки из файла и открывает/закрывает позиции в тестере стратегий клиентского терминала. При наличии такого советника, мы сможем добавить в него код для изменения условий выхода из позиций и сравнить, как при этом изменилась торговля, и что бы было, если бы...
Что нам это даст? Ещё один инструмент для поиска лучших результатов, для внесения корректировок в торговлю, которая некоторое время уже велась на счёте; визуальное тестирование позволит в динамике видеть верно ли открывались позиции на том, или ином инструменте, и в нужное ли время они закрывались, и т.д. И главное — какой-то новый алгоритм можно просто добавить в код советника, протестировать, получить результат и внести корректировки в советники, работающие на этом счёте.
Автор: Artyom Trishkin