English Русский 中文 Español Português
preview
MQL5における建値機能の実装(第1回):基底クラスと固定ポイントの建値モード

MQL5における建値機能の実装(第1回):基底クラスと固定ポイントの建値モード

MetaTrader 5 |
37 0
Niquel Mendoza
Niquel Mendoza


はじめに

ストップロスを建値(損益分岐点)へ移動することは、保有中ポジションのリスクを抑えるためによく用いられる取引管理手法です。価格が一定ポイント以上含み益方向へ進んだ時点で、ストップロスを建値まで移動させます。これにより、相場が急反転した場合でも、損失を回避しやすくなります

建値機能の適用には、主に2つのアプローチがあります。

  • 価格がテイクプロフィットに到達しなかった場合でも、損失なしでポジションを決済できるようにすること
  • ストップロスを建値より数ポイント有利な位置へ移動し、利益を一部確保すること

本連載は、3種類の建値機能を実装していきます。今回はその第1回として、システムの基盤クラスを作成し、今後の拡張の土台となるシンプルな建値モデルを実装します。


建値モードとは

システムに建値ロジックを組み込む前に、その基本的な仕組みを理解することが重要です。

簡単に言えば、建値モードとは、エントリー後に価格が一定以上有利方向へ進んだ際に、ストップロスを建値へ移動させる仕組みです。


    be1

    図1:ストップロスレベルを変更する前の売りポジション

    図1では、ストップロスは以前の記事で作成したOrder Blocksインジケーターのシグナルレベルに設定されています。

     be2

    図2:ストップロス修正後の売りポジション

    図2では、新しいローソク足の形成後、システムが自動的にストップロスを指定された距離まで移動しています。この距離は設定値である150ポイントに対応しています。これは、前述した建値処理を反映した動作です。

    次に、建値モードが有効化された後の挙動を確認します。

    be3


    図3:建値レベルで決済された売りポジション

    図3の通り、価格は大きな逆行をすることなく、事前に調整された建値レベルで決済されています。このケースでは、ストップロスによる損失は発生せず、小幅な利益または損失回避が実現されています。

    このように建値機能には、価格がテイクプロフィットに到達しない場合でも、損失なしで決済できる可能性があります。
    一定の条件下では、利益を確保したままリスクをゼロに近づけることができるというメリットがあります。

    一方で、この手法を使用することにはいくつかのデメリットもあります。場合によっては、ストップロスを移動していなければ、取引がテイクプロフィットの目標に到達していた可能性もあります。これは採用している戦略次第です。

    今回のケースでは、オーダーブロックのシグナルを扱う際には、建値モードを適用することが一般的に推奨されます。なぜなら、オーダーブロックが機能しない場合もあり、保有中のポジションを保護することが非常に重要になるためです。

    ATRに基づく建値モード

    もう一つのポジション管理方法として、ATR (Average True Range)に基づいて、ストップロスを動的に建値へ移動する方法があります。

    この手法は先ほどの方法と似ていますが、固定のポイント数を使う代わりに、乗数パラメータを使用します。この乗数を使うことで、市場ボラティリティを考慮したストップロス距離を計算できます。このアプローチは、ゴールドのようにボラティリティの高い銘柄に対して、より柔軟に機能します。

    たとえば、固定の建値水準として150ポイント(ゴールドで1.50ドル相当)を設定した場合でも、経済指標の発表前後のような局面では、ゴールド価格が3.00ドル以上動くことも容易にあります。そのような状況では、ATRに基づく動的な調整を用いることで、市場の動きにより適応でき、通常の価格変動によって取引が早期に決済されてしまうことを防ぐことができます。

    したがって、ATRベースの手法では固定的な距離は使用せず、現在の市場環境に応じて動的に調整されます。

    リスクリワード比(RRR)に基づく建値モード

    最後に、リスクリワードレシオ(RRR)に基づいてストップロスを建値へ移動する方法があります。RRRとは、取引におけるリスクと期待される利益の関係を示す指標であり、テイクプロフィットの値をストップロスの値で割ることで計算されます。

    RRRを理解することは、より良いポジション管理のために重要です。

    • RRRが高い場合は、目標到達に必要な値幅が大きくなるため、一般的に勝率は低くなる傾向があります。
    • 一方で、RRRが低い場合は、テイクプロフィットに到達する確率は高くなりますが、市場が逆行した際の損失が大きくなる可能性があります。

      RRRに基づく建値モードの考え方はシンプルです。たとえば、テイクプロフィットがストップロスの2倍で設定されている場合、RRRは1:2となります。

      この手法では、初期リスクに対して1:1の比率に到達した時点で、ストップロスを建値へ移動させるように設定できます。つまり、市場がすでにリスク分を相殺できるだけ進んだ段階で、取引が自動的に保護されることになります。

      例は以下の通りです。

      • 1:1の比率に到達した時点で、ストップロスを移動してポジションを保護する
      • 1:2、1:3といった比率でさらに先へストップロスを進めるように設定する(戦略の種類による)

        このアプローチにより、市場が有利な方向へ十分に進んだ後に、リスクリワード比に基づいて取引を保護することができます。


        必要な構造体および列挙型

        このセクションでは、建値機能のコアとなる構造体を定義します。クラス本体を作成する前に、必要な構造体と列挙型を定義していきます。処理は明確で、シンプルかつ分かりやすいものにします。建値機能の実装においては、リスク管理用に作成したすべての列挙型を使うわけではなく、そのうち必要なものだけを使用します。それでは始めましょう。

        建値モデルを適用するために必要なコア情報を保存するためのシンプルな構造体を作成します。この構造体には以下が含まれていなければなりません。

        • 取引チケット
        • price_to_beat(市場がこの価格を超えたときにストップロスをbreakeven_priceへ移動させるための基準価格)
        • 条件成立後に設定されるストップロスがレベルが新しいレベルとなるbreakeven_price
        • レベル判定に使用するポジションタイプ

          構造体は以下のようになります。

          struct position_be
           {
            ulong              ticket;           //Position Ticket
            double             breakeven_price;  //Be price
            double             price_to_beat;    //Price to exceed to reach break even
            ENUM_POSITION_TYPE type;             //Position type
           };

          建値タイプの列挙型

          先に述べた3種類の建値発動タイプを選択できるようにするための列挙型を作成します。これは各取引の管理を最適化するために設計されています。

          enum ENUM_BREAKEVEN_TYPE
           {
            BREAKEVEN_TYPE_RR = 0,           //By RR
            BREAKEVEN_TYPE_FIXED_POINTS = 1, //By FixedPoints
            BREAKEVEN_TYPE_ATR = 2           //By Atr
           };

          建値モードの列挙型

          すべての取引戦略が同じ動作をするわけではありません。そのため、保有ポジションに対して建値をどのように適用するかを明確に選択できる列挙型を作成します。

          enum ENUM_BREAKEVEN_MODE
           {
            BREAKEVEN_MODE_AUTOMATIC,
            BREAKEVEN_MODE_MANUAL
           };

          • BREAKEVEN_MODE_AUTOMATICは、OnTradeTransactionを使用してすべての新規取引を自動的に追跡します。トレーダーが追加で何かを行う必要はありません。システムが即座に取引を検出し、保護し、管理します。
          • BREAKEVEN_MODE_MANUALモードは、最大限の柔軟な設定を提供します。トレーダーはブレイクイーブン機能で保護するチケットと、条件判定を開始するタイミングを自分で選択できます。このモードは、ディスクリートスキャルピングや高精度な取引セットアップなど、手動選択が必要な戦略に最適です。


          基底クラスCBreakEvenBaseの作成

          基底クラスの定義を始める前に、以前の記事で開発したリスク管理システムを含めます。

          #include <Risk_Management.mqh>

          CBreakEvenBaseのprotected変数

          以下に、クラスの定義と内部変数を示します。各変数についての説明も提供します。

          //+------------------------------------------------------------------+
          //| Main class to apply break even                                   |
          //+------------------------------------------------------------------+
          class CBreakEvenBase
           {
          protected:
            CTrade             obj_trade;     //CTrade object
            MqlTick            tick;          //tick structure
            string             symbol;        //current symbol
            double             point_value;   //value of the set symbol point
            position_be        PositionsBe[];  //array of positions of type Positions
            ulong              magic;         //magic number of positions to make break even
            bool               pause;         //Boolean variable to activate the pause of the review, this is used to prevent the array from going out of range
            int                num_params;    //Number of parameters the class needs
            ENUM_BREAKEVEN_MODE breakeven_mode; //Break even mode, manual or automatic
            bool               allow_extra_logs;
            .
            .
           };

          以下は、注文の送信と変更をおこなう変数です。 

          CTrade             obj_trade;    //CTrade object

          以下は、最後に記録されたティックに関する情報を格納するMqlTick構造体です。 

          MqlTick            tick;         //tick structure

          以下は、分析対象となる金融商品の名称です。 

          string             symbol;       //current symbol

          以下は、選択された銘柄の1ポイントの値です。 

          double             point_value;  //value of the set symbol point

          以下は、建値モードが適用されるポジションの配列です。 

          position_be        PositionsBe[]; //array of positions of type Positions

          以下は、取引の固有識別子(マジックナンバー)です。 

          ulong              magic;        //magic number of positions to make break even

          以下は、処理を一時停止するためのフラグです。 

          bool               pause;         //Boolean variable to activate the pause of the review, this is used to prevent the array from going out of range

          以下は、クラスが必要とするパラメータの数です。

          int                num_params;    //Number of parameters the class needs

          以下は、建値適用モード(手動または自動)です。

          ENUM_BREAKEVEN_MODE breakeven_mode; //Break even mode, manual or automatic

          以下は、追加のログエントリを作成する機能です。

          bool               allow_extra_logs;

          CBreakEvenBase関数

          このセクションでは、後ほど実装される様々なクラスが継承する関数を定義し始めます。

          コンストラクタ

          コンストラクタは、どのクラスにおいても重要な要素です。CBreakEvenBaseクラスでは、内部変数を初期化し、クラスが正しく動作するために必要な値を設定する役割を持ちます。

          このコンストラクタは3つのパラメータを受け取ります。まずsymbolはbidおよびaskデータを取得するための銘柄です。次にmagicは特定の注文を識別するための値です。そしてmodeは建値管理のタイプを示し、自動または手動のいずれかを指定します。

          CBreakEvenBase(string symbol_, ulong magic_, ENUM_BREAKEVEN_MODE mode_);

          コンストラクタの定義

          コンストラクタが実行される際、pauseとallow_extra_logs変数はまずfalseに初期化されます。その後、受け取ったmodeの値が有効かどうかを確認します。無効な場合、エラーメッセージを出力し、ExpertRemoveを呼び出してエキスパートアドバイザー(EA)をチャートから削除し、不正な設定のままコードが実行され続けることを防ぎます。

          //+------------------------------------------------------------------+
          //| Constructor                                                       |
          //+------------------------------------------------------------------+
          CBreakEvenBase::CBreakEvenBase(string symbol_, ulong magic_, ENUM_BREAKEVEN_MODE mode_)
            : pause(false), allow_extra_logs(false)
           {
            if(magic_ != NOT_MAGIC_NUMBER)
              obj_trade.SetExpertMagicNumber(magic_);
          
            if(mode_ != BREAKEVEN_MODE_MANUAL && mode_ != BREAKEVEN_MODE_AUTOMATIC)
             {
              printf("%s:: Error critico el modo del break even %s, es invalido", __FUNCTION__, EnumToString(mode_));
              ExpertRemove();
             }
          
            this.symbol = symbol_;
            this.num_params = 0;
            this.magic = magic_;
            this.breakeven_mode = mode_;
            this.point_value = SymbolInfoDouble(symbol_, SYMBOL_POINT);
           }
          

          対応する値は、引数として受け取った値を用いてsymbol、magic、およびbreakeven_modeパラメータにも割り当てられます。

          また、銘柄のポイント値はpoint_value変数に保存され、パラメータ数は0で初期化されます。CBreakEvenBaseから継承される各クラスは、それぞれのコンストラクタ内で独自のパラメータ数を定義します。

          さらに、magic_変数がNOT_MAGIC_NUMBERと等しい場合、マジックナンバーを設定するためのCTradeクラスのメンバ関数は実行されません。 

          注記:NOT_MAGIC_NUMBERはリスク管理インクルードファイル内で定義されたマクロです。この定義はマジックナンバーを使用しないことを示すために使用されます。したがって、建値機能が使用される場合、それはすべての取引に対して適用されます(ただし建値モードが自動の場合に限ります)。

          デストラクタ

          デストラクタは、管理対象のポジションを格納する配列が使用していたメモリを解放するために使用されます。この関数は、オブジェクトが破棄される際に自動的に実行されます。

          //+------------------------------------------------------------------+
          //| Destructor                                                       |
          //+------------------------------------------------------------------+
          CBreakEvenBase::~CBreakEvenBase()
           {
            ArrayFree(PositionsBe);
           }


          共通クラス関数の実装

          次に、基底クラスCBreakEvenBaseに含まれる関数を定義します。これらの関数は、後に実装される派生クラスによって使用され、また拡張されます。

          まず、クラスが必要とするパラメータの数を返す関数を宣言します。この関数は、protected変数num_paramsの値を返します。また、この関数はオブジェクトの状態を変更しないことを保証するためconstとして定義され、さらに子クラスによるオーバーライドを防ぐためfinalが付与されています。

          virtual inline int GetNumParams() const final { return num_params; }

          次に、新しいposition_be構造体をPositionsBe配列に追加するためのAdd関数を宣言します。この関数は、チケット、建値、ストップロス、ポジションタイプなどの必要な取引データを受け取ります。

          virtual bool       Add(ulong post_ticket, double open_price, double sl_price, ENUM_POSITION_TYPE position_type) = 0;

          この関数は純粋仮想関数として宣言されているため、CBreakEvenBaseを継承するすべてのクラスは、それぞれ独自の実装を持つ必要があります。

          このクラスの主な役割は、建値処理を適用することです。この関数は、PositionsBe配列に格納されたすべてのポジションに対して、建値調整を実行します。そのため、forループを使用して配列を順番に走査します。

          処理をおこなう前に、配列のサイズが0より大きいこと、そしてpause変数がfalseであることを確認します。これらの条件が満たされない場合、関数は即座に終了します。

          position_be構造体には、すでにターゲットレベルとトリガー価格が含まれているため、必要なのは市場条件が満たされているかどうかの確認のみです。

          • ポジションがBUYの場合:ask価格がprice_to_beat以上かどうかを確認
          • ポジションがSELLの場合:bid価格がprice_to_beat以下かどうかを確認

          条件が成立した場合、そのポジションはチケットで識別され、現在のテイクプロフィット値が計算されます。その後、CTradeクラスのPositionModifyメソッドを使用して修正が実行されます。最後に、このポジションは配列から削除対象としてマークされます。

          //+------------------------------------------------------------------+
          //| Function to make break even                                      |
          //+------------------------------------------------------------------+
          void CBreakEvenBase::BreakEven(void)
           {
            if(this.PositionsBe.Size() < 1 || pause)
              return;
          
            SymbolInfoTick(this.symbol, tick);
          
            int indices_to_remove[];
          
            for(int i = 0 ; i < ArraySize(this.PositionsBe) ; i++)
             {
              if((this.PositionsBe[i].type == POSITION_TYPE_BUY && tick.ask >= this.PositionsBe[i].price_to_beat) ||
                 (this.PositionsBe[i].type == POSITION_TYPE_SELL && tick.bid <= this.PositionsBe[i].price_to_beat))
              {
                  if(!PositionSelectByTicket(this.PositionsBe[i].ticket))
                  {
                   printf("%s:: Error al seleccionar el ticket %I64u",__FUNCTION__,this.PositionsBe[i].ticket);
                   ExtraFunctions::AddArrayNoVerification(indices_to_remove, i);
                   continue;
                  }
                  
                  double position_tp = PositionGetDouble(POSITION_TP);
                  obj_trade.PositionModify(this.PositionsBe[i].ticket, this.PositionsBe[i].breakeven_price, position_tp);
                  ExtraFunctions::AddArrayNoVerification(indices_to_remove, i);
              }
             }
          
            ExtraFunctions::RemoveMultipleIndexes(this.PositionsBe, indices_to_remove);
           }

          新規取引が発生した際にPositionsBe配列へ構造体を追加するため、OnTradeTransactionイベント内で呼び出される関数を定義します。この関数はOnTradeTransactionEventという名前で、取引トランザクションが発生するたびに実行されます。

          virtual void       OnTradeTransactionEvent(const MqlTradeTransaction& trans) final;

          PositionsBe配列にポジションを追加する前に、HistoryDealSelect関数を使用して対応するチケットを選択する必要があります。次に、建値処理の対象となるエントリータイプを判定します。

          エントリーが成行約定、すなわちDEAL_ENTRY_INの場合、ポジションが正常に作成されていること、建値モードが自動(BREAKEVEN_MODE_AUTOMATIC)であること、約定のマジックナンバーが内部変数magicと一致していること、またはmagicがNOT_MAGIC_NUMBERであることを確認します。

          すべての条件が満たされた場合、Add関数を使用してポジションをPositionsBe配列に追加します。allow_extra_logsが有効になっている場合は、ポジションが登録されたことを示すメッセージをログに出力します。

          一方で、エントリーが決済、すなわちDEAL_ENTRY_OUTに該当し、ポジションが完全に決済された場合は、対応するチケットをPositionsBe配列から削除します。この削除処理による競合を避けるため、pause変数は一時的にtrueに設定されます。

          関数のコードは以下の構造になっています。

          //+------------------------------------------------------------------+
          //| OnTradeTransactionEvent                                          |
          //+------------------------------------------------------------------+
          void CBreakEvenBase::OnTradeTransactionEvent(const MqlTradeTransaction &trans)
           {
            if(trans.type != TRADE_TRANSACTION_DEAL_ADD)
              return;
          
            HistoryDealSelect(trans.deal);
            ENUM_DEAL_ENTRY entry = (ENUM_DEAL_ENTRY)HistoryDealGetInteger(trans.deal, DEAL_ENTRY);
            bool pos = PositionSelectByTicket(trans.position);
          
            if(breakeven_mode == BREAKEVEN_MODE_AUTOMATIC)
             {
              ulong position_magic = (ulong)HistoryDealGetInteger(trans.deal, DEAL_MAGIC);
          
              if(entry == DEAL_ENTRY_IN && pos && (this.magic == position_magic || this.magic == NOT_MAGIC_NUMBER))
               {
                if(Add(trans.position, PositionGetDouble(POSITION_PRICE_OPEN), PositionGetDouble(POSITION_SL), (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE)))
                  if(this.allow_extra_logs)
                    printf("%s:: Se a añadido el ticket %I64u del array de posiciones", __FUNCTION__, trans.position);
                return;
               }
             }
          
            if(entry == DEAL_ENTRY_OUT && pos == false)
             {
              this.pause = true;
          
              if(ExtraFunctions::RemoveIndexFromAnArrayOfPositions(PositionsBe, trans.position))
                if(this.allow_extra_logs)
                  printf("%s:: Se a eliminado el ticket %I64u del array de posiciones", __FUNCTION__, trans.position);
          
              this.pause = false;
             }
           }

          いくつかの派生クラスでは、さまざまなパラメータ設定が必要になります。たとえば、シンプルな調整では2つの整数値が必要ですが、ATRベースのような手法では、期間、時間足、乗数といったデータが必要になります。

          基底クラスでは、異なるパラメータを持つ複数のバージョンの仮想関数を定義することはできないため、必要な設定値を保持するためにMqlParam型の配列を使用する方式を採用します。これにより、各派生クラスは受け取った値を内部で解釈し、適切に割り当てることができます。

          virtual void       Set(MqlParam &params[]) = 0;

          最後に、追加の情報ログの有効・無効を切り替える関数を追加します。これは実行時にチケットの追加や削除などのアクションを記録したい場合に有用です。

          virtual void       SetExtraLogs(bool allow_extra_logs_) final { this.allow_extra_logs = allow_extra_logs_; }


          CBreakEvenSimpleクラスの開発

          内部変数

          基底クラスの準備が完了したため、CBreakEvenSimpleクラスの実装を開始します。このクラスは、固定ポイント方式を使用して建値を調整します。

          このために、2つの変数を宣言します。

          int                extra_points_be, points_be;

          • extra_points_be:建値が有効化された後に、建値レベルをさらに移動させる追加ポイント数
          • points_be:価格が何ポイント利益方向に進んだ時点で調整を実行するかを決定する値

          コンストラクタ

          コンストラクタでは初期値を設定します。基底クラスのコンストラクタも呼び出し、継承されたパラメータを初期化します。コンストラクタ本体では内部変数を0に初期化し、このクラスが必要とするパラメータ数を定義します。この場合は2です。

                               CBreakEvenSimple(string symbol_, ulong magic_, ENUM_BREAKEVEN_MODE mode_)
          :                CBreakEvenBase(symbol_, magic_, mode_) { this.extra_points_be = 0; this.points_be = 0; this.num_params = 2;}

          パラメータを設定するための関数

          内部変数extra_points_beとpoints_beに値を割り当てるために、MqlParam型の配列を使用します。この構造体はMQL5言語の一部であり、通常はChartIndicatorAdd()関数でインジケーターを追加する際に使用されます。

          本ケースでは、この仕組みをクラスへの値受け渡しに利用します。Set関数は基底クラスからオーバーライドされ、「override」キーワードを使用します。

          void               Set(MqlParam &params[]) override;

          関数内では、params配列のサイズが2未満でないことを確認します。条件を満たさない場合は情報メッセージとしてエラーを表示します。条件を満たす場合は、対応する値を割り当てるためにSetSimple関数を呼び出します。

          //+------------------------------------------------------------------+
          //| Set attributes of CBreakEvenSimple class with MqlParam array     |
          //+------------------------------------------------------------------+
          void CBreakEvenSimple::Set(MqlParam &params[])
           {
            if(params.Size() < 2)
             {
              printf("%s:: Error setting simple break-even, the size of the params array %I32u to less than 2", __FUNCTION__, params.Size());
              return;
             }
          
            SetSimple(int(params[0].integer_value), int(params[1].integer_value));
           }

          SetSimple関数

          SetSimple関数は、パラメータ配列を使用せずにpoints_beおよびextra_points_beの値を直接設定するための関数です。この場合、関数呼び出し時に整数値がそのまま渡されます。

          //+------------------------------------------------------------------+
          //| Function to set member variables without using MalParams         |
          //+------------------------------------------------------------------+
          void CBreakEvenSimple::SetSimple(int points_be_, int extra_points_be_)
           {
            if(points_be_ <= 0)
             {
              printf("%s:: Error when setting the break even value for fixed points, be points %I32d are invalid.", __FUNCTION__, extra_points_be_);
              ExpertRemove();
              return;
             }
          
            if(extra_points_be_ < 0)
             {
              printf("%s:: Error when setting the break even value for fixed points, extra points %I32d are invalid.", __FUNCTION__, extra_points_be_);
              ExpertRemove();
              return;
             }
          
            if(extra_points_be_ >= points_be_)
             {
              printf("%s:: Warning: The break even points (breakeven_price) is greater than the breakeven points (price_to_beat)\nTherefore the value of the extra breakeven points will be modified 0.",
                     __FUNCTION__);
              this.points_be = points_be_; //0
              this.extra_points_be = 0; //1
              return;
             }
          
            this.points_be = points_be_; //0
            this.extra_points_be = extra_points_be_; //1
           }

          このメソッドは、値がコード内で直接定義されている場合や、特定のケースでMqlParam型の使用を避ける必要がある場合に有用です。

          ポジションデータをPositionsBe配列に追加するための関数

          CBreakEvenSimpleクラスの実装を進めるために、Add関数をオーバーライドする必要があります。この関数はposition_be構造体の主要な値を計算および登録し、それをPositionsBe配列に格納する役割を持ちます。

          open_priceパラメータは、breakeven_priceおよびprice_to_beatの計算の基準として使用されます。これらの変数は、Stop Lossを移動させるレベルと、調整を発動させるために価格が到達すべきレベルを定義します。

          breakeven_priceの計算には以下の式を使用します。

          • 買いポジションの場合:

          break_even_price = open_price + (point_value * extra_points_be)

          • 売りポジションの場合:

          break_even_price = open_price - (point_value * extra_points_be)

          一方、price_to_beatの計算では同じ式を使用しますが、extra_points_beの代わりにpoints_beを使用します。

          これらの値を決定した後、position_be構造体に必要なデータを格納します。その後、この構造体はAddArrayNoVerification関数を使用してPositionsBe配列に追加されます。この関数は前章のリスク管理セクションで既に使用したものです。

          実装コードは以下のようになります。

          //+----------------------------------------------------------------------------------------------+
          //| Create a new structure and add it to the main array using the 'AddToArrayBe' function        |
          //+----------------------------------------------------------------------------------------------+
          bool CBreakEvenSimple::Add(ulong post_ticket, double open_price, double sl_price, ENUM_POSITION_TYPE position_type)
           {
            position_be new_pos;
            new_pos.breakeven_price =  position_type == POSITION_TYPE_BUY ? open_price + (point_value * extra_points_be) : open_price - (point_value * extra_points_be);
            new_pos.type =  position_type;
            new_pos.price_to_beat = position_type == POSITION_TYPE_BUY ? open_price + (point_value * points_be) : open_price - (point_value * points_be) ;
            new_pos.ticket = post_ticket;
            ExtraFunctions::AddArrayNoVerification(this.PositionsBe,new_pos); 
            return true;
           }

          完全なクラスコード:

          //+------------------------------------------------------------------+
          //| class CBreakEvenSimple                                           |
          //+------------------------------------------------------------------+
          class CBreakEvenSimple : public CBreakEvenBase
           {
          private:
            int                extra_points_be, points_be;
          
          public:
                               CBreakEvenSimple(string symbol_, ulong magic_, ENUM_BREAKEVEN_MODE mode_)
              :                CBreakEvenBase(symbol_, magic_, mode_) { this.extra_points_be = 0; this.points_be = 0; this.num_params = 2;}
          
          
            bool               Add(ulong post_ticket, double open_price, double sl_price, ENUM_POSITION_TYPE position_type) override;
            void               Set(MqlParam &params[]) override;
            void               SetSimple(int points_be_, int extra_points_be_);
           };
          
          //+----------------------------------------------------------------------------------------------+
          //| Create a new structure and add it to the main array using the 'AddToArrayBe' function        |
          //+----------------------------------------------------------------------------------------------+
          bool CBreakEvenSimple::Add(ulong post_ticket, double open_price, double sl_price, ENUM_POSITION_TYPE position_type)
           {
            position_be new_pos;
            new_pos.breakeven_price =  position_type == POSITION_TYPE_BUY ? open_price + (point_value * extra_points_be) : open_price - (point_value * extra_points_be);
            new_pos.type =  position_type;
            new_pos.price_to_beat = position_type == POSITION_TYPE_BUY ? open_price + (point_value * points_be) : open_price - (point_value * points_be) ;
            new_pos.ticket = post_ticket;
            ExtraFunctions::AddArrayNoVerification(this.PositionsBe, new_pos);
            return true;
           }
          
          //+------------------------------------------------------------------+
          //| Set attributes of CBreakEvenSimple class with MqlParam array     |
          //+------------------------------------------------------------------+
          void CBreakEvenSimple::Set(MqlParam &params[])
           {
            if(params.Size() < 2)
             {
              printf("%s:: Error setting simple break-even, the size of the params array %I32u to less than 2", __FUNCTION__, params.Size());
              return;
             }
          
            SetSimple(int(params[0].integer_value), int(params[1].integer_value));
           }
          
          //+------------------------------------------------------------------+
          //| Function to set member variables without using MalParams         |
          //+------------------------------------------------------------------+
          void CBreakEvenSimple::SetSimple(int points_be_, int extra_points_be_)
           {
            if(points_be_ <= 0)
             {
              printf("%s:: Error when setting the break even value for fixed points, be points %I32d are invalid.", __FUNCTION__, extra_points_be_);
              ExpertRemove();
              return;
             }
          
            if(extra_points_be_ < 0)
             {
              printf("%s:: Error when setting the break even value for fixed points, extra points %I32d are invalid.", __FUNCTION__, extra_points_be_);
              ExpertRemove();
              return;
             }
          
            if(extra_points_be_ >= points_be_)
             {
              printf("%s:: Warning: The break even points (breakeven_price) is greater than the breakeven points (price_to_beat)\nTherefore the value of the extra breakeven points will be modified 0.",
                     __FUNCTION__);
              this.points_be = points_be_; //0
              this.extra_points_be = 0; //1
              return;
             }
          
            this.points_be = points_be_; //0
            this.extra_points_be = extra_points_be_; //1
           }
          //+------------------------------------------------------------------+

          これで固定ポイント方式の建値メソッドの基本実装は完了です。次のセクションでは、前回のリスク管理記事で開発したOrder Blocks EAを使用して、このクラスの機能をテストします。


          建値モードのテスト

          本セクションでは、固定ポイント方式の建値モードを統合するためにOrder Blocks EAを修正します。目的はシステムの動作を確認し、ポジションがこのようなアクティブ管理下でどのように振る舞うかを評価することです。

          まず、EAのパラメータを修正し、新しい専用セクションを追加します。

          sinput group "-----| Break Even |----"

          このセクションには、建値機能に関連するすべてのパラメータを配置します。現時点では固定ポイント方式のみが実装されているため、有効・無効を切り替えるための汎用boolパラメータを追加します。

          input bool use_be = true;                         // Enable Break Even usage

          次に、この方式専用のサブセクションを定義します。

          sinput group "- BreakEven based on Fixed Points -"
          input int be_fixed_points_to_put_be = 200;         // Points traveled needed to activate Break Even
          input int be_fixed_points_extra = 100;             // Extra adjustment points when activating Break Even
          

          機能統合のため、CBreakEvenSimple型のオブジェクトを作成します。将来的には、複数の方式を単一インターフェースで管理できるマネージャ型へ置き換える予定です。

          CBreakEvenSimple break_even(_Symbol, Magic, BREAKEVEN_MODE_AUTOMATIC);

          OnInit関数内で、use_beが有効な場合はSetSimple関数を用いて設定値を適用します。

          //---
            if(use_be)
              break_even.SetSimple(be_fixed_points_to_put_be, be_fixed_points_extra);
          

          建値管理を正しく動作させるため、OnTradeTransaction内でbreak_evenオブジェクトのOnTradeTransactionEventを呼び出します。これにより、約定情報の追加・削除が配列へ反映されます。

          //+------------------------------------------------------------------+
          //| TradeTransaction function                                        |
          //+------------------------------------------------------------------+
          void OnTradeTransaction(const MqlTradeTransaction& trans,
                                  const MqlTradeRequest& request,
                                  const MqlTradeResult& result)
           {
            risk.OnTradeTransactionEvent(trans);
            break_even.OnTradeTransactionEvent(trans);
           }
          

          最後にOnTick内では、ユーザーが機能を有効化している場合のみ建値処理を実行します。

          if(use_be) break_even.BreakEven();   

          これらの変更により、指定条件を満たした場合にEAは自動的に建値機能を適用できるようになります。 

          バックテスト

          建値モードを搭載したOrder Blocksエキスパートアドバイザーのバックテストは、資金管理の制約を設けずに実施しました。利益および損失の上限・下限は設定していません。取引はストップロスまたはテイクプロフィットに到達した場合のみ決済される設計としました。リスクリワード比は1:2で、テイクプロフィットはストップロスの2倍に設定されています。

          1. 一般設定

            図4:バックテストの一般設定

          2. 建値機能なしのバックテスト結果

          図5:建値機能を使用しないバックテストチャート

          図5は、建値機能を使用しない場合のEAの挙動を示しています。このテストでは、初期資金15,000ドルが8,700ドルまで減少し、約7,000ドルの損失となりました。この結果は、建値機能の有無を評価するための基準となります。

          3. 建値機能付きバックテスト結果

          図6:建値機能を使用したバックテストチャート

          建値機能を使用したバックテストチャート2回目のテストでは、建値モードを有効化し、be_fixed_points_extraを100ポイント、be_fixed_points_to_put_beを600ポイントに設定しました。

          このテストでは、全体的に推移がより緩やかになり、連続した損失局面において資金減少幅は小さくなりました。残高は10,900ドルから7,900ドルへ減少し、損失は約3,000ドルとなりました。1回目のテストとの差分である3,000ドルは、建値機能が損失連続時の影響を軽減できることを示しています。

          一方で制約も確認されました。両テストともに、2024年3月頃までは取引がマイナス方向で推移していましたが、2024年3月1日以降に変化が見られ、連続した利益取引が発生しました。最初のバックテストでは残高は16,000ドルを超えましたが、建値モードを有効化した場合は10,900ドルにとどまりました。

          この違いの一因として、建値機能による早期決済の影響が考えられます。ポジション建値後に一時的な価格戻りが発生し、その後本来の方向へ進む場合、建値機能がない場合は損失を抱えながらも最終的に利益で決済される可能性があります。一方で建値機能が有効な場合、その時点でゼロ決済となり、本来得られた利益を逃す可能性があります。

          この結果は、多くの取引がテイクプロフィットに到達するまで含み損状態で維持されていた可能性を示しています。そのため、固定ポイント方式の建値機能は、価格の押し戻しが頻繁に発生する環境では結果が制限される場合があります。

          高ボラティリティ環境では、この設定は迅速に作動し資金を保護する一方で、早期決済を引き起こす可能性があります。逆に低ボラティリティ環境ではトリガーされにくくなります。したがって、固定ポイント方式の建値機能は、1取引あたりの利益最大化よりも資金保護を優先するトレーダーに適した手法です。


          結論

          本記事では、建値の概念、MQL5での実装、そしていくつかの派生的な手法について分析しました。ここで解説した内容はすべて、前回のリスク管理記事で作成したOrder Blocks EAに適用されています。

          分析の締めくくりとして、建値モードを有効にした場合と無効にした場合のシステム挙動を比較しました。その結果、連続した利益トレードの局面では建値機能の使用が残高の成長を制限する可能性がある一方で、連続した損失局面では大きな損失へのエクスポージャーを軽減できることが確認されました。この二面性は、リスクとリターンの間に一定のバランスが存在することを示していますが、実際の有効性は、利益や損失の制約といった外部条件を排除した基礎ストラテジーの統計結果に依存します。

          また、建値モードは積極的な取引を追求するものではなく、エグジットをより厳密に管理しながら、安定した結果の推移を重視するトレーダーに適しているという結論に至りました。

          以下は、この記事で使用または更新されたファイルです。

          ファイル名 種類 説明 
           Risk_Management.mqh  .mqh(ヘッダーファイル) 前回のリスク管理記事で開発したリスク管理クラスを含むファイル
           Order_Block_Indicador_New_Part_2.mq5 .mq5(インジケーター) Order Blockインジケーターのコード
           Order Block EA MetaTrader 5.mq5  .mq5 (EA) 建値機能を統合したOrder Blockロボットのコード
           OB_SET_WITHOUT_BREAKEVEN.set .set(設定ファイル) 建値機能を使用しない、最初のバックテストの設定
           OB_SET_WITH_BREAKEVEN.set .set(設定ファイル) 建値モードを有効にした2回目のバックテストの設定
           PositionManagement.mqh .mqh(ヘッダーファイル) 建値計算機能のコードを含むmqhファイル


          MetaQuotes Ltdによりスペイン語から翻訳されました。
          元の記事: https://www.mql5.com/es/articles/17957

          添付されたファイル |
          MQL5.zip (210.17 KB)
          初級から中級まで:構造体(VII) 初級から中級まで:構造体(VII)
          本日の記事では、さまざまな要素の構造化に関する問題をどのように解決し、よりシンプルで魅力的な設計へと導くかについて解説します。内容は学習向けであり、そのため本番環境のコードを構成するものではありませんが、ここで扱う概念や知識を十分に理解することが極めて重要です。これにより、今後提示するコードをよりスムーズに追えるようになります。
          FX裁定取引:リスク管理を伴う公正価値への回帰を目指す行列取引システム FX裁定取引:リスク管理を伴う公正価値への回帰を目指す行列取引システム
          本記事では、クロスレート計算アルゴリズムの詳細な説明、不均衡マトリクスの可視化、さらに効率的な取引のためのMinDiscrepancyおよびMaxRiskパラメータの最適な設定方法について解説します。本システムは、クロスレートを用いて各通貨ペアの「公正価値」を自動的に算出し、価格が公正価値より低い方向へ乖離した場合には買いシグナルを、高い方向へ乖離した場合には売りシグナルを生成します。
          市場シミュレーション(第19回):SQL入門(II) 市場シミュレーション(第19回):SQL入門(II)
          最初のSQLに関する記事でも説明したように、SQLにすでに組み込まれていることを実現するために、わざわざ時間をかけて手続きをプログラミングする意味はありません。しかし、基礎を理解していなければ、SQLを使って何かをおこなうことも、このツールが提供する機能を十分に活用することもできません。そこで今回の記事では、データベースにおける基本的な作業をどのようにおこなうかを見ていきます。
          カオス最適化アルゴリズム(COA):続編 カオス最適化アルゴリズム(COA):続編
          引き続き、カオス最適化アルゴリズムの研究を進めていきます。記事の後半では、アルゴリズムの実装、テスト、および結論といった実践的な側面について述べます。