
モメンタムピンボールトレーディング戦略
イントロダクション
この記事では、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 の完全なルールは次の通り作り出されます。
- D1では、最後の日の LBR/RSI の値は、売られ過ぎの領域内にある必要があります。(-30 を下回る)
- 新しい日の最初の時間足を閉じた後、保留中の買いオーダーをその足の最大値よりも高く配置します。
- 予約オーダーのトリガ後、最初の時間足の下にストップロスを配置します。
- ポジションが損失でクローズされた場合、もう一度同じレベルで保留中の売りオーダーを配置します。
- 一日の終わりまでに、ポジションがプラスの場合、次の日に残します。 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 で、日足の終値でインジケーターの値が使用されます。 継続的に現在の値を変更しません。 したがって、このようなソリューションの実質的な障害はありません。
ここでは、計算アルゴリズムROCとRSIは、その結果のオシレーター曲線を描画します。 必要な値を検出するために、買われ過ぎと売られ過ぎの領域の塗りつぶしをさまざま色で追加します。 これを行うには、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; }
その後、検出されたトレーディングの方向を返すことによって、関数をストップするだけです。
では、ポジションシグナル決済の条件の解析をプログラムしましょう。 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 % 86400 — 86400 * 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; // 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; }
この日がまだ決済していないとオーダーの将来がまだ不定である場合は、メインプログラムのループを継続する意味はありません:
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 |
MetaQuotes Ltdによってロシア語から翻訳されました。
元の記事: https://www.mql5.com/ru/articles/2825





- 無料取引アプリ
- 8千を超えるシグナルをコピー
- 金融ニュースで金融マーケットを探索