Обсуждение статьи "Библиотека для простого и быстрого создания программ для MetaTrader (Часть XXV): Обработка ошибок, возвращаемых торговым сервером"
Биржевые символы, брокер Открытие, version 5.00 build 2190.
Извините там в 23 части было продолжение обсуждения, но я закрутился и пропустил. В общем скачал 25 версию проблема осталась. В обсуждении 23 части Вы написали что надо вставлять добавленные строки в onInit'е после определенных строк, но этих строк выделенных Вами зеленым у меня в onInit'е вообще нет ни в 23 версии ни в 25, вот весь onInit :
//--- Установка глобальных переменных советника
prefix=MQLInfoString(MQL_PROGRAM_NAME)+"_";
for(int i=0;i<TOTAL_BUTT;i++)
{
butt_data[i].name=prefix+EnumToString((ENUM_BUTTONS)i);
butt_data[i].text=EnumToButtText((ENUM_BUTTONS)i);
}
lot=NormalizeLot(Symbol(),fmax(InpLots,MinimumLots(Symbol())*2.0));
magic_number=InpMagic;
stoploss=InpStopLoss;
takeprofit=InpTakeProfit;
distance_pending=InpDistance;
distance_stoplimit=InpDistanceSL;
slippage=InpSlippage;
trailing_stop=InpTrailingStop*Point();
trailing_step=InpTrailingStep*Point();
trailing_start=InpTrailingStart;
stoploss_to_modify=InpStopLossModify;
takeprofit_to_modify=InpTakeProfitModify;
//--- Инициализация библиотеки DoEasy
OnInitDoEasy();
//--- Проверка и удаление неудалённых графических объектов советника
if(IsPresentObects(prefix))
ObjectsDeleteAll(0,prefix);
//--- Создание панели кнопок
if(!CreateButtons(InpButtShiftX,InpButtShiftY))
return INIT_FAILED;
//--- Установка состояния кнопки активизации трейлингов
ButtonState(butt_data[TOTAL_BUTT-1].name,trailing_on);
//tr.SetCorrectTypeExpiration();
engine.TradingSetCorrectTypeExpiration();
engine.TradingSetCorrectTypeFilling();
//--- Проверка воспроизведения стандартного звука по макроподстановке и пользовательского звука по описанию
engine.PlaySoundByDescription(SND_OK);
Sleep(600);
engine.PlaySoundByDescription(TextByLanguage("Звук упавшей монетки 2","The sound of a falling coin 2"));
//---
return(INIT_SUCCEEDED);
Биржевые символы, брокер Открытие, version 5.00 build 2190.
Извините там в 23 части было продолжение обсуждения, но я закрутился и пропустил. В общем скачал 25 версию проблема осталась. В обсуждении 23 части Вы написали что надо вставлять добавленные строки в onInit'е после определенных строк, но этих строк выделенных Вами зеленым у меня в onInit'е вообще нет ни в 23 версии ни в 25, вот весь onInit :
Вы используете тестовый советник из статьи 25 - "TestDoEasyPart25.mq5" ?
Сейчас я вставил onInit из версии 23 со вставленными мной добавочными функциями, но я использовал и версию 25 в которой тоже нет строк которые у Вас выделены зеленым в версии 23 и версия 25 тоже не работает как и 23.
Я не пойму, о которых строках, выделенных зелёным, вы говорите.
И, пожалуйста, не стоит использовать тестовый советник из другой статьи - к каждой статье прилагается свой советник для тестирования - его
и нужно использовать, и найденные ошибки сообщать относительно правильного тестового советника, а не из другой статьи.
Ладно, извините, применительно к последней статье:
ордера открываются, а все отложенные нет, в журнале пишет 2019.11.21 12:59:20.689 2019.11.18 10:00:02 failed sell stop limit 2.00 Si-12.19 at 63972 (64022) sl: 64072 tp: 63972 [Invalid expiration] и 2019.11.21 12:59:20.689 2019.11.18 10:00:02 Торговая попытка #3. Ошибка : Неверная дата истечения ордера в запросе
Ладно, извините, применительно к последней статье:
ордера открываются, а все отложенные нет, в журнале пишет 2019.11.21 12:59:20.689 2019.11.18 10:00:02 failed sell stop limit 2.00 Si-12.19 at 63972 (64022) sl: 64072 tp: 63972 [Invalid expiration] и 2019.11.21 12:59:20.689 2019.11.18 10:00:02 Торговая попытка #3. Ошибка : Неверная дата истечения ордера в запросе
Хорошо, спасибо, разберусь.
Можете приложить сюда скрин со спецификацией символа, на котором получаете ошибку?
Скрин спецификации Si-12.19 в файле.
В этой версии библиотеки, равно как и в следующей, я пропустил проверку типов заливки ордеров и типов их экспирации. В следующей (27) статье будут исправления.
Пока могу предложить при отсылке торгового запроса явно указывать тип экспирации. Например, для выставления отложенного ордера Sell Limit, нужно дописать в тестовом советнике:
//--- Если нажата кнопка BUTT_SELL_LIMIT: Выставить SellLimit else if(button==EnumToString(BUTT_SELL_LIMIT)) { //--- Устанавливаем ордер SellLimit engine.PlaceSellLimit(lot,Symbol(),distance_pending,stoploss,takeprofit,magic,TextByLanguage("Отложенный SellLimit","Pending order SellLimit"),0,ORDER_TIME_DAY); }
И так сделать во всех строках, в которых есть вызов методов установки
отложенных ордеров.
Артем, спасибо за проделанную работу, очень вдумчиво.
Я, если честно, не совсем понял смысла композитных магиков...
Предположим, я собираюсь разбить сделки в советнике на несколько групп с разными magic numbers.
В классическом подходе я впоследствии перебираю ордера и сравниваю магики, рассовывая результат по группам.
В библиотеке, я так понимаю, не многое меняется в этой ситуации - просто выделение этих номеров можно как-то систематизировать? Или есть еще что-то, на что они будут влиять?
И еще есть определенный слой функционала, которого мне категорически недостает в Вашей библиотеке: агрегация.
Очень часто нужно ответить на вопрос, каков совокупный профит по набору параметров (магик, символ, тип ордера). И совокупный лот, например. И уровень безубытка. И количество сделок.
Эти агрегированные величины нужны повсеместно и многократно, вычислять их по выборке в каждом новом методе - явный оверкилл.
Было бы здорово завести набор агрегаций, сконфигурировав каждую набором критериев выборки (символ, тип, магик, номер группы) и предоставить доступ к ним в CEngine - и обновлять раз в Refresh.
Если у Вас не было планов такого рода доработок, я мог бы дописать и поделиться... но только у нас существенно разный стиль кода, этот кусок будет выглядеть инопланетянином.
Артем, спасибо за проделанную работу, очень вдумчиво.
Я, если честно, не совсем понял смысла композитных магиков...
Предположим, я собираюсь разбить сделки в советнике на несколько групп с разными magic numbers.
В классическом подходе я впоследствии перебираю ордера и сравниваю магики, рассовывая результат по группам.
В библиотеке, я так понимаю, не многое меняется в этой ситуации - просто выделение этих номеров можно как-то систематизировать? Или есть еще что-то, на что они будут влиять?
И еще есть определенный слой функционала, которого мне категорически недостает в Вашей библиотеке: агрегация.
Очень часто нужно ответить на вопрос, каков совокупный профит по набору параметров (магик, символ, тип ордера). И совокупный лот, например. И уровень безубытка. И количество сделок.
Эти агрегированные величины нужны повсеместно и многократно, вычислять их по выборке в каждом новом методе - явный оверкилл.
Было бы здорово завести набор агрегаций, сконфигурировав каждую набором критериев выборки (символ, тип, магик, номер группы) и предоставить доступ к ним в CEngine - и обновлять раз в Refresh.
Если у Вас не было планов такого рода доработок, я мог бы дописать и поделиться... но только у нас существенно разный стиль кода, этот кусок будет выглядеть инопланетянином.
Про информацию, записанную в значение Magic Number:
вы можете для создания различных групп использовать для каждой группы свой собственный магик. Например, если магик советника 123, то
магик первой группы будет 124, магик второй группы 125, третьей - 126, и т.д..
Библиотека предлагает иной способ создания различных групп - номер каждой подгруппы хранится прямо в значении Magic Number. ТОгда магик
советника - это тоже идентификатор группы, но он вынесен в независимую группу, которая называется MagicID - идентификатор магика
советника. И есть ещё две группы. В каждой из них есть 15 подгрупп. И каждой из подгрупп можно задать свой идентификатор.
Это даст большую гибкость при работе с группами.
Например: Мы хотим перемещать сетку отложенных ордеров за ценой - добавляем их в группу 1 в подгруппу 1. Группа 1 перемещается за ценой. Подгруппа 1 перемещается по МА. Теперь какие-то из тех ордеров, которые перемещаются за ценой (группа 1) мы хотим перемещать по Parabolic SAR. Даём им подгруппу 2. Тогда группа 1 - перемещается за ценой, но подгруппа 1 перемещается по МА, а подгруппа 2 - по Parabolic SAR.
Ордера срабатывают, превращаясь в позиции - можно задать свои группы для модификации стоплосс, и свои подгруппы в этой группе для модификации по разным значениям. Алгоритмы модификации записываем в подгруппы.
В общем - полёт фантазии. Простым магиком тоже можно, но придётся самому придумывать логику отслеживания различных групп.
По второму вопросу:
Есть класс CSelect. Он доступен из программы, и предоставляет способы выбора и поиска, о которых вы пишете, из всех существующих коллекций: Account, Event, Order, Symbol.
Можно выбрать объекты каждой коллекции в список по всем критериям. В созданном списке можно заново отобрать по уточняющим критериям, можно найти максимальную и минимальную величины по критерию выборки.
Впрочем, далее будут пользовательские функции (много позже) для быстрого и удобного доступа ко всем свойствам всех коллекций и поиска в них.
Но пока - только посредством CSelect, и тогда, когда вам это нужно. Класс статический, поэтому доступ к к его методам через "::" Например, CSelect::ByOrderProperty().
Да, кстати, и пример использования в программе есть прямо в тестовом советнике - например, в его функциях трейлинга:
//+------------------------------------------------------------------+ //| Трал самых дальних отложенных ордеров | //+------------------------------------------------------------------+ void TrailingOrders(void) { MqlTick tick; if(!SymbolInfoTick(Symbol(),tick)) return; double stop_level=StopLevel(Symbol(),2)*Point(); //--- Получаем список всех установленных ордеров CArrayObj* list=engine.GetListMarketPendings(); //--- Выбираем из списка только ордера по текущему символу list=CSelect::ByOrderProperty(list,ORDER_PROP_SYMBOL,Symbol(),EQUAL); //--- Выбираем из списка только ордера в направлении Buy CArrayObj* list_buy=CSelect::ByOrderProperty(list,ORDER_PROP_DIRECTION,ORDER_TYPE_BUY,EQUAL); //--- Сортируем список по дистанции от цены в пунктах (по прибыли в пунктах) list_buy.Sort(SORT_BY_ORDER_PROFIT_PT); //--- Получаем индекс ордера в направлении Buy с наибольшей дистанцией int index_buy=CSelect::FindOrderMax(list_buy,ORDER_PROP_PROFIT_PT); if(index_buy>WRONG_VALUE) { COrder* buy=list_buy.At(index_buy); if(buy!=NULL) { //--- Если ордер установлен ниже цены (BuyLimit), и его необходимо "поднимать" за ценой if(buy.TypeOrder()==ORDER_TYPE_BUY_LIMIT) { //--- Рассчитываем новую цену установки ордера и стоп-уровни от новой цены double price=NormalizeDouble(tick.ask-trailing_stop,Digits()); double sl=(buy.StopLoss()>0 ? NormalizeDouble(price-(buy.PriceOpen()-buy.StopLoss()),Digits()) : 0); double tp=(buy.TakeProfit()>0 ? NormalizeDouble(price+(buy.TakeProfit()-buy.PriceOpen()),Digits()) : 0); //--- Если рассчитанная цена ниже, чем дистанция StopLevel, отложенная от цены установки ордера Ask (соблюдена дистанция по StopLevel) if(price<tick.ask-stop_level) { //--- Если рассчитанная цена выше, чем шаг трала, отложенный от цены установки ордера - модифицируем цену установки ордера if(price>buy.PriceOpen()+trailing_step) { engine.ModifyOrder((ulong)buy.Ticket(),price,sl,tp,-1); } } } //--- Если ордер установлен выше цены (BuyStop и BuyStopLimit), и его необходимо "опускать" за ценой else { //--- Рассчитываем новую цену установки ордера и стоп-уровни от новой цены double price=NormalizeDouble(tick.ask+trailing_stop,Digits()); double sl=(buy.StopLoss()>0 ? NormalizeDouble(price-(buy.PriceOpen()-buy.StopLoss()),Digits()) : 0); double tp=(buy.TakeProfit()>0 ? NormalizeDouble(price+(buy.TakeProfit()-buy.PriceOpen()),Digits()) : 0); //--- Если рассчитанная цена выше, чем дистанция StopLevel, отложенная от цены установки ордера Ask (соблюдена дистанция по StopLevel) if(price>tick.ask+stop_level) { //--- Если рассчитанная цена ниже, чем шаг трала, отложенный от цены установки ордера - модифицируем цену установки ордера if(price<buy.PriceOpen()-trailing_step) { engine.ModifyOrder((ulong)buy.Ticket(),price,sl,tp,-1); } } } } } //--- Выбираем из списка только ордера в направлении Sell CArrayObj* list_sell=CSelect::ByOrderProperty(list,ORDER_PROP_DIRECTION,ORDER_TYPE_SELL,EQUAL); //--- Сортируем список по дистанции от цены в пунктах (по прибыли в пунктах) list_sell.Sort(SORT_BY_ORDER_PROFIT_PT); //--- Получаем индекс ордера в направлении Sell с наибольшей дистанцией int index_sell=CSelect::FindOrderMax(list_sell,ORDER_PROP_PROFIT_PT); if(index_sell>WRONG_VALUE) { COrder* sell=list_sell.At(index_sell); if(sell!=NULL) { //--- Если ордер установлен выше цены (SellLimit), и его необходимо "опускать" за ценой if(sell.TypeOrder()==ORDER_TYPE_SELL_LIMIT) { //--- Рассчитываем новую цену установки ордера и стоп-уровни от новой цены double price=NormalizeDouble(tick.bid+trailing_stop,Digits()); double sl=(sell.StopLoss()>0 ? NormalizeDouble(price+(sell.StopLoss()-sell.PriceOpen()),Digits()) : 0); double tp=(sell.TakeProfit()>0 ? NormalizeDouble(price-(sell.PriceOpen()-sell.TakeProfit()),Digits()) : 0); //--- Если рассчитанная цена выше, чем дистанция StopLevel, отложенная от цены установки ордера Bid (соблюдена дистанция по StopLevel) if(price>tick.bid+stop_level) { //--- Если рассчитанная цена ниже, чем шаг трала, отложенный от цены установки ордера - модифицируем цену установки ордера if(price<sell.PriceOpen()-trailing_step) { engine.ModifyOrder((ulong)sell.Ticket(),price,sl,tp,-1); } } } //--- Если ордер установлен ниже цены (SellStop и SellStopLimit), и его необходимо "поднимать" за ценой else { //--- Рассчитываем новую цену установки ордера и стоп-уровни от новой цены double price=NormalizeDouble(tick.bid-trailing_stop,Digits()); double sl=(sell.StopLoss()>0 ? NormalizeDouble(price+(sell.StopLoss()-sell.PriceOpen()),Digits()) : 0); double tp=(sell.TakeProfit()>0 ? NormalizeDouble(price-(sell.PriceOpen()-sell.TakeProfit()),Digits()) : 0); //--- Если рассчитанная цена ниже, чем дистанция StopLevel, отложенная от цены установки ордера Bid (соблюдена дистанция по StopLevel) if(price<tick.bid-stop_level) { //--- Если рассчитанная цена выше, чем шаг трала, отложенный от цены установки ордера - модифицируем цену установки ордера if(price>sell.PriceOpen()+trailing_step) { engine.ModifyOrder((ulong)sell.Ticket(),price,sl,tp,-1); } } } } } } //+------------------------------------------------------------------+
![MQL5 - Язык торговых стратегий для клиентского терминала MetaTrader 5](https://c.mql5.com/i/registerlandings/logo-2.png)
- Бесплатные приложения для трейдинга
- 8 000+ сигналов для копирования
- Экономические новости для анализа финансовых рынков
Вы принимаете политику сайта и условия использования
Опубликована статья Библиотека для простого и быстрого создания программ для MetaTrader (Часть XXV): Обработка ошибок, возвращаемых торговым сервером:
После того, как мы отправили торговый приказ на сервер, не стоит считать, что "дело сделано". Теперь нам необходимо проверить коды ошибок, ну или отсутствие ошибок. В статье рассмотрим обработку ошибок, возвращаемых торговым сервером, подготовим базу для создания отложенных торговых запросов.
В последних версиях MetaTrader 5, начиная с билда 2201 в тестере появилась возможность установить параметры символа, на котором проводится тестирование. Таким образом, возможно задать ограничения для торговли на символе и протестировать поведение библиотеки при обнаружении установленных для символа ограничениях.
Для вызова окна настроек символа нужно кликнуть по кнопке, находящейся справа от выбора тестируемого таймфрейма:
Установим для символа разрешение торговли только длинными позициями, и поставим ограничение объёма одновременно открытых позиций и установленных отложенных ордеров в одну сторону равным 0.5.
Таким образом мы сможем торговать долько длинными позициями, и иметь в рынке максимальный совокупный объём позиций и ордеров на покупку не более 0.5 лота. Т.е. при открытии позиций лотом 0.1, мы сможем открыть только пять позиций, или установить один отложенный ордер на покупку и открыть четыре позиции:
Здесь для чистоты эксперимента, конечно же, нужно было отключить автоматическое закрытие позиций при превышении прибыли на заданную в настройках величину. Но, в принципе, и так видно, что открыть короткую позицию в самом начале мы не смогли — получили предупреждение о том, что на символе разрешены только покупки, а далее, при попытках открыть количество позиций, совокупный объём которых превысит 0.5 лота, мы получаем сообщение о невозможности открыть позицию, так как будет превышен максимальный совокупный объём позиций и ордеров в одном направлении.
Автор: Artyom Trishkin