Обсуждение статьи "Библиотека для простого и быстрого создания программ для MetaTrader (Часть XXV): Обработка ошибок, возвращаемых торговым сервером"

 

Опубликована статья Библиотека для простого и быстрого создания программ для MetaTrader (Часть XXV): Обработка ошибок, возвращаемых торговым сервером:

После того, как мы отправили торговый приказ на сервер, не стоит считать, что "дело сделано". Теперь нам необходимо проверить коды ошибок, ну или отсутствие ошибок. В статье рассмотрим обработку ошибок, возвращаемых торговым сервером, подготовим базу для создания отложенных торговых запросов.

В последних версиях MetaTrader 5, начиная с билда 2201 в тестере появилась возможность установить параметры символа, на котором проводится тестирование. Таким образом, возможно задать ограничения для торговли на символе и протестировать поведение библиотеки при обнаружении установленных для символа ограничениях.

Для вызова окна настроек символа нужно кликнуть по кнопке, находящейся справа от выбора тестируемого таймфрейма:

Установим для символа разрешение торговли только длинными позициями, и поставим ограничение объёма одновременно открытых позиций и установленных отложенных ордеров в одну сторону равным 0.5.

Таким образом мы сможем торговать долько длинными позициями, и иметь в рынке максимальный совокупный объём позиций и ордеров на покупку не более 0.5 лота. Т.е. при открытии позиций лотом 0.1, мы сможем открыть только пять позиций, или установить один отложенный ордер на покупку и открыть четыре позиции:


Здесь для чистоты эксперимента, конечно же, нужно было отключить автоматическое закрытие позиций при превышении прибыли на заданную в настройках величину. Но, в принципе, и так видно, что открыть короткую позицию в самом начале мы не смогли — получили предупреждение о том, что на символе разрешены только покупки, а далее, при попытках открыть количество позиций, совокупный объём которых превысит 0.5 лота, мы получаем сообщение о невозможности открыть позицию, так как будет превышен максимальный совокупный объём позиций и ордеров в одном направлении.

Автор: Artyom Trishkin

 
 Здравствуйте! Скачал последнюю версию библиотеки и Експерт Part_23, поставил на тестер визуальный режим и рыночные
ордера открываются, а все отложенные нет, в журнале пишет "2019.10.27 10:13:32.157 2019.09.23 10:00:02   failed sell stop limit 2.00 RTS-12.19 at 135750 (135800) sl: 135900 tp: 135600 [Invalid expiration].

Биржевые символы, брокер Открытие, 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);

 

 
Alexander:
 Здравствуйте! Скачал последнюю версию библиотеки и Експерт Part_23, поставил на тестер визуальный режим и рыночные
ордера открываются, а все отложенные нет, в журнале пишет "2019.10.27 10:13:32.157 2019.09.23 10:00:02   failed sell stop limit 2.00 RTS-12.19 at 135750 (135800) sl: 135900 tp: 135600 [Invalid expiration].

Биржевые символы, брокер Открытие, version 5.00 build 2190.

  Извините там в 23 части было продолжение обсуждения, но я закрутился и пропустил. В общем скачал 25 версию проблема осталась. В обсуждении 23 части Вы написали что надо вставлять добавленные строки в onInit'е после определенных строк, но этих строк выделенных Вами зеленым у меня в onInit'е вообще нет ни в 23 версии ни в 25, вот весь onInit :

Вы используете тестовый советник из статьи 25 - "TestDoEasyPart25.mq5" ?

 
 Сейчас я вставил onInit из версии 23 со вставленными мной добавочными функциями, но я использовал и версию 25 в которой тоже нет строк которые у Вас выделены зеленым в версии 23 и версия 25 тоже не работает как и 23.
 
Alexander:
 Сейчас я вставил onInit из версии 23 со вставленными мной добавочными функциями, но я использовал и версию 25 в которой тоже нет строк которые у Вас выделены зеленым в версии 23 и версия 25 тоже не работает как и 23.

Я не пойму, о которых строках, выделенных зелёным, вы говорите.

И, пожалуйста, не стоит использовать тестовый советник из другой статьи - к каждой статье прилагается свой советник для тестирования - его и нужно использовать, и найденные ошибки сообщать относительно правильного тестового советника, а не из другой статьи.

 

 Ладно, извините, применительно к последней статье:

 Здравствуйте! Скачал последнюю версию библиотеки и Експерт Part_25, поставил на тестер визуальный режим и рыночные

ордера открываются, а все отложенные нет, в журнале пишет 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. Ошибка : Неверная дата истечения ордера в запросе

Биржевые символы, брокер Открытие, version 5.00 build 2190.
 
Alexander:

 Ладно, извините, применительно к последней статье:

 Здравствуйте! Скачал последнюю версию библиотеки и Експерт Part_25, поставил на тестер визуальный режим и рыночные

ордера открываются, а все отложенные нет, в журнале пишет 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. Ошибка : Неверная дата истечения ордера в запросе

Биржевые символы, брокер Открытие, version 5.00 build 2190.

Хорошо, спасибо, разберусь.

Можете приложить сюда скрин со спецификацией символа, на котором получаете ошибку?

 
 Скрин спецификации Si-12.19 в файле.
Файлы:
 
Alexander:
 Скрин спецификации 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.

Если у Вас не было планов такого рода доработок, я мог бы дописать и поделиться... но только у нас существенно разный стиль кода, этот кусок будет выглядеть инопланетянином.

 
rigal:

Артем, спасибо за проделанную работу, очень вдумчиво.

Я, если честно, не совсем понял смысла композитных магиков... 

Предположим, я собираюсь разбить сделки в советнике на несколько групп с разными 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);
                 }
              }
           }
        }
     }
  }
//+------------------------------------------------------------------+
Причина обращения: