Акцептирование SL/TP-ордеров

 

В этой ветке пойдет речь об ордерах, которые создаются в результате срабатывания SL/TP-уровней открытых позиций.

Была написана сложная, но рабочая функция получения тика, который являлся триггером для заданного SL/TP-ордера.

#define SEARCH_TICK(A, B)                             \
{                                                     \
  if (!(Ticks[Pos].##A B Price) && ((Pos <= 1) ||     \
      (!StopLevel && (Ticks[Pos - 1].##A == Price)))) \
    Tick = Ticks[Pos];                                \
  else                                                \
  {                                                   \
    while ((Pos >= 0) && !(Ticks[Pos].##A B Price))   \
      Tick = Ticks[Pos--];                            \
                                                      \
    if (!Tick.time)                                   \
    {                                                 \
      while ((Pos >= 0) && (Ticks[Pos].##A B Price))  \
        Pos--;                                        \
                                                      \
      while ((Pos >= 0) && !(Ticks[Pos].##A B Price)) \
        Tick = Ticks[Pos--];                          \
    }                                                 \
  }                                                   \
}

// Получение тика, который акцептировал TP/SL-ордер.
bool GetAcceptedTick( const ulong Ticket, MqlTick &Tick, const bool PrintFlag = false )
{
  bool Res = false;

  if (!IsStopped() && HistoryOrderSelect2(Ticket))
  {
    const ENUM_ORDER_REASON Reason = (ENUM_ORDER_REASON)HistoryOrderGetInteger(Ticket, ORDER_REASON);

    if ((Reason == ORDER_REASON_TP) || (Reason == ORDER_REASON_SL))
    {
      const long CreateTime = HistoryOrderGetInteger(Ticket, ORDER_TIME_SETUP_MSC);
      const long DoneTime = HistoryOrderGetInteger(Ticket, ORDER_TIME_DONE_MSC);
      const string Symb = HistoryOrderGetString(Ticket, ORDER_SYMBOL);
      const ENUM_ORDER_STATE State = (ENUM_ORDER_STATE)HistoryOrderGetInteger(Ticket, ORDER_STATE);
      const ENUM_ORDER_TYPE Type = (ENUM_ORDER_TYPE)HistoryOrderGetInteger(Ticket, ORDER_TYPE);
      const double Price = HistoryOrderGetDouble(Ticket, ORDER_PRICE_OPEN);

      const ulong TicketOpen = HistoryOrderGetInteger(Ticket, ORDER_POSITION_ID);

      if (SymbolInfoInteger(Symb, SYMBOL_EXIST) && TicketOpen && HistoryOrderSelect2(TicketOpen))
      {
      #define TOSTRING(A) ", " + #A + " = " + (string)(A)
        const int digits = (int)SymbolInfoInteger(Symb, SYMBOL_DIGITS);

        const int StopLevel = (int)SymbolInfoInteger(Symb, SYMBOL_TRADE_STOPS_LEVEL);
        long PositionCreated = HistoryOrderGetInteger(TicketOpen, ORDER_TIME_DONE_MSC);
        
        // Условие может сработать при частичном исполнении.
        if ((PositionCreated >= CreateTime) && HistorySelectByPosition(TicketOpen) && HistoryDealsTotal())
        {
          PositionCreated = HistoryDealGetInteger(HistoryDealGetTicket(0), DEAL_TIME_MSC);
          
          HistoryOrderSelect(TicketOpen); // Не HistoryOrderSelect2, т.к. нужно HistoryOrdersTotal() <= 1.
        }

      #define HOUR (3600 * 1000)
        long From = MathMax(PositionCreated,    // Время открытия позиции
                            CreateTime - HOUR); // Час - просто с запасом.

        MqlTick Ticks[];

        ResetLastError();
        int Pos = CopyTicksRange(Symb, Ticks, COPY_TICKS_INFO, From, CreateTime) - 1;

        if ((Pos < 0) && !_LastError && (From == PositionCreated))
          Pos = CopyTicksRange(Symb, Ticks, COPY_TICKS_INFO, From -= HOUR, CreateTime) - 1;

        if (Pos >= 0)
        {
          const MqlTick LastTick = Ticks[Pos];

          Tick.time = 0;

          if (Type == ORDER_TYPE_BUY)
          {
            if (Reason == ORDER_REASON_TP)
              SEARCH_TICK(ask, >)
            else
              SEARCH_TICK(ask, <)
          }
          else if (Reason == ORDER_REASON_TP)
            SEARCH_TICK(bid, <)
          else
            SEARCH_TICK(bid, >)

          if (!(Res = /*(Pos >= 0) && */Tick.time))
            Alert(__FUNCSIG__ + ": Error!"); // Ошибка при расчетах
          else if (PrintFlag) // Выводим найденный тик.
          {
            Print("Last Tick " + TickToString(LastTick, digits));

            Print("Accepted Tick " + TickToString(Tick, digits));
            Print("Accepted Length = " + (string)(CreateTime - Tick.time_msc) + " ms.");
          }
        }
        else // В случае ошибки CopyTicks - сообщаем.
          Alert(__FUNCSIG__ + ": CopyTicksRange(" + Symb + ", " + TimeToString(From) + ", " +
                                                TimeToString(CreateTime) + ") = " + (string)(Pos + 1) + TOSTRING(_LastError));

        if (PrintFlag || !Res) // Распечатываем данные ордера.
          Print("Order " + (string)Ticket + " " + EnumToString(Type) + " " + Symb + " " + TimeToString(CreateTime) + " " +
                           DoubleToString(Price, digits) + " " + EnumToString(Reason) + " " + EnumToString(State) + " " +
                           TimeToString(DoneTime) + ", Position " + (string)TicketOpen + " created " +
                           TimeToString(PositionCreated) + TOSTRING(StopLevel) + "\n");
      }
    }
  }

  return(Res);
}

// Преобразование времени в миллисекундах в строку.
string TimeToString( const long time, const int FlagTime = TIME_DATE | TIME_SECONDS)
{
  return(TimeToString((datetime)time / 1000, FlagTime) + "." + IntegerToString(time % 1000, 3, '0'));
}

// Преобразование тика в строку.
string TickToString( const MqlTick &Tick, const int digits )
{
  return(TimeToString(Tick.time_msc) + " " + DoubleToString(Tick.bid, digits) + " " + DoubleToString(Tick.ask, digits));
}

// Правильный выбор исторического ордера.
bool HistoryOrderSelect2( const ulong Ticket)
{
  return(((HistoryOrderGetInteger(Ticket, ORDER_TICKET) == Ticket) || HistoryOrderSelect(Ticket)));
}


Применение этой замечательной функции и стало причиной создания данной ветки. Уверен, что все баги в коде не отловил, но для истории и понимания, что это, действительно, непросто, привел полный листинг.

 

Изучая вопросы исполнения TP-ордеров, заметил, что некоторые TP-ордера создавались со значительным отставанием от тиков, которые их акцептировали.

Разбор полетов показал, что эта ситуация повторяется не только на разных брокерах, но и в ситуации, когда торговля идет на Терминале, который стоит на той же машине, где и Торговый Сервер. Т.е. с очень низким пингом и единственным торговым счетом для Торгового сервера.



Написание функции GetAcceptedTick позволило основательно изучить вопрос и конструктивно продемонстрировать проблему.


Скрипт.

Итак, в прицепе лежит следующий скрипт.

// Скрипт выводит самое длительное или конкретное акцептирование SL/TP-ордера.
#property script_show_inputs

input datetime inFrom = D'2020.01.01'; // С какого времени проверять ордера
input ulong inTicket = 0;              // Отдельно проверяемый тикет

// Возвращает самый медленный TP/SL-ордер с определенной даты.
ulong GetSlowestOrder( const datetime From );

// Распечатывает подробности акцепта SL/TP-ордера.
void PrintOrder( const ulong MaxTicket );

void OnStart()
{
  Print("\n\nStart " + MQLInfoString(MQL_PROGRAM_NAME) + TOSTRING(inFrom) + TOSTRING(inTicket) + "\n");
  
  PrintOrder(inTicket ? inTicket : GetSlowestOrder(inFrom));
}


Результат его запуска на MQ-Demo.

Total Orders (from 2020.09.01 00:00:00) = 58493, calculated = 439
Calculation time = 00:00:11.328, Performance = 38.0 orders/sec.

ServerName: MetaQuotes-Demo

Last Tick 2020.09.30 19:07:32.917 1.80181 1.80205
Accepted Tick 2020.09.30 19:07:32.716 1.80178 1.80202
Accepted Length = 357 ms.
Order 726444166 ORDER_TYPE_BUY GBPAUD 2020.09.30 19:07:33.073 1.80206 ORDER_REASON_TP ORDER_STATE_FILLED 2020.09.30 19:07:33.082, Position created 2020.09.30 17:21:17.933, StopLevel = 0

Orders (2) before 726444166 with PositionID = 725926764:
------------------------
Checked Orders = 0
------------------------


Скрипт утверждает, что нашел TP-ордер и тик, который был триггером его создания (выделены цветом в тексте). Казалось бы, если на торговом сервере (тем более на демо) цена дошла до TP-уровня открытой позиции, то должен тут же создаться (не обязательно исполниться) соответствующий TP-ордер. Однако, в данной ситуации это произошло далеко не мгновенно, а через 357 миллисекунд!


Наперед скажу, что даже задержка в одну миллисекунду - это не мало. Успеть - в алготрейдинге значимый глагол.


Проверка.

Не будем доверять слепо скрипту, а проверим данную ситуацию вручную. Итак, вот наш ордер.


А здесь виден найденный скриптом соответствующий акцептирующий тик.


Стрелочкой показано, между какими тиками родился TP-ордер. По скринам хорошо видно, что скрипт оказался прав, и создание TP-ордера произошло с огромным лагом на стороне Торгового сервера.


Итог.

Теперь есть инструмент, который показывает величины тормозов на стороне Торгового сервера при торговле через TP/SL-уровни. На данный момент они огромны. И это, однозначно, серьезный минус платформы, который нужно править.

К сожалению, акцепт отложек невозможно засечь, т.к. эта информация недоступна на стороне Терминала. Но почти железно, наличие существенных лагов со стороны TP/SL-ордеров не может не сказываться на лагах при исполнении отложек. Т.к. причина, похоже, одной природы.


В общем, MT5-платформа тормозит на данный момент на 100% конкретно в этих ситуациях. И требует исправлений, пока не будет нулевой лаг.


Призываю делиться результатами со своих счетов. Поспособствуйте улучшению MT5!

Файлы:
 
fxsaber:

Призываю делиться результатами со своих счетов. Поспособствуйте улучшению MT5!

Total Orders (from 2020.11.01 00:00:00) = 21725, calculated = 10465
Calculation time = 00:04:33.609, Performance = 38.0 orders/sec.

ServerName: RannForex-Server

Last Tick 2020.11.16 00:34:35.201 104.630 104.640
Accepted Tick 2020.11.16 00:34:06.309 104.627 104.639
Accepted Length = 28894 ms.
Order 1715452 ORDER_TYPE_SELL USDJPY 2020.11.16 00:34:35.203 104.627 ORDER_REASON_TP ORDER_STATE_REJECTED 2020.11.16 00:34:35.217, Position created 2020.11.16 00:33:51.196, StopLevel = 0

Orders (4) before 1715452 with PositionID = 1715287:
-----------------------
Last Tick 2020.11.16 00:34:06.309 104.627 104.639
Accepted Tick 2020.11.16 00:34:06.309 104.627 104.639
Accepted Length = 3 ms.
Order 1715425 ORDER_TYPE_SELL USDJPY 2020.11.16 00:34:06.312 104.625 ORDER_REASON_TP ORDER_STATE_REJECTED 2020.11.16 00:34:06.327, Position created 2020.11.16 00:33:51.196, StopLevel = 0

Checked Orders = 1
------------------------

 28 секунд лаг! Наверное, в таких ситуациях уже лучше обращаться к брокеру.

 
2020.11.25 02:42:17.718 CheckOrders (EURUSD,H1) ServerName: ICMarkets-MT5
2020.11.25 02:42:17.718 CheckOrders (EURUSD,H1) 
2020.11.25 02:42:17.718 CheckOrders (EURUSD,H1) Last Tick 2020.11.24 23:00:49.327 1.33569 1.33570
2020.11.25 02:42:17.718 CheckOrders (EURUSD,H1) Accepted Tick 2020.11.24 23:00:49.327 1.33569 1.33570
2020.11.25 02:42:17.718 CheckOrders (EURUSD,H1) Accepted Length = 7 ms.
2020.11.25 02:42:17.718 CheckOrders (EURUSD,H1) Order 106887648 ORDER_TYPE_BUY GBPUSD 2020.11.24 23:00:49.334 1.33572 ORDER_REASON_TP ORDER_STATE_FILLED 2020.11.24 23:00:49.830, Position created 2020.11.24 22:57:47.071, StopLevel = 0
2020.11.25 02:42:17.718 CheckOrders (EURUSD,H1) 
2020.11.25 02:42:17.719 CheckOrders (EURUSD,H1) Orders (2) before 106887648 with PositionID = 106886713:
2020.11.25 02:42:17.719 CheckOrders (EURUSD,H1) ------------------------
2020.11.25 02:42:17.719 CheckOrders (EURUSD,H1) Checked Orders = 0
2020.11.25 02:42:17.719 CheckOrders (EURUSD,H1) ------------------------
2020.11.25 02:47:22.624 CheckOrders (EURUSD,H1) ServerName: ICMarkets-MT5
2020.11.25 02:47:22.624 CheckOrders (EURUSD,H1) 
2020.11.25 02:47:22.633 CheckOrders (EURUSD,H1) Last Tick 2020.11.18 12:44:37.354 1.18748 1.18748
2020.11.25 02:47:22.633 CheckOrders (EURUSD,H1) Accepted Tick 2020.11.18 12:44:37.354 1.18748 1.18748
2020.11.25 02:47:22.633 CheckOrders (EURUSD,H1) Accepted Length = 17 ms.
2020.11.25 02:47:22.633 CheckOrders (EURUSD,H1) Order 105637485 ORDER_TYPE_SELL EURUSD 2020.11.18 12:44:37.371 1.18749 ORDER_REASON_SL ORDER_STATE_FILLED 2020.11.18 12:44:37.476, Position created 2020.11.17 22:24:15.116, StopLevel = 0
2020.11.25 02:47:22.633 CheckOrders (EURUSD,H1) 
2020.11.25 02:47:22.634 CheckOrders (EURUSD,H1) Orders (2) before 105637485 with PositionID = 105516718:
2020.11.25 02:47:22.634 CheckOrders (EURUSD,H1) ------------------------
2020.11.25 02:47:22.634 CheckOrders (EURUSD,H1) Checked Orders = 0
2020.11.25 02:47:22.634 CheckOrders (EURUSD,H1) ------------------------
 
2020.11.25 02:50:58.687 CheckOrders (EURUSD,H1) ServerName: OctaFX-Real
2020.11.25 02:50:58.687 CheckOrders (EURUSD,H1) 
2020.11.25 02:50:58.687 CheckOrders (EURUSD,H1) Last Tick 2020.11.23 18:14:35.081 1.18108 1.18115
2020.11.25 02:50:58.687 CheckOrders (EURUSD,H1) Accepted Tick 2020.11.23 18:14:35.081 1.18108 1.18115
2020.11.25 02:50:58.687 CheckOrders (EURUSD,H1) Accepted Length = 11 ms.
2020.11.25 02:50:58.687 CheckOrders (EURUSD,H1) Order 8950107 ORDER_TYPE_SELL EURUSD 2020.11.23 18:14:35.092 1.18105 ORDER_REASON_TP ORDER_STATE_FILLED 2020.11.23 18:14:35.104, Position created 2020.11.23 18:11:38.678, StopLevel = 20
2020.11.25 02:50:58.687 CheckOrders (EURUSD,H1) 
2020.11.25 02:50:58.688 CheckOrders (EURUSD,H1) Orders (2) before 8950107 with PositionID = 8950014:
2020.11.25 02:50:58.688 CheckOrders (EURUSD,H1) ------------------------
2020.11.25 02:50:58.688 CheckOrders (EURUSD,H1) Checked Orders = 0
2020.11.25 02:50:58.688 CheckOrders (EURUSD,H1) ------------------------
 
2020.11.25 02:54:37.912 CheckOrders (EURUSD,H1) ServerName: Pepperstone-MT5-Live01
2020.11.25 02:54:37.912 CheckOrders (EURUSD,H1) 
2020.11.25 02:54:37.934 CheckOrders (EURUSD,H1) Last Tick 2020.09.03 01:00:02.426 106.199 106.199
2020.11.25 02:54:37.934 CheckOrders (EURUSD,H1) Accepted Tick 2020.09.03 01:00:02.426 106.199 106.199
2020.11.25 02:54:37.934 CheckOrders (EURUSD,H1) Accepted Length = 4 ms.
2020.11.25 02:54:37.934 CheckOrders (EURUSD,H1) Order 18982771 ORDER_TYPE_SELL USDJPY 2020.09.03 01:00:02.430 106.191 ORDER_REASON_TP ORDER_STATE_FILLED 2020.09.03 01:00:02.466, Position created 2020.09.02 22:57:47.081, StopLevel = 0
2020.11.25 02:54:37.934 CheckOrders (EURUSD,H1) 
2020.11.25 02:54:37.935 CheckOrders (EURUSD,H1) Orders (2) before 18982771 with PositionID = 18975080:
2020.11.25 02:54:37.935 CheckOrders (EURUSD,H1) ------------------------
2020.11.25 02:54:37.935 CheckOrders (EURUSD,H1) Checked Orders = 0
2020.11.25 02:54:37.935 CheckOrders (EURUSD,H1) ------------------------
 

очередной взрыв мозга от fxsaber. даже не знаю что сказать.

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

для штатного фикса придется

- стерпеть шквал критики от разрабов и доказать что лаг имеет место быть (учитывая нюансы которых вы не можете знать, например железо серверной части брокера)

- убедить что он критичен

- дождаться фикса

- усердно пинать брокера обновить серверную часть на последний билд, что может быть гораздо сложнее всех предыдущих пунктов.

 
ну и заезженное МТ не для ХФТ )
 
ServerName: RannForex-Server
Accepted Length = 28894 ms.

Вот тут есть серьезное подозрение на те самые крутые технологии брокера, ради которых вы у него и торгуете.

Что-то тормозит в плагинах пользовательской обработки лимитников.

 
Andrey Khatimlianskii:

Вот тут есть серьезное подозрение на те самые крутые технологии брокера, ради которых вы у него и торгуете.

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

 
Andrei Trukhanovich:

для штатного фикса придется

- стерпеть шквал критики от разрабов и доказать что лаг имеет место быть (учитывая нюансы которых вы не можете знать, например железо серверной части брокера)

Форум по трейдингу, автоматическим торговым системам и тестированию торговых стратегий

Акцептирование SL/TP-ордеров

fxsaber, 2020.11.25 00:47

Результат его запуска на MQ-Demo.

Total Orders (from 2020.09.01 00:00:00) = 58493, calculated = 439
Calculation time = 00:00:11.328, Performance = 38.0 orders/sec.

ServerName: MetaQuotes-Demo


В другой ветке было неоднократно заявлено, что даже Терминал тормозит от огромного количества факторов. Как следствие, гораздо более сложный Торговый сервер обязан тормозить еще больше. Надеюсь все же, что алгоритмическая оптимизация еще возможна. Даже 5 мс лаг - уже очень плохо. Что уж говорить о сотнях миллисекунд.

Причина обращения: