English Русский 中文 Español
preview
MQL5における建値機能の実装(第2回):ATRとRRRに基づく建値機構

MQL5における建値機能の実装(第2回):ATRとRRRに基づく建値機構

MetaTrader 5 |
12 0
Niquel Mendoza
Niquel Mendoza


はじめに

皆さん、建値機構に関する記事の第2回へようこそ。今回は、第1回で実装が完了していなかったさまざまな建値方式のプログラミングを引き続きおこないます。さらに、ユーザーが建値モードを簡単に選択できるようにする補助クラスも作成します。まず、ATRベースの建値方式について検討し、次にRRRに関連する方式を取り上げ、最後に建値機構を選択して使用するためのクラスを実装します。最後に比較分析をおこない、リスク管理シリーズの前回の記事で開発したOrder Blocksエキスパートアドバイザー(EA)にとって、どの建値方式が最も適しているのかを判断します。


ATRに基づくCBreakEvenAtrクラスの開発

前回の記事でも説明したように、ATRに基づく建値方式では、建値を有効化する距離や、建値とポジションの建玉価格との差を、固定ポイントでは指定しません。その代わりにATR値に対する倍率(マルチプライヤー)を使用します。この方法により、建値を有効化する距離と、実際に設定される建値の両方を、より柔軟に設定できます。

ATRの倍率が高い場合、通常はオフセットが大きくなるため、建値が発動する頻度は低くなります。 反対に、倍率を低くすると、通常よりも頻繁に建値が発動する可能性があります。ポジションに十分な値幅を伸ばす余地を与える戦略では、このことが収益性に悪影響を及ぼす可能性があります。

この方式をMQL5で実装するために、CBreakEvenAtrクラスを作成します。このクラスは、あらゆる建値方式の基底クラスであるCBreakEvenBaseを継承します。

//--- class CBreakEvenAtr
class CBreakEvenAtr : public CBreakEvenBase

ATRを使用するためには、インジケータのデータを取得、つまりその値をコピーするためのハンドルが必要になります。さらに、atr_buffというdouble型の配列を使用します。この配列には、インジケータからコピーされたデータが格納されます。

また、データのコピーを開始する位置を示す整数変数atr_idxも必要になります。たとえば、値が0の場合は、現在のATR値からコピーがおこなわれます。各アクセスでは、バッファから1つの要素だけがコピーされます。

次に、2つの倍率を用意します。1つは建値レベルを設定する距離に使用し、もう1つは建値を有効化する距離に使用します。

private変数

private:
  int                atr_handle;
  double             atr_buff[];
  int                atr_idx;
  double             atr_multiplier_be;
  double             atr_multiplier_extra_be;

コンストラクタとデストラクタ

コンストラクタでは、private変数をデフォルト値で初期化します。さらに、ATR値を格納するatr_buff配列を準備します。また、このクラスが5つのパラメータを必要とすることを指定するため、num_params変数にも対応する値を設定します。

コンストラクタは3つのパラメータを受け取り、それらをCBreakEvenBase基底クラスに渡して初期化します。

  CBreakEvenAtr(string symbol_, ulong magic_, ENUM_BREAKEVEN_MODE mode_)
    :                CBreakEvenBase(symbol_, magic_, mode_), atr_handle(INVALID_HANDLE), atr_idx(0), atr_multiplier_be(1.0), atr_multiplier_extra_be(1.0)
   {                 ArraySetAsSeries(atr_buff, true); this.num_params = 5; }

デストラクタでは、ATRインジケータハンドルと、ATR値を格納する配列が解放されます。

//+------------------------------------------------------------------+
//| Destructor                                                       |
//+------------------------------------------------------------------+
CBreakEvenAtr::~CBreakEvenAtr(void)
 {
  ArrayFree(atr_buff);
  if(this.atr_handle != INVALID_HANDLE)
    IndicatorRelease(this.atr_handle);
 }

パラメータ設定用の関数

ATRベースの建値クラスの定義と宣言を引き続き進め、次に内部変数を設定するためのメソッドを定義します。まず、SetSimple関数を定義します。これらは、MqlParamsを使用せずにクラスに必要なパラメータを設定するために使用されます。建値モードが常にATRベースになることがすでに分かっている場合に適しています。これらの関数には、2つのバージョンが用意されています。

  void               SetSimple(int atr_handle_, int atr_idx_, double atr_multiplier_extra_be_, double atr_multiplier_be_);
  void               SetSimple(int atr_period, int atr_idx_, double atr_multiplier_extra_be_, double atr_multiplier_be_, ENUM_TIMEFRAMES timeframe);

最初のSetSimple関数では、まず倍数などのパラメータが有効であることを確認します。

  if(atr_multiplier_extra_be_ >= atr_multiplier_be_)
   {
    printf("%s: Error | The multiplier of the atr to calculate the price be is greater than or equal to the multiplier to set the be", __FUNCSIG__);
    ExpertRemove();
   }

ここでは、建値を発動させる距離に用いるATR倍率が、建値価格そのものを設定する距離に用いる倍率より小さいことを確認します。これは、無効なストップレベルなどのエラーが発生するのを防ぐために必要です。

次に、ATRインデックスが常に0以上であることを確認します。

  if(atr_idx_ < 0)
   {
    printf("%s: Critical error | Atr index is less than 0", __FUNCTION__);
    ExpertRemove();
   }

最後に、ATRインジケーターのハンドルが無効でないことを確認します。無効な場合は、エキスパートアドバイザー(EA)はチャートから削除されます。

  if(atr_handle_ == INVALID_HANDLE)
   {
    printf("%s: Critical Error | The handle of the indicator atr is invalid, last error: %I32d", __FUNCTION__, GetLastError());
    ExpertRemove();
   }

すべてのチェックが完了したら、パラメータをクラスの内部変数に割り当てます。

  this.atr_idx = atr_idx_;
  this.atr_multiplier_be = atr_multiplier_be_;
  this.atr_multiplier_extra_be = atr_multiplier_extra_be_;
  this.atr_handle = atr_handle_;

完全なコード

//+------------------------------------------------------------------------------------+
//| Function to set the values ​​of the CBreakEvenAtr class without using MqlParams      |
//| Using the handle instead of period and timeframe                                   |
//+------------------------------------------------------------------------------------+
void CBreakEvenAtr::SetSimple(int atr_handle_, int atr_idx_, double atr_multiplier_extra_be_, double atr_multiplier_be_)
 {
  if(atr_multiplier_extra_be_ >= atr_multiplier_be_)
   {
    printf("%s: Error | The multiplier of the atr to calculate the price be is greater than or equal to the multiplier to set the be", __FUNCSIG__);
    ExpertRemove();
   }

  if(atr_idx_ < 0)
   {
    printf("%s: Critical error | Atr index is less than 0", __FUNCTION__);
    ExpertRemove();
   }

  if(atr_handle_ == INVALID_HANDLE)
   {
    printf("%s: Critical Error | The handle of the indicator atr is invalid, last error: %I32d", __FUNCTION__, GetLastError());
    ExpertRemove();
   }

  this.atr_idx = atr_idx_;
  this.atr_multiplier_be = atr_multiplier_be_;
  this.atr_multiplier_extra_be = atr_multiplier_extra_be_;
  this.atr_handle = atr_handle_;
 }

2番目のSetSimple関数には、追加のパラメータが必要です。この関数はハンドルを受け取る代わりにATR期間を要求します。ハンドルはこの関数内で設定します。次に、最初のSetSimple関数が呼び出され、作成されたハンドルが渡されます。

//+------------------------------------------------------------------------------------+
//| Function to set the values ​​of the CBreakEvenAtr class without using MqlParams      |
//| Configuring the handle with the period and timeframe.                              |
//+------------------------------------------------------------------------------------+
void CBreakEvenAtr::SetSimple(int atr_period, int atr_idx_, double atr_multiplier_extra_be_, double atr_multiplier_be_, ENUM_TIMEFRAMES timeframe)
 {
  TesterHideIndicators(true);
  ResetLastError();
  this.atr_handle = iATR(this.symbol, timeframe, atr_period);
  TesterHideIndicators(false);
  
  SetSimple(atr_handle,atr_idx_,atr_multiplier_extra_be_,atr_multiplier_be_);
 }

次に、建値機構の基底クラスを継承するSet関数を定義します。この関数は、CBreakEvenセレクタークラスで使用されます。前回の記事で説明したように、基底クラスのSetSimple関数をオーバーライドすることはできません。これは、Set関数がどのようなパラメータを受け取るのかが事前には分からないためです。

そのため、Set関数では動的なパラメータを扱えるようにします。SetSimpleには、必要なパラメータ数が異なる2つのバージョンがあります。そのためSet関数では、入力されたパラメータ数が正しいかどうかを確認するために、2つのうち最も多いパラメータ数を基準として使用します。また、すでに作成済みのハンドルを使用してパラメータが渡された場合と、ハンドルを使用せずにパラメータが渡された場合を区別するために、HANDLE_INSTEAD_OF_PERIODというdefineを作成し、デフォルト値を0にします。

#define HANDLE_INSTEAD_OF_PERIOD 0

このdefineは、params配列の最後のインデックスに配置します。その値が0の場合は、すでに作成済みのハンドルを使用して設定されると判断します。一方、値が0以外の場合は、あらかじめハンドルを作成していないSetSimple関数を使用します。

つまり、上記の内容を踏まえると、Set関数の本体は次のようになります。

//+------------------------------------------------------------------+
//| Function for setting CBreakEvenAtr variables with MqlParams |
//+------------------------------------------------------------------+
void CBreakEvenAtr::Set(MqlParam &params[])
{
  // Check that params has the correct size; ATR requires 5 parameters
  if(params.Size() < 5)
  {
    printf("%s: Critical error | The size of the MqlParams array %I32u is less than 5", __FUNCTION__, params.Size());
    ExpertRemove();
    return;
  }

  // If the last parameter is HANDLE_INSTEAD_OF_PERIOD, use SetSimple with a handle
  if(params[4].integer_value == HANDLE_INSTEAD_OF_PERIOD)
  {
    SetSimple(
      (int)params[3].integer_value,   // atr_handle
      (int)params[0].integer_value,   // atr_idx
      params[2].double_value,         // atr_multiplier_extra_be
      params[1].double_value          // atr_multiplier_be
    );
  }
  else
  {
    // Otherwise, use SetSimple with period and timeframe
    SetSimple(
      (int)params[4].integer_value,   // atr_period
      (int)params[0].integer_value,   // atr_idx
      params[2].double_value,         // atr_multiplier_extra_be
      params[1].double_value,         // atr_multiplier_be
      (ENUM_TIMEFRAMES)params[3].integer_value // timeframe
    );
  }
}

建値を実装するための関数

ATRベースの建値モードを実装するために、.Add関数をオーバーライドします。この関数は、新しいポジションが建てられるたびに実行されます。つまり、基底クラスのOnTradeTransactionEvent関数の中から呼び出されます。.Addメソッドの本体では、建値価格と建値を発動させるトリガー価格の2つの価格を決定する必要があります。

breakeven_priceとprice_to_beat(建値を発動させ、ポジションのストップロスを変更するためのトリガー価格)など、必要な価格を計算するために、固定ポイント方式の建値クラスで使用したものと同様の計算式を使用します。

  • 買いポジションの場合

break_even_price = open_price + (atr_buff[0] * atr_multiplier_extra_be)

  • 売りポジションの場合

break_even_price = open_price - (atr_buff[0]* atr_multiplier_extra_be)

この計算式では、建値価格を算出するためにextra_points_beの代わりにatr_multiplier_extra_beを使用しています。トリガー価格であるprice_to_beatの計算式も同じです。ただし、atr_multiplier_extra_beの代わりにatr_multiplier_be変数を使用します。

必要な代入値を決定したら、.Add関数を定義します。まず、this.atr_idxの位置からATRデータをコピーし、1つの要素だけを取得します。そして、CopyBufferの戻り値が1未満ではないことを確認します。もし1未満の場合はfalseを返します。これは、チケットを建値配列に追加できなかったことを意味します。

  ResetLastError();

  if(CopyBuffer(this.atr_handle, 0, this.atr_idx, 1, this.atr_buff) < 1)
   {
    printf("%s: Error | When copying the data of the indicator atr, last error %I32d", __FUNCTION__, GetLastError());
    return false;
   }

次に、new_pos構造体を作成します。この構造体には、BreakEven()関数で使用する重要な情報が格納されます。そして、そのメンバーに、建値価格、トリガー価格(上記の計算式を使用)、チケット番号、ポジションタイプ(買い/売り)といった必要な値を代入します。

  position_be new_pos;
  new_pos.breakeven_price =  position_type == POSITION_TYPE_BUY ? open_price + (atr_buff[0] * atr_multiplier_extra_be) : open_price - (atr_buff[0] * atr_multiplier_extra_be);
  new_pos.type =  position_type;
  new_pos.price_to_beat = position_type == POSITION_TYPE_BUY ? open_price + (atr_buff[0] * atr_multiplier_be) : open_price - (atr_buff[0] * atr_multiplier_be);
  new_pos.ticket = post_ticket;
  ExtraFunctions::AddArrayNoVerification(this.PostionsBe, new_pos);

.Add関数の完全なコード

//+------------------------------------------------------------------+
//| Function to add an element to the positions array                |
//+------------------------------------------------------------------+
bool CBreakEvenAtr::Add(ulong post_ticket, double open_price, double sl_price, ENUM_POSITION_TYPE position_type)
 {
  ResetLastError();

  if(CopyBuffer(this.atr_handle, 0, this.atr_idx, 1, this.atr_buff) < 1)
   {
    printf("%s: Error | When copying the data of the indicator atr, last error %I32d", __FUNCTION__, GetLastError());
    return false;
   }

  position_be new_pos;
  new_pos.breakeven_price =  position_type == POSITION_TYPE_BUY ? open_price + (atr_buff[0] * atr_multiplier_extra_be) : open_price - (atr_buff[0] * atr_multiplier_extra_be);
  new_pos.type =  position_type;
  new_pos.price_to_beat = position_type == POSITION_TYPE_BUY ? open_price + (atr_buff[0] * atr_multiplier_be) : open_price - (atr_buff[0] * atr_multiplier_be);
  new_pos.ticket = post_ticket;
  ExtraFunctions::AddArrayNoVerification(this.PostionsBe, new_pos);
  return true;
 }


RRRRベースのCBreakEvenRRクラスの基本概念と設計

RRR(リスクリワード比率)に基づく建値モードは、ATRと同様に、もう一つの動的な建値方式です。簡単に言えば、ポジションがストップロスから一定の距離に設定された指定レベルに到達すると、ストップロスをポジションの建玉価格(エントリー価格)から一定の距離に移動させる方式です。たとえば、比率の値が1の場合、現在価格が条件「現在価格 ≧ エントリー価格+ストップロスまでのポイント距離」を満たした時点で建値が発動します。

まず、いくつか変更を加えます。前回の記事では、エントリー価格から固定されたポイント数だけストップロスを移動させていました。今回のパートでは、さらにATRを基準としてこの移動量を計算できる機能を追加します。これにより、RRRRベースの建値をさらに動的にすることができます。

それでは、プログラミングを始めましょう。

すでに説明したように、建値価格の計算方法としてATRを選択できるようにするには、列挙型を作成する必要があります。この列挙型によって、建値価格の計算方法を固定ポイント数による計算、ATR値 × 対応する倍数による計算の2つから選択できるようにします。

enum ENUM_TYPE_EXTRA_BE_BY_RRR
 {
  EXTRA_BE_RRR_BY_ATR,         //By Atr
  EXTRA_BE_RRR_BY_FIXED_POINTS //By Fixed Points
 };

このクラスは、CBreakEvenBase基底クラスをpublicに継承します。

class CBreakEvenRR : public CBreakEvenBase
 {
private:

まずは主要な変数を定義します。建値メカニズムが作動する係数を格納する変数が必要になります。

double             coefficient_rr; //Coefficient of rr

建値の計算タイプも保存する必要があります。

ENUM_TYPE_EXTRA_BE_BY_RRR type;

選択されたモードに応じて、ATR倍数またはすでにpoint_valueを乗算した値のいずれかを格納する変数が続きます。

  double             extra_value_be; //Extra value that will be added to the opening price of the position to obtain the breakeven price
  //Note: if the type is atr this will contain the atr multiplier, if not it will contain the value already multiplied by the point value

ATRを統合するには、ハンドル、値を格納する配列、およびデータコピーを開始するインデックスを格納するための整数変数という、3つの必須変数が必要です。

  int                handle_atr;
  int                idx_atr;
  double             atr_buff[];

コンストラクタ 

クラスコンストラクタは、メインクラスと同じ3つのパラメータを受け取ります。

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

コンストラクタ本体内では、内部クラス変数を初期化し、基底クラスのコンストラクタを呼び出します。

//+------------------------------------------------------------------+
//| Constructor                                                       |
//+------------------------------------------------------------------+
void CBreakEvenRR::CBreakEvenRR(string symbol_, ulong magic_, ENUM_BREAKEVEN_MODE mode_)
  : CBreakEvenBase(symbol_, magic_, mode_),
    handle_atr(INVALID_HANDLE),
    idx_atr(0),
    coefficient_rr(1.0),
    extra_value_be(100.0),
    type(EXTRA_BE_RRR_BY_FIXED_POINTS)
 {
  ArraySetAsSeries(atr_buff, true);
  this.num_params = 6;
 }

デストラクタ

デストラクタでは、ATRデータを格納する配列とATRインジケータハンドルが解放されます。

//+------------------------------------------------------------------+
//| Destructor                                                       |
//+------------------------------------------------------------------+
CBreakEvenRR::~CBreakEvenRR(void)
 {
  ArrayFree(this.atr_buff);
  if(handle_atr != INVALID_HANDLE)
    IndicatorRelease(this.handle_atr);
 }

パラメータ設定用の関数

クラスのパラメータを設定する関数では、ATRベースの建値クラスと同様のロジックを持つ関数をさらに2つ作成します。一方はATRインジケータのハンドルを受け取る関数で、もう一方は時間足やATR期間などのパラメータを受け取り、関数内部でハンドルを直接作成する関数です。

まず、ハンドルを受け取る関数を定義します。

関数の冒頭では、無効な変数値が存在しないことを確認するとともに、type_extra変数に格納されている建値タイプが有効な値であることを確認します。

//+------------------------------------------------------------------+
//| Function to set break even values ​​by rr without MqlParams        |
//+------------------------------------------------------------------+
void CBreakEvenRR::SetSimple(double rr_a_put_the_break_even, ENUM_TYPE_EXTRA_BE_BY_RRR type_extra, double atr_multiplier_or_extra_points, int idx_atr_, int atr_handle_)
 {
  if(coefficient_rr <= 0.00)
   {
    printf("%s: Critical Error | The %+f coefficient of 'reward' is invalid", __FUNCTION__, coefficient_rr);
    ExpertRemove();
    return;
   }

  if(atr_multiplier_or_extra_points <= 0.00)
   {
    printf("%s: Error | The ATR multiplier or extra points value %f is less than or equal to 0", __FUNCTION__, atr_multiplier_or_extra_points);
    ExpertRemove();
    return;
   }

  if(type_extra != EXTRA_BE_RRR_BY_ATR && type_extra != EXTRA_BE_RRR_BY_FIXED_POINTS)
   {
    printf("%s: Critical error | The extra value type %s is invalid", __FUNCTION__, EnumToString(type_extra));
    ExpertRemove();
    return;
   }

いずれかのチェックが失敗した場合、現在のチャートからEAを削除し、エラーメッセージを表示します。

次に、対応する値を内部変数に割り当てます。

 this.type = type_extra;
 this.coefficient_rr = rr_a_put_the_break_even;

type_extra変数の値がEXTRA_BE_RRR_BY_ATRの場合、ATRインデックスを確認します。

  if(type_extra == EXTRA_BE_RRR_BY_ATR)
   {
    if(idx_atr_ < 0)
     {
      printf("%s: Error | The ATR index %I32d is invalid", __FUNCTION__, idx_atr_);
      ExpertRemove();
      return;
     }
    else
      this.idx_atr = idx_atr_;

チェックが成功した場合、idx_atr_パラメータの値を内部変数idx_atrに割り当てます。

次に、ハンドルが有効かどうかを確認します。有効でない場合は、EAを削除します。チェックが成功した場合、atr_handle_パラメータの値を内部変数handle_atrに割り当てます。さらに、extra_value_be変数にはatr_multiplier_or_extra_pointsの値が割り当てられます。

    if(atr_handle_  == INVALID_HANDLE)
     {
      printf("%s: Critical Error | The handle of the indicator atr is invalid, last error: %I32d", __FUNCTION__, GetLastError());
      ExpertRemove();
      return;
     }
    else
      this.handle_atr = atr_handle_;

    this.extra_value_be = atr_multiplier_or_extra_points;
  }
  else
    this.extra_value_be =  atr_multiplier_or_extra_points  * this.point_value;

次に、ATRハンドルを作成するためのパラメータを受け取る2つ目の関数について説明します。

  void               SetSimple(double rr_a_put_the_break_even, ENUM_TYPE_EXTRA_BE_BY_RRR type_extra, double atr_multiplier_or_extra_points, int idx_atr_, ENUM_TIMEFRAMES tf_atr, int atr_period_);

まず、type_extraおよびatr_period_パラメータの値が有効であることを確認します。

//+------------------------------------------------------------------+
//| Function to set break even values ​​by rr without MqlParams        |
//+------------------------------------------------------------------+
void CBreakEvenRR::SetSimple(double rr_a_put_the_break_even, ENUM_TYPE_EXTRA_BE_BY_RRR type_extra, double atr_multiplier_or_extra_points, int idx_atr_, ENUM_TIMEFRAMES tf_atr, int atr_period_)
 {
  if(type_extra != EXTRA_BE_RRR_BY_ATR && type_extra != EXTRA_BE_RRR_BY_FIXED_POINTS)
   {
    printf("%s: Critical error | The extra value type %s is invalid", __FUNCTION__, EnumToString(type_extra));
    ExpertRemove();
    return;
   }

  if(atr_period_ < 0)
   {
    printf("%s: Error | The ATR period %I32d is invalid", __FUNCTION__, atr_period_);
    ExpertRemove();
    return;
   }

いずれかの値が無効な場合、現在のチャートからEAを削除します。

type_extraの値がEXTRA_BE_RRR_BY_ATRの場合、tf_atrおよびatr_period_パラメータを使用してATRインジケータハンドルを作成します。

  if(type_extra == EXTRA_BE_RRR_BY_ATR)
   {
    TesterHideIndicators(true);
    ResetLastError();
    this.handle_atr = iATR(this.symbol, tf_atr, atr_period_);
    TesterHideIndicators(false);
   }

最後に、最初のSetSimple関数を呼び出します。

  SetSimple(rr_a_put_the_break_even, type_extra, atr_multiplier_or_extra_points, idx_atr_, this.handle_atr);
 }

次に、クラスパラメータを設定するために使用され、breakeven基底クラスから継承されている.Set()関数をオーバーライドします。

  void               Set(MqlParam &params[]) override;

Set関数と同様に、まずparams配列のサイズがこのクラスに必要なパラメータの数以上であることを確認します。

//+------------------------------------------------------------------+
//| Function to set break even values ​​by rr with MqlParams           |
//+------------------------------------------------------------------+
void CBreakEvenRR::Set(MqlParam &params[])
 {
  if((int)params.Size() < num_params)
   {
    printf("%s: Error | The size of the MqlParams array %I32u to set the be by rr is less than 2", __FUNCTION__, params.Size());
    ExpertRemove();
    return;
   }

チェックが失敗した場合、EAを削除します。

params配列を使用してクラスの内部値を設定するために、HANDLE_INSTEAD_OF_PERIODのdefineを適用します。このdefineは、params配列にすでに作成済みのATRハンドルが含まれているかどうかを示します。そうであれば、最初のSetSimple関数が使用されます。そうでない場合は、2番目のSetSimple関数が呼び出され、ハンドルが作成されます。

  if(params[5].integer_value == HANDLE_INSTEAD_OF_PERIOD)
   {
    //->  (0)double rr_a_put_the_break_even,(1) ENUM_TYPE_EXTRA_BE_BY_RRR type_extra, (2)double atr_multiplier_or_extra_points, (3)int idx_atr_, (4)int atr_handle_
    SetSimple(params[0].double_value, (ENUM_TYPE_EXTRA_BE_BY_RRR)params[1].integer_value, params[2].double_value, (int)params[3].integer_value, (int)params[4].integer_value);
   }
  else
   {
    //->  (0)double rr_a_put_the_break_even,(1) ENUM_TYPE_EXTRA_BE_BY_RRR type_extra, (2)double atr_multiplier_or_extra_points, (3)int idx_atr_, (4)ENUM_TIMEFRAMES tf_atr, (5)int atr_period_
    SetSimple(params[0].double_value, (ENUM_TYPE_EXTRA_BE_BY_RRR)params[1].integer_value, params[2].double_value, (int)params[3].integer_value, (ENUM_TIMEFRAMES)params[4].integer_value, (int)params[5].integer_value);
   }
 }

.Add関数

Add関数の中では、まず、未決済ポジションに対して建値機構を適用する役割を持つCBreakEven関数を正しく実行するために、CBreakEvenBaseクラスで必要となる値を設定していきます。

最初に、ポジションにストップロスが設定されているかどうかを確認します。

//+------------------------------------------------------------------+
//| Function to add an element to the positions array                |
//+------------------------------------------------------------------+
bool CBreakEvenRR::Add(ulong post_ticket, double open_price, double sl_price, ENUM_POSITION_TYPE position_type)
 {
  if(sl_price <= 0.00)
   {
    printf("%s: Error | Position %I64u with stop loss %+f has sl less than 0", __FUNCTION__, post_ticket, sl_price);
    return false;
   }

ストップロスが設定されていない場合は、falseを返し、そのポジションはPositionsBe配列に追加されません。

次に、valという名前のdouble型変数を作成します。この変数には、エントリー価格からどれだけ離れた位置にストップロスを設定するか、その距離を格納します。type変数の値がEXTRA_BE_RRR_BY_ATRの場合は、内部変数idx_atrの位置からATRデータをコピーし、1つの要素だけを取得します。コピーされた要素数が1未満の場合は、falseを返し、エラーメッセージを表示します。正常にコピーできた場合は、valにatr_buff配列のインデックス0の値を掛けます。

 double val = this.extra_value_be;

  if(type == EXTRA_BE_RRR_BY_ATR)
   {
    ResetLastError();
    if(CopyBuffer(this.handle_atr, 0, this.idx_atr, 1, this.atr_buff) < 1)
     {
      printf("%s: Error | When copying the data of the indicator atr, last error %I32d", __FUNCTION__, GetLastError());
      return false;
     }
    else
      val *= this.atr_buff[0];
   }

次に、ストップロスが設定される距離が有効であることを確認します。つまり、無効なストップエラーを回避するために、建値機構が作動する距離よりも小さいことを確認します。

  double diff = MathAbs(open_price - sl_price);

  if((diff * coefficient_rr) <= val)
   {
    printf("%s: Error | The distance from the opening price %f where the stoploss is located is greater than or equal to the price to trigger the breakeven", __FUNCTION__, this.extra_value_be);
    return false;
   }

最後に、すべてのチェックが成功した場合、以下の式を使用して、新しいストップロス価格であるbreakeven_priceと、建値モードを有効にするトリガー価格であるprice_to_beatを計算します。

新しいストップロス(建値価格)の場合:

  • 買いポジションの場合

break_even_price = open_price + val

  • 売りポジションの場合

break_even_price = open_price - val

建値を有効化する価格(price_to_beat)の場合

  • 買いポジションの場合

price_to_beat = open_price + (coefficient_rr * diff)

  • 売りポジションの場合

price_to_beat = open_price - (coefficient_rr * diff)

position_be構造体を作成し、ポジションタイプ、チケット、および2つの価格レベル(建値価格と目標価格)といった必要な値を割り当てます。

その後、この構造体をPositionsBe配列に追加します。

  position_be new_pos;
  new_pos.breakeven_price =  position_type == POSITION_TYPE_BUY ? open_price + val : open_price - val;
  new_pos.type =  position_type;
  new_pos.price_to_beat = position_type == POSITION_TYPE_BUY ? open_price + (coefficient_rr * diff) : open_price - (coefficient_rr * diff);
  new_pos.ticket = post_ticket;

  ExtraFunctions::AddArrayNoVerification(this.PostionsBe, new_pos);
  return true;
 }


CBreakEvenクラス:建値モードの選択と動的設定

効率よく使用する建値モードを選択するために、セレクタークラスを作成します。このクラスは、建値のロジックを実行することと、ユーザーが選択した建値タイプに応じて、内部ポインタを適切な派生型へ変換することを担当します。

まず、BreakEvenParams構造体を定義します。この構造体にはMqlParam配列を格納し、この配列には、各建値タイプに必要なパラメータを格納します。

struct BreakEvenParams
 {
  MqlParam           params[];
 };

 次に、クラス自体とそのprivateセクションを宣言します。

//---
class CBreakEven
 {
private:
  ulong              magic;
  string             symbol;
  ENUM_BREAKEVEN_MODE breakeven_mode;

  BreakEvenParams    parameters[];
  CBreakEvenBase*    CreateBreakEven(ENUM_BREAKEVEN_TYPE type);

privateセクションには、マジックナンバー、銘柄、および建値モードが含まれています。また、考慮される3種類の建値のパラメータを格納するためのBreakEvenParams型の配列も含まれています。CBreakEvenBaseポインタを返す関数も宣言されています。この関数は、以下のいずれかの関数で呼び出され、内部ポインタを設定します。

最後に、CBreakEvenBase*変数が作成されます。この変数は、ユーザーが選択した建値モードに応じて、SetInternalPointer関数内で構成され、必要な型にキャストされます。

CBreakEvenBase*    obj;

コンストラクタ

コンストラクタは、建値基底クラスのコンストラクタと同じパラメータを持ちます。

CBreakEven(ulong magic_, string symbol_, ENUM_BREAKEVEN_MODE mode);

コンストラクタ内では、内部変数の値を初期化し、サポートされている3つの建値タイプに対応する3つのパラメータ配列のサイズを設定します。 

//+------------------------------------------------------------------+
//| Constructor                                                       |
//+------------------------------------------------------------------+
CBreakEven::CBreakEven(ulong magic_, string symbol_, ENUM_BREAKEVEN_MODE mode)
 {
  this.magic = magic_;
  this.symbol = symbol_;
  this.breakeven_mode = mode;
  ArrayResize(parameters, 3);
 }

デストラクタ

デストラクタ内では、まずパラメータ配列を解放します。

//+------------------------------------------------------------------+
//| Destructor                                                       |
//+------------------------------------------------------------------+
CBreakEven::~CBreakEven()
 {
  ArrayFree(this.parameters);

建値ポインタオブジェクトを削除する前に、それが動的オブジェクトであるかどうかを確認し、動的オブジェクトである場合は削除します。

  if(CheckPointer(this.obj) == POINTER_DYNAMIC)
   {
    delete obj;
    obj = NULL;
   }
 }

パラメータ設定用の関数

パラメータ配列内の値を設定するために、3つの建値モードのパラメータを設定できる5つの関数を作成します。ATR用が2つ、RRR用が2つ、固定ポイント用が1つです。

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

まずはRRR関数から始めましょう。前述の通り、2つ存在します。ATRハンドルを渡します。

void               SetBeByRR(double rr_a_put_the_break_even, ENUM_TYPE_EXTRA_BE_BY_RRR type_extra, double atr_multiplier_or_extra_points, int idx_atr_, int atr_handle_);

また、もう一方の要件として、一般的なパラメータとATRハンドルを作成するためのパラメータが必要です。

void               SetBeByRR(double rr_a_put_the_break_even, ENUM_TYPE_EXTRA_BE_BY_RRR type_extra, double atr_multiplier_or_extra_points, int idx_atr_, ENUM_TIMEFRAMES tf_atr, int atr_period_);

まず、ATRハンドルを渡す必要がある最初の関数を定義します。この関数では、他の関数と同様に、まずparams配列のサイズを6に設定します。

ArrayResize(parameters[int(BREAKEVEN_TYPE_RR)].params, 6);

次に、params配列の各インデックスに対応する値を割り当てます。

  parameters[BREAKEVEN_TYPE_RR].params[0].double_value = rr_a_put_the_break_even;
  parameters[BREAKEVEN_TYPE_RR].params[1].integer_value = (int)type_extra;
  parameters[BREAKEVEN_TYPE_RR].params[2].double_value = atr_multiplier_or_extra_points;
  parameters[BREAKEVEN_TYPE_RR].params[3].integer_value = idx_atr_;
  parameters[BREAKEVEN_TYPE_RR].params[4].integer_value = atr_handle_;

最後のインデックスである5には、HANDLE_INSTEAD_OF_PERIODの値を設定します。これにより、CBreakEvenRRクラスのパラメータ設定関数は、どの関数を呼び出すべきかを判断できます。この場合は、ATRインジケータのハンドルを引数として受け取る関数が呼び出されます。

parameters[BREAKEVEN_TYPE_RR].params[5].integer_value = HANDLE_INSTEAD_OF_PERIOD;

2番目の関数についても手順は同様ですが、以前はATRハンドル値が格納されていたインデックス4にはATR時間枠が、インデックス5にはATR期間が格納されます。

//+------------------------------------------------------------------+
//| Set rr without handle                                            |
//+------------------------------------------------------------------+
void CBreakEven::SetBeByRR(double rr_a_put_the_break_even, ENUM_TYPE_EXTRA_BE_BY_RRR type_extra, double atr_multiplier_or_extra_points, int idx_atr_, ENUM_TIMEFRAMES tf_atr, int atr_period_)
 {
  ArrayResize(parameters[int(BREAKEVEN_TYPE_RR)].params, 6);
  parameters[BREAKEVEN_TYPE_RR].params[0].double_value = rr_a_put_the_break_even;
  parameters[BREAKEVEN_TYPE_RR].params[1].integer_value = (int)type_extra;
  parameters[BREAKEVEN_TYPE_RR].params[2].double_value = atr_multiplier_or_extra_points;
  parameters[BREAKEVEN_TYPE_RR].params[3].integer_value = idx_atr_;
  parameters[BREAKEVEN_TYPE_RR].params[4].integer_value = (int)tf_atr;
  parameters[BREAKEVEN_TYPE_RR].params[5].integer_value = atr_period_;
 }

完全なコード

//+------------------------------------------------------------------+
//| Set rr without handle                                            |
//+------------------------------------------------------------------+
void CBreakEven::SetBeByRR(double rr_a_put_the_break_even, ENUM_TYPE_EXTRA_BE_BY_RRR type_extra, double atr_multiplier_or_extra_points, int idx_atr_, ENUM_TIMEFRAMES tf_atr, int atr_period_)
 {
  ArrayResize(parameters[int(BREAKEVEN_TYPE_RR)].params, 6);
  parameters[BREAKEVEN_TYPE_RR].params[0].double_value = rr_a_put_the_break_even;
  parameters[BREAKEVEN_TYPE_RR].params[1].integer_value = (int)type_extra;
  parameters[BREAKEVEN_TYPE_RR].params[2].double_value = atr_multiplier_or_extra_points;
  parameters[BREAKEVEN_TYPE_RR].params[3].integer_value = idx_atr_;
  parameters[BREAKEVEN_TYPE_RR].params[4].integer_value = (int)tf_atr;
  parameters[BREAKEVEN_TYPE_RR].params[5].integer_value = atr_period_;
 }

//+------------------------------------------------------------------+
//| Set rr with handle                                               |
//+------------------------------------------------------------------+
void CBreakEven::SetBeByRR(double rr_a_put_the_break_even, ENUM_TYPE_EXTRA_BE_BY_RRR type_extra, double atr_multiplier_or_extra_points, int idx_atr_, int atr_handle_)
 {
  ArrayResize(parameters[int(BREAKEVEN_TYPE_RR)].params, 6);
  parameters[BREAKEVEN_TYPE_RR].params[0].double_value = rr_a_put_the_break_even;
  parameters[BREAKEVEN_TYPE_RR].params[1].integer_value = (int)type_extra;
  parameters[BREAKEVEN_TYPE_RR].params[2].double_value = atr_multiplier_or_extra_points;
  parameters[BREAKEVEN_TYPE_RR].params[3].integer_value = idx_atr_;
  parameters[BREAKEVEN_TYPE_RR].params[4].integer_value = atr_handle_;
  parameters[BREAKEVEN_TYPE_RR].params[5].integer_value = HANDLE_INSTEAD_OF_PERIOD;
 }

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

params配列の値を設定するためにも、RRR建値の場合と同様に2つの関数を使用しますが、パラメータの数は少なくなります。

どちらのパラメータ設定関数も、正確に5つのパラメータを持ちます。ハンドルを必要とする最初のものは、次のように定義されます。

void CBreakEven::SetBeByAtr(int atr_idx, double atr_multiplier_be, double atr_multiplier_extra_be, int atr_handle)

この関数を説明する際、最初のステップは、params配列内のparameters配列のサイズを設定することです。

ArrayResize(parameters[int(BREAKEVEN_TYPE_ATR)].params, 5);

次に、params配列の5つの要素すべてに値が割り当てられます。

  parameters[BREAKEVEN_TYPE_ATR].params[0].integer_value = atr_idx;
  parameters[BREAKEVEN_TYPE_ATR].params[1].double_value = atr_multiplier_be;
  parameters[BREAKEVEN_TYPE_ATR].params[2].double_value = atr_multiplier_extra_be;
  parameters[BREAKEVEN_TYPE_ATR].params[3].integer_value = atr_handle;

最後の要素であるインデックス4には、HANDLE_INSTEAD_OF_PERIODの値を設定する必要があります。これにより、CBreakEvenAtrクラスのメンバー関数のうち、ATRインジケータのハンドルを引数として受け取る関数を選択できるようになります。

parameters[BREAKEVEN_TYPE_ATR].params[4].integer_value = HANDLE_INSTEAD_OF_PERIOD;

2番目の関数では、インデックス3と4の値のみが変更されます。

インデックス3には、ATR時間足の値が含まれます。

parameters[BREAKEVEN_TYPE_ATR].params[3].integer_value = int(timeframe);

そして、インデックス4にはATR期間の値が含まれます。

parameters[BREAKEVEN_TYPE_ATR].params[4].integer_value = atr_period;

完全なコード

//+------------------------------------------------------------------+
//| Set atr (without handle)                                         |
//+------------------------------------------------------------------+
void CBreakEven::SetBeByAtr(int atr_period, int atr_idx_, double atr_multiplier_extra_be_, double atr_multiplier_be_, ENUM_TIMEFRAMES timeframe)
 {
  ArrayResize(parameters[int(BREAKEVEN_TYPE_ATR)].params, 5);
  parameters[BREAKEVEN_TYPE_ATR].params[0].integer_value = atr_idx_;
  parameters[BREAKEVEN_TYPE_ATR].params[1].double_value = atr_multiplier_be_;
  parameters[BREAKEVEN_TYPE_ATR].params[2].double_value = atr_multiplier_extra_be_;
  parameters[BREAKEVEN_TYPE_ATR].params[3].integer_value = int(timeframe);
  parameters[BREAKEVEN_TYPE_ATR].params[4].integer_value = atr_period;
 }

//+------------------------------------------------------------------+
//| Set atr (with handle)                                            |
//+------------------------------------------------------------------+
void CBreakEven::SetBeByAtr(int atr_idx, double atr_multiplier_be, double atr_multiplier_extra_be, int atr_handle)
 {
  ArrayResize(parameters[int(BREAKEVEN_TYPE_ATR)].params, 5);
  parameters[BREAKEVEN_TYPE_ATR].params[0].integer_value = atr_idx;
  parameters[BREAKEVEN_TYPE_ATR].params[1].double_value = atr_multiplier_be;
  parameters[BREAKEVEN_TYPE_ATR].params[2].double_value = atr_multiplier_extra_be;
  parameters[BREAKEVEN_TYPE_ATR].params[3].integer_value = atr_handle;
  parameters[BREAKEVEN_TYPE_ATR].params[4].integer_value = HANDLE_INSTEAD_OF_PERIOD;
 }

固定ポイント方式の建値パラメータを設定する関数

最後はポイントベースの建値です。これには2つのパラメータのみが必要です。CBreakEvenクラス内では、以下のように宣言します。

void               SetBeByFixedPoints(int points_be_, int extra_points_be_);

それを定義するために、まず対応するparams配列のサイズを2に設定します。

ArrayResize(parameters[int(BREAKEVEN_TYPE_FIXED_POINTS)].params, 2);

次に、インデックス0を建値有効化のためのポイント数、インデックス1を追加ポイント数として割り当てます。

  parameters[BREAKEVEN_TYPE_FIXED_POINTS].params[0].integer_value = points_be_;
  parameters[BREAKEVEN_TYPE_FIXED_POINTS].params[1].integer_value = extra_points_be_;

完全なコード

//+------------------------------------------------------------------+
//| Set Fixed Point be                                               |
//+------------------------------------------------------------------+
void CBreakEven::SetBeByFixedPoints(int points_be_, int extra_points_be_)
 {
  ArrayResize(parameters[int(BREAKEVEN_TYPE_FIXED_POINTS)].params, 2);
  parameters[BREAKEVEN_TYPE_FIXED_POINTS].params[0].integer_value = points_be_;
  parameters[BREAKEVEN_TYPE_FIXED_POINTS].params[1].integer_value = extra_points_be_;
 }

建値型に基づいてポインタを取得する関数

ユーザーが設定したタイプに応じて必要な建値モードを取得するために、CBreakEvenBaseポインタを返す関数を作成します。

CBreakEvenBase *CBreakEven::CreateBreakEven(ENUM_BREAKEVEN_TYPE type)

まず、各ケースが建値のタイプに対応するswitch文を作成します。タイプ変数がいずれの型にも一致しない場合、デフォルトでNULLが返されます。一致するものが見つかった場合、関数は対応する子クラスへのポインタを返します。

  switch(type)
   {
    case BREAKEVEN_TYPE_ATR:
      return new CBreakEvenAtr(this.symbol, this.magic, this.breakeven_mode);

    case BREAKEVEN_TYPE_RR:
      return new CBreakEvenRR(this.symbol, this.magic, this.breakeven_mode);

    case BREAKEVEN_TYPE_FIXED_POINTS:
      return new CBreakEvenSimple(this.symbol, this.magic, this.breakeven_mode);

    default:
      return NULL;
   }

完全なコード

//+------------------------------------------------------------------+
//| Dynamically create the correct BreakEven                         |
//+------------------------------------------------------------------+
CBreakEvenBase *CBreakEven::CreateBreakEven(ENUM_BREAKEVEN_TYPE type)
 {
  switch(type)
   {
    case BREAKEVEN_TYPE_ATR:
      return new CBreakEvenAtr(this.symbol, this.magic, this.breakeven_mode);

    case BREAKEVEN_TYPE_RR:
      return new CBreakEvenRR(this.symbol, this.magic, this.breakeven_mode);

    case BREAKEVEN_TYPE_FIXED_POINTS:
      return new CBreakEvenSimple(this.symbol, this.magic, this.breakeven_mode);

    default:
      return NULL;
   }
  return NULL;
 }

objポインタの内部値を設定する関数

最後に、内部ポインタに値を割り当てるために、建値型をパラメータとして受け取るSetInternalPointerという名前の関数を作成します。

void SetInternalPointer(ENUM_BREAKEVEN_TYPE type)

まず、objポインタの状態を確認することから始めます。

  if(CheckPointer(this.obj) == POINTER_DYNAMIC)
   {
    delete this.obj;
    this.obj = NULL;
   }

ポインタが動的である場合は、それを削除します。

次に、objポインタはCreateBreakEven関数で初期化され、選択された建値タイプに応じてポインタが返されます。代入後、それがNULLかどうかを確認します。その場合は、エラーメッセージを表示し、現在のチャートからEAを削除します。

  this.obj = CreateBreakEven(type);

  if(this.obj == NULL)
   {
    printf("%s: Critical error | The type %d is invalid.", __FUNCTION__, type);
    ExpertRemove();
    return;
   }

パラメータ配列のサイズが、必要なパラメータを格納するのに十分かどうかを確認します。このチェックが失敗した場合、オブジェクトを削除し、ポインタをNULLに設定し、チャートからEAを削除します。

  if((int)parameters[type].params.Size() < obj.GetNumParams())
   {
    printf("%s: Error | The parameter array for %s is too small (%I32u elements)",
           __FUNCTION__, EnumToString(type), parameters[type].params.Size());
    delete obj;
    obj = NULL;
    ExpertRemove();
    return;
   }

最後に、基底クラスのSet関数を使用して必要な値を設定します。

obj.Set(parameters[type].params);

完全なコード

//+------------------------------------------------------------------+
//| Set Pointer                                                      |
//+------------------------------------------------------------------+
void CBreakEven::SetInternalPointer(ENUM_BREAKEVEN_TYPE type)
 {
  if(CheckPointer(this.obj) == POINTER_DYNAMIC)
   {
    delete this.obj;
    this.obj = NULL;
   }

  this.obj = CreateBreakEven(type);

  if(this.obj == NULL)
   {
    printf("%s: Critical error | The type %d is invalid.", __FUNCTION__, type);
    ExpertRemove();
    return;
   }

  if((int)parameters[type].params.Size() < obj.GetNumParams())
   {
    printf("%s: Error | The parameter array for %s is too small (%I32u elements)",
           __FUNCTION__, EnumToString(type), parameters[type].params.Size());
    delete obj;
    obj = NULL;
    ExpertRemove();
    return;
   }

  obj.Set(parameters[type].params);
 }
//+------------------------------------------------------------------+


さまざまな建値タイプでOrder Blocks EAをテストする

この最終セクションでは、ストラテジーテスターの3つの建値モードのテストに焦点を当てます。

まずはパラメータの設定から始めましょう。ATRに基づいたストップロスとテイクプロフィットを使用します。ストップロスのATR乗数は3.0、テイクプロフィットのATR乗数は6.0に設定され、つまり1:2の比率となります。取引時間は、GMT-3の午前3時から午後2時までとなります。

設定

 Настройки

図1:Order Blocks EAのバックテスト設定

図に示すように、ロボットは金(XAUUSD)でテストされ、M5(5分足)を選択します。このモデルは、実際のティックデータに基づいており、レバレッジは1:30、初期残高は10,000米ドルです。

建値機構なしの初期バックテスト

より明確な比較をおこなうため、最初のテストは建値モードを有効にせずに実施します。

 без безубытка

図2:建値機構なしのOrder Blocks EAのバックテストチャート

バックテストを開始する前に、重要な点を確認しておく必要があります。今回得られた結果は、特定のパラメータ設定に対して得られた結果です。そのため、ここで示す結論を、すべての戦略や任意のパラメータ設定にそのまま一般化することはできません。ただし、同程度の値を使用する場合には、おおよその評価をおこなうことができます。

たとえば、以下のバックテストでは、固定ポイント方式の建値をテストします。パラメータは、建値価格に100ポイントの追加幅、建値の発動条件を200ポイントに設定します。したがって、このバックテストから得られる結論は、建値の発動値が200ポイント前後、追加の建値幅が100ポイント前後の場合にも適用できると考えられます。

ポイントベースの建値によるバックテスト

固定ポイント方式のバックテストでは、建値の発動を200ポイント、追加幅を100ポイントに設定します。得られた結果は以下のとおりです。

безубыток по пунктам

図3:固定ポイント方式の建値を使用したOrder Blocks EAの取引結果チャート

図から分かるように、小さな利益で終了するポジションが多数連続して発生しています。これは予想どおり、建値機構の影響を受けたものです。さらに、この固定ポイント方式の建値によるバックテストでは、2025年4月16日から4月29日頃にかけてプラスの含み益が発生していたポジションが、損失を出すことなく決済されています。一方、最初のバックテストでは、この期間に損失が発生していました。

このことから、ポイントベースの建値はより保守的なアプローチである可能性があります。ただし、その一方で、将来的に得られる利益を制限してしまう可能性もあります。その結果、このバックテストの最終残高はわずか12,000 USDでした。一方、最初のバックテストでは最終残高が約15,800 USDでした。これは、固定ポイント方式の建値は損失を減らす一方で、利益も大きく制限することを示しています。

ATRベースの建値によるバックテスト

безубыток по ATR

図4:ATRベースの建値を使用したOrder Blocks EAのバックテストチャート

2回目のATRベースの建値によるバックテストでは、チャートに改善が見られます。まず、この建値タイプが利益が続く局面と損失が続く局面 にどのような影響を与えたのかを分析します。

利益が連続する局面では、この建値は固定ポイント方式ほど積極的には動作しません。一方、損失が連続する局面では、損失を適度に抑制しています。これは、最初の大きなドローダウンの局面にはっきりと表れています。このドローダウンは2024年の初めから3月まで続きました。このバックテストの残高曲線は、最初のバックテストの曲線とかなり似た動きをしています。

したがって、この建値モードはより穏やかな方式であると結論づけることができます。固定ポイント方式ほど積極的ではありません。これは、ボラティリティに応じてATRの値が変化するため、建値の発動価格がATRの値によって高くなったり低くなったりするためです。

RRRベースの建値によるバックテスト

безубыток по RR

図5:ストップロスとテイクプロフィットの比率を1:2に設定し、RRRベースの建値を使用したOrder Blocks EAの取引結果チャート

このバックテストから分かるのは、損失が連続する局面、特に2024年1月と2月では、建値なしのバックテストとRRRベースの建値を使用したバックテストの差が小さいということです。これは、これらの損失が実際の損失だったことを示しています。つまり、ポジション保有中に含み益となる局面がほとんど、あるいはまったくなかったことを示しています。

利益が連続する局面では、利益の伸びが適度に制限されています。たとえば2024年7月には、最初のバックテストではほぼすべてのポジションがテイクプロフィットで決済されていました。しかし、RRRベースの建値を使用すると、この上昇局面は大きく停滞しました。これは、EAがレンジ相場の中で取引していたことが原因である可能性があります。価格が明確なトレンドを形成しなかったため、建値機構が発動して、明確な方向性が形成される前にポジションが決済された可能性があります。

以上から、RRRベースの建値はより保守的な方式であると結論づけることができます。RRRベースでは、建値を発動させるために価格がより大きく移動する必要があるためです。したがって、これはアグレッシブな戦略には最適な選択肢ではありません。一方で、1:3や1:5のような高いリスクリワード比率を前提としたスイング型戦略には有効である可能性があります。これを確認するため、次にリスクリワード比率1:3でバックテストを実行します。

безубыток по RR 2

図6:ストップロスとテイクプロフィットの比率を1:3に設定し、RRRベースの建値を使用したOrder Blocks EAの取引結果チャート

新しいバックテストでは、1:2の場合よりも良い結果が得られました。主な変化として、最低残高は約9,500 USDとなり、これはATRベースの建値によるバックテスト結果とほぼ同じです。また、最初の損失連続期間である1月から2月にかけての損失も、最初のバックテストほど大きくありませんでした。

しかし、4月には、3種類の建値方式の中で最も長い損失連続期間が確認されます。その期間は約5か月間続きました。これは、金価格が約5か月間にわたって横ばい(レンジ)で推移した期間と一致しています。最初のRRR建値バックテストと比較すると、これは、ポジションがすぐに決済されたのではなく、マイナスの含み損とプラスの含み益の間を行き来していた可能性を示しています。つまり、ストップロスとテイクプロフィットの幅がかなり大きかったと考えられます。

問題は、この新しいバックテストでは、ストップロスのサイズが元の3分の1に縮小されていることです。そのため、ポジションが損失で決済される確率が高くなりました。これは、横ばい相場の期間中、ポジションの保有時間が長くなり、決済されるまでの間に含み益と含み損の間を何度も行き来していた可能性を示しています。


結論

この記事では、MQL5におけるATRベースおよびRRRベースの建値機能の実装を完了しました。さらに、異なる建値タイプを1つのクラスで管理できるクラスを作成しました。これにより、新しい建値モードを使用するたびにパラメータを再入力することなく、建値タイプを簡単に切り替えられるようになりました。

最後に、それぞれの建値タイプについてバックテストを実施し、結果を分析しました。その結果、今回指定したパラメータでは、固定ポイント方式の建値が最も積極的であることが分かりました。一方、RRRベースの建値はより消極的(保守的)ですが、その分、利益をより強く制限します。ATRベースの建値は、この2つの中間に位置する方式と言えます。

まとめると、どの建値タイプが各戦略にとって最適なのかを一概に判断することはできません。これは、考慮すべきさまざまな要因によって結果が変わるためです。そのため、各戦略について個別に評価する必要があります。ただし、推奨方法としては、複数の建値タイプをバックテストし、使用する戦略に最も適したものを選択することができます。

今回の3種類の建値方式によるバックテストからは、RRRベースの建値は、あらかじめリスクリワード比率が1:3以上に設定されている戦略に適していると考えられます。これは5回目のバックテストでも確認されており、純利益は4回目のバックテストの約3倍になりました。

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

ファイル名 種類 説明 
 Risk_Management.mqh  .mqh(ヘッダーファイル) 前回のリスク管理記事で開発したリスク管理クラスを含むファイル
 Order_Block_Indicador_New_Part_2.mq5 .mq5(インジケーター) Order Blockインジケーターのコード
 Order Block EA MT5.mq5  .mq5 (EA) 建値メカニズムを統合したOrder Block EAコード
 OB_SET_WITHOUT_BREAKEVEN.set .set(設定ファイル) 建値機能を使用しない、最初のバックテストの設定
 OB_SET_BREAKEVEN_POINTS.set .set(設定ファイル) 固定ポイント方式の建値を使用した2回目のバックテスト用設定
 OB_SET_BREAKEVEN_ATR.set .set(設定ファイル) ATRベースの建値を使用した3回目のバックテスト用設定
 OB_SET_BREAKEVEN_RR_1_2.set .set(設定ファイル) RRR 1:2の建値を使用した4回目のバックテスト用設定
 OB_SET_BREAKEVEN_RR_1_3.set .set(設定ファイル) RRR 1:3の建値を使用した5回目のバックテスト用設定
 PositionManagement.mqh .mqh(ヘッダーファイル)  建値メカニズムのコードを含むMQHファイル


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

添付されたファイル |
MQL5.zip (242.42 KB)
MQL5での量子ニューラルネットワーク(第3回):量子ビットに基づく仮想量子プロセッサ MQL5での量子ニューラルネットワーク(第3回):量子ビットに基づく仮想量子プロセッサ
数学的アナロジーではなく、量子原理に基づく本格的な量子シミュレータを使用した取引システムの構築に焦点を当てています。このシステムでは、3つの仮想量子ビット、量子ゲート、重ね合わせの原理を使用して市場を分析します。また、MQL5で実装されたMetaTrader 5用自動売買EAとして実装されています。最大の成果は、単なるシミュレーションから、金融情報処理における実際の量子原理の利用へ移行したことです。
初級から中級まで:FileSaveとFileLoad 初級から中級まで:FileSaveとFileLoad
FileSaveおよびFileLoadのライブラリ関数を使用するいくつかの方法について見ていきます。特定の状況では、いくつかの制限や扱いにくさがあるため、これらの関数は用途が限られていると考える人も多いでしょう。しかし、この2つの関数がどのように動作するのかを正しく理解しておけば、さまざまな場面で多くの手間を省くことができます。また、これらはログファイルを扱うための優れた方法でもあります。
エラー 146 (「トレードコンテキスト ビジー」) と、その対処方法 エラー 146 (「トレードコンテキスト ビジー」) と、その対処方法
この記事では、MT4において複数のEAの衝突をさける方法を扱います。ターミナルの操作、MQL4の基本的な使い方がわかる人にとって、役に立つでしょう。
科学者コミュニティ最適化 (CoSO):実装編 科学者コミュニティ最適化 (CoSO):実装編
科学コミュニティを題材にした最適化手法の話題を引き続き取り上げます。CoSOは完成されたソリューションとして捉えるべきではなく、将来性のある研究プラットフォームとして考えるべきです。適切に発展させることで、CoSOは、適応性と変化への耐性が重要であり、計算時間がそれほど重要視されないタスクにおいて、その有用性を発揮できる可能性があります。