English Русский 中文 Español Deutsch Português
preview
カスタムシンボル。実用的な基礎

カスタムシンボル。実用的な基礎

MetaTrader 5テスター | 10 12月 2020, 10:54
1 089 0
Stanislav Korotky
Stanislav Korotky

MetaTrader5は、独自のクオートとティックを持つカスタムシンボルを作成する機能を備えています。 これらは、ターミナルインターフェイスからも、MQL APIを介してプログラムレベルでもアクセスすることができます。 カスタムシンボルは標準チャートに表示され、インジケータの適用、オブジェクトのマーキング、さらにはシンボルに基づいてトレード戦略を作成することができます。

カスタムシンボルは、ブローカーや外部リソースから提供される実際のシンボル相場をデータソースとして使用することができます。 この記事では、トレーダーの追加の分析ツールを提供するワーキングシンボルを変換するための一般的な方法を検討します。

  • 同じボリューム及び同じ範囲のチャート
  • ティックチャート
  • ローソク足の形の変換による相場のタイムシフト
  • レンコン

さらに、EAを、EAが稼働しているチャート上の派生カスタムシンボルに関連付けられた実物のシンボルをトレードするための仕組みを開発します。

この記事では、ソース(標準)シンボルチャートは黒背景を使用し、カスタムシンボルチャートは白背景を使用します。

同じボリューム/レンジチャート

同じボリュームチャートとは、足チャートで囲ったボリュームが等しくなるという原理に基づいた足チャートのことです。 通常のチャートでは、時間枠の大きさに合わせて指定された間隔で新しい足が形成されます。 等値線チャートでは、ティックまたは実量の合計があらかじめ設定された値に達すると、各足が形成されると考えられています。 その後、プログラムは次の足の金額の計算を開始します。 もちろん、出来高が計算されると値動きもコントロールされますので、チャート上には通常の4つの値動きが表示されます。始値、高値、安値、終値です。

等倍チャートの横軸はまだクロノロジーを意味しますが、各足のタイムスタンプは任意で、各時間帯のボラティリティ(トレードの数や大きさ)に依存します。 多くのトレーダーは、一定のタイムフレーム値と比較して、変化する相場のより適切な説明を提供するために、この足形成方法を検討します。

残念ながら、MetaTrader4もMetaTrader5も、同等のボリュームチャートを提供していません。 これらは特別な方法で生成されるべきです。

MetaTrader4では、オフラインチャートを使って行うことができます。 この方法については、同じボリュームチャートリバイズの記事で説明します。

カスタムシンボルを使用して、同じアルゴリズムをMetaTrader5に実装することができます。 このタスクをするために、指定された記事からノントレーディングのEAを使用して、MetaTrader5のMQL APIに適応させてみましょう。

元のファイルEqualVolumeBars.mq4はEqualVolumeBars.mq5に名前が変更され、若干の修正が加えられています。 特に、インプットパラメータを記述する'extern'キーワードは'input'に置き換えられています。 StartYearとStartMonthという2つのパラメータの代わりに、StartDateという1つのパラメータが使用します。 MetaTrader4で標準外のタイムフレームを設定するために使用されていたCustomPeriodは、現在では必要ないため削除されました。

MetaTrader4のボリュームはすべてティックボリュームであり、足のティック数(価格の変化)を表していることに注意してください。 当初のアイデアは、M1足(ティックボリューム付き)または他のブローカーから提供されたティックを含む外部csvファイルを処理し、時間単位でインプットティックをカウントし、指定されたティック数に達するとすぐに新しい等積足を形成するというものでしました。 足/バーは、MetaTrader4でオフラインチャートとして開くことができるhstファイルに書き込まれました。

MetaTrader5では、csvファイルの読み込みとhstファイルの書き込みに関連するコード部分は必要ありません。 その代わり、カスタムシンボルAPIを使用して、実際のティックヒストリーやフォーム足を読み取ることができます。 さらに、MetaTrader5では、ブローカーが実際のボリュームとティックを提供できるようになりました(為替商品の場合ですが、通常はFX商品では利用できません)。 このモードを有効にすると、等ボリューム足はティックの数ではなく、実際のボリュームで作成することができます。

FromM1インプットパラメータは、EAがM1足を処理するか(デフォルトではtrue)、ティックヒストリーを処理するか(false)を決定します。 ティック処理を開始する際には、時間とディスク容量を多く必要とする可能性があるため、あまり遠い過去を先頭に選択しないようにしてください。 すでにティックのヒストリーを操作したことがある人は、自分のPCの能力と利用可能なリソースを理解しているでしょう。

等幅の足も同様の方法でプロットされています。 しかし、ここでは、価格が指定されたポイント数を通過すると、新しい足が開きます。 足はティックモード(FromM1 == false)でのみ利用可能です。

チャートの種類 - EqualTickVolumes、EqualRealVolumes、RangeBars - は、WorkMode インプットパラメータによって設定されます。

カスタムシンボルを扱う最も便利な方法は、シンボルライブラリ (fxsaberによる) を使うことです。 includeディレクティブを使ってEAに接続することができます。

  #include <Symbol.mqh>

あとは、現在のチャートシンボルを元にカスタムシンボルを作成します。 以下のように行います。

  if(!SymbolSelect(symbolName, true))
  {
    const SYMBOL Symb(symbolName);
    Symb.CloneProperties(_Symbol);
    
    if(!SymbolSelect(symbolName, true))
    {
      Alert("Can't select symbol:", symbolName, " err:", GetLastError());
      return INIT_FAILED;
    }
  }

ここで symbolName はカスタムシンボル名の文字列です。

この初期化フラグメントとカスタムシンボル管理に関連した他の多くの補助的なタスク(特に、既存のヒストリーのリセット、新しいカスタムシンボルでチャートを開く)は、すべてのプログラムで同様の方法で実行されます。 関連するソースコードは、以下の添付ファイルで確認できます。 2次的な重要性があるますが、この記事では省略します。

新しい等ボリューム足が表示されたり、現在の等ボリューム足が変更されたりすると、WriteToFile 関数が呼び出されます。 この関数は、MetaTrader5のCustomRatesUpdateを呼び出すことで実装されています。

  void WriteToFile(datetime t, double o, double l, double h, double c, long v, long m = 0)
  {
    MqlRates r[1];
    
    r[0].time = t;
    r[0].open = o;
    r[0].low = l;
    r[0].high = h;
    r[0].close = c;
    r[0].tick_volume = v;
    r[0].spread = 0;
    r[0].real_volume = m;
    
    int code = CustomRatesUpdate(symbolName, r);
    if(code < 1)
    {
      Print("CustomRatesUpdate failed: ", GetLastError());
    }
  }

驚くべきことに、M1足のサイクル(FromM1 = trueモード)はMQL4版とほぼ同じなので、WriteToFile関数を適応させるだけで、M1足用の機能的なMQL5のコードを受け取ることができます。 変更が必要なのはRefreshWindowでのティックの生成だけです。 MetaTrader4では、Windowsメッセージを送信してオフラインチャート上のティックをエミュレートすることで行われました。 MetaTrader5ではCustomTicksAdd関数を使用します。

  void RefreshWindow(const datetime t)
  {
    MqlTick ta[1];
    SymbolInfoTick(_Symbol, ta[0]);
    ta[0].time = t;
    ta[0].time_msc = ta[0].time * 1000;
    if(CustomTicksAdd(symbolName, ta) == -1)
    {
      Print("CustomTicksAdd failed:", GetLastError(), " ", (long) ta[0].time);
      ArrayPrint(ta);
    }
  }

ティック生成は、カスタムシンボルチャート上でOnTickイベントを呼び出し、そのようなチャート上で実行されているEAがトレードできるようにする可能性があります。 しかし、この技術には余分な動作が必要で、これについては後述します。

ティックヒストリーから等値線を生成するモード(FromM1=false)は、もう少し複雑です。 これは、標準のCopyTicks/CopyTicksRange関数を使用して実際のティックを読み取る必要があります。 この関数はすべてTicksBufferクラスに実装されています。

  #define TICKS_ARRAY 10000
  
  class TicksBuffer
  {
    private:
      MqlTick array[];
      int tick;
    
    public:
      bool fill(ulong &cursor, const bool history = false)
      {
        int size = history ? CopyTicks(_Symbol, array, COPY_TICKS_ALL, cursor, TICKS_ARRAY) : CopyTicksRange(_Symbol, array, COPY_TICKS_ALL, cursor);
        if(size == -1)
        {
          Print("CopyTicks failed: ", GetLastError());
          return false;
        }
        else if(size == 0)
        {
          if(history) Print("End of CopyTicks at ", (datetime)(cursor / 1000));
          return false;
        }
        
        cursor = array[size - 1].time_msc + 1;
        tick = 0;
      
        return true;
      }
      
      bool read(MqlTick &t)
      {
        if(tick < ArraySize(array))
        {
          t = array[tick++];
          return true;
        }
        return false;
      }
  };

TICKS_ARRAY の断片で 'fill' メソッドでリクエストされたティックは 'array' に追加され、そこから 'read' メソッドで一つずつ読み込まれます。 このメソッドは、M1ヒストリー足と同様のティックヒストリーを扱うためのアルゴリズムを実装しています(完全なソースコードは添付ファイルで提供されています)。

    TicksBuffer tb;
    
    while(tb.fill(cursor, true) && !IsStopped())
    {
      MqlTick t;
      while(tb.read(t))
      {
        ...
        // New or first bar
        if(IsNewBar() || now_volume < 1)
        {
          WriteToFile(...);
        }
      }
    }

EAが起動するたびに、指定されたカスタムシンボルが存在する場合、Reset関数を使用して既存のヒストリーを消去します。 必要に応じて、ヒストリーを保存し、前世代が終了した位置に足を生成し続けることで、この動作を改善することができます。

EqualVolumeBars.mq4 のソースコードと、結果として得られる EqualVolumeBars.mq5 のソースコードを比較することができます。

では、新しいEAの仕組みを見ていきましょう。 こちらはEAが動いているEURUSDのH1チャートです。

MetaTrader5のEURUSD H1チャートのEqualVolumeBarsEA

MetaTrader5のEURUSD H1チャートのEqualVolumeBarsEA

下記は、1本の足が1000ティックで構成されているEAが作成した等ボリュームチャートです。

MetaTrader5のEqualVolumeBarsEAによって生成された、足ごとに1000ティックのEURUSDの等ボリュームチャート。

MetaTrader5のEqualVolumeBarsEAによって生成された、足ごとに1000ティックのEURUSDの等ボリュームチャート。

直近の1本を除いて、すべての足のティックボリュームが等しくなっていることに注意してください。

もう一つの動作モード、イコールレンジチャートを確認してみましょう。 100ポイントの揺れのある足で構成されたチャートです。

MetaTrader5のEqualVolumeBarsEAによって生成された、1足あたり100ポイントのEURUSDイコールレンジチャート

MetaTrader5のEqualVolumeBarsEAによって生成された、1足あたり100ポイントのEURUSDイコールレンジチャート

また、このEAでは、為替商品のリアルボリュームモードを使用することができます。

MetaTrader5でEqualVolumeBarsEAを買いして生成された足ごとに10000の実際のボリュームとLKOHオリジナルチャート

MetaTrader5でEqualVolumeBarsEAを買いして生成された1足あたり10000の実際のボリュームを持つLKOH等ボリュームチャート

MetaTrader5のEqualVolumeBarsEAによって生成された、1足あたり10000の実質ボリュームを持つ元の(a)と等価ボリューム(b)のLKOHチャート。

計算には常にM1足かティックヒストリーのどちらかが使用されているため、EAが稼働しているシンボルのタイムフレームは重要ではありません。

カスタムシンボルチャートのタイムフレームは、M1(ターミナルで利用可能な最小のタイムフレーム)に等しくなければなりません。 したがって、足の時間は、通常、密接に形成モーメントに対応します。 しかし、強い相場の動きの中では、ティック数やボリュームの大きさが1分間に数本の足を形成すると、足の時間は実際のものよりも先になります。 相場が落ち着いてくると、等ボリューム足のタイムマークが正常に戻ってきます。 このプラットフォームの制限は、そのようなチャートのまさにアイデアは、絶対時間からをインプットすることですので、等ボリュームまたは等レンジの足については、おそらく特に重要ではありません。

ティックチャート

MetaTrader5のティックチャートは、Market Watchウィンドウで確認できます。 なぜか正規のチャートとは実装が異なります。 それは、限られた数のティック(私が知る限り、2000まで)を表示し、それは小さく、全画面に拡大することはできません、そしてそれは指標、オブジェクトおよびエキスパートアドバイザーを使用する能力など、標準チャートによって提供されるすべての機能を欠いています。

MetaTrader5 マーケットウォッチウィンドウのティックチャート

MetaTrader5 マーケットウォッチウィンドウのティックチャート

では、MetaTrader5はリアルティックをネイティブでサポートし、ハイフリークエンシー・トレーディング(HFT)を提供しているのに、標準の分析ツールはティックに対応していないのはなぜなのでしょうか? 一部のトレーダーは、ティックをあまりにも小さな実体、あるいはノイズであると考えています。 他のトレーダーは、ティックから利益を得ようとします。 そのため、ティックを標準のチャートにスケール関数で表示したり、テンプレートを適用したり、EAを利用するためのイベントを開催したりすると便利かもしれません。 カスタムシンボル関数を使用して実装することができます。

ここでも、CopyTicksやCustomRatesUpdateなどの既知のMQL API関数を使用することができます。 それらを利用して、現在のチャートシンボルに基づいてカスタムシンボルを生成するノントレーディング・エキスパート・アドバイザーを実装することができます。 ここでは、カスタムシンボルヒストリーの各M1足が個別のティックとなっています。 このようなソースコードの例は、以下のTicks2Bars.mq5ファイルに添付されています。 例えば、EURUSDチャート(任意のタイムフレーム)でEAを実行すると、EURUSD_ticksシンボルが作成されます。

EAのインプットは以下の通りです。

  • Limit - 起動後に作成される足(ティック)の数、デフォルトは1000です。0を設定すると、過去のヒストリーなしでオンラインティック生成が開始されます。
  • Reset - このオプションは、起動後に以前のティック/足のヒストリーをリセットします。
  • LoopBack - このオプションは循環バッファモードを有効にします。このモードでは、(Limit よりも高いインデックスを持つ)ティックが "quote "の内部配列から押し出され、新しいティックが最初に追加されると、チャートには常にLimit足が存在するようになります。
  • EmulateTicks - このオプションは、EAコールの重要な関数であるカスタムシンボルの新しいティックの到着イベントをエミュレートします。
  • RenderBars - 足/ティック表示メソッド.OHLC または HighLow; デフォルトでは OHLC; このモードでは、ティックはボディ(高 = ask, 低 = bid, 直近の(利用可能な場合) = close; 直近の = 0 の場合、オープンとクローズは、前のティック以降の値動きの方向に応じて、適切な高値または安値のいずれかに等しい; HighLow モードでは、ティックは、高 = ask, 低 = bid, オープン = close = (ask + bid) / 2 のピンバーとして表示されます。

主な操作は「適用」関数で行います。

  bool apply(const datetime cursor, const MqlTick &t, MqlRates &r)
  {
    static MqlTick p;
    
    // eliminate strange things
    if(t.ask == 0 || t.bid == 0 || t.ask < t.bid) return false;
    
    r.high = t.ask;
    r.low = t.bid;
    
    if(t.last != 0)
    {
      if(RenderBars == OHLC)
      {
        if(t.last > p.last)
        {
          r.open = r.low;
          r.close = r.high;
        }
        else
        {
          r.open = r.high;
          r.close = r.low;
        }
      }
      else
      {
        r.open = r.close = (r.high + r.low) / 2;
      }
      
      if(t.last < t.bid) r.low = t.last;
      if(t.last > t.ask) r.high = t.last;
      r.close = t.last;
    }
    else
    {
      if(RenderBars == OHLC)
      {
        if((t.ask + t.bid) / 2 > (p.ask + p.bid) / 2)
        {
          r.open = r.low;
          r.close = r.high;
        }
        else
        {
          r.open = r.high;
          r.close = r.low;
        }
      }
      else
      {
        r.open = r.close = (r.high + r.low) / 2;
      }
    }
    
    r.time = cursor;
    r.spread = (int)((t.ask - t.bid)/_Point);
    r.tick_volume = 1;
    r.real_volume = (long)t.volume;
  
    p = t;
    return true;
  }

この関数では、現在の時刻'カーソル'のMqlTick構造体のフィールドがMqlRates構造体のフィールドに移植され、ヒストリーに書き込まれます。

下図は、カスタムシンボルのティック足チャート(比較に標準的なティックチャートを示しています)です。

MetaTrader5のEURUSDティックチャートの完全な機能

MetaTrader5のEURUSDティックチャートの完全な機能

インジケータやオブジェクト、EAを使ってティック分析やトレードを自動化するチャートです。

ティック足チャートの足の時間は架空のものであることに注意してください。 ループバックモードを有効にすると、直近の足は常に1分単位の正確な現在時刻を持ち、前の足は1分単位のステップで離れています(MetaTrader5の最小タイムフレームサイズです)。 LoopBackモードを無効にすると、エキスパートの起動時間から足の時間が1分ずつ増えていくので、初期値以上の足はすべて仮想的な未来になってしまいます。

ただし、右端のM1足は、直近のティックと現在の「終値」(または「最後」)の価格に対応します。 オンラインとテスターの両方のEAを使用して、そのようなチャートでのトレードを可能にします。 オンラインで動作させるためには、「XY_ticks」のシンボルチャートからオリジナルのXYシンボルをトレードできるようにしなければならないため、EAには若干の修正が必要です(カスタムシンボルはターミナル内にしか存在せず、サーバー上では知られていません)。 上記の例では、すべてのトレードオーダーにおいて「EURUSD_ticks」を「EURUSD」に置き換える必要があります。

EAがインジケータからトレードシグナルを受け取る場合は、現在のタスクシンボルではなく、カスタムシンボルチャート上にそのインスタンスを作成し、このタスクシンボルチャート上でこのEAを実行すれば十分かもしれません。 しかし、この方法は必ずしも適用できるとは限りません。 カスタムシンボルのトレードにEAを適応させる別の方法については、さらに説明します。

ティックチャートで機能する上での困難は、迅速に更新されるという事実に関連します。 このため、手動でチャートを分析してマークすることはほとんど不可能です。

ティッククォート "を使用して提示されたアプローチは、通常のインジケータやオブジェクトを使用することができますが、内部バッファに特別なデータを蓄積し、バッファに基づいてシグナルを計算することなく、スキャルピング戦略のテストを可能にします。

タイムシフトとロウソク足のメタモルフォーゼ

多くのトレーダーは、練習でローソク足のパターンをメインまたは追加のシグナルとして使用します。 この方法は視覚的に情報を得ることができますが、重要なデメリットがあります。

ローソク足のパターンは、足の連続の事前に定義された形状を記述します。 すべての足は、時間の経過とともに価格が変化することで形成されます。 しかし、チャートは人為的に足に対応するセグメントに分割され、特定のタイムゾーン(ブローカーによって選択された)に整列された時間を提示していますが、時間は本質的に連続します。 例えば、H1足チャートを数分(例えば15分)ずらすと、足の形状が変わる可能性が高いです。 その結果、以前に存在していたパターンが完全に消えてしまい、別の場所に新しいパターンが形成されてしまうことがあります。 しかし、プライスアクションそのものは同じです。

人気のローソク足のパターンをいくつか見てみると、似たような値動きで形成されていることがよくわかりますし、足計算の開始時間によって見た目の差が生じていることがわかります。 例えば、時間軸を半分ずつずらすと、「ハンマー」は「ピアシング」に変身します)、「ハングマン」は「ダーククラウドカバー」に変身します。 シフト値や局所的な価格の変化によっては、弱気の「巻き込み」や強気の「巻き込み」に変わるパターンもあります。 下の時間帯に切り替えると、上記のパターンはすべて「モーニングスター」や「イーブニングスター」になってしまうことがあります。

つまり、各パターンは、時間とスケールの計算開始からの関数です。 特に、始点を変えることで、一つの図形を検出し、相当する他の図形を全て遮断することができます。

反転パターン(動きの始まりを決定することができますので、トレーダーの間で人気があります)は、次の簡略化された形で提示することができます。

価格の反転と等価なローソク足の構造

価格の反転と等価なローソク足の構造

上昇反転と下降反転のそれぞれについて、2種類の足構成の変形例を示す概略図です。 似たような意味のパターンが増える可能性があることは先に見てきました。 全てを追跡するのは合理的ではないでしょう。 より便利なソリューションは、任意の時間の開始のローソク足のパターンを決定する関数でしょう。

さらに、時間変換は、他の時間帯のGMTに対する相対的な許容可能なシフトを表している可能性があり、理論的には、新しいローソク足のパターンは、時間帯で形成されたものと同様に、この新しい領域でも動作するはずであることを意味します。 トレーディングサーバーは、世界中のすべてのタイムゾーンに位置しているため、どこかのトレーダーは間違いなく絶対に異なるシグナルを見ることができます。 すべてのシグナルに価値があります。

ローソク足パターンのファンは、開始点に応じて、その変動性を考慮に入れるべきであるという結論に達することができます。 そこで便利なのがカスタムシンボルです。

シンボルベースのカスタムシグナルは、指定した値で未来または過去にシフトしたタイムスタンプを持つ足やティックを構築することができます。 このシフト値は、選択されたタイムフレームの足の一部として解釈することができます。 価格や動きは変わらないが、でも面白い結果が得られます。 第一に、このようなシフトがなければ気づかないであろうローソク足のパターンの検出を提供します。 第2に、実際には不完全な足が一つ先に見えます。

例えば、気配値を5分前にずらすと、M15チャートの足は、元のチャートよりも1/3早く(1時間の10分目、25分目、40分目、55分目)に形成します。 シフトが重要でない場合は、オリジナルチャートとカスタムチャートのパターンはほぼ同じになりますが、カスタムチャートからのシグナル(インジケータシグナルを含む足で計算されたもの)の方が早く来るようになります。

このようなタイムシフトされたカスタムシンボルの作成は、TimeShift.mq5 EAで実装されています。

シフト値はソースShiftパラメータで指定します(秒単位)。 このEAはティックを使用して動作し、Startパラメータで指定された日付を起点として、変換された相場のヒストリーを開始時に計算することができます。 OnTick イベント生成モードが有効で、EmulateTicks パラメータが提供されている場合(デフォルトでは true)、ティックはオンラインで処理されます。

時間変換は、ヒストリー的なティックのためとオンラインティックのシンプルに実行されます:例えば、'add'関数は後者の場合に使用します。

  ulong lastTick;
  
  void add()
  {
    MqlTick array[];
    int size = CopyTicksRange(_Symbol, array, COPY_TICKS_ALL, lastTick + 1, LONG_MAX);
    if(size > 0)
    {
      lastTick = array[size - 1].time_msc;
      for(int i = 0; i < size; i++)
      {
        array[i].time += Shift;
        array[i].time_msc += Shift * 1000;
      }
      if(CustomTicksAdd(symbolName, array) == -1)
      {
        Print("Tick error: ", GetLastError());
      }
    }
  }
  
  void OnTick(void)
  {
    ...
    if(EmulateTicks)
    {
      add();
    }
  }

ソースと修正したEURUSDのH1チャートは以下の通りです。

タイムシフトEAによるEURUSD H1チャート

タイムシフトEAによるEURUSD H1チャート

ハーフバース(30分)のシフトが入ると、図が変わります。

30分足シフトのEURUSD H1カスタムチャート

30分足シフトのEURUSD H1カスタムチャート

ローソク足のパターンをよく知っている人は、間違いなく、元のチャートにはなかった多くの差に気づくでしょう。 このように、カスタムシンボルチャートに通常のローソク足インジケータを適用することで、2倍のアラートやトレードチャンスを得ることができます。 これをEAを使って自動化することもできますが、ここではカスタムシンボルチャートから本物のシンボルをトレードするためのEAを探る必要があります。 このタスクは記事の最後に考えます。 ここで、おそらく最も人気のあるカスタムチャートタイプである「レンコン」について考えてみましょう。

レンコン

ノントレーディングのEARenkoTicks.mq5を使って、レンコンチャートを実装していきます。 このEAは、実際のティックを処理しながら、カスタムシンボルの気配値としてレンコンを生成します(MetaTrader5では、ブローカーから入手可能)。 任意のソースシンボルのクオート(足)と、RenkoTicksが動作しているタスクチャートのタイムフレームを使用することができます。

レンコンを生成する際に、カスタムシンボルの代わりにインジケータや描画(オブジェクトやキャンバス上での描画)を使うことができますが、どちらの場合も擬似的に生成されたチャートにインジケータやスクリプトを使うことはできません。

すべてのレンコン足はM1のタイムフレーム上に形成されています。 これは意図的に行われています。例えばボラティリティーの高い時間帯などに、レンコン足が早く次々と形成されることがあるためで、足間の時間はできるだけ短くすべきです。 1分はMetaTrader5でサポートされている最小です。 よって、レンコンチャートは常にM1のタイムフレームを持つべきなのです。 レンコンチャートを別の時間軸に切り替えても意味がありません。 各1分足の開始時刻は、該当するレンコン足の形成が開始される時刻と一致します。 このような1分足の時刻はフェイクなので、代わりに次の1分足の開始時刻を確認する必要があります。

残念ながら、時には1分以内に数本のレンコン足を形成しなければならないこともあります。 MetaTrader5ではこれができないため、EAでは隣接するM1足のシーケンスとして足を生成し、1分ごとに人為的にカウント数を増やします。 そのため、レンコン足の正式な時刻は、実際の時刻と一致しない場合があります(先行する場合があります)。 例えば、レンコンのサイズが100pipsの場合、12:00:00に発生して10秒かかった300pipsの動きは、12:00:00、12:00:05、12:00:10にレンコン足を作成したことになります。 その代わり、12:00、12:01、12:02に足が生成されます。

気配値ヒストリーで発生した場合、過去から転送されたこのようなレンコン足が、元のチャートの後段の足から形成された他の足と重なってしまうという問題が発生することがあります。 仮に12:02に100ポイントの別の動きが起きたとすると、12:02の開場時間のレンコン足を生成する必要がありますが、この時間はすでに混雑しています! このような競合を解決するために、エキスパート・アドバイザーは、リクエストされたカウントが既にビジー状態である場合、次の形成された時間を1分ずつ強制的に増加させる特別なモードを備えています。 このモードは、デフォルトでfalseに設定されているSkipOverflowsパラメータによって設定されます(足は重ならず、代わりに必要に応じて未来に移動します)。 SkipOverflowsがtrueの場合、時間が重なった足チャートは互いに上書きされ、結果として得られるレンコンは完全には正しくありません。

このような強い動きと「先行」足の生成がリアルタイムで可能であることに注意してください - この場合、足は実際に将来的に形成されます 今回の例では、12:00, 12:01, 12:02の開店時間を持つレンコン足は、12:00:10に存在することになります! 分析やトレードの際に考慮すべきことです。

この問題を解決する方法はいくつかありますが、例えばレンコン足のサイズを大きくするなどの方法があります。 しかし、明らかな欠点があります - これはレンコンの精度を低下させます。 つまり、 より粗い気配値の動きを登録し、より少ない足を生成します。 もう一つの可能な方法は、古い足をパック(左にシフト)することですが、インジケータやオブジェクトをリペイントする必要があります。

特定のプラットフォームの関数のため、EAは直近のレンコン足の開始時間に等しい時間で架空のティックを生成します。 唯一の目的は、トレードEAでOnTickハンドラを起動することです。 ティックをそのままオリジナルのシンボルからカスタムシンボルに翻訳してしまうと、レンコンの構造そのものが台無しになってしまいます。 繰り返しになりますが、強い動きを例にとり、実際の時刻が12時00分になっているティックをレンコンチャートに送ってみます。 しかし、このティックの時刻は、直近の(現在の)0小節ではなく、開始時刻12:00の2小節に対応します。 その結果、そのようなティックは、12時のレンコン足(ヒストリーにある)を台無しにしたり、エラーを出したりします。 レンコンは動きが鈍いと逆に甘えてしまうことがあります。 気配値が1本の足の範囲内に長くある場合、レンコン足は同じ始値時間を維持しますが、新しいティックは0本目のレンコン足よりも1分以上長い時間を持つことがあります。 このようなティックがレンコンチャートに送られると、「未来」に幻の足を形成することになります。

ヒストリー的なレンコンのティックは、1つのボックスに1つのティックというミニマルなスタイルで形成されていることに注意してください。 オンラインでタスクしているときは、すべてのティックがrenkoに送信されます。

他のカスタムシンボルと同様に、このアプローチでは、レンコンチャート上の任意のインジケータ、スクリプト、オブジェクトを使用して、EAを使用してトレードを行うことができます。

主なパラメータ

  • RenkoBoxSize - レンコン足のサイズをポイント単位で指定します。
  • ShowWicks - 本体の表示フラグ、true がデフォルトです。
  • EmulateOnLineChart - ティック送信フラグ,デフォルトではtrue.
  • OutputSymbolName — 生成されたレンコンのカスタムシンボル名、デフォルトでは空の文字列 - 名前は「Symbol_T_Type_Size」として形成され、シンボルは現在のタスクシンボル、Tはティックモードシンボル、タイプはウィックス付きの "r"(レンガ)、サイズ - RenkoBoxSize;例: "EURUSD_T_r100"
  • Reset - レンコンチャート全体の再計算のフラグ、デフォルトではfalseに設定されています。 trueに設定した場合は結果を待ってからfalseに戻しておくと、ターミナルの再起動のたびに再計算が行われるのを避けることができます。 このモードは、あるポジションでレンコン足の生成に失敗した場合に便利です。 EAは最後に利用可能なレンコン足から計算を続けることができるので、通常はこのオプションは常に無効になっています。
  • StartFrom, StopAt - ヒストリー期間の開始と終了。デフォルトではゼロが使用され、利用可能なヒストリー全体が使用することを意味します。 初めてのEA使用時には、リアルティックでレンコン足を発生させた時のシステム速度を評価するために、StartFromを最近の過去に設定しておくことをお勧めします。
  • デフォルトではfalseに設定されています。必要な計算が前のボックスで既に占有されている場合、新しいバータイムが強制的に1分増加することを意味します。
  • CloseTimeMode - trueの場合、ボックスはすべてクローズ時に形成されます(ボックスごとに1つの「ティック」イベント); デフォルトはfalseです。

Renkoクラスは、ティックストリームを処理し、それを基に新しいレンコン足を作成します。 その主な構成要素は、以下の擬似コードで示されています。

  class Renko
  {
    protected:
      bool incrementTime(const datetime time);
      void doWriteStruct(const datetime dtTime, const double dOpen, const double dHigh, const double dLow, const double dClose, const double dVol, const double dRealVol, const int spread);
      
    public:
      datetime checkEnding();
      void continueFrom(const datetime time);
      void doReset();
  
      void onTick(const MqlTick &t);
      void updateChartWindow(const double bid = 0, const double ask = 0);
  };

protectedメソッド incrementTimeとdoWriteStructはそれぞれ、次の空き時間、指定した時間に最も近い、次のレンコンボックスのM1サンプルに切り替え、CustomRatesUpdate呼び出しを使って足自体を書き込みます。 公開部分の最初の3つのメソッドは、起動時のアルゴリズムの初期化を担当します。 このEAは、以前のレンコン相場が存在するかどうかをチェックし(ヒストリーの終了日時を返すcheckEndingメソッドで行われます)、存在するかどうかに応じて、EAはcontinueFromメソッドを使って指定した瞬間から継続するか(内部変数の値を復元する)、またはdoResetを使って「空」の状態からのティックを処理するかのいずれかを行います。

onTickメソッドはティックごとに呼び出され(ヒストリーでもオンラインでも)、必要に応じてdoWriteStructを使ってレンコン足を形成します(有名なRenkoLiveChart.mq4EAのアルゴリズムを少し修正して使っています)。 EAの設定でティックエミュレーションが指定されている場合は、updateChartWindowが追加で呼び出されます。 以下にフルソースコードを添付します。

TickProviderクラスは、Renkoオブジェクトへのティックの「配信」を担当します。

  class TickProvider
  {
    public:
      virtual bool hasNext() = 0;
      virtual void getTick(MqlTick &t) = 0;
  
      bool read(Renko &r)
      {
        while(hasNext() && !IsStopped())
        {
          MqlTick t;
          getTick(t);
          r.onTick(t);
        }
        
        return IsStopped();
      }
  };

EAでの基本シンボルのティックヒストリーとオンラインタスク時のOnTickイベントキューという2つの異なるソースからティックを読み取るための共通のインターフェイスを宣言しているため、抽象的です。 read' メソッドは、仮想メソッド hasNext() と getTick() を使用するユニバーサルティックループです。

ティック履歴は、おなじみの方法でHistoryTickProviderクラスで読み取られます:それは、日ごとにティックが要求されるCopyTicksRangeとMqlTick array[]中間バッファを使用しています。

  class HistoryTickProvider : public TickProvider
  {
    private:
      datetime start;
      datetime stop;
      ulong length;     // in seconds
      MqlTick array[];
      int size;
      int cursor;
      
      int numberOfDays;
      int daysCount;
      
    protected:
      void fillArray()
      {
        cursor = 0;
        do
        {
          size = CopyTicksRange(_Symbol, array, COPY_TICKS_ALL, start * 1000, MathMin(start + length, stop) * 1000);
          Comment("Processing: ", DoubleToString(daysCount * 100.0 / (numberOfDays + 1), 0), "% ", TTSM(start));
          if(size == -1)
          {
            Print("CopyTicksRange failed: ", GetLastError());
          }
          else
          {
            if(size > 0 && array[0].time_msc < start * 1000) // prevent older than requested data returned
            {
              start = stop;
              size = 0;
            }
            else
            {
              start = (datetime)MathMin(start + length, stop);
              if(size > 0) daysCount++;
            }
          }
        }
        while(size == 0 && start < stop);
      }
    
    public:
      HistoryTickProvider(const datetime from, const long secs, const datetime to = 0): start(from), stop(to), length(secs), cursor(0), size(0)
      {
        if(stop == 0) stop = TimeCurrent();
        numberOfDays = (int)((stop - start) / DAY_LONG);
        daysCount = 0;
        fillArray();
      }
  
      bool hasNext() override
      {
        return cursor < size;
      }
  
      void getTick(MqlTick &t) override
      {
        if(cursor < size)
        {
          t = array[cursor++];
          if(cursor == size)
          {
            fillArray();
          }
        }
      }
  };

CurrentTickProvider オンラインティック・プロバイダ・クラス。

  class CurrentTickProvider : public TickProvider
  {
    private:
      bool ready;
      
    public:
      bool hasNext() override
      {
        ready = !ready;
        return ready;
      }
      
      void getTick(MqlTick &t) override
      {
        SymbolInfoTick(_Symbol, t);
      }
  };

簡略化した形でのティック処理の主な部分はこんな感じです。

  const long DAY_LONG = 60 * 60 * 24;
  bool _FirstRun = true;
  
  Renko renko;
  CurrentTickProvider online;
  
  void OnTick(void)
  {
    if(_FirstRun)
    {
      // find existing renko tail to supersede StartFrom
      const datetime trap = renko.checkEnding();
      if(trap > TimeCurrent())
      {
        Print("Symbol/Timeframe data not ready...");
        return;
      }
      if((trap == 0) || Reset) renko.doReset();
      else renko.continueFrom(trap);
  
      HistoryTickProvider htp((trap == 0 || Reset) ? StartFrom : trap, DAY_LONG, StopAt);
      
      const bool interrupted = htp.read(renko);
      _FirstRun = false;
      
      if(!interrupted)
      {
        Comment("RenkoChart (" + (string)RenkoBoxSize + "pt): open ", _SymbolName, " / ", renko.getBoxCount(), " bars");
      }
      else
      {
        Print("Interrupted. Custom symbol data is inconsistent - please, reset or delete");
      }
    }
    else if(StopAt == 0) // process online if not stopped explicitly
    {
      online.read(renko);
    }
  }

最初の開始時に、renkoのヒストリーの終わりが検索され、開始時刻StartFromまたはヒストリーから(見つかった場合)HistoryTickProviderオブジェクトが作成され、その後、すべてのティックが読み込まれます。 それ以降のティックはすべてCurrentTickProviderオブジェクトを通じてオンラインで処理されます(Renkoオブジェクトと同様にグローバルコンテキストで作成されます)。

2019年から100pipsの足サイズでEURUSDをベースにしたレンコンチャートを作成してみましょう。 そのためには、StartFrom以外のデフォルト設定でEURUSD H1チャート上でEAを実行します。 時間枠が重要なのは、EAが利用可能なレンコンヒストリーを持って再起動された場合のみで、この場合、レンコンの再計算は、直近のレンコンブロック以外の1つのレンコンブロックが該当する足の時間のインデントで開始されます。

例えば、オリジナルEURUSDのH1の場合。

RenkoTicksEAを使ったEURUSD H1チャート

RenkoTicksEAを使ったEURUSD H1チャート

以下のようなチャートが送られてきます。

ブロックサイズ100ポイントのEURUSDレンコンチャート

ブロックサイズ100ポイントのEURUSDレンコンチャート

視覚的にわかりやすくするために、MAを2つ追加しました。

レンコンマークの気配値が出てきたので、いよいよトレード用のテストEAを開発してみましょう。

2つのMAのクロスに基づいたトレーディング・エキスパート・アドバイザー

2つの移動平均線のクロスである最もシンプルなトレード戦略の1つを使用してみましょう。 前のスクリーンショットがその考え方を示します。 短期MA(赤)が長期MA(青)と上向きまたは下向きにクロスしたら、それぞれ買いまたは売りします。 これは反転のシステムです。

ゼロからEAを作成するのは難しいと思いますが、MetaTrader5では、標準クラスのライブラリ(ターミナルに付属)に基づいてEAを作成できるMQLウィザードが用意されています。 プログラミングに慣れていないトレーダーにはとても便利です。 結果として得られるコード構造は、多数のロボットに共通しているため、メインタスクであるカスタムシンボルでのトレードにロボットを適応させるために使用することをお勧めします。 標準ライブラリなしで作成されたEAも同じ方法で適応させることができますが、作成方法が大きく異なるため、プログラマーは必要に応じて適切な修正を提供しなければなりません(一般的に、経験豊富なプログラマーは、例を使用して他のどのようなEAでも適応させることができます)。

奇妙なことに、標準ライブラリには、最も人気のあるストラテジーの一つであるそれにも関わらず、2つのMAのクロスのシグナルがありません(少なくともアルゴリズムトレードを学習する際には、最も人気があります)。 したがって、適切なシグナルモジュールを書く必要があります。 これをSignal2MACross.mqhと呼ぼう。 MQL ウィザードで使用するシグナルに必要なルールを満たしているコードです。

"ヘッダ "から始まり、適切なフォーマットでシグナルの説明が書かれた特別なコメントで、メタエディタからアクセスできるようになっています。

  //--- wizard description start
  //+------------------------------------------------------------------+
  //| Description of the class                                         |
  //| Title=Signals of 2 MAs crosses                                   |
  //| Type=SignalAdvanced                                              |
  //| Name=2MA Cross                                                   |
  //| ShortName=2MACross                                               |
  //| Class=Signal2MACross                                             |
  //| Page=signal_2mac                                                 |
  //| Parameter=SlowPeriod,int,11,Slow MA period                       |
  //| Parameter=FastPeriod,int,7,Fast Ma period                        |
  //| Parameter=MAMethod,ENUM_MA_METHOD,MODE_LWMA,Method of averaging  |
  //| Parameter=MAPrice,ENUM_APPLIED_PRICE,PRICE_OPEN,Price type       |
  //| Parameter=Shift,int,0,Shift                                      |
  //+------------------------------------------------------------------+
  //--- wizard description end

クラス名(行Class)は、さらにMQLコード内の実際のクラス名と一致している必要があります。 シグナルには、2つのMAに代表される5つのパラメータがあります。2つの期間(高速と低速)、平均化方法、価格タイプ、シフトです。

このクラスはCExpertSignalを継承します。 CiMA インジケータオブジェクトの 2 つのインスタンス、タスクパラメータを持つ変数、パラメータセッターメソッド(メソッド名はヘッダの名前と一致しなければなりません)を含んでいます。 また、このクラスでは、インジケータの初期化時に呼び出される仮想メソッドや、設定の確認や買いシグナル・売りシグナルの判定時に呼び出される仮想メソッドを再定義します。

  class Signal2MACross : public CExpertSignal
  {
    protected:
      CiMA              m_maSlow;         // object-indicator
      CiMA              m_maFast;         // object-indicator
      
      // adjustable parameters
      int               m_slow;
      int               m_fast;
      ENUM_MA_METHOD    m_method;
      ENUM_APPLIED_PRICE m_type;
      int               m_shift;
      
      // "weights" of market models (0-100)
      int               m_pattern_0;      // model 0 "fast MA crosses slow MA"
  
    public:
                        Signal2MACross(void);
                       ~Signal2MACross(void);
                       
      // parameters setters
      void             SLowPeriod(int value) { m_slow = value; }
      void              FastPeriod(int value) { m_fast = value; }
      void              MAMethod(ENUM_MA_METHOD value) { m_method = value; }
      void              MAPrice(ENUM_APPLIED_PRICE value) { m_type = value; }
      void              Shift(int value) { m_shift = value; }
      
      // adjusting "weights" of market models
      void              Pattern_0(int value) { m_pattern_0 = value; }
      
      // verification of settings
      virtual bool      ValidationSettings(void);
      
      // creating the indicator and timeseries
      virtual bool      InitIndicators(CIndicators *indicators);
      
      // checking if the market models are formed
      virtual int       LongCondition(void);
      virtual int       ShortCondition(void);
  
    protected:
      // initialization of the indicators
      bool              InitMA(CIndicators *indicators);
      
      // getting data
      double FastMA(int ind) { return(m_maFast.Main(ind)); }。
      doubleSLowMA(int ind) { return(m_MAlow.Main(ind)); }。
  };

このクラスでは、唯一の戦略(パターンまたはモデル)を記述しています:高速MAが低速MAとクロスするとき、買い(上向きのクロスの)または売り(下向きのクロスの)が初期化されます。 このモデルの加重は、デフォルトでは100になります。

  Signal2MACross::Signal2MACross(void) : m_slow(11), m_fast(7), m_method(MODE_LWMA), m_type(PRICE_OPEN), m_shift(0), m_pattern_0(100)
  {
  }

ポジションオープン条件は、以下の2つの方法で決定されます(厳密には、コードはクロスではなく、一方のMAの相対的なポジションをチェックしていますが、常に相場に出ているシステムでは効果は同じになります)。

  int Signal2MACross::LongCondition(void)
  {
    const int idx = StartIndex();
    
    if(FastMA(idx) >SLowMA(idx))
    {
      return m_pattern_0;
    }
    return 0;
  }
  
  int Signal2MACross::ShortCondition(void)
  {
    const int idx = StartIndex();
  
    if(FastMA(idx) <SLowMA(idx))
    {
      return m_pattern_0;
    }
    return 0;
  }

StartIndex関数は親クラスで定義されています。 コードを見ればわかるように、インデックスはシグナルを分析した足の番号です。 EA設定で全てのティックベースの操作が選択されている場合(Expert_EveryTick = true、詳細参照)、開始インデックスは0になります。

Signal2MACross.mqhファイルをMQL5/Include/Expert/Signal/MySignalsフォルダに保存し、メタエディタを再起動して(起動している場合)、MQLウィザードで新しいモジュールをピックアップします。

これでシグナルに基づいてEAを生成することができます。 メニューの「ファイル」→「新規作成」を選択し、ウィザードダイアログを開きます。 そして、以下のステップに従ってください。

  1. 「エキスパートアドバイザ(生成)」を選択
  2. EA 名、例えば Experts\Examples\MA2Crossをセット
  3. シグナル"Signals of 2 MAs crosses(2つのMAのクロスのシグナル )"を追加
  4. トレーリングストップが使用されていない "を使用する
  5. "固定ボリュームトレード "を利用する

その結果、以下のEAコードが得られます。

  #include <Expert\Expert.mqh>
  #include <Expert\Signal\MySignals\Signal2MACross.mqh>
  #include <Expert\Trailing\TrailingNone.mqh>
  #include <Expert\Money\MoneyFixedLot.mqh>
  
  //+------------------------------------------------------------------+
  //| Inputs                                                           |
  //+------------------------------------------------------------------+
  // inputs for expert
  input string             Expert_Title              = "MA2Cross";  // Document name
  ulong                    Expert_MagicNumber        = 7623;
  bool                     Expert_EveryTick          = false;
  // inputs for main signal
  input int                Signal_ThresholdOpen      = 10;          // Signal threshold value to open [0...100]
  input int                Signal_ThresholdClose     = 10;          // Signal threshold value to close [0...100]
  input double             Signal_PriceLevel         = 0.0;         // Price level to execute a deal
  input double             Signal_StopLevel          = 0.0;         // Stop Loss level (in points)
  input double             Signal_TakeLevel          = 0.0;         // Take Profit level (in points)
  input int                Signal_Expiration         = 0;           // Expiration of pending orders (in bars)
  input int                Signal_2MACross_SlowPeriod = 11;         // 2MA Cross(11,7,MODE_LWMA,...)SLow MA period
  input int                Signal_2MACross_FastPeriod = 7;          // 2MA Cross(11,7,MODE_LWMA,...) Fast Ma period
  input int                Signal_2MACross_SlowPeriod = 11;         // 2MA Cross(11,7,MODE_LWMA,...)SLow MA period
  input ENUM_APPLIED_PRICE Signal_2MACross_MAPrice   = PRICE_OPEN;  // 2MA Cross(11,7,MODE_LWMA,...) Price type
  input int                Signal_2MACross_Shift     = 0;           // 2MA Cross(11,7,MODE_LWMA,...) Shift
  input double             Signal_2MACross_Weight    = 1.0;         // 2MA Cross(11,7,MODE_LWMA,...) Weight [0...1.0]
  // inputs for money
  input double             Money_FixLot_Percent      = 10.0;        // Percent
  input double             Money_FixLot_Lots         = 0.1;         // Fixed volume
  
  //+------------------------------------------------------------------+
  //| Global expert object                                             |
  //+------------------------------------------------------------------+
  CExpert ExtExpert;
  
  //+------------------------------------------------------------------+
  //| Initialization function of the expert                            |
  //+------------------------------------------------------------------+
  int OnInit()
  {
    // Initializing expert
    if(!ExtExpert.Init(Symbol(), Period(), Expert_EveryTick, Expert_MagicNumber))
    {
      printf(__FUNCTION__ + ": error initializing expert");
      ExtExpert.Deinit();
      return(INIT_FAILED);
    }
    // Creating signal
    CExpertSignal *signal = new CExpertSignal;
    if(signal == NULL)
    {
      printf(__FUNCTION__ + ": error creating signal");
      ExtExpert.Deinit();
      return(INIT_FAILED);
    }
    
    ExtExpert.InitSignal(signal);
    signal.ThresholdOpen(Signal_ThresholdOpen);
    signal.ThresholdClose(Signal_ThresholdClose);
    signal.PriceLevel(Signal_PriceLevel);
    signal.StopLevel(Signal_StopLevel);
    signal.TakeLevel(Signal_TakeLevel);
    signal.Expiration(Signal_Expiration);
    
    // Creating filter Signal2MACross
    Signal2MACross *filter0 = new Signal2MACross;
    if(filter0 == NULL)
    {
      printf(__FUNCTION__ + ": error creating filter0");
      ExtExpert.Deinit();
      return(INIT_FAILED);
    }
    signal.AddFilter(filter0);
    
    // Set filter parameters
    filter0.SlowPeriod(Signal_2MACross_SlowPeriod);
    filter0.FastPeriod(Signal_2MACross_FastPeriod);
    filter0.MAMethod(Signal_2MACross_MAMethod);
    filter0.MAPrice(Signal_2MACross_MAPrice);
    filter0.Shift(Signal_2MACross_Shift);
    filter0.Weight(Signal_2MACross_Weight);
  
    ...
    
    // Check all trading objects parameters
    if(!ExtExpert.ValidationSettings())
    {
      ExtExpert.Deinit();
      return(INIT_FAILED);
    }
    
    // Tuning of all necessary indicators
    if(!ExtExpert.InitIndicators())
    {
      printf(__FUNCTION__ + ": error initializing indicators");
      ExtExpert.Deinit();
      return(INIT_FAILED);
    }
  
    return(INIT_SUCCEEDED);
  }

MA2Cross.mq5のフルコードをこの記事に添付します。 コンパイル、ストラテジーテスターでのテスト、さらにはカスタムレンコンを含む任意のシンボルでの最適化の準備ができています。 正確にはレンコンにまだ興味があるので、少し説明する必要があります。

各レンコンブロックは、その「長方形」の形で、価格の動きによって完全に形成されるまで存在しません。 次のブロックが出現したとき、その終値だけでなく、始値も知りません。 ブロックが最終的にクローズしたとき、決定的で最も特徴的なのは終値です。 そのため、EAのSignal_2MACross_MAPriceパラメータのデフォルト値はPRICE_CLOSEに変更されています。 他の価格タイプで実験することもできますが、レンコンの考え方は、時間からのゲントアンバウンドだけでなく、レンガの大きさに応じて量子化することで、小さな価格変動を取り除くことができます。

0番目のレンコン足は常に不完全であることに注意してください(ほとんどの場合、長方形ではなく、本体のないローソク足です)、1番目のレンコン足からのシグナルを使用する理由です。 このため、Expert_EveryTickパラメータの値をfalseに設定します。

100ポイントのブロックサイズでEURUSDベースのカスタムレンコンを生成します。 その結果、シンボルEURUSD_T_r100を取得します。 テスターで選択します。 必ずM1のタイムフレームを設定してください。

例えば、2019-2020年(前半)の期間、デフォルトの期間を7と11に設定した場合のEAの動作を見てみましょう(他の組み合わせは最適化を使用して独立してチェックすることができます)。

EURUSDから派生した100ポイントレンコンチャートでのMA2CrossCustom戦略の結果

EURUSDから派生した100ポイントレンコンチャートでのMA2CrossCustom戦略の結果

カスタムシンボルと実際のシンボルを比較するために、MA2Crossと同様にWorkSymbolパラメータを空にしたMA2CrossCustomEAのレポートを提供します。 次のセクションでは、MA2CrossからMA2CrossCustomを取得する方法を考えてみましょう。

トレードテーブルからわかるように、トレードはブロックサイズの倍数の価格で実行されます:価格が完全に一致し、買い価格はスプレッドサイズによって異なります(ソースコードでこの動作を変更したい場合は、その形成中に登録された最大スプレッド値を各足に保存します)。 レンコンはソースチャートで使用されている価格タイプで構築されています。 今回の場合はBidです。 為替商品は最終価格を使用します。

EURUSDベースのカスタムレンコンシンボルをトレードするときの表

EURUSDベースのカスタムレンコンシンボルをトレードするときの表

今となっては結果が良すぎるように思えます。 実際、隠れたニュアンスがたくさんあります。

テスターでのレンコン・シンボルトレードは、始値、M1 OHLC、ティックによって、任意のモードでの結果の精度に影響を与えます。

標準レンコンの足始値は、足がマークされている時点で必ず到達するわけではありませんが、後から到達するケースが多いです(価格がレンコンサイズ内でしばらくの間上下に「歩く」ため、最終的には方向転換して反転足を形成する可能性があるからです)。 足のマーキング時間は、前の足の完了時間です。

レンコン足はM1足、つまり1分足の期間が決まっているので、終値は終値に対応していません。

標準外のレンコンを生成することが可能で、足は開始時刻ではなく完了時刻でマークされます。 そうすると、終値が終値に対応することになります。 ただし、始値は終値よりも1分早くなるので、実質的な始値には対応しません(終値にレンコンサイズをプラス/マイナスしたものです)。

レンコン分析は、形成された足で行われることになっていますが、その特徴的な価格は終値です。足ごとの操作では、テスターは現在の(直近の)足の始値のみを提供します(終値によるモードはありません)。 ここで、足の始値は、定義上、予測可能な価格です。 確定足(通常は1日から)からのシグナルを使用する場合、トレードはとにかく0番目の足の現在の価格で実行されます。 ティックモードを使用する場合でも、各足構成に基づく基準点を用いて、共通のルールに従って、テスターがレンコンのティックを生成します。 このテスターは、(M1足を使って視覚的にエミュレートしようとしている)レンコンクオートの特定の構造と動作を考慮していません。 仮想的に足全体の一回限りの形成を想像する場合、まだ実体があります - そして、そのような足に、テスターは、始値から始まるティックを生成します。 足のティックボリュームを1に等しく設定すると、足の構成が失われてしまいます(等しくOHLCを持つ価格ラベルになってしまいます)。

したがって、すべてのレンコンの構築方法は、カスタムレンコンシンボルをテストする際に、オーダー実行アーチファクトを持つことになります。

言い換えれば、レンコンの構造自体がレンコンの足の大きさに等しいステップで未来を覗き見ているので、レンコンのシンボルにテスター・グレインがあるということです。

これが別のレンコン足ではなく、実際のシンボル上のトレードオーダーの実行と組み合わせてトレードシステムをテストする必要がある理由です

レンコンは分析とタイミング-相場に参入するタイミングを提供します。

今のところ、カスタムシンボルでのトレードが可能なEAかどうかはテストしただけです。 これより、テスターのみでのEA適用に制限を設けています。 EAをユニバーサルなものにするためには、つまり、元のシンボルをオンラインでトレードできるようにするためには、あるものを追加する必要があります。 これは過最適な結果が出た場合の解決策としても役立ちます。

カスタムシンボルチャートでのトレードのEAの適応

カスタムシンボルはクライアントターミナルでしか知られておらず、トレードサーバー上には存在しません。 明らかに、カスタムシンボルチャートをチューニングするこのEAは、オリジナルシンボル(カスタムシンボルがベースになっている)のすべてのトレードオーダーを生成しなければなりません。 この問題の最もシンプルな解決策として、EAはオリジナルのシンボルチャート上で動作させることができますが、カスタムシンボルからのシグナル(例えば、インジケータからのシグナル)を受信することができます。 しかし、多くのトレーダーは全体像を見ることを好みます。 さらに、選択的なコード変更はエラーを発生させる可能性があります。 ソースコードへの編集は最小限に抑えることが望ましい。

残念ながら、元々のシンボルの名前と、それを元に作られたレンコンの名前は、プラットフォーム自体ではリンクできません。 便利な解決策としては、カスタムシンボルのプロパティに文字列フィールド「origin」または「parent」があり、そこに実際のシンボルの名前を書くことができます。 デフォルトでは空になります。 しかし、満たされたとき、プラットフォームは自動的にすべてのトレードオーダーとヒストリーのリクエストでシンボルを置き換えることになります。 この仕組みはプラットフォームには存在しないので、自分たちで実装する必要があります。 ソースとカスタムシンボルの名前は、パラメータを使用して設定します。 カスタムシンボルプロパティには、適切な意味を持つフィールド - SYMBOL_BASISがあります。 しかし、カスタムシンボルの任意の生成器(任意のMQLプログラム)がパラメータを正しく満たすこと、あるいはこの目的に正確に使用することを保証することはできないので、別の解決策を提供する必要があります。

このために、CustomOrderクラスを開発しました(以下に添付されているCustomOrder.mqhを参照してください)。 トレードオーダーやヒストリーリクエストの送信に関連するすべてのMQL API関数のラッパメソッドを含んでおり、そのパラメータにはインストルメントのシンボル名を含む文字列があります。 これらのメソッドは、カスタムシンボルを現在のタスクシンボルに置き換えたり、その逆を行ったりします。 他のすべてのAPI関数は "フック "を必要としません。 コードは以下の通りです。

  class CustomOrder
  {
    private:
      static string workSymbol;
      
      static void replaceRequest(MqlTradeRequest &request)
      {
        if(request.symbol == _Symbol && workSymbol != NULL)
        {
          request.symbol = workSymbol;
          if(request.type == ORDER_TYPE_BUY
          || request.type == ORDER_TYPE_SELL)
          {
            if(request.price == SymbolInfoDouble(_Symbol, SYMBOL_ASK)) request.price = SymbolInfoDouble(workSymbol, SYMBOL_ASK);
            if(request.price == SymbolInfoDouble(_Symbol, SYMBOL_BID)) request.price = SymbolInfoDouble(workSymbol, SYMBOL_BID);
          }
        }
      }
      
    public:
      static void setReplacementSymbol(const string replacementSymbol)
      {
        workSymbol = replacementSymbol;
      }
      
      static bool OrderSend(MqlTradeRequest &request, MqlTradeResult &result)
      {
        replaceRequest(request);
        return ::OrderSend(request, result);
      }
      
      static bool OrderCalcProfit(ENUM_ORDER_TYPE action, string symbol, double volume, double price_open, double price_close, double &profit)
      {
        if(symbol == _Symbol && workSymbol != NULL)
        {
          symbol = workSymbol;
        }
        return ::OrderCalcProfit(action, symbol, volume, price_open, price_close, profit);
      }
      
      static string PositionGetString(ENUM_POSITION_PROPERTY_STRING property_id)
      {
        const string result = ::PositionGetString(property_id);
        if(property_id == POSITION_SYMBOL && result == workSymbol) return _Symbol;
        return result;
      }
      
      static string OrderGetString(ENUM_ORDER_PROPERTY_STRING property_id)
      {
        const string result = ::OrderGetString(property_id);
        if(property_id == ORDER_SYMBOL && result == workSymbol) return _Symbol;
        return result;
      }
      
      static string HistoryOrderGetString(ulong ticket_number, ENUM_ORDER_PROPERTY_STRING property_id)
      {
        const string result = ::HistoryOrderGetString(ticket_number, property_id);
        if(property_id == ORDER_SYMBOL && result == workSymbol) return _Symbol;
        return result;
      }
      
      static string HistoryDealGetString(ulong ticket_number, ENUM_DEAL_PROPERTY_STRING property_id)
      {
        const string result = ::HistoryDealGetString(ticket_number, property_id);
        if(property_id == DEAL_SYMBOL && result == workSymbol) return _Symbol;
        return result;
      }
      
      static bool PositionSelect(string symbol)
      {
        if(symbol == _Symbol && workSymbol != NULL) return ::PositionSelect(workSymbol);
        return ::PositionSelect(symbol);
      }
      
      static string PositionGetSymbol(int index)
      {
        const string result = ::PositionGetSymbol(index);
        if(result == workSymbol) return _Symbol;
        return result;
      }
      ...
  };
  
  static string CustomOrder::workSymbol = NULL;

ソースコードの編集を最小限に抑えるために、以下のマクロを使用しています(すべてのメソッドに対して)。

  bool CustomOrderSend(const MqlTradeRequest &request, MqlTradeResult &result)
  {
    return CustomOrder::OrderSend((MqlTradeRequest)request, result);
  }
  
  #define OrderSend CustomOrderSend

すべての標準 API 関数呼び出しを CustomOrder クラスのメソッドに自動的にリダイレクトすることを可能にします。 そのためには、CustomOrder.mqhをEAにインクルードして、タスクシンボルを設定します。

  #include <CustomOrder.mqh>
  #include <Expert\Expert.mqh>
  ...
  input string WorkSymbol = "";
  
  int OnInit()
  {
    if(WorkSymbol != "")
    {
      CustomOrder::setReplacementSymbol(WorkSymbol);
      
      // force a chart for the work symbol to open (in visual mode only)
      MqlRates rates[1];
      CopyRates(WorkSymbol, PERIOD_H1, 0, 1, rates);
    }
    ...
  }

重要なのは、#include <CustomOrder.mqh> ディレクティブが他のすべてのものよりも先に来ることです。 したがって、接続された標準ライブラリを含むすべてのソースコードに影響を与えます。 ワイルドカードを指定しない場合、接続されたCustomOrder.mqhはEAに影響を与えず、標準API関数に制御を移します。

修正されたMA2CrossEAはMA2CrossCustom.mq5に名前が変更されました。

これで、他の設定はすべてそのままに、WorkSymbolをEURUSDに設定して、テストを開始することができます。 現在、このEAはレンコンのシンボルチャート上で動作していますが、実際にEURUSDをトレードします。

リアルEURUSDのシンボルマークをトレードした時の100ポイントレンコンチャートでのMA2CrossCustom戦略の結果

リアルEURUSDのシンボルマークをトレードした時の100ポイントレンコンチャートでのMA2CrossCustom戦略の結果

今回は現実に近い結果になっています。

EURUSDのトレードでは、価格はレンコン足の終値より顕著に異なります。 レンコン足は常に1分足の始まりでマークされていますが(プラットフォームのM1時間枠の制限です)、価格は1分足の中の任意の瞬間に足の境界線を越えているからです。 このEAは、バーバイバーモード(テスターモードと混同しないように)でチャート上で動作するため、シグナルの出現は、通常は価格が異なるEURUSDの分足足のオープニングに「移動」されます。 平均的には、1トレードあたりの分足足のレンジの計算上の期待値が誤差となります。

EURUSDから派生したレンコンチャートからEAが行うEURUSDトレード

EURUSDから派生したレンコンチャートからEAが行うEURUSDトレード

不一致を排除するために、EAはすべてのティックを処理する必要がありますが、テスターのティック生成のロジックはレンコン形成のロジックとは異なることがわかりました。特に、反転足の始値は常に前の足の終値に対してレンコンブロックと等しいギャップがあります。

この問題はオンライントレードには存在しません。

標準ライブラリを使用せずに書かれた別のEAを使用してCustomOrderの関数を確認しましょう。 数式の計算に関する記事からExprBot EAを使用します。また、2つのMAの交差戦略を利用し、MT4Ordersライブラリを使用して取引操作を行います。 修正されたEA ExprBotCustom.mq5は、必要なヘッダファイル(ExpresSParserSフォルダ)とともに以下に添付されています。

2019年~2020年(上半期)のレンジで、同じ設定(期間7/11、平均型LWMA、1小節目のCLOSE価格)での結果です。

EURUSDから派生した100ポイントレンコンチャートでのExprBotCustom戦略の結果

EURUSDから派生した100ポイントレンコンチャートでのExprBotCustom戦略の結果

本物のEURUSDシンボルをトレードしたときの100ポイントレンコンチャートでのExprBotCustom戦略の結果

本物のEURUSDシンボルをトレードしたときの100ポイントレンコンチャートでのExprBotCustom戦略の結果

これらの結果は、MA2CrossCustomEAで得られた結果とよく似ています。

提案されたアプローチが問題を解決していると結論づけることができます。 しかし、現在のCustomOrderの実装は基本的な最低限のものでしかありません。 トレード戦略やタスクシンボルの仕様によっては、改善が必要な場合があります。

結論

ブローカーが提供するワーキングシンボルのクオートを使用してカスタムシンボルを生成するための方法を検討しました。 特殊なデータの一般化と蓄積アルゴリズムにより、通常の相場を別の角度から見ることができ、高度なトレードシステムを構築することができます。

もちろん、カスタムシンボルはより多くの可能性を提供します。 この技術の潜在的な応用範囲ははるかに広いでしょう。 例えば、合成シンボル、ボリュームデルタ、および第3者のデータソースを使用することができます。 説明されているプログラムの変換により、標準的なMetaTrader5のチャート、ストラテジーテスター、オンラインモードで可能性を使用することができます。

MetaQuotes Ltdによってロシア語から翻訳されました。
元の記事: https://www.mql5.com/ru/articles/8226

添付されたファイル |
MQL5CUST.zip (61.87 KB)
DoEasyライブラリの時系列(第48部): 単一サブウィンドウでの単一バッファ複数銘柄・複数期間指標 DoEasyライブラリの時系列(第48部): 単一サブウィンドウでの単一バッファ複数銘柄・複数期間指標
本稿では、単一の指標バッファを使用して、指標サブウィンドウを構築および操作するための複数銘柄・複数期間標準指標の作成例について説明します。プログラムのメインウィンドウで動作し、データを表示するための複数のバッファを持つ標準指標を操作するためのライブラリクラスを準備します。
ニューラルネットワークが簡単に(第2回): ネットワークのトレーニングとテスト ニューラルネットワークが簡単に(第2回): ネットワークのトレーニングとテスト
第2回目の今回は、引き続きニューラルネットワークの勉強をし、作成したCNetクラスをEAで使用した例を考えていきます。 学習時間、予測精度ともに同様の結果を示す2つのニューラルネットワークモデルを用いてタスクを行います。
取引システムの開発における勾配ブースティング(CatBoost)素朴なアプローチ 取引システムの開発における勾配ブースティング(CatBoost)素朴なアプローチ
PythonでCatBoost分類器を訓練してモデルをmql5にエクスポートし、モデルパラメータとカスタムストラテジーテスターを解析します。Python言語とMetaTrader5ライブラリは、データの準備とモデルの訓練に使用されます。
トレンドとは何か、相場の構造はトレンドかレンジかで決まるのか? トレンドとは何か、相場の構造はトレンドかレンジかで決まるのか?
トレーダーはよくトレンドやレンジについて話しますが、トレンドやレンジとは何かを理解している人はほとんどおらず、概念を明確に説明できる人はさらにいません。 基本的な用語について考察することは、多くの場合、偏見や誤解の固まりに悩まされます。 しかし、利益を上げたいのであれば、概念の数学的・論理的な意味を理解する必要があります。 今回は、トレンドとレンジの本質に迫るとともに、相場の構造がトレンドなのか、レンジなのか、何か別のものなのかを定義してみたいと思います。 また、トレンド相場やレンジ相場で利益を出すための最適な戦略についても考えていきたいと思います。