OrderSendAsync не возвращает номер тикета (OnTradeTransaction - ловля блох или асинхронных хаос? )

 

Уважаемые форумчане,

Хочу поделиться мнением и послушать мнения и опыт коллег, по поводу OrderSend, OrderSendAsync и обработчика OnTradeTransaction. Обработчик OnTrade, на мой взгляд, жалкий рудимент, не заслуживающий большого внимания, в силу ограниченного и упрощенного функционала.

Опишу, с точки зрения полученного опыта, достоинства и недостатки торговых функций OrderSend, OrderSendAsync...

OrderSend
Достоинства:
  1. Простота использования,
Недостатки:
  1. Задержки (на демо-серверах MetaQuotes получал задержки более минуты).
  2. Непредсказуемость результата (даже получив код возврата 10009 вам не гарантирован результат - все равно приходится анализировать OnTradeTransaction)
  3. Блокирование потока исполнения (вы полностью теряете управление, пока функция не вернет управление, а следовательно вы никак не в состоянии повлиять на исполнение Эксперта и обработать критические ситуации)
OrderSendAsync
Достоинства:
  1. Полный контроль исполнения (хотя он обманчив, о чем собственно этот пост)
Недостатки:
  1. Низкий уровень информативности работы функции:
    • Обработка структуры MqlTradeResult после вызова функции, практически, бесполезна:
      • retcode ничего вам не даст. В лучшем случае вы получите 10008 - "ордер размещен", хотя правильнее было бы сказать "ордер передан" (или помещен в очередь серверного обработчика);
      • Вы можете получить нулевой deal (тикет сделки),
    • Реальный результат отправки приказа и его тикет возможно получить только при последующей обработке транзакции типа TRADE_TRANSACTION_REQUEST, через связку по request_id;
    • request_id малоинформативен и привязан только к типу TRADE_TRANSACTION_REQUEST и не имеет сквозной привязки к транзакциям по данной сделке,
  2. Непредсказуемость очередности прихода транзакций в обработчик OnTradeTransaction:
    • Логично было бы предположить, что транзакция запроса (TRADE_TRANSACTION_REQUEST) должна прийти первой, а после нее уже должны идти TRADE_TRANSACTION_(ORDER, DEAL и HISTORY)
    • По факту, тип TRADE_TRANSACTION_(ORDER, DEAL и HISTORY) может прийти первым, а уже затем придет TRADE_TRANSACTION_REQUEST, в следствие чего имеем ситуацию:
      • Транзакцию мы можем "связать" с результатом действия OrderSendAsync и определить ее успешность только посредством  TRADE_TRANSACTION_REQUEST;
      • Если транзикции TRADE_TRANSACTION_(ORDER, DEAL и HISTORY) пришли раньше TRADE_TRANSACTION_REQUEST - получаем "несвязанные", с передачей приказа, транзакции;
      • Временной лаг нарушения очередности может достигать, по моим наблюдениям, больше нескольких секунд,
  3. Для разрешения ситуации в п.2 необходимо буфферизировть поток транзакций:
    • Отловить в потоке TRADE_TRANSACTION_REQUEST по request_id - результат запроса, и если повезло, номер тикета;
    • Если ошибка - тут все очевидно - уходим в обработку;
    • Если успешно - нужно просмотреть весь буфер транзакций ретроспективно, по номеру тикета :
      • Если нашли "подтверждающую" сделку транзакцию - помечаем сделку как выполненную и "идем" дальше по своим делам;
      • Не нашли - уходим в ожидание нужной "подтверждающей" транзакции,
    • Если у вас много сделок - это выливается в весьма затратное занятие, и по процессорному времени и по объему памяти;
    • Буфер получается не FIFO. Т.е. нужно строить связанный список и заниматься "менеджментом" этого буфера
  4. В итоге, учитывая тот факт, что все это мы должны делать в одном потоке, мы возвращаемся к тем же проблемам, которые присутствуют в функции OrderSend.

Коллеги, буду признателен, если хорошо знающий эту тему человек, укажет на мои идеологические заблуждения или подкинет свежую идею.

Немного предыстории и размышлений...

До некоторого момента пользовался OrderSend, но при этом отслеживал окончание транзакции через OnTradeTransaction (поскольку 10009 ровным счетом ничего вам не гарантирует). Все бы ничего, но при тестировании на демо-серверах MetaQuotes одолели меня непредвиденные и, порой, чудовищные задержки OrderSend (более минуты).

Окончательно перешел на OrderSendAsync, но и тут не все гладко пошло - процентов 70% запросов не возвращают номер тикета. Не опустил руки...

Исхода из предпосылки что транзакции типа TRADE_TRANSACTION_REQUEST пройдут заведомо раньше, чем все остальные, написал код:

void OnTradeTransaction
....
if(type==TRADE_TRANSACTION_REQUEST)     {
  for( p=0; p<ArrSize; p++ )    {
    if( SignalDesc[p].mn == request.magic || SignalDesc[p].ReqID == result.request_id ) {
       /* 
          - Тут ловим транзацию по ID, который мы запомнили ранее, при отсылке запроса.
          - Смотрим TradeResut, потому что в структуре MqlTradeResult вам вернется в лучшем случае 10008, но не реальная ошибка,
          - Получаем из result злополучный тикет. 
       */
    }
  }

Соотв-но, делаем анализ маджика и request_id на совпадение, смотрим реальную торговую ошибку.

"Слава богу, что request_id  всё же возвращается в результате OrderSendAsync!" - думал я... Но радость была недолгой, когда обнаружил что транзакции типа TRADE_TRANSACTION_(ORDER, DEAL и HISTORY) прошли гораздо раньше REQUEST-a. В результате транзакция остается неотслеженной и не привязанной к запросу, поскольку в структуре  MqlTradeTransaction зацепиться не за что, если у тебя нет тикета, после отправки запроса OrderSendAsync.

Красивых решений я не вижу. Единственным выходом, на мой взгляд, может быть "коллекционирование" всех транзакций, в типе TRADE_TRANSACTION_REQUESTловить ID, смотреть ошибку и если повезет - номер тикета, после чего делать ретроспективный анализ транзакций типа TRADE_TRANSACTION_(ORDER, DEAL и HISTORY) на совпадение по тикету. Учитывая, что мониторинг невозможно сделать в отдельном потоке, всё это превращается в какую-то монстро-образную задачу и выигрыш от использования OrderSendAsync просто сводится на "нет". Проще смирится OrderSend и с его огромными задержками.

Кто-нибудь "обошёл" эти асинхронные грабли?

Господа разработчики, идеологи MetaQuotes, может всё же сделать хотя бы тикет обязательным к возвращению функцией OrderSendAsync или request_id сделать сквозным для всех типов транзакций. Без всего этого смысл использования асинхронного режима стремиться к нулю, а трудозатраты неоправданно возрастают.

Благодарю, что хватило сил прочесть до конца ))

 
решение - магик.
 
Vladimir Belozercev:

Уважаемые форумчане,

Хочу поделиться мнением и послушать мнения и опыт коллег, по поводу OrderSend, OrderSendAsync и обработчика OnTradeTransaction. Обработчик OnTrade, на мой взгляд,

Вы отправляете по одному ордеру на один тик, или несколько ордеров подряд, в цикле?

 
Alexey Viktorov:

Вы отправляете по одному ордеру на один тик, или несколько ордеров подряд, в цикле?

Я понимаю к чему Вы клоните... Об это я давно уже споткнулся и "излечился от детской иллюзорности восприятия" )).

После исполнения  OrderSend, OrderSendAsync управление отдается основному обработчику OnTick(). Если этого не сделать то невозможно получить и обработать транзакции. Когда я писал об однопоточности, я именно это имел в виду.

Это моя, видимо несбыточная, мечта, когда хотя бы OnTick, OnTrade и OnTradeTransaction будут выполняться в отдельных потоках... У меня, кстати, до сих пор вызывает недоумение появление функции Sleep. Смысл? Тяжелые циклические обработчики там сделать невозможно из-за однопоточности, а в легких - использование Sleep не имеет смысла, но это уже отдельная тема для разговора ))

 
Vladimir Belozercev:

Я понимаю к чему Вы клоните... Об это я давно уже споткнулся и "излечился от детской иллюзорности восприятия" )).

После исполнения  OrderSend, OrderSendAsync управление отдается основному обработчику OnTick(). Если этого не сделать то невозможно получить и обработать транзакции. Когда я писал об однопоточности, я именно это имел в виду.

Это моя, видимо несбыточная, мечта, когда хотя бы OnTick, OnTrade и OnTradeTransaction будут выполняться в отдельных потоках... У меня, кстати, до сих пор вызывает недоумение появление функции Sleep. Смысл? Тяжелые циклические обработчики там сделать невозможно из-за однопоточности, а в легких - использование Sleep не имеет смысла, но это уже отдельная тема для разговора ))

Хоть я и мало чего понял в этом тексте, я уверен в том, что вы не поняли к чему был мой вопрос.

Если на одном тике был отправлен только один ордер, то в OnTradeTransaction будут все его параметры. Предположительно, TRADE_TRANSACTION_REQUEST приходит последним не случайно. И поскольку до этого было размещение ордера и сделки в историю, то все нужные параметры мы можем получить из других структур функции OnTradeTransaction. Но если несколько ордеров подряд, то я не уверен что будет совпадение. Не получится-ли так, что TRADE_TRANSACTION_REQUEST от первого отправленного ордера а все параметры от последнего. Это надо внимательно проверять.

 

Прошу прощения за свои 5 копеек. В том смысле, что по существу проблемы ничего сказать не могу.

Просто я вообще не понимаю зачем нужно всё это отслеживать?

По уму (на мой взгляд), я должен отправить запрос на открытие позиции и получить гарантированный и точный ответ открыта она или нет и её ID. Если будет дополнительная информация о том по какой причине позиция не открыта - замечательно,  нет - главное я буду знать, что позиция не открыта. А все промежуточные проверки должны проводиться автоматически.

 
Vladimir Belozercev:

Уважаемые форумчане,

Хочу поделиться мнением и послушать мнения и опыт коллег, по поводу OrderSend, OrderSendAsync и обработчика OnTradeTransaction. Обработчик OnTrade, на мой взгляд, жалкий рудимент, не заслуживающий большого внимания, в силу ограниченного и упрощенного функционала.

...


Использую только OrderSendAsync при реальной торговле на МОЕХ. Причина - различное время исполнения заявок от нескольких миллисекунд до десятков секунд.

Общий алгоритм:

1. Отправляю в цикле заявки по нескольким инструментам через OrderSendAsync.
2. Ставлю флаг запрета торговли в true.
3. Фиксирую время отправки заявок.
4. Отслеживаю поступление транзакций в OnTradeTransaction.
5. После получения подтверждения совершения сделок по всем инструментам сбрасываю флаг запрета торговли в false.
6. Если подтверждения нет за определенный промежуток времени нет, то сбрасываю 
флаг запрета торговли в false по тайм ауту.

Рабочий пример использования функции  OnTradeTransaction. 

//+------------------------------------------------------------------+
//|  OnTradeTransaction                                              |
//+------------------------------------------------------------------+
void OnTradeTransaction(const MqlTradeTransaction& trans,
                        const MqlTradeRequest& request,
                        const MqlTradeResult& result)
   {
// ищем сделку, добавленную в историю   
if(trans.type==TRADE_TRANSACTION_DEAL_ADD)
     {
// выбираем сделку для дальнейшего анализа
      HistoryDealSelect(trans.deal);
// проверяем что это сделка на вход в рынок      
      if(HistoryDealGetInteger(trans.deal,DEAL_ENTRY)==DEAL_ENTRY_IN && trans.order > 0)
         {
          for(int i=0;i<symbols_total;i++)
            {
             // проверяем символ сделки
	     if(symbols_set[i].symbol==trans.symbol)
               {
                // считаем объем и цену сделки (одна заявка может быть разбита на несколько сделок по разным ценам)
		symbols_set[i].result_volume+=trans.volume;
                symbols_set[i].price_open+=trans.volume*trans.price;
      
                // если весь объем заявки исполнен, то ставим флаг активности позиции по инструменту = истина и считаем итоговую цену открытия позиции
                if(symbols_set[i].request_volume==symbols_set[i].result_volume)
                  {
                   symbols_set[i].price_open/=symbols_set[i].result_volume;
                   symbols_set[i].active=true;
                  }
               }
            }
         }
	// проверяем что это сделка на выход из рынка     
      else if(HistoryDealGetInteger(trans.deal,DEAL_ENTRY)==DEAL_ENTRY_OUT && trans.order > 0)
         {
          for(int i=0;i<symbols_total;i++)
            {
             // проверяем символ сделки
	     if(symbols_set[i].symbol==trans.symbol)
               {
                //считаем закрытий объем, так как одна заявка может быть разбита на несколько сделок
		symbols_set[i].result_volume-=trans.volume;
               
                // если весь объем позиции закрыт, то ставим флаг активности инструмента в ложь и сбрасываем цену открытия в 0 
                if(symbols_set[i].result_volume==0)
                  {
                   symbols_set[i].active=false;
                   symbols_set[i].price_open=0;
                  }
               }
            }
         }
     }
   }
Документация по MQL5: Основы языка / Функции / Функции обработки событий
Документация по MQL5: Основы языка / Функции / Функции обработки событий
  • www.mql5.com
В языке MQL5 предусмотрена обработка некоторых предопределенных событий. Функции для обработки этих событий должны быть определены в программе MQL5: имя функции, тип возвращаемого значения, состав параметров (если они есть) и их типы должны строго соответствовать описанию функции-обработчика события. Именно по типу возвращаемого значения и по...
 
Alexey Viktorov:

Хоть я и мало чего понял в этом тексте, я уверен в том, что вы не поняли к чему был мой вопрос.

Если на одном тике был отправлен только один ордер, то в OnTradeTransaction будут все его параметры. Предположительно, TRADE_TRANSACTION_REQUEST приходит последним не случайно. И поскольку до этого было размещение ордера и сделки в историю, то все нужные параметры мы можем получить из других структур функции OnTradeTransaction. Но если несколько ордеров подряд, то я не уверен что будет совпадение. Не получится-ли так, что TRADE_TRANSACTION_REQUEST от первого отправленного ордера а все параметры от последнего. Это надо внимательно проверять.

Алексей, спасибо за Ваш комментарий. Я понял Вашу мысль, но увы, это не тот случай...

Это чистый эксперимент без каких бы то ни было инфлюэнций. Отправка одной позиции - ловля транзакций (лог приложил, если любопытно - можете взглянуть). И поверьте, в этой теме я давно, и даже используя OrderSend я завершение операции "выставляю" ТОЛЬКО по транзакции. Я бы наверно и продолжал не нем "сидеть", если бы не "поведенческие" изменения в демо-сервере MetaQuotes, которые я описал выше. И, отчасти, я даже благодарен этому обстоятельству, потому что это позволило выявить слабые места в обработке, но к сожалению, из доступных методов, нормального решения и получается, поэтому и появился этот тред.

Вопрос ведь не в том, что в OnTradeTransactionпридут не все транзакции. При правильной обработке, не нарушая поточности обработки, вы получите их ВСЕ, но... Вопрос в том, что при работе с OrderSendAsync, из идентификационных параметров вы гарантировано получаете только result.request_id. При этом result.request_id в транзакции фигурирует только в типе TRADE_TRANSACTION_REQUEST, который вам придет ОДИН раз.

Все остальные транзакции по этому приказу вы сможете идентифицировать только по номеру тикета. При этом, если ТОРГОВЫЕ ТРАНЗАКЦИИ прошли раньше, чем ТРАНЗАКЦИЯ ЗАПРОСА, то их идентификация превращается в весьма витиеватое занятие.

Файлы:
 
Сергей Таболин:

Прошу прощения за свои 5 копеек. В том смысле, что по существу проблемы ничего сказать не могу.

Просто я вообще не понимаю зачем нужно всё это отслеживать?

По уму (на мой взгляд), я должен отправить запрос на открытие позиции и получить гарантированный и точный ответ открыта она или нет и её ID. Если будет дополнительная информация о том по какой причине позиция не открыта - замечательно,  нет - главное я буду знать, что позиция не открыта. А все промежуточные проверки должны проводиться автоматически.

Сергей, Вы абсолютно правы! Я тоже так считаю. OrderSend именно так и должна работать. Оправил - получил гарантированный результат! Со всеми своими недостатками, но железобетонный! Но по факту этого нет...

Если Вы, при анализе результата операции OrderSend, получили 10009 - это ровным счетом ничего не значит. И это не голословное утверждение. Это факт, который я обнаружил лично (и мне, в одной из тем https://www.mql5.com/ru/forum/304239/page52#comment_11355367 на этом форуме, уважаемый fxsaber внес просветление в этом вопросе так же). Позволю процитировать: "fxsaber: Синхронный вариант дожидается размещения ордера (не открытия позиции). Асинхронный - ничего не ждет.". Добавить нечего.

Резюме:

Используя только OrderSend вы, рано или поздно, столкнетесь с ситуацией, когда 10009 - "размещение" есть, а позиции нет.

Надеюсь я ответил на Ваш вопрос: "зачем нужно всё это отслеживать?".

Новая версия платформы MetaTrader 5 build 2007: Экономический календарь, MQL5-программы в виде сервисов
Новая версия платформы MetaTrader 5 build 2007: Экономический календарь, MQL5-программы в виде сервисов
  • 2019.04.16
  • www.mql5.com
21 февраля 2019 года будет выпущена обновленная версия платформы MetaTrader 5...
 
Vladimir Mikhailov:


Использую только OrderSendAsync при реальной торговле на МОЕХ. Причина - различное время исполнения заявок от нескольких миллисекунд до десятков секунд.

Общий алгоритм:

1. Отправляю в цикле заявки по нескольким инструментам через OrderSendAsync.
2. Ставлю флаг запрета торговли в true.
3. Фиксирую время отправки заявок.
4. Отслеживаю поступление транзакций в OnTradeTransaction.
5. После получения подтверждения совершения сделок по всем инструментам сбрасываю флаг запрета торговли в false.
6. Если подтверждения нет за определенный промежуток времени нет, то сбрасываю 
флаг запрета торговли в false по тайм ауту.

Рабочий пример использования функции  OnTradeTransaction. 

Владимир, большое спасибо за развернутый ответ! Но, к Вашему счастью и к моему сожалению, у Вас ситуация немного проще. У Вас все приказы "обезличены", т.е. выполнены по принципу - "выстрелил-и забыл".

У меня немного другой случай. Мне каждый приказ нужно привязать к "источнику" сигнала, для последующего управления, при этом, если ТОРГОВЫЕ ТРАНЗАКЦИИ прошли раньше, чем ТРАНЗАКЦИЯ ЗАПРОСА, то их идентификация, без предварительной буферизации всех полученных, невозможна. А с буферизацией почти все преимущества использования OrderSendAsync теряются из-за появления больших вычислительных расходов.

 
Vladimir Belozercev:

............

Надеюсь я ответил на Ваш вопрос: "зачем нужно всё это отслеживать?".

зачем нужно всё это отслеживать? == все промежуточные проверки должны проводиться автоматически

Это я к тому, что, возможно, кто-нибудь, может быть даже MQ, напишет простую библиотеку по работе именно с открытием-закрытием позиции. Чтобы можно было написать

Position.Open(цена, тип, проскальзыване, SL, TP, магик, структура ответа)

она сама проверила все транзакции и заполнила ответ типа

  • позиция открыта
  • цена открытия
  • ID позиции
  • тикет
  • причина отказа
  • ....
Что-то подобное есть, но, как я понимаю, там проверок транзакций нет.