モメンタムピンボールトレーディング戦略

Alexander Puzanov | 29 1月, 2018


イントロダクション

この記事では、L. Raschke と L. ConnorsによるStreet Smarts: High Probability Short-Term Trading Strategiesで説明されているトレード戦略のプログラミングを続行し、テストに専念します。 このセクションでの本格的なTSは、2つの日足で構成されるパターンを操作するモメンタムピンボールです。 最初の足によって第2のトレード方向が定義されます。2番目の足の初めの価格の動きは、エントリーとイグジットのレベルを決定します。

この記事の目的は、すでに MQL5 を習得しているプログラマに、オブジェクト指向プログラミングの簡略化されたメソッドが適用されるモメンタムピンボール TS を実現するための亜種の1つを示すことです。 本格的な OOP ではなく、クラスが存在しないので、構造体に置き換えられます。 クラスとは対照的に、このタイプのオブジェクトのコードとアプリケーションの設計は、これまで慣れ親しんだ手続き型プログラミングとは異なります。 一方、構造体によって提供される関数は、このようなタスクを解決するのに十分なものではありません。

前の記事のように、最初に、シグナルブロックモジュールを作成し、-このモジュールを使用する裁量トレードおよびヒストリーのインジケーターを作成します。 3番目のプログラムは、自動化されたトレードEAとなります。また、シグナルモジュールを使用します。 結論として、著者は20年前のクオートで働かせたので、新しいクオートでEAをテストします。


モメンタムピンボール TS のルール

L. Raschkeと L. Connorsは、このトレード戦略のルールをコンパイルする理由となったGeorge Taylorによって記述された技術を使用するときに不確実性に直面した。 日の初めのテイラーの戦略は、そのトレードの方向性を定義します。-これは、買いの日か売りの日か決定します。 しかし、著者の実際のトレードは、多くの場合、この取り決めに違反しています。

より確実に次の日のトレードの方向性を決定するために、著者らは、ROC (変化率) インジケーターを適用していました。 RSI (相対強度指数) オシレーターがその値に適用され、ROC 値の周期がよく見えるようになりました。 最後に、TS の著者は、シグナルレベルを追加しました。RSI上の買われ過ぎと売られ過ぎの領域の境界線です。 そのようなインジケーターのライン ( Linda Bradford RaschkeからLBR/RSIとしました) は、最も可能性がある売りの日および買いの日を検出するために示されます。 以下はLBR/RSI の詳細です。

買いの記入項目のモメンタムピンボール TS の完全なルールは次の通り作り出されます。

  1. D1では、最後の日の LBR/RSI の値は、売られ過ぎの領域内にある必要があります。(-30 を下回る)
  2. 新しい日の最初の時間足を閉じた後、保留中の買いオーダーをその足の最大値よりも高く配置します。
  3. 予約オーダーのトリガ後、最初の時間足の下にストップロスを配置します。
  4. ポジションが損失でクローズされた場合、もう一度同じレベルで保留中の売りオーダーを配置します。
  5. 一日の終わりまでに、ポジションがプラスの場合、次の日に残します。 2番目のトレード日では、ポジションを閉じなければなりません。

以下の2つのインジケーターの助けを借りて、エントリのルールの可視化は、次のようになります。

-日の時間枠の LBR/RSI は、売られ過ぎの領域にある (2017 10 月30日を参照してください)


—未定義のタイムフレーム (M1 から D1) のインジケーター TS_Momentum_Pinball は、1日の最初の1時間のトレードレベルと価格範囲を表示し、レベルが計算されます。


決済するためのルールが明確に本の中で定義されていません。著者は、トレーリングの使用について言及しています。

売りのルールは類似しています-LBR/RSI は買われ過ぎ (70 より高い) にあるべきで、未決済のオーダーは最初の足の安値に置かれるべきです。



LBR/RSI インジケーター

もちろん、シグナルを受信するために必要なすべての計算は、シグナルモジュールで実行することができますが、自動トレード同様にマニュアルトレードも考慮に入れます。 買われ過ぎ/売られ過ぎの領域のハイライトと別のインジケータ LBR/RSI を持つことは、マニュアルバージョンのパターンの視覚的な識別の便宜に有用です。 そして、最適化するために、 LBR/RSI の2つのバージョンをプログラムしません (インジケーター用のバッファとロボット用のバッファなし)。 標準のiCustom関数を使用して、外部インジケーターをシグナルモジュールに接続してみましょう。 このインジケーターは、リソースを集中的に評価を行うことはありませんし、各ティックでする必要はありません。TS で、日足の終値でインジケーターの値が使用されます。 継続的に現在の値を変更しません。 したがって、このようなソリューションの実質的な障害はありません。

ここでは、計算アルゴリズムROCRSIは、その結果のオシレーター曲線を描画します。 必要な値を検出するために、買われ過ぎと売られ過ぎの領域の塗りつぶしをさまざま色で追加します。 これを行うには、5つのバッファを表示し、さらに4つの補助計算を行う必要があります。

標準設定 (RSI 期間と2つの領域の境界の値) は、元のトレーディングシステムのルールによって提供されていない別のものを追加します。 計算のため、日足の終値だけでなく、より有益な中央値、典型的または加重平均価格を使用することができます。 実際には、実験として、ユーザーは、 ENUM_APPLIED_PRICEによって提供される7つのバリアントのいずれかを選択できます。

バッファの宣言、ユーザーテキストボックス、および初期化ブロックは次のようになります。

#property indicator_separate_window
#property indicator_buffers  9
#property indicator_plots    3
#property indicator_label1  “Overbought area"
#property indicator_type1   DRAW_FILLING
#property indicator_color1  C'255,208,234'
#property indicator_width1  1
#property indicator_label2  “Oversold area"
#property indicator_type2   DRAW_FILLING
#property indicator_color2  C'179,217,255'
#property indicator_width2  1
#property indicator_label3  "RSI от ROC"
#property indicator_type3   DRAW_LINE
#property indicator_style3  STYLE_SOLID
#property indicator_color3  clrTeal
#property indicator_width3  2
#property indicator_minimum 0
#property indicator_maximum 100
input ENUM_APPLIED_PRICE  TS_MomPin_Applied_Price = PRICE_CLOSE;  // Price for ROC calculation
input uint    TS_MomPin_RSI_Period = 3;                           // RSI Period
input double  TS_MomPin_RSI_Overbought = 70;                      // RSI Oversold level
input double  TS_MomPin_RSI_Oversold = 30;                        // RSI overbought level
double
  buff_Overbought_High[], buff_Overbought_Low[],                  // overbought area background
  buff_Oversold_High[], buff_Oversold_Low[],                      // oversold area background
  buff_Price[],                                                   // array of bar calculated prices
  buff_ROC[],                                                     // ROC array from calculated prices
  buff_RSI[],                                                     // RSI from ROC
  buff_Positive[], buff_Negative[]                                // auxiliary arrays for RSI calculation
;
int OnInit() {
  // designation of indicator buffers:
  
  // overbought area
  SetIndexBuffer(0, buff_Overbought_High, INDICATOR_DATA);
    PlotIndexSetDouble(0, PLOT_EMPTY_VALUE, EMPTY_VALUE);
    PlotIndexSetInteger(0, PLOT_SHOW_DATA, false);
  SetIndexBuffer(1, buff_Overbought_Low, INDICATOR_DATA);
  
  // oversold area
  SetIndexBuffer(2, buff_Oversold_High, INDICATOR_DATA);
    PlotIndexSetDouble(1, PLOT_EMPTY_VALUE, EMPTY_VALUE);
    PlotIndexSetInteger(1, PLOT_SHOW_DATA, false);
  SetIndexBuffer(3, buff_Oversold_Low, INDICATOR_DATA);
  
  // RSI curve
  SetIndexBuffer(4, buff_RSI, INDICATOR_DATA);
    PlotIndexSetDouble(2, PLOT_EMPTY_VALUE, EMPTY_VALUE);
  
  // auxiliary buffers for RSI calculation
  SetIndexBuffer(5, buff_Price, INDICATOR_CALCULATIONS);
  SetIndexBuffer(6, buff_ROC, INDICATOR_CALCULATIONS);
  SetIndexBuffer(7, buff_Negative, INDICATOR_CALCULATIONS);
  SetIndexBuffer(8, buff_Positive, INDICATOR_CALCULATIONS);
  
  IndicatorSetInteger(INDICATOR_DIGITS, 2);
  IndicatorSetString(INDICATOR_SHORTNAME, "LBR/RSI");
  
  return(INIT_SUCCEEDED);
  }

標準イベントハンドラ OnCalculate で、2つの別々のループを配置します。最初は、ROC データ配列を準備します。2番目は、この配列データに基づいてオシレーター値を計算します。

リンダシクによって提案された変更バージョンのレートでは、お互いに続く足の価格ではなく、間の足が欠落して比較する必要があります。 つまり、TS では、トレード日の1日と3営業日から、ぞれの日の価格変動を使用します。 これはむしろ簡単です。このループで買われ過ぎと売られ過ぎの領域の背景を配置します。 また、価格タイプ選択関数を必ず実装してください。

int
  i_RSI_Period = int(TS_MomPin_RSI_Period),         // transfer of RSI period into int type
  i_Bar, i_Period_Bar                               // two bar indices for simultaneous application
;
double
  d_Sum_Negative, d_Sum_Positive,                   // auxiliary variables for RSI calculation
  d_Change                                          // auxiliary variable for ROC calculation
;
// Fill in ROC buffer and fill areas:
i_Period_Bar = 1;
while(++i_Period_Bar < rates_total && !IsStopped()) {
// calculated bar price:
  switch(TS_MomPin_Applied_Price) {
    case PRICE_CLOSE:     buff_Price[i_Period_Bar] = Close[i_Period_Bar]; break;
    case PRICE_OPEN:      buff_Price[i_Period_Bar] = Open[i_Period_Bar]; break;
    case PRICE_HIGH:      buff_Price[i_Period_Bar] = High[i_Period_Bar]; break;
    case PRICE_LOW:       buff_Price[i_Period_Bar] = Low[i_Period_Bar]; break;
    case PRICE_MEDIAN:    buff_Price[i_Period_Bar] = 0.50000 * (High[i_Period_Bar] + Low[i_Period_Bar]); break;
    case PRICE_TYPICAL:   buff_Price[i_Period_Bar] = 0.33333 * (High[i_Period_Bar] + Low[i_Period_Bar] + Open[i_Period_Bar]); break;
    case PRICE_WEIGHTED:  buff_Price[i_Period_Bar] = 0.25000 * (High[i_Period_Bar] + Low[i_Period_Bar] + Open[i_Period_Bar] + Open[i_Period_Bar]); break;
  }
  // difference of bar calculated prices (ROC value):
  if(i_Period_Bar > 1) buff_ROC[i_Period_Bar] = buff_Price[i_Period_Bar] - buff_Price[i_Period_Bar - 2];
  
  // background filling:
  buff_Overbought_High[i_Period_Bar] = 100;
  buff_Overbought_Low[i_Period_Bar] = TS_MomPin_RSI_Overbought;
  buff_Oversold_High[i_Period_Bar] = TS_MomPin_RSI_Oversold;
  buff_Oversold_Low[i_Period_Bar] = 0;
    }

2番目のループ (RSI 計算) には特性がないため、このタイプの標準オシレーターのアルゴリズムはほぼ完全に繰り返されます。

i_Period_Bar = prev_calculated - 1;
if(i_Period_Bar <= i_RSI_Period) {
  buff_RSI[0] = buff_Positive[0] = buff_Negative[0] = d_Sum_Positive = d_Sum_Negative = 0;
  i_Bar = 0;
  while(i_Bar++ < i_RSI_Period) {
    buff_RSI[0] = buff_Positive[0] = buff_Negative[0] = 0;
    d_Change = buff_ROC[i_Bar] - buff_ROC[i_Bar - 1];
    d_Sum_Positive += (d_Change > 0 ? d_Change : 0);
    d_Sum_Negative += (d_Change < 0 ? -d_Change : 0);
  }
  buff_Positive[i_RSI_Period] = d_Sum_Positive / i_RSI_Period;
  buff_Negative[i_RSI_Period] = d_Sum_Negative / i_RSI_Period;
  
  if(buff_Negative[i_RSI_Period] != 0)
    buff_RSI[i_RSI_Period] = 100 - (100 / (1. + buff_Positive[i_RSI_Period] / buff_Negative[i_RSI_Period]));
  else
    buff_RSI[i_RSI_Period] = buff_Positive[i_RSI_Period] != 0 ? 100 : 50;
  
  i_Period_Bar = i_RSI_Period + 1;
}

i_Bar = i_Period_Bar - 1;
while(++i_Bar < rates_total && !IsStopped()) {
  d_Change = buff_ROC[i_Bar] - buff_ROC[i_Bar - 1];
  
  buff_Positive[i_Bar] = (buff_Positive[i_Bar - 1] * (i_RSI_Period - 1) + (d_Change> 0 ? d_Change : 0)) / i_RSI_Period;
  buff_Negative[i_Bar] = (buff_Negative[i_Bar - 1] * (i_RSI_Period - 1) + (d_Change <0 ? -d_Change : 0)) / i_RSI_Period;
  
  if(buff_Negative[i_Bar] != 0)
    buff_RSI[i_Bar] = 100 - 100. /(1. + buff_Positive[i_Bar] / buff_Negative[i_Bar]);
  else
    buff_RSI[i_Bar] = buff_Positive[i_Bar] != 0 ? 100 : 50;
}

LBR_RSI.mq5と命名し、ターミナルデータカタログの標準インジケータフォルダに配置してみましょう。 この名前はシグナルモジュールの iCustom 関数で指定されますので、変更しないでください。

シグナルモジュール

EAとインジケータに接続されたシグナルモジュールで、モメンタムピンボールトレーディング戦略のユーザー設定を配置します。 著者らは、LBR/RSI インジケーター (期間 rsi = 3、買われ過ぎレベル = 30、売られ過ぎレベル = 70) を計算するための固定値を提供します。 しかし、実験の変更を行います, ちょうどポジションの決済メソッドのような形です。 すべてをプログラムし、ユーザーが必要なオプションを選択する関数があります:

  • ストップロスレベルのトレーリングのポジションを閉じます;
  • 次の日の朝に閉じます;
  • ポジション初日の極値のブレイクスルーを2日目に待ちます。

"朝" は、より特定の定義が必要で、無形の概念です。 RaschkeとConnorsはそれについて言及していませんが、新しい日の最初の足 (他の TS ルールで適用される) へのバインドは、24時間のタイムスケールの ' 朝 ' のラベルを指すと仮定するのが妥当です。

1日の最初の時間のボーダーからのオフセット-2 つのより多くの TS の設定について;オフセットでは、予約オーダーの配置レベルと StopLoss レベルを指定する必要があります。

enum ENUM_EXIT_MODE {     // List of exit methods
  CLOSE_ON_SL_TRAIL,      // only by trailing
  CLOSE_ON_NEW_1ST_CLOSE, // by closing of the 1st bar of the following day
  CLOSE_ON_DAY_BREAK      // by break-through of extremum of the position opening day
};
// user settings
input ENUM_APPLIED_PRICE  TS_MomPin_Applied_Price = PRICE_CLOSE;     // Momentum Pinball: Prices for ROC calculation
input uint    TS_MomPin_RSI_Period = 3;                              // Momentum Pinball: RSI period
input double  TS_MomPin_RSI_Overbought = 70;                         // Momentum Pinball: RSI oversold level
input double  TS_MomPin_RSI_Oversold = 30;                           // Momentum Pinball: RSI overbought level
input uint    TS_MomPin_Entry_Offset = 10;                           // Momentum Pinball: Offset of entry level from borders H1 (in points)
input uint    TS_MomPin_Exit_Offset = 10;                            // Momentum Pinball: Offset of exit level from borders H1 (in points)
  input ENUM_EXIT_MODE  TS_MomPin_Exit_Mode = CLOSE_ON_SL_TRAIL;       // Momentum Pinball: Profitable position closing method

メインモジュール関数fe_Get_Entry_Signalは、このソースで説明されている他の TS の後続のアナモジュールと同様に、RaschkeとConnorsの著書から以前のトレード戦略のシグナルモジュール関数と統合されます。 この関数に渡されたパラメータのパッケージ、変数へのリンク、戻り値の型が同じであることを意味します。

ENUM_ENTRY_SIGNAL fe_Get_Entry_Signal(      // Two-candle pattern analysis (D1 + H1)
  datetime  t_Time,                         // current time
  double&    d_Entry_Level,                 // entry level (link to the variable)
  double&    d_SL,                          // StopLoss level (link to the variable)
  double&    d_TP,                          // TakeProfit level (link to the variable)
  double&    d_Range_High,                  // high of the range's 1st hourly bar (link to the variable)
  double&    d_Range_Low                    // low of the range's 1st hourly bar (link to the variable)
) {
  // function body
  }

以前のバージョンのように、ロボットから関数を呼び出すときに、各ティックで再びすべてを計算しません。 代わりに、静的変数の計算レベルをティックの間に格納します。 ただし、裁量トレードインジケーターでこの関数を使用すると、実質的な差があります。また、インジケータから関数を呼び出す際の静的変数のゼロを指定する必要があります。 インジケーターからの呼び出しとロボットからの呼び出しを区別するために、 t_Time変数を適用します。 インジケータは、これを反転させ、その値を負にします:

static ENUM_ENTRY_SIGNAL se_Trade_Direction = ENTRY_UNKNOWN;   // trading direction for today
static double
  // variables for storing calculated levels between ticks
  sd_Entry_Level = 0,
  sd_SL = 0, sd_TP = 0,
  sd_Range_High = 0, sd_Range_Low = 0
;
if(t_Time < 0) {                                               // only for call from indicator
  sd_Entry_Level = sd_SL = sd_TP = sd_Range_High = sd_Range_Low = 0;
  se_Trade_Direction = ENTRY_UNKNOWN;
}
// by default apply earlier saved levels of entries/exits:
    d_Entry_Level = sd_Entry_Level; d_SL = sd_SL; d_TP = sd_TP; d_Range_High = sd_Range_High; d_Range_Low = sd_Range_Low;

次に、初めて関数を呼び出すときに LBR/RSI インジケーターハンドルを受け取るコードを探します。

static int si_Indicator_Handle = INVALID_HANDLE;
if(si_Indicator_Handle == INVALID_HANDLE) {
  // to receive indicator handle when calling the function for the first time:
  si_Indicator_Handle = iCustom(_Symbol, PERIOD_D1, "LBR_RSI",
    TS_MomPin_Applied_Price,
    TS_MomPin_RSI_Period,
    TS_MomPin_RSI_Overbought,
    TS_MomPin_RSI_Oversold
  );
  
  if(si_Indicator_Handle == INVALID_HANDLE) { // indicator handle not received
    if(Log_Level > LOG_LEVEL_NONE) PrintFormat("%s: error of receiving LBR_RSI indicator handle #%u", __FUNCTION__, _LastError);
    return(ENTRY_INTERNAL_ERROR);
  }
  }

24時間ごとにロボットは、最後の毎日の足でインジケーター値を分析し、許可されているトレードの方向性を確立する必要があります。 または LBR/RSI 値が中立領域にある場合は、トレードを無効にする必要があります。 インディケータバッファからのこの値の取得コードと、ロギング関数を備えた、裁量トレードインジケータからの呼び出しの可能性のあるエラーと特殊性に応じて、その分析をします:

static int si_Indicator_Handle = INVALID_HANDLE;
if(si_Indicator_Handle == INVALID_HANDLE) {
  // receiving indicator handle at first function call:
  si_Indicator_Handle = iCustom(_Symbol, PERIOD_D1, "LBR_RSI",
    TS_MomPin_Applied_Price,
    TS_MomPin_RSI_Period,
    TS_MomPin_RSI_Overbought,
    TS_MomPin_RSI_Oversold
  );
  
  if(si_Indicator_Handle == INVALID_HANDLE) {       // handle not received
    if(Log_Level > LOG_LEVEL_NONE) PrintFormat("%s: error of indicator handle receipt LBR_RSI #%u", __FUNCTION__, _LastError);
    return(ENTRY_INTERNAL_ERROR);
  }
}
// to find out the time of previous day daily bar:
datetime ta_Bar_Time[];
if(CopyTime(_Symbol, PERIOD_D1, fabs(t_Time), 2, ta_Bar_Time) < 2) {
  if(Log_Level > LOG_LEVEL_NONE) PrintFormat("%s: CopyTime: error #%u", __FUNCTION__, _LastError);
  return(ENTRY_INTERNAL_ERROR);
}
// previous day analysis, if this is the 1st call today:
static datetime st_Prev_Day = 0;
if(t_Time < 0) st_Prev_Day = 0;                     // only for call from indicator
if(st_Prev_Day < ta_Bar_Time[0]) {
  // zeroing of previous day parameters:
  se_Trade_Direction = ENTRY_UNKNOWN;
  d_Entry_Level = sd_Entry_Level = d_SL = sd_SL = d_TP = sd_TP = d_Range_High = sd_Range_High = d_Range_Low = sd_Range_Low = 0;
  
  // retrieve value LBR/RSI of previous day:
  double da_Indicator_Value[];
  if(1 > CopyBuffer(si_Indicator_Handle, 4, ta_Bar_Time[0], 1, da_Indicator_Value)) {
    if(Log_Level > LOG_LEVEL_NONE) PrintFormat("%s: CopyBuffer: error #%u", __FUNCTION__, _LastError);
    return(ENTRY_INTERNAL_ERROR);
  }
  
  // if anything is wrong with LBR/RSI value:
  if(da_Indicator_Value[0] > 100. || da_Indicator_Value[0] < 0.) {
    if(Log_Level > LOG_LEVEL_NONE) PrintFormat("%s: Indicator buffer value error (%f)", __FUNCTION__, da_Indicator_Value[0]);
    return(ENTRY_UNKNOWN);
  }
  
  st_Prev_Day = ta_Bar_Time[0];                     // attempt counted
  
  // remember trading direction for today:
  if(da_Indicator_Value[0] > TS_MomPin_RSI_Overbought) se_Trade_Direction = ENTRY_SELL;
  else se_Trade_Direction = da_Indicator_Value[0] > TS_MomPin_RSI_Oversold ? ENTRY_NONE : ENTRY_BUY;
  
  // to log:
  if(Log_Level == LOG_LEVEL_DEBUG) PrintFormat("%s: Trading direction for %s: %s. LBR/RSI: (%.2f)",
    __FUNCTION__,
    TimeToString(ta_Bar_Time[1], TIME_DATE),
    StringSubstr(EnumToString(se_Trade_Direction), 6),
    da_Indicator_Value[0]
  );
  }

許可されたトレード方向を明確にしました。 次のタスクは、エントリのレベルと損失の制限 (ストップロス) を決定します。 また、24時間ごとに1回の実行で十分です。(右の時間枠の日付の最初の足を閉じた後)。 しかし、マニュアルトレードインジケータ関数の特殊性に応じて、少しアルゴリズムが複雑になります。 インディケータがリアルタイムシグナルレベルを検出するだけでなく、履歴にマークを付けなければならないという事実によって引き起こされます。

// no signal search today
if(se_Trade_Direction == ENTRY_NONE) return(ENTRY_NONE);
// analysis of today’s first bar H1, unless this is already done:
if(sd_Entry_Level == 0.) {
  // to receive data of last 24 bars H1:
  MqlRates oa_H1_Rates[];
  int i_Price_Bars = CopyRates(_Symbol, PERIOD_H1, fabs(t_Time), 24, oa_H1_Rates);
  if(i_Price_Bars == WRONG_VALUE) {                      // handling of CopyRates function error
    if(Log_Level > LOG_LEVEL_NONE) PrintFormat("%s: CopyRates: error #%u", __FUNCTION__, _LastError);
    return(ENTRY_INTERNAL_ERROR);
  }
  
  // among 24 bars to find the 1st bar of today and to remember High, Low:
  int i_Bar = i_Price_Bars;
  while(i_Bar-- > 0) {
    if(oa_H1_Rates[i_Bar].time < ta_Bar_Time[1]) break;      // last bar H1 of previous day
    
    // borders of H1 1st bar range:
    sd_Range_High = d_Range_High = oa_H1_Rates[i_Bar].high;
    sd_Range_Low = d_Range_Low = oa_H1_Rates[i_Bar].low;
  }
  // H1 1st bar is not closed yet:
  if(i_Price_Bars - i_Bar < 3) return(ENTRY_UNKNOWN);
  
  // to calculate trading levels:
  
  // level of market entry:  
  d_Entry_Level = _Point * TS_MomPin_Entry_Offset;           // auxiliary calculations
  sd_Entry_Level = d_Entry_Level = se_Trade_Direction == ENTRY_SELL ? d_Range_Low - d_Entry_Level : d_Range_High + d_Entry_Level;
  //
sd_SL = d_SL = se_Trade_Direction = = ENTRY_BUY?
             
 d_Range_Low - d_SL : d_Range_High + d_SL;
  }

その後、検出されたトレーディングの方向を返すことによって、関数をストップするだけです。

return(se_Trade_Direction);

では、ポジションシグナル決済の条件の解析をプログラムしましょう。 3つの亜種の1つ (ストップロスレベルのトレーリングストップ) はすでに以前のバージョンのEAのコードで実現されています。 他のバリアントは、集計では、価格とエントリーの時間、計算のポジション方向を必要とします。 現在の時刻と選択した決済メソッドを関数fe_Get_Exit_Signalに渡します。

ENUM_EXIT_SIGNAL fe_Get_Exit_Signal(    // Detection of position closing signal
  double            d_Entry_Level,      // entry level
  datetime          t_Entry_Time,       // entry time
  ENUM_ENTRY_SIGNAL e_Trade_Direction,  // trade direction
  datetime          t_Current_Time,     // current time
  ENUM_EXIT_MODE    e_Exit_Mode         // exit mode
) {
  static MqlRates soa_Prev_D1_Rate[];   // data of D1 bar for previous day
  static int si_Price_Bars = 0;         // auxiliary counter
  if(t_Current_Time < 0) {              // to distinguish a call from indicator and a call from Expert Advisor
    t_Current_Time = -t_Current_Time;
    si_Price_Bars = 0;
  }
  double
    d_Curr_Entry_Level,
    d_SL, d_TP,
    d_Range_High,  d_Range_Low
  ;
  
  if(e_Trade_Direction < 1) {          // no positions, to zero everything
    si_Price_Bars = 0;
  }
  
  switch(e_Exit_Mode) {
    case CLOSE_ON_SL_TRAIL:            // only on trail
            return(EXIT_NONE);
                      
    case CLOSE_ON_NEW_1ST_CLOSE:       // on closing of next day 1st bar
            if((t_Current_Time - t_Current_Time % 86400)
              ==
              (t_Entry_Time - t_Current_Time % 86400)
            ) return(EXIT_NONE);       // day of position opening not finished yet
            
            if(fe_Get_Entry_Signal(t_Current_Time, d_Curr_Entry_Level, d_SL, d_TP, d_Range_High, d_Range_Low)
              < ENTRY_UNKNOWN
            ) {
              if(Log_Level > LOG_LEVEL_ERR) PrintFormat("%s: 1st bar of the following day is closed", __FUNCTION__);
              return(EXIT_ALL);
            }
            return(EXIT_NONE);         // not closed
            
    case CLOSE_ON_DAY_BREAK:           // upon break-through of extremum of the position opening day
            if((t_Current_Time - t_Current_Time % 86400)
              ==
              (t_Entry_Time - t_Current_Time % 86400)
            ) return(EXIT_NONE);       // position opening day not finished yet
            
            if(t_Current_Time % 86400 > 36000) return(EXIT_ALL); // time out
            
            if(si_Price_Bars < 1) {
              si_Price_Bars = CopyRates(_Symbol, PERIOD_D1, t_Current_Time, 2, soa_Prev_D1_Rate);
              if(si_Price_Bars == WRONG_VALUE) { // handling of CopyRates function error
                if(Log_Level > LOG_LEVEL_NONE) PrintFormat("%s: CopyRates: error #%u", __FUNCTION__, _LastError);
                return(EXIT_UNKNOWN);
              }
              
              if(e_Trade_Direction == ENTRY_BUY) {
                if(soa_Prev_D1_Rate[1].high < soa_Prev_D1_Rate[0].high) return(EXIT_NONE);        // did not break-through
                
                if(Log_Level > LOG_LEVEL_ERR) PrintFormat("%s: price broke-through yesterday’s High: %s > %s", __FUNCTION__, DoubleToString(soa_Prev_D1_Rate[1].high, _Digits), DoubleToString(soa_Prev_D1_Rate[0].high, _Digits));
                return(EXIT_BUY);
              } else {
                if(soa_Prev_D1_Rate[1].low > soa_Prev_D1_Rate[0].low) return(EXIT_NONE);          // did not break through
                
                if(Log_Level > LOG_LEVEL_ERR) PrintFormat("%s: price broke through yesterday’s Low: %s < %s", __FUNCTION__, DoubleToString(soa_Prev_D1_Rate[1].low, _Digits), DoubleToString(soa_Prev_D1_Rate[0].low, _Digits));
                return(EXIT_SELL);
              }
            }
            
            return(EXIT_NONE); // for each
  }
  
  return(EXIT_UNKNOWN);
  }

ここでは、' トレーリング決済 ' オプションが選択されている場合には ' cap ' を持っています。この関数は、解析なしでシグナルの不在を返します。 他の2つのオプションについては、イベントの発生 ' 朝になった ' と ' 昨日の極値をブレイクしている ' が識別されます。 この関数によって返される ENUM_EXIT_SIGNAL 型の値のバリアントは、エントリシグナル値 (ENUM_ENTRY_SIGNAL) の類似したリストと非常によく似ています。

enum ENUM_EXIT_SIGNAL {  // The list of exit signals
  EXIT_UNKNOWN,          // not identified
  EXIT_BUY,              // close buys
  EXIT_SELL,             // close sells
  EXIT_ALL,              // close all
  EXIT_NONE              // close nothing
  };

マニュアルトレードのインジケーター

上記のシグナルモジュールは、自動トレードにロボットに適用する必要があります。 以下のこのアプリケーションメソッドを詳しくみましょう。 まず、ターミナルのチャートで TS 特性をより明確に考慮するためのツールを作成してみましょう。 変更なしでシグナルモジュールを適用し、計算されたトレードレベルを表示するインジケーターになります。(-予約オーダーの配置レベルとストップロスレベル。) このインジケーターで収益性の高いトレードを閉じる簡略化されたバリアントによって提供されます。 言及したように、このモジュールでは、取引決済シグナルを検出するためのより複雑なアルゴリズムをプログラムしているが、ロボットの実装に取っておきましょう。

トレードレベルに加えて、このインジケーターは、適用されているレベルである理由を明確にするために、一日の最初の時間の足を埋めることになります。 このようなマーキングは、モメンタムのピンボール戦略のほとんどのルールの長所と短所を視覚的に評価するのに役立ちます-ストラテジーテスターのレポートから得ることができないものを発見します。 テスタースタティスティクスで追加されたビジュアル分析は、TS ルールをより効率的にすることが容易になります。

通常のマニュアルトレードのインジケーターを適用するために、リアルタイムのトレーダーの通知システムを追加してみましょう。 このような通知には、シグナルモジュールによって推奨されるエントリの方向が、予約オーダーおよび非常口 (ストップロス) の配置レベルと共同で含まれます。 通知配信には3つの方法があります。-テキストとサウンドシグナル、電子メールメッセージと携帯電話へのプッシュ通知と標準ポップアップウィンドウです。

すべての要件が表示されます。 したがって、プログラミングに進みましょう。 チャート上で計画したすべてのオブジェクトを描画するためには、インジケータが必要です。DRAW_FILLING タイプの1つのバッファ (一日の最初の時間の足範囲を埋めるために) と3つのバッファは、トレードレベルを表示します。 (エントリレベル、テイクプロフィット、ストップロス) 1つ (予約オーダーの配置レベル) は、トレードの方向に応じて色 (DRAW_COLOR_LINE のタイプ) を変更する関数を持っている必要があります。他には、単一色タイプ DRAW_LINE を持っているのに十分です:

#property indicator_chart_window
#property indicator_buffers  6
#property indicator_plots    4
#property indicator_label1  “1st hour of a day"
#property indicator_type1   DRAW_FILLING
#property indicator_color1  C'255,208,234', C'179,217,255'
#property indicator_width1  1
#property indicator_label2  “Entry level"
#property indicator_type2   DRAW_COLOR_LINE
#property indicator_style2  STYLE_DASHDOT
#property indicator_color2  clrDodgerBlue, clrDeepPink
#property indicator_width2  2
#property indicator_label3  "Stop Loss"
#property indicator_type3   DRAW_LINE
#property indicator_style3  STYLE_DASHDOTDOT
#property indicator_color3  clrCrimson
#property indicator_width3  1
#property indicator_label4  "Take Profit"
#property indicator_type4   DRAW_LINE
#property indicator_color4  clrGreen
  #property indicator_width4  1

ここで、リストを宣言し、その一部はインジケーターでは必要ありません (エキスパートアドバイザでのみ使用されます) が、シグナルモジュール関数に従事しています。 列挙型の変数は、ログの処理と、さまざまなポジションの決済メソッドに必要です。また、インジケータでこれを省略します。ここでは、プリセットレベル (テイクプロフィット) でシンプルな利確のモデルがあることを思い出してみましょう。 変数の宣言に続いて、外部モジュールを接続し、ユーザ設定をリストし、グローバル変数を宣言します。

enum ENUM_LOG_LEVEL {  // The list of logging levels
  LOG_LEVEL_NONE,      // logging disabled
  LOG_LEVEL_ERR,       // only info about errors
  LOG_LEVEL_INFO,      // errors + robot comments
  LOG_LEVEL_DEBUG      // all without exclusions
};
enum ENUM_ENTRY_SIGNAL {  // List of entry signals
  ENTRY_BUY,              // buy signal
  ENTRY_SELL,             // sell signal
  ENTRY_NONE,             // no signal
  ENTRY_UNKNOWN,          // status indefinite
  ENTRY_INTERNAL_ERROR    // internal function error
};
enum ENUM_EXIT_SIGNAL {  // The list of exit signals
  EXIT_UNKNOWN,          // not identified
  EXIT_BUY,              // close buys
  EXIT_SELL,             // close sells
  EXIT_ALL,              // close all
  EXIT_NONE              // close nothing
};
#include <Expert\Signal\Signal_Momentum_Pinball.mqh>     // signal module of ‘Momentum Pinball’ TS
input uint    TS_MomPin_Take_Profit = 10;                // Momentum Pinball: Take Profit (in points)
input bool    Show_1st_H1_Bar = true;                    // Show day 1st hourly bar range?
input bool    Alert_Popup = true;                        // Alert: Show pop-up window?
input bool    Alert_Email = false;                       // Alert: Send e-mail?
input string  Alert_Email_Subj = "";                     // Alert: Subjects of e-mail alert
input bool    Alert_Push = true;                         // Alert: Send push-notification?
input uint  Days_Limit = 7;                              // History layout depth (calendar days)
ENUM_LOG_LEVEL  Log_Level = LOG_LEVEL_DEBUG;             // Logging mode
double
  buff_1st_H1_Bar[], buff_1st_H1_Bar_Zero[],             // buffers for filling day 1st hourly bar range
  buff_Entry[], buff_Entry_Color[],                      // buffers of pending order line
  buff_SL[],                                             // buffer of StopLoss line
  buff_TP[],                                             // buffer of TakeProfit line
  gd_Entry_Offset = 0,                                   // TS_MomPin_Entry_Offset in symbol prices
  gd_Exit_Offset = 0                                     // TS_MomPin_Exit_Offset in symbol prices
  ;

初期化関数は顕著ではありません。-ここでは、バッファ用に宣言された配列にインジケーターバッファのインデックスを割り当てます。 同様に、ここではポイントからシンボル価格へのユーザー設定を変換してみましょう。少なくともほとんどのリソース消費量を減らすために行われるべきではなく、メインプログラムの実行中にそのような変換を何千もの時間を実行します。

int OnInit() {
  //IndicatorSetInteger(INDICATOR_DIGITS, _Digits);
IndicatorSetString(INDICATOR_SHORTNAME"モメンタムピンボール");

戻る(INIT_SUCCEEDED);
             

  }

このシリーズの前の記事のインジケーターコードでは、プログラム構造が作成されたので、ティック間の任意の型の情報を格納することが指定されています。 なぜこのような構造が必要とされるか疑問に思うかもしれません。 ここでは、何の変更もせずにそのままにします。 「ブラウニー」の完全な関数セットのこのインジケーターの版では、新しい足の初めのフラグだけ実行します。 しかし、もしより高度なマニュアルトレードのインジケーターを作りたい場合は、他の ' ブラウニー ' 関数を使用します。 go_Brownieの完全なコードは、この記事に添付されているインジケーターのソースコードファイル (TS_Momentum_Pinball mq5) のトレーリングストップにあります。 同様に、そこで、通知プッシュ関数コードf_Do_Alertを見ることができます。また、この一連の記事の前のインジケーターと比較しても変更がないので、詳細に検討する必要はありません。

標準の tick 受信イベントハンドラ (OnCalculate) の内部では、プログラムのメインループの開始前に、必要な変数を宣言する必要があります。 メインループの最初の呼び出しではない場合、再計算の範囲は、現在の実際の足だけで制限する必要があります。 初期化後の最初のループ呼び出しである場合は、残りのデータからのインジケーターバッファのクリアを配置する必要があります。 これが行われていない場合は、タイムフレームを切り替えるときに、実際の領域が塗りつぶされたままになります。 また、main 関数の呼び出しは、1つの足につき1回限定する必要があります。 go_Brownie構造体を使用すると便利です。

go_Brownie.f_Update(prev_calculated, prev_calculated);     // “feed” data to Brownie
datetime t_Time = TimeCurrent();                           // last known server time
int
  i_Period_Bar = 0,                                        // auxiliary counter
  i_Current_TF_Bar = 0                                     // loop beginning bar index
;
if(go_Brownie.b_First_Run) {                               // if this is the 1st launch
  i_Current_TF_Bar = rates_total — Bars(_Symbol, PERIOD_CURRENT, t_Time — t_Time % 8640086400 * Days_Limit, t_Time);
  // clearing buffer at re-initialization:
  ArrayInitialize(buff_1st_H1_Bar, 0); ArrayInitialize(buff_1st_H1_Bar_Zero, 0);
  ArrayInitialize(buff_Entry, 0); ArrayInitialize(buff_Entry_Color, 0);
  ArrayInitialize(buff_TP, 0);
  ArrayInitialize(buff_SL, 0);
} else if(!go_Brownie.b_Is_New_Bar) return(rates_total);   // waiting for bar closing
else {                                                     // new bar
  // minimum re-calculation depth - from day beginning:
  i_Current_TF_Bar = rates_total — Bars(_Symbol, PERIOD_CURRENT, t_Time — t_Time % 86400, t_Time);
}
ENUM_ENTRY_SIGNAL e_Entry_Signal = ENTRY_UNKNOWN;          // entry signal
double
  d_SL = WRONG_VALUE,                                      // SL level
  d_TP = WRONG_VALUE,                                      // TP level
  d_Entry_Level = WRONG_VALUE,                             // entry level
  d_Range_High = WRONG_VALUE, d_Range_Low = WRONG_VALUE    // pattern's 1 st bar range borders
;
datetime
  t_Curr_D1_Bar = 0,                                       // current D1 bar time (pattern's 2 nd bar)
  t_Last_D1_Bar = 0,                                       // time of the last bar D1, on which signal was available
  t_Entry_Bar = 0                                          // pending order placement bar time
;
// making sure that initial re-calculation bar index is within allowed frames:
  i_Current_TF_Bar = int(fmax(0, fmin(i_Current_TF_Bar, rates_total — 1)));

さて、メインのタスクループをプログラムしてみましょう。 各反復の開始時には、シグナルモジュールからデータを受信し、パフォーマンスを制御し、エラーが発生した場合は、次のループの繰り返し処理に遷移を配置する必要があります (シグナルが使用できない場合)。

while(++i_Current_TF_Bar < rates_total && !IsStopped()) {                // iterate over the current TF bars
  // receiving data from signal module:
  e_Entry_Signal = fe_Get_Entry_Signal(-Time[i_Current_TF_Bar], d_Entry_Level, d_SL, d_TP, d_Range_High, d_Range_Low);
  if(e_Entry_Signal == ENTRY_INTERNAL_ERROR) {                           // error of data copying from external indicator buffer
    // calculations and drawing should be repeated on the next tick:
    go_Brownie.f_Reset();
    return(rates_total);
  }
    if(e_Entry_Signal > 1) continue;                                       //この足にはアクティブなシグナルがありません

問題の足のシグナルが検出され、推定エントリレベルが返された場合は、まず利益レベルを計算します (利益を取る)。

t_Curr_D1_Bar = Time[i_Current_TF_Bar] - Time[i_Current_TF_Bar] % 86400; //足が属する日の開始

そして、このプロセスのトレードは、新しい日の最初の足である場合、ヒストリーの上にレイアウト、プロットされます:

t_Curr_D1_Bar = Time[i_Current_TF_Bar] - Time[i_Current_TF_Bar] % 86400;            // start of the day the bar belongs to
if(t_Last_D1_Bar < t_Curr_D1_Bar) {                                                 // this is 1st bar of the day, on which signal is available
    t_Entry_Bar = Time[i_Current_TF_Bar];                                             //トレード開始時刻を記憶

レベルの計算で使用される1日の最初の時間足の背景から開始:

// Background filling 1st hour bars:
if(Show_1st_H1_Bar) {
  i_Period_Bar = i_Current_TF_Bar;
  while(Time[--i_Period_Bar] >= t_Curr_D1_Bar && i_Period_Bar > 0)
    if(e_Entry_Signal == ENTRY_BUY) {                               // bullish pattern
      
      buff_1st_H1_Bar_Zero[i_Period_Bar] = d_Range_High;
      buff_1st_H1_Bar[i_Period_Bar] = d_Range_Low;
    } else {                                                        // bearish pattern
      buff_1st_H1_Bar[i_Period_Bar] = d_Range_High;
      buff_1st_H1_Bar_Zero[i_Period_Bar] = d_Range_Low;
    }
  }

次に、予約オーダーがオープンポジションになるまで予約オーダーの配置を描画します。 (つまり、価格がこのレベルに触れるとき)

// Entry line till crossed by a bar:
i_Period_Bar = i_Current_TF_Bar - 1;
if(e_Entry_Signal == ENTRY_BUY) {                               // bullish pattern
  while(++i_Period_Bar < rates_total) {
    if(Time[i_Period_Bar] > t_Curr_D1_Bar + 86399) {            // day end
      e_Entry_Signal = ENTRY_NONE;                              // pending order did not trigger
      break;
    }
    
    // extend line:
    buff_Entry[i_Period_Bar] = d_Entry_Level;
    buff_Entry_Color[i_Period_Bar] = 0;
    
    if(d_Entry_Level <= High[i_Period_Bar]) break;               // entry was on this bar
  }
} else {                                                         // bearish pattern
  while(++i_Period_Bar < rates_total) {
    if(Time[i_Period_Bar] > t_Curr_D1_Bar + 86399) {             // day end
      e_Entry_Signal = ENTRY_NONE;                               // pending order did not trigger
      break;
    }
    
    // extend line:
    buff_Entry[i_Period_Bar] = d_Entry_Level;
    buff_Entry_Color[i_Period_Bar] = 1;
    
    if(d_Entry_Level >= Low[i_Period_Bar]) break;               // entry was on this bar
  }
  }

価格が日の決済前に計算されたレベルを達成できなかった場合は、メインループの次の手順に進みます。

if(e_Entry_Signal == ENTRY_NONE) {                 // pending order did not trigger before the day end
  i_Current_TF_Bar = i_Period_Bar;                 // this day bars are not interesting to us any more
  continue;
  }

この日がまだ決済していないとオーダーの将来がまだ不定である場合は、メインプログラムのループを継続する意味はありません:

if(i_Period_Bar >= rates_total - 1) break;        //現在の (決済していない) 日が終了

2つのフィルターの後に、イベントの1つの可能なバリアントだけが、予約オーダーがトリガーされます。 予約オーダーの実行足を検索してみましょう。この足で始まる、ブレイクイーブンの利益と損失のレベルまでのいずれかの価格で、ポジションを閉じるまでクロスされます。 ポジションの開始と決済が単一の足で行われる場合、線はチャート上に表示されるように、1つの足によって過去に拡張する必要があります。

// order triggered, find position closing bar:
i_Period_Bar = fmin(i_Period_Bar, rates_total - 1);
buff_SL[i_Period_Bar] = d_SL;
while(++i_Period_Bar < rates_total) {
  if(TS_MomPin_Exit_Mode == CLOSE_ON_SL_TRAIL) {
    if(Time[i_Period_Bar] >= t_Curr_D1_Bar + 86400) break;        // this is the following day bar
    
    // Lines TP and SL until the bar crossing one of them:
    buff_SL[i_Period_Bar] = d_SL;
    buff_TP[i_Period_Bar] = d_TP;
    
    if((
      e_Entry_Signal == ENTRY_BUY && d_SL >= Low[i_Period_Bar]
      ) || (
      e_Entry_Signal == ENTRY_SELL && d_SL <= High[i_Period_Bar]
    )) {                                                          // SL exit
      if(buff_SL[int(fmax(0, i_Period_Bar - 1))] == 0.) {
   // beginning and end on a single bar, extend it by 1 bar to the past
        buff_SL[int(fmax(0, i_Period_Bar - 1))] = d_SL;
        buff_TP[int(fmax(0, i_Period_Bar - 1))] = d_TP;
      }
      break;
    }
    
    if((
      e_Entry_Signal == ENTRY_BUY && d_TP <= High[i_Period_Bar]
      ) || (
      e_Entry_Signal == ENTRY_SELL && d_SL >= Low[i_Period_Bar]
    )) {                                                         // TP exit
      if(buff_TP[int(fmax(0, i_Period_Bar - 1))] == 0.) {
        // beginning and end on a single bar, extend it by 1 bar to the past
        buff_SL[int(fmax(0, i_Period_Bar - 1))] = d_SL;
        buff_TP[int(fmax(0, i_Period_Bar - 1))] = d_TP;
      }
      break;
    }
  }
  }

プログラムのメインループでは、その日の残りの足のポジションを閉じた後に省略することができます。

i_Period_Bar = i_Current_TF_Bar;
t_Curr_D1_Bar = Time[i_Period_Bar] - Time[i_Period_Bar] % 86400;
while(
  ++i_Period_Bar < rates_total
  &&
  t_Curr_D1_Bar == Time[i_Period_Bar] - Time[i_Period_Bar] % 86400
  ) i_Current_TF_Bar = i_Period_Bar;

ここでは、メインループコードが終了します。 現在の足でシグナルが検出された場合は、通知を配置する必要があります。

i_Period_Bar = rates_total - 1;                                            // current bar
if(Alert_Popup + Alert_Email + Alert_Push == 0) return(rates_total);       // all disabled
if(t_Entry_Bar != Time[i_Period_Bar]) return(rates_total);                 // no signal on this bar
// message wording:
string s_Message = StringFormat("ТС Momentum Pinball: needed %s @ %s, SL: %s",
  e_Entry_Signal == ENTRY_BUY ? "BuyStop" : "SellStop",
  DoubleToString(d_Entry_Level, _Digits),
  DoubleToString(d_SL, _Digits)
);
// alert:
  f_Do_Alert(s_Message, Alert_Popup, false, Alert_Email, Alert_Push, Alert_Email_Subj);

完全なインジケータコードは、以下に添付された TS_Momentum_Pinball.mq5 ファイルに記載されています。

モメンタムピンボール TS をテストするためのEA

基本的なEAの関数は、Raschke Connorsの著書から別のトレード戦略のテストの準備をするときに多少拡張する必要があります。 前の記事の詳細な説明と共に、このバージョンの基礎として取られたソースコードがあります。 ここでは、実質的な変更を扱い、繰り返すことはありません。

最初の補足は、トレードロボットの前のバージョンで利用できなかった決済シグナルのリストです。 さらに、エントリシグナルのリストに ENTRY_INTERNAL_ERROR ステータスが追加されました。 番号付きリストは、上記の学習インジケータと同じ列挙型リストとは異なります。 ロボットコードでは、標準ライブラリトレーディングオペレーションクラスの接続文字列の前に配置します。 記事への添付ファイルの Street_Smarts_Bot_MomPin.mq5 では、文字列は 24...32 です。

2番目の変更は、シグナルモジュールがポジションを閉じるシグナルを提供するという事実と繋がっています。 このシグナルでも動作するように、対応するコードブロックを追加してみましょう。 以前のロボットバージョンでは、既存のポジションが新しいかどうかをチェックする条件演算子 ' if ' があります (文字列 139)。StopLoss の初期レベルを配置するために使用されます。 このバージョンでは、' if ' 演算子を別の ' else ' に追加して、シグナルモジュールを呼び出すためのコードブロックを対応させます。 結果を呼び出す場合は、EAがポジションを閉じる必要があります。

} else {                       // not new position
                               // conditions for position closing ready?
  ENUM_EXIT_SIGNAL e_Exit_Signal = fe_Get_Exit_Signal(d_Entry_Level, datetime(PositionGetInteger(POSITION_TIME)), e_Entry_Signal, TimeCurrent(), TS_MomPin_Exit_Mode);
  if((
      e_Exit_Signal == EXIT_BUY && e_Entry_Signal == ENTRY_BUY
    ) || (
      e_Exit_Signal == EXIT_SELL && e_Entry_Signal == ENTRY_SELL
    ) || e_Exit_Signal == EXIT_ALL
  ) {
                              // it must be closed
    CTrade o_Trade;
    o_Trade.LogLevel(LOG_LEVEL_ERRORS);
    o_Trade.PositionClose(_Symbol);
    return;
  }
  }

ロボットのソースコードでは、文字列は 171...186 です。

トレードレベルへの距離の充足を制御する関数のコードには変更がありますfb_Is_Acceptable_Distance (文字列 424...434)。


戦略バックテスト

L. Raschke と L. Connorsによる著書でよく知られるようになったトレーディングシステムを勉強するためのツール (インジケーターとEA) のカップルを作成しました。 EAバックテストの原則は、ツールの一つであるトレードロボットの生存率をチェックすることです。 したがって、パラメータを最適化していないテストをデフォルトの設定で実行されました。

すべての完全な結果は、添付のアーカイブに用意されています。ここでは、バランスバリエーションチャートのみが提供されます。 最新の相場条件下での TS パフォーマンスの評価 (パラメータ最適化なし) の2番目の (重要度) の目的の実例としてのみです。 著者は、前世紀後半からのチャートで戦略をテストしていました。

MetaQuotes デモサーバのクオートで2014の最初からEAのテストでバランスバリエーションチャート。 シンボル-EURJPY、タイムフレーム-H1:


シンボル-EURJPY、タイムフレーム-H1:


貴金属 (XAUUSD) の同じ期間と同じ時間枠のクオートで変更を設定せずにテストすると、バランスのバリエーションのチャートは次のようになります。


結論

「ストリート・ スマート」に記載されているモメンタムピンボールトレードシステムのルール: 高確率短期トレード戦略は、インジケーターとEAのコードに対して実行されます。 残念ながら、説明はそれほど詳細ではありませんし、ポジションの追跡と決済のルールに複数の種類があります。 したがって、トレードシステムの特殊性を詳細に研究したい人にとっては、ロボットの最適パラメータとアルゴリズムを選択するのは広大な作業になるでしょう。 作成されたコードでは、これを行うことができます。加えて、うまくいけば、ソースコードは、オブジェクト指向プログラミングの勉強に役立ちます。

MQL5.zip のソースコード、コンパイル済みファイル、およびライブラリは、それぞれのカタログに配置されます。 それぞれの詳細:

# ファイル名 種類  詳細 
 1  LBR_RSI.mq5  indicator ROC と RSI を統合したインジケーター。 開始日のトレード方向 (または無効状態) を決定するために使用
 2  TS_Momentum_Pinball.mq5  indicator この TS による裁量トレードのインジケーター。 計算されたエントリと決済のレベルを表示し、基本計算を実行する最初の時間範囲をハイライトします。
 3   Signal_Momentum_Pinball.mqh  library  関数、構造、およびユーザー設定のライブラリ。 インジケーターとEAによって使用されます。
 4  Street_Smarts_Bot_MomPin.mq5   EA この TS による自動トレードのEA