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注文が、それを受け付けたティックから大きく遅れて作成されていることに気づきました。

報告会では、異なるブローカーだけでなく、取引サーバーと同じマシンにあるターミナルで取引を行う場合にも、この状況が繰り返されることがわかりました。I.e. 非常に低い ping で、Trading Server の唯一の取引アカウントです。



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ミリ秒後に起きたのです


先に断っておきますが、1ミリ秒の遅延でも小さすぎるということはありません。To be in timeは、アルゴトレーディングでは意味のある動詞です。


検証。

スクリプトを盲信せず、この状況を手動で確認しよう。というわけで、今回のオーダーはこちら。


スクリプトが検出した対応するアクセプタンスティックは、ここで見ることができます。


矢印は、TP注文が生まれたティックの間を示しています。スクリーンショットを見ると、スクリプトが正しく、トレードサーバー側で大きなラグを伴ってTP注文の作成が行われたことがよくわかります。


結論から言うと、

TP/SLレベル経由の取引時に、Trade Server側のラグ値を表示するツールが追加されました。現時点では、巨大なものです。そしてこれは間違いなく、プラットフォームの重大な欠点であり、是正されなければならない。

残念ながら、保留中の注文の受付は、端末側で情報を取得できないため、登録することはできません。しかし、ほとんどの場合、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の脳内爆発です。

ブローカーと一緒にボトルネックを見つけ、可能であればそれを解決するように努力するしかないと思います。

自社で修正する場合は

- 開発者からの批判に耐え、ラグが発生したことを証明する(ブローカーのサーバーハードウェアなど、わからないことを考慮する)。

- 重要であることを納得させる

- てあてをまつ

- ブローカーに最新ビルドへのアップグレードを熱心に勧めるが、これはこれまでのすべての項目よりはるかに難しいかもしれない。

 
とハキハキしたMTはHFT向けではない )
 
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


ターミナルですら膨大な数の要因から減速していることは、別のスレッドで繰り返し述べられている。その結果、より複雑なTrading Serverは、さらに速度を落とすことになります。アルゴリズムによる最適化は、まだまだ可能だと期待しています。5msのラグでもすでに非常に悪い。数百ミリ秒はおろか。