MQL5での取引戦略の自動化(第43回):適応型線形回帰チャネル戦略
はじめに
前回の記事(第42回)では、MetaQuotes Language 5 (MQL5)において、カスタムのセッション開始時刻と分単位のオープニングレンジ期間を設定可能にし、選択した時間足で実際の高値・安値を自動判定し、ブレイクアウト方向にのみエントリーする、セッションベースのオープニングレンジブレイクアウト(ORB)システムを開発しました。第43回では、適応型線形回帰チャネル戦略を開発します。
このシステムは、ユーザー定義期間にわたって線形回帰ラインと標準偏差バンドを計算します。また、トレンド相場であることを保証するため、絶対傾きが最小閾値を超えた場合にのみ有効化されます。さらに、価格がチャネル幅の設定可能な割合を超えて乖離した場合にはチャネルを自動的に再生成し、チャネル内からの明確なブレイクアウト時にエントリーします。本記事では以下のトピックを扱います。
本記事の終わりまでに、塗りつぶされた偏差ゾーン、ブレイクアウト検出、中央ラインクロスによる決済、通常モードと逆モードを備えた、動的な回帰チャネルを維持する実用的なMQL5プログラムを手に入れることができます。それでは進めていきましょう。
適応型線形回帰チャネルフレームワークの理解
線形回帰チャネル戦略は、一定数のバーに対して最小二乗法による線形回帰直線を適用し、基礎となるトレンドの方向と強さを特定し、その後、回帰直線の上下に指定した標準偏差分だけ平行なバンドを追加して上限と下限の境界を形成します。これにより、トレンド相場における期待される価格範囲を表す動的な価格チャネルが生成されます。価格がチャネル内で振動している場合はトレンド継続を示し、境界へのタッチや軽微なブレイクアウトは押し目と戻りのエントリー機会を提供し、大きな乖離はトレンドの消耗や新しいデータに基づく回帰の再計算の必要性を示唆します。通常は、価格が下側チャネルの下で決済した場合に買い、上側チャネルの上で決済した場合に売りをおこないます。
本実装では、設定可能な期間にわたって回帰の傾き、切片、および標準偏差を計算し、方向性のある動きを確認するために絶対傾きが最小閾値を超えた場合にのみチャネルを作成します。チャネルは期間内の最も古いバーを起点として、その長さの一定割合だけ未来に延長されます。また、上半分(ピンク)と下半分(ライトグリーン)の2つの塗りつぶしゾーンと、上限、中間、下限の各境界に対する実線のトレンドラインで構成されます。新しいバーが作成されるたびに、価格がチャネル内に収まっている場合はチャネルを右方向に1バー延長し、価格がチャネル幅の定義された割合を超えて乖離した場合はチャネルを完全に再生成します。
取引は、チャネル内からの明確なブレイクアウト時にエントリーし、固定pipsのストップロス/テイクプロフィット、方向ごとの最大同時ポジション数、逆モードオプションを備えています。すべての同方向ポジションは、価格が中間ラインを横切った時点で即座に決済されます。上限、中間、下限チャネルのラベルは右端に追従し、すべてのエントリーには矢印が表示されます。逆モードでは売買ロジックが単純に入れ替わり、同一プログラムでトレンド継続ではなく平均回帰型のブレイクアウトを取引することが可能になります。これは、逆の戦略を取りたい場合に有用であると考えられます。要するに、以下は本システムの目的を視覚的に示したものです。

MQL5での実装
MQL5でプログラムを作成するには、まずMetaEditorを開き、ナビゲーターで[Experts]フォルダを探します。[新規]タブをクリックして指示に従い、ファイルを作成します。ファイルが作成されたら、コーディング環境で、まずプログラム全体で使用する入力パラメータとグローバル変数をいくつか宣言する必要があります。
//+------------------------------------------------------------------+ //| Linear Regression Channel EA.mq5 | //| Copyright 2025, Allan Munene Mutiiria. | //| https://t.me/Forex_Algo_Trader | //+------------------------------------------------------------------+ #property copyright "Copyright 2025, Allan Munene Mutiiria." #property link "https://t.me/Forex_Algo_Trader" #property version "1.00" #include <Trade\Trade.mqh> //+------------------------------------------------------------------+ //| Global Variables | //+------------------------------------------------------------------+ CTrade obj_Trade; //--- Trade object //+------------------------------------------------------------------+ //| Enums | //+------------------------------------------------------------------+ enum TradeMode { // Define trade mode enum Normal, // Normal Inverse // Inverse }; //+------------------------------------------------------------------+ //| Input Parameters | //+------------------------------------------------------------------+ input int RegressionPeriod = 100; // Period for regression calculation input double Deviations = 2.0; // Standard deviation multiplier for channel input double MinSlopeThreshold = 0.00001; // Min absolute slope to identify clear trend (low for detection) input int UpdateThresholdPercent = 30; // Update threshold in percent of channel width (e.g., 30 for 30%) input double ExtensionPercent = 50.0; // Initial extension percent of channel length to the right input TradeMode TradeDirection = Normal; // Trade Mode input double Lots = 0.01; // Lot size input int StopLossPips = 100; // Stop loss in pips input int TakeProfitPips = 100; // Take profit in pips input int MaxBuys = 2; // Maximum open buy positions input int MaxSells = 2; // Maximum open sell positions input int MagicNumber = 123456; // Magic number for positions input int Slippage = 3; // Slippage
実装は、#include <Trade\Trade.mqh>でtradeライブラリをインクルードすることから開始します。これは、注文の実行およびポジション管理をおこなうためのCTradeクラスを提供します。プログラム全体で注文送信やポジションの変更に使用するために、CTradeクラスからobj_Tradeオブジェクトをグローバルに宣言します。
次に、TradeMode列挙型を定義し、2つのオプションを用意します。Normalは標準的なチャネルへのプルバックトレード(上昇トレンドでは下側バンド下への押しで買い、下降トレンドでは上側バンド上への戻りで売り)を意味し、Inverseはロジックを反転させて平均回帰型のブレイクアウトトレードをおこなうためのものです。続いて、プログラムのプロパティから直接調整可能な入力パラメータを設定します。これには、線形回帰計算に使用するバー数を指定するRegressionPeriod、チャネル幅の標準偏差倍率(一般的には約95%の包含を想定して2.0が用いられます)であるDeviations、市場がトレンド状態にあると見なしてチャネルを作成するために必要な最小絶対傾きを示すMinSlopeThresholdが含まれます。そのほかのパラメータについても、理解しやすいようにコメントを追加しています。これらの入力により、プログラムを変更することなく、チャネルの挙動、リスク管理、取引スタイルを完全に制御することができます。ここで使用している値はデフォルトであり、適応のためにいつでも変更可能です。コンパイルすると、以下のウィンドウが表示されるはずです。

入力の設定が完了したので、プログラム全体で使用するいくつかのグローバル変数の定義に進むことができます。
//--- Global variables datetime lastBarTime = 0; //--- Last bar time double channelUpper, channelLower, channelMiddle; //--- Channel levels string channelName = "LRC_Channel"; //--- Channel name string upperLabelName = "LRC_Upper_Label"; //--- Upper label name string middleLabelName = "LRC_Middle_Label"; //--- Middle label name string lowerLabelName = "LRC_Lower_Label"; //--- Lower label name bool hasValidChannel = false; //--- Valid channel flag datetime fixedTimeOld = 0; //--- Fixed old time double slope_global = 0, intercept_global = 0, stdDev_global = 0; //--- Global slope, intercept, stdDev long period_sec = PeriodSeconds(_Period); //--- Period seconds int arrowCounter = 0; //--- Arrow counter double current_right_x = 0; //--- Current right x
続いて、適応型チャネルのロジックと可視化をサポートするために、追加のグローバル変数を宣言します。lastBarTimeは、直近で処理されたバーのタイムスタンプを追跡するために使用します。現在のチャネルの予測レベルは、ブレイクアウト判定時に素早く参照できるよう、channelUpper、channelLower、channelMiddleに格納します。定数文字列ではオブジェクト名を定義します。channelNameはマルチパートのチャネルオブジェクトのベースとしてLRC_Channelを使用します。upperLabelName、middleLabelName、lowerLabelNameはそれぞれ各境界を識別する移動テキストラベルの名前です。
ブール値のhasValidChannelフラグは現在トレンド回帰チャネルが有効かどうかを示します。fixedTimeOldは回帰期間内で最も古いバーの日時を保持し、x座標計算の一貫性を確保します。計算された回帰パラメータはslope_global、intercept_global、stdDev_globalとしてグローバルに保持します。これにより毎ティックごとに再計算することなく将来のプロジェクションに再利用できます。period_secはPeriodSeconds(_Period)によって1バーあたりの秒数を取得し、時間からx座標への正確な変換に使います。arrowCounterはエントリー用の矢印に一意な名前を付けるための整数カウンタです。current_right_xはチャネル右端のx座標を追跡し、バーごとにチャネルを拡張できるようにします。これによりチャネルオブジェクト全体を再生成せずに1バー単位で正確に延長でき、描画のちらつきを防げます。以上で準備が整ったので、チャネル描画用のヘルパー関数の定義から実装を始めます。
//+-----------------------------------------------------------------------------------+ //| Create Linear Regression Channel using channels for fill and trendlines for lines | //+-----------------------------------------------------------------------------------+ bool ChannelCreate(const long chart_ID, const string name, const int sub_window, datetime time1, datetime time2) { double price1_middle = intercept_global; //--- Price1 middle double price2_middle = intercept_global + slope_global * current_right_x; //--- Price2 middle double price1_upper = price1_middle + Deviations * stdDev_global; //--- Price1 upper double price2_upper = price2_middle + Deviations * stdDev_global; //--- Price2 upper double price1_lower = price1_middle - Deviations * stdDev_global; //--- Price1 lower double price2_lower = price2_middle - Deviations * stdDev_global; //--- Price2 lower // Upper-middle fill channel string um_name = name + "_um"; //--- UM name if (!ObjectCreate(chart_ID, um_name, OBJ_CHANNEL, sub_window, time1, price1_upper, time2, price2_upper, time1, price1_middle)) { //--- Create UM channel Print(__FUNCTION__, ": failed to create upper-middle channel! Error code = ", GetLastError()); //--- Log error return(false); //--- Return failure } ObjectSetInteger(chart_ID, um_name, OBJPROP_COLOR, clrPink); //--- Set color ObjectSetInteger(chart_ID, um_name, OBJPROP_STYLE, STYLE_SOLID); //--- Set style ObjectSetInteger(chart_ID, um_name, OBJPROP_WIDTH, 1); //--- Set width ObjectSetInteger(chart_ID, um_name, OBJPROP_FILL, true); //--- Set fill ObjectSetInteger(chart_ID, um_name, OBJPROP_BACK, true); //--- Set back ObjectSetInteger(chart_ID, um_name, OBJPROP_SELECTABLE, true); //--- Set selectable ObjectSetInteger(chart_ID, um_name, OBJPROP_SELECTED, false); //--- Set not selected ObjectSetInteger(chart_ID, um_name, OBJPROP_RAY_RIGHT, false); //--- Set no ray ObjectSetInteger(chart_ID, um_name, OBJPROP_HIDDEN, false); //--- Set not hidden ObjectSetInteger(chart_ID, um_name, OBJPROP_ZORDER, 0); //--- Set zorder // Middle-lower fill channel string ml_name = name + "_ml"; //--- ML name if (!ObjectCreate(chart_ID, ml_name, OBJ_CHANNEL, sub_window, time1, price1_middle, time2, price2_middle, time1, price1_lower)) { //--- Create ML channel Print(__FUNCTION__, ": failed to create middle-lower channel! Error code = ", GetLastError()); //--- Log error return(false); //--- Return failure } ObjectSetInteger(chart_ID, ml_name, OBJPROP_COLOR, clrLightGreen); //--- Set color ObjectSetInteger(chart_ID, ml_name, OBJPROP_STYLE, STYLE_SOLID); //--- Set style ObjectSetInteger(chart_ID, ml_name, OBJPROP_WIDTH, 1); //--- Set width ObjectSetInteger(chart_ID, ml_name, OBJPROP_FILL, true); //--- Set fill ObjectSetInteger(chart_ID, ml_name, OBJPROP_BACK, true); //--- Set back ObjectSetInteger(chart_ID, ml_name, OBJPROP_SELECTABLE, true); //--- Set selectable ObjectSetInteger(chart_ID, ml_name, OBJPROP_SELECTED, false); //--- Set not selected ObjectSetInteger(chart_ID, ml_name, OBJPROP_RAY_RIGHT, false); //--- Set no ray ObjectSetInteger(chart_ID, ml_name, OBJPROP_HIDDEN, false); //--- Set not hidden ObjectSetInteger(chart_ID, ml_name, OBJPROP_ZORDER, 0); //--- Set zorder // Upper trendline string upper_name = name + "_upper"; //--- Upper name if (!ObjectCreate(chart_ID, upper_name, OBJ_TREND, sub_window, time1, price1_upper, time2, price2_upper)) { //--- Create upper trend Print(__FUNCTION__, ": failed to create upper trendline! Error code = ", GetLastError()); //--- Log error return(false); //--- Return failure } ObjectSetInteger(chart_ID, upper_name, OBJPROP_COLOR, clrRed); //--- Set color ObjectSetInteger(chart_ID, upper_name, OBJPROP_STYLE, STYLE_SOLID); //--- Set style ObjectSetInteger(chart_ID, upper_name, OBJPROP_WIDTH, 1); //--- Set width ObjectSetInteger(chart_ID, upper_name, OBJPROP_BACK, false); //--- Set foreground ObjectSetInteger(chart_ID, upper_name, OBJPROP_SELECTABLE, true); //--- Set selectable ObjectSetInteger(chart_ID, upper_name, OBJPROP_SELECTED, false); //--- Set not selected ObjectSetInteger(chart_ID, upper_name, OBJPROP_RAY_RIGHT, false); //--- Set no ray ObjectSetInteger(chart_ID, upper_name, OBJPROP_HIDDEN, false); //--- Set not hidden ObjectSetInteger(chart_ID, upper_name, OBJPROP_ZORDER, 1); //--- Set zorder // Middle trendline string middle_name = name + "_middle"; //--- Middle name if (!ObjectCreate(chart_ID, middle_name, OBJ_TREND, sub_window, time1, price1_middle, time2, price2_middle)) { //--- Create middle trend Print(__FUNCTION__, ": failed to create middle trendline! Error code = ", GetLastError()); //--- Log error return(false); //--- Return failure } ObjectSetInteger(chart_ID, middle_name, OBJPROP_COLOR, clrBlue); //--- Set color ObjectSetInteger(chart_ID, middle_name, OBJPROP_STYLE, STYLE_SOLID); //--- Set style ObjectSetInteger(chart_ID, middle_name, OBJPROP_WIDTH, 1); //--- Set width ObjectSetInteger(chart_ID, middle_name, OBJPROP_BACK, false); //--- Set foreground ObjectSetInteger(chart_ID, middle_name, OBJPROP_SELECTABLE, true); //--- Set selectable ObjectSetInteger(chart_ID, middle_name, OBJPROP_SELECTED, false); //--- Set not selected ObjectSetInteger(chart_ID, middle_name, OBJPROP_RAY_RIGHT, false); //--- Set no ray ObjectSetInteger(chart_ID, middle_name, OBJPROP_HIDDEN, false); //--- Set not hidden ObjectSetInteger(chart_ID, middle_name, OBJPROP_ZORDER, 1); //--- Set zorder // Lower trendline string lower_name = name + "_lower"; //--- Lower name if (!ObjectCreate(chart_ID, lower_name, OBJ_TREND, sub_window, time1, price1_lower, time2, price2_lower)) { //--- Create lower trend Print(__FUNCTION__, ": failed to create lower trendline! Error code = ", GetLastError()); //--- Log error return(false); //--- Return failure } ObjectSetInteger(chart_ID, lower_name, OBJPROP_COLOR, clrGreen); //--- Set color ObjectSetInteger(chart_ID, lower_name, OBJPROP_STYLE, STYLE_SOLID); //--- Set style ObjectSetInteger(chart_ID, lower_name, OBJPROP_WIDTH, 1); //--- Set width ObjectSetInteger(chart_ID, lower_name, OBJPROP_BACK, false); //--- Set foreground ObjectSetInteger(chart_ID, lower_name, OBJPROP_SELECTABLE, true); //--- Set selectable ObjectSetInteger(chart_ID, lower_name, OBJPROP_SELECTED, false); //--- Set not selected ObjectSetInteger(chart_ID, lower_name, OBJPROP_RAY_RIGHT, false); //--- Set no ray ObjectSetInteger(chart_ID, lower_name, OBJPROP_HIDDEN, false); //--- Set not hidden ObjectSetInteger(chart_ID, lower_name, OBJPROP_ZORDER, 1); //--- Set zorder return(true); //--- Return success } //+------------------------------------------------------------------+ //| Delete the channel | //+------------------------------------------------------------------+ bool ChannelDelete(const long chart_ID, const string name) { bool success = true; //--- Init success if (!ObjectDelete(chart_ID, name + "_um")) { //--- Delete um Print(__FUNCTION__, ": failed to delete um! Error code = ", GetLastError()); //--- Log error success = false; //--- Set failure } if (!ObjectDelete(chart_ID, name + "_ml")) { //--- Delete ml Print(__FUNCTION__, ": failed to delete ml! Error code = ", GetLastError()); //--- Log error success = false; //--- Set failure } if (!ObjectDelete(chart_ID, name + "_upper")) { //--- Delete upper Print(__FUNCTION__, ": failed to delete upper! Error code = ", GetLastError()); //--- Log error success = false; //--- Set failure } if (!ObjectDelete(chart_ID, name + "_middle")) { //--- Delete middle Print(__FUNCTION__, ": failed to delete middle! Error code = ", GetLastError()); //--- Log error success = false; //--- Set failure } if (!ObjectDelete(chart_ID, name + "_lower")) { //--- Delete lower Print(__FUNCTION__, ": failed to delete lower! Error code = ", GetLastError()); //--- Log error success = false; //--- Set failure } return(success); //--- Return success }
ここではChannelCreate関数を実装し、単一の組み込みチャネルオブジェクトに依存せず、塗りつぶしチャネルとトレンドラインを組み合わせて視覚的な線形回帰チャネルを構築します。これによりカラーゾーンと明確な境界ラインを実現します。ここまでで分かる通り、挙動を明確に再現するために可視化にも同じくらい注意を払っています。まず保存済みのグローバル回帰値を使ってチャネル両端の正確な価格座標を計算します。左側の中央価格はintercept_global、右側の中央は「intercept_global + slope_global * current_right_x」とし、そこから「Deviations * stdDev_global」を加減することで上下の価格を両端それぞれで求めます。
塗りつぶしゾーンの作成では、最初に上部と中央のセクションを構築します。ベース名に_umを付加して一意の名前を作成し、ObjectCreateとOBJ_CHANNELを使って左上から右上、左中央の3点でチャネルを描画します。このチャネルはピンク色、実線、幅1、塗りつぶし有効、背景配置、選択可能だが未選択、右方向レイなし、非表示ではない、zオーダー0に設定します。同様の手順で中央と下部のセクションも_mlサフィックスで作成します。左中央から右中央、左下の3点で別のOBJ_CHANNELを作成し、こちらはライトグリーンで同じスタイル設定にすることで2トーンの塗り分けを実現します。
次に視認性を高めるため、3本の境界ラインを個別のトレンドラインとして描画します。「_upper」サフィックスで左上から右上のOBJ_TRENDを作成し、色は赤、実線、幅1、前面表示、選択可能、レイなし、zオーダー1に設定します。中央は_middleで青、下部は_lowerで緑として同様に作成します。塗りとラインを分離することで、価格がチャネル内にあっても色分けと境界の両方が明確に維持されます。いずれかのObjectCreateが失敗した場合はGetLastErrorでログ出力しfalseを返します。すべて成功した場合はtrueを返します。
あわせてChannelDelete関数も定義し、チャネルを再作成またはリセットする際に5つの構成要素をまとめて削除できるようにします。_um、_ml、_upper、_middle、_lowerの各オブジェクトをフルネームで削除し、失敗があればログを出しますが処理は継続します。最終的にすべて削除できればtrue、いずれかでエラーがあればfalseを返します。削除にはObjectDeleteを使用します。削除にはObjectDeleteを使用します。これらの関数により、価格の大きなブレイクアウトや新しいトレンド発生時にチャネル全体を再構築できるようになります。今後は矢印によるシグナル表示とチャネルラベルの更新も実装していきます。
//+------------------------------------------------------------------+ //| Draw arrow on chart for signal | //+------------------------------------------------------------------+ void DrawArrow(bool isBuy, datetime time, double price) { string name = "SignalArrow_" + IntegerToString(arrowCounter++); //--- Arrow name ObjectCreate(0, name, OBJ_ARROW, 0, time, price); //--- Create arrow ObjectSetInteger(0, name, OBJPROP_ARROWCODE, isBuy ? 233 : 234); //--- Set code ObjectSetInteger(0, name, OBJPROP_COLOR, isBuy ? clrGreen : clrRed); //--- Set color ObjectSetInteger(0, name, OBJPROP_WIDTH, 2); //--- Set width ObjectSetInteger(0, name, OBJPROP_ANCHOR, isBuy ? ANCHOR_TOP : ANCHOR_BOTTOM); //--- Set anchor ObjectSetInteger(0, name, OBJPROP_SELECTABLE, false); //--- Set not selectable ChartRedraw(0); //--- Redraw chart } //+------------------------------------------------------------------+ //| Update channel labels | //+------------------------------------------------------------------+ void UpdateLabels(datetime labelTime) { double label_x = (double)(labelTime - fixedTimeOld) / period_sec; //--- Calc label x double middlePrice = intercept_global + slope_global * label_x; //--- Calc middle double upperPrice = middlePrice + Deviations * stdDev_global; //--- Calc upper double lowerPrice = middlePrice - Deviations * stdDev_global; //--- Calc lower // Upper label if (ObjectFind(0, upperLabelName) < 0) { //--- Check no upper label ObjectCreate(0, upperLabelName, OBJ_TEXT, 0, labelTime, upperPrice); //--- Create upper label } else { //--- Exists ObjectMove(0, upperLabelName, 0, labelTime, upperPrice); //--- Move upper label } ObjectSetString(0, upperLabelName, OBJPROP_TEXT, "Upper Channel"); //--- Set text ObjectSetInteger(0, upperLabelName, OBJPROP_COLOR, clrRed); //--- Set color ObjectSetInteger(0, upperLabelName, OBJPROP_ANCHOR, ANCHOR_LEFT_UPPER); //--- Set anchor ObjectSetInteger(0, upperLabelName, OBJPROP_SELECTABLE, false); //--- Set not selectable // Middle label if (ObjectFind(0, middleLabelName) < 0) { //--- Check no middle label ObjectCreate(0, middleLabelName, OBJ_TEXT, 0, labelTime, middlePrice); //--- Create middle label } else { //--- Exists ObjectMove(0, middleLabelName, 0, labelTime, middlePrice); //--- Move middle label } ObjectSetString(0, middleLabelName, OBJPROP_TEXT, "Middle Channel"); //--- Set text ObjectSetInteger(0, middleLabelName, OBJPROP_COLOR, clrBlue); //--- Set color ObjectSetInteger(0, middleLabelName, OBJPROP_ANCHOR, ANCHOR_LEFT); //--- Set anchor ObjectSetInteger(0, middleLabelName, OBJPROP_SELECTABLE, false); //--- Set not selectable // Lower label if (ObjectFind(0, lowerLabelName) < 0) { //--- Check no lower label ObjectCreate(0, lowerLabelName, OBJ_TEXT, 0, labelTime, lowerPrice); //--- Create lower label } else { //--- Exists ObjectMove(0, lowerLabelName, 0, labelTime, lowerPrice); //--- Move lower label } ObjectSetString(0, lowerLabelName, OBJPROP_TEXT, "Lower Channel"); //--- Set text ObjectSetInteger(0, lowerLabelName, OBJPROP_COLOR, clrGreen); //--- Set color ObjectSetInteger(0, lowerLabelName, OBJPROP_ANCHOR, ANCHOR_LEFT_LOWER); //--- Set anchor ObjectSetInteger(0, lowerLabelName, OBJPROP_SELECTABLE, false); //--- Set not selectable ChartRedraw(0); //--- Redraw chart }
次にDrawArrow関数を実装し、トレードシグナルが発生した際にチャート上へ明確な視覚マーカーを配置します。オブジェクト名はSignalArrow_とインクリメントされるarrowCounterを組み合わせて一意に生成し、指定された時間と価格にOBJ_ARROW を作成します。買いシグナルにはwingdingsの233上向き矢印、売りシグナルには234下向き矢印を使用します。買いは緑、売りは赤で色付けし、視認性のため幅は2に設定します。矢印はローソク足から正しく指すように、買いは上側アンカー、売りは下側アンカーに設定します。誤操作を防ぐため選択不可にし、最後にチャートを即時再描画します。MQL5にはwingdingsコードが用意されているため、必要に応じて好みのものに変更できます。

続いてUpdateLabels関数を実装し、チャネル右端に追従する説明用テキストラベルを常に正確な位置へ表示します。チャネルの延長や再生成に合わせて滑らかに移動させます。まずlabelTimeからfixedTimeOldを引いてperiod_secで割ることで対応するx座標を求め、そのx値とグローバル回帰パラメータを使って、その時点の中央、上部、下部の価格を正確に計算します。
3つのラベル上部中央下部それぞれについて、まずObjectFindで既存かどうかを確認します。存在しない場合はlabelTimeと計算済み価格にOBJ_TEXTを新規作成し、存在する場合はObjectMoveで新しい位置へ移動します。テキストはUpper Channel、Middle Channel、Lower Channelを設定し、色はそれぞれ赤青緑に対応させます。アンカーは左上、左中央、左下に設定し、ラインと重ならず横に整列するようにします。ラベルは選択不可に設定し、最後にチャートを再描画します。これでチャネルの識別と描画にこれらの関数を使用できるようになります。さらにコードをモジュール化するため、判定ロジックも関数として実装していきます。
//+------------------------------------------------------------------+ //| Create channel if clear trend | //+------------------------------------------------------------------+ void CreateChannelIfTrend() { if (Bars(_Symbol, _Period) < RegressionPeriod + 1) return; //--- Return if insufficient bars double closeArray[]; //--- Close array ArraySetAsSeries(closeArray, true); //--- Set as series if (CopyClose(_Symbol, _Period, 1, RegressionPeriod, closeArray) != RegressionPeriod) return; //--- Copy close or return double sumX = 0, sumY = 0, sumXY = 0, sumX2 = 0; //--- Init sums int n = RegressionPeriod; //--- Set n for (int i = 0; i < n; i++) { //--- Iterate double x = (double)(n - 1 - i); //--- Calc x double y = closeArray[i]; //--- Get y sumX += x; //--- Add x sumY += y; //--- Add y sumXY += x * y; //--- Add xy sumX2 += x * x; //--- Add x2 } double slope = (n * sumXY - sumX * sumY) / (n * sumX2 - sumX * sumX); //--- Calc slope // Check for clear trend if (MathAbs(slope) < MinSlopeThreshold) { //--- Check no trend hasValidChannel = false; //--- Reset channel ChannelDelete(0, channelName); //--- Delete channel ObjectDelete(0, upperLabelName); //--- Delete upper label ObjectDelete(0, middleLabelName); //--- Delete middle label ObjectDelete(0, lowerLabelName); //--- Delete lower label return; //--- Return } double intercept = (sumY - slope * sumX) / n; //--- Calc intercept // Calculate stdDev double sumRes2 = 0; //--- Init res2 sum for (int i = 0; i < n; i++) { //--- Iterate double x = (double)(n - 1 - i); //--- Calc x double predicted = intercept + slope * x; //--- Calc predicted double res = closeArray[i] - predicted; //--- Calc res sumRes2 += res * res; //--- Add res2 } double variance = sumRes2 / (n - 2); //--- Calc variance double stdDev = MathSqrt(variance); //--- Calc stdDev // Store for projection slope_global = slope; //--- Set global slope intercept_global = intercept; //--- Set global intercept stdDev_global = stdDev; //--- Set global stdDev // Fixed anchors with initial extension (future time2) fixedTimeOld = iTime(_Symbol, _Period, RegressionPeriod); //--- Set old time datetime fixedTimeNew = iTime(_Symbol, _Period, 1); //--- Set new time long channel_sec = fixedTimeNew - fixedTimeOld; //--- Calc channel sec long extension_sec = (long)(channel_sec * (ExtensionPercent / 100.0)); //--- Calc extension datetime time_extended = fixedTimeNew + (datetime)extension_sec; //--- Calc extended time current_right_x = (double)(time_extended - fixedTimeOld) / period_sec; //--- Calc right x // Delete old and create new ChannelDelete(0, channelName); //--- Delete channel if (!ChannelCreate(0, channelName, 0, fixedTimeOld, time_extended)) return; //--- Create channel or return hasValidChannel = true; //--- Set valid channel double channelWidth = 2 * Deviations * stdDev_global; //--- Calc width double channelWidthPoints = channelWidth / _Point; //--- Calc width points Print("Channel created: slope=" + DoubleToString(slope, 8) + ", range=" + DoubleToString(channelWidth, _Digits) + " (" + DoubleToString(channelWidthPoints, 0) + " points), times: " + TimeToString(fixedTimeOld) + " to " + TimeToString(time_extended)); //--- Log channel UpdateLabels(time_extended); //--- Update labels ChartRedraw(0); //--- Redraw chart }
ここではCreateChannelIfTrend関数を実装し、線形回帰の完全な計算をおこなうと同時にチャネルを構築または更新するかを判断します。これにより明確なトレンド時のみチャネルを表示します。まず十分なヒストリカルバーが存在するかを確認します。少なくとも「RegressionPeriod + 1」が必要で、満たさない場合はエラー回避のため即座にreturnします。次に終値用の動的配列を宣言し、ArraySetAsSeriesでシリーズとして設定してインデックス0が最新バーになるようにします。その後CopyCloseでシフト1からRegressionPeriod本分の終値を取得し、失敗した場合は早期returnします。
最小二乗法のための合計変数を初期化し、期間内でループ処理をおこないます。各バーiについてxはn-1-iとし、最も古いバーが「x = n-1」、最新が「x = 0」となるようにします。yは終値とし、sumX、sumY、sumXY、sumX2を順に加算します。これはトレンド計算に必須です。その後標準的な式で傾きを計算します。傾きの絶対値がMinSlopeThreshold未満の場合はレンジと判断し、hasValidChannelをfalseに設定してChannelDeleteとObjectDeleteで既存のチャネルとラベルを削除してreturnします。これによりレンジ相場で意味のない水平チャネルが描画されるのを防ぎます。ただし、レンジ相場で取引したい場合はこのチェックは省略可能です。
傾きが十分な場合はinterceptを「sumY - slope × sumX」をnで割って求めます。続いて標準偏差を計算します。2回目のループで各xに対する予測価格を求め、実際の終値との差の二乗を計算して合計し、n-2で割って分散を求め、その平方根を取って標準偏差とします。傾き、切片、偏差は後のプロジェクション用にグローバル変数へ保存します。チャネルの左端は最も古いバーの時間をiTimeのシフトRegressionPeriodで取得してfixedTimeOldに設定します。右側の基準は直近確定バーシフト1とし、チャネルの時間長を秒で計算します。その後ExtensionPercent分だけ右へ延長し、その延長先の正確なx座標をcurrent_right_xとして計算します。
既存チャネルはChannelDeleteで削除し、新しいマルチパートチャネルをfixedTimeOldから延長時間までChannelCreateで構築します。成功した場合はhasValidChannelをtrueに設定し、傾き、価格幅とポイント幅、時間範囲などの情報をログ出力します。続いてUpdateLabelsで右端へラベルを更新し、チャートを再描画します。これで十分なデータがある場合、OnInitイベントハンドラからこの関数を呼び出して最初のチャネルを描画できます。
//+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { obj_Trade.SetExpertMagicNumber(MagicNumber); //--- Set magic number CreateChannelIfTrend(); //--- Initial try return(INIT_SUCCEEDED); //--- Return success } //+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { ChannelDelete(0, channelName); //--- Delete channel ObjectDelete(0, upperLabelName); //--- Delete upper label ObjectDelete(0, middleLabelName); //--- Delete middle label ObjectDelete(0, lowerLabelName); //--- Delete lower label }
OnInitイベントハンドラでは、まずobj_TradeオブジェクトにSetExpertMagicNumberでMagicNumberを設定します。これにより、すべての発注にこの識別子が付与され、適切な管理が可能になります。次にCreateChannelIfTrendを呼び出し、起動時点で利用可能な最新データを使って回帰チャネルの構築を試みます。これにより、市場に十分なトレンドがある場合は初期チャネルが生成されます。最後にINIT_SUCCEEDEDIを返して初期化完了を示します。
OnDeinit関数ではクリーンアップをおこないます。ChannelDeleteを呼び出して回帰チャネルの5つの構成要素(塗りつぶし2つとトレンドライン3本)を削除し、その後upperLabelName、middleLabelName、lowerLabelNameを使ってObjectDeleteで3つの移動ラベルを個別に削除します。これによりプログラム停止後に不要なオブジェクトがチャートに残ることを防ぎます。コンパイルすると、次の結果が得られます。

画像から分かるように、トレンドが存在し、かつ計算に十分なバーがある場合にチャネルが初期化されます。ここからはティック関数内でバーの増加に応じて管理および更新をおこないます。ただし不要な負荷を避けるため、この処理は新しいバーのときのみ実行する必要があります。そのために以下の関数を定義します。
//+------------------------------------------------------------------+ //| Check if new bar has opened | //+------------------------------------------------------------------+ bool IsNewBar() { datetime currentBarTime = iTime(_Symbol, _Period, 0); //--- Get current time if (currentBarTime != lastBarTime) { //--- Check new lastBarTime = currentBarTime; //--- Update last return true; //--- Return true } return false; //--- Return false }
IsNewBar関数では、現在の時間足で新しいバーが完全に形成されたかどうかを判定します。iTimeを使ってシフト0のバー開始時刻を取得しcurrentBarTimeに格納します。この値をグローバル変数lastBarTimeと比較し、異なっていれば新しいバーと判断します。その場合lastBarTimeを更新してtrueを返し、処理継続のシグナルとします。一致している場合はfalseを返し、そのティックでは重い処理をスキップします。一致している場合はfalseを返し、そのティックでは重い処理をスキップします。これで、新しいバーが生成されたときのOnTickイベントハンドラでこれを使用できるようになりました。
//+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { // Check for new bar if (!IsNewBar()) return; //--- Return if not new bar // Scan every bar if no channel if (!hasValidChannel) { //--- Check no channel CreateChannelIfTrend(); //--- Create if trend if (!hasValidChannel) return; //--- Return if no channel } // Project values manually for completed bar (shift 1) datetime previousTime = iTime(_Symbol, _Period, 1); //--- Get previous time int x = Bars(_Symbol, _Period, fixedTimeOld, previousTime) - 1; //--- Calc x channelMiddle = intercept_global + slope_global * x; //--- Calc middle channelUpper = channelMiddle + Deviations * stdDev_global; //--- Calc upper channelLower = channelMiddle - Deviations * stdDev_global; //--- Calc lower // Project for shift 2 datetime time2 = iTime(_Symbol, _Period, 2); //--- Get time 2 if (time2 <= fixedTimeOld) return; //--- Return if invalid int x2 = Bars(_Symbol, _Period, fixedTimeOld, time2) - 1; //--- Calc x2 double middle2 = intercept_global + slope_global * x2; //--- Calc middle2 double upper2 = middle2 + Deviations * stdDev_global; //--- Calc upper2 double lower2 = middle2 - Deviations * stdDev_global; //--- Calc lower2 // Get closes double closePrevious = iClose(_Symbol, _Period, 1); //--- Get close previous double close2 = iClose(_Symbol, _Period, 2); //--- Get close 2 if (closePrevious == 0 || close2 == 0) return; //--- Return if invalid // Check if beyond end datetime current_time2 = (datetime)ObjectGetInteger(0, channelName + "_middle", OBJPROP_TIME, 1); //--- Get current time2 if (previousTime > current_time2) { //--- Check beyond end Print("Bars beyond channel end: previousTime=" + TimeToString(previousTime) + ", current_time2=" + TimeToString(current_time2) + " - recreating channel"); //--- Log recreate CreateChannelIfTrend(); //--- Recreate channel return; //--- Return } }
OnTick関数では、まずIsNewBarを呼び出して新しいバーが形成されたかを確認します。新しいバーでなければ即座にreturnし、同一ローソク足内でロジックが複数回実行されるのを防ぎます。これにより処理は常に確定バーに同期されます。hasValidChannelがfalseの場合(現在有効な回帰チャネルが存在しない場合)はCreateChannelIfTrendを呼び出して最新データを再評価し、傾きが最小閾値を満たしていれば新しいチャネルを構築します。それでもhasValidChannelがfalseのままであればレンジ相場と判断し、そのバーでは処理を終了します。
有効なチャネルが存在する場合、直近確定バー(シフト1)に対して回帰値を手動でプロジェクションします。iTimeでpreviousTimeを取得し、fixedTimeOldからpreviousTimeまでのBars数から1を引いてx座標を算出し、そのxを使って中央と上下のチャネル値を計算します。これにより前バーの終値時点における正確なチャネル位置が得られます。同様にシフト2のバーについてもプロジェクションをおこないます。これらの値を使うことで、前バーがその一つ前のバーの時点でチャネル内からブレイクアウトしたかどうかを判定できます。次に実際の終値を取得します。closePreviousはシフト1、close2はシフト2から取得し、どちらかが0であれば無効として早期returnします。
最後に安全チェックをおこないます。ObjectGetInteger を使って「channelName + _middle」の右端の時間OBJPROP_TIMEのインデックス1を取得しcurrent_time2に格納します。previousTimeがこの値を超えている場合、価格がチャネルの描画終端をすでに超えていることを意味します。その場合はログを出力し、CreateChannelIfTrendを呼び出して即座にチャネルを再構築し、その後returnします。コンパイルすると、次のようになります。

ここまでは順調です。トレンドが確認されたときに新しいバーでチャネルが更新されることが分かります。次はブレイクアウトを検出し、チャネルを延長するか再描画するかのロジックに進みます。そのためのコードの断片を以下に実装します。
// Check if breakout (deviation > threshold * width) for recreation double channelWidth = channelUpper - channelLower; //--- Calc width double channelWidthPoints = channelWidth / _Point; //--- Calc width points double updateThreshold = UpdateThresholdPercent / 100.0; //--- Calc threshold double deviation = MathMax(closePrevious - channelUpper, channelLower - closePrevious); //--- Calc deviation double deviationPercent = (deviation / channelWidth) * 100; //--- Calc percent if (deviation > updateThreshold * channelWidth) { //--- Check breakout Print("Breakout detected - deviation: " + DoubleToString(deviation, _Digits) + " (" + DoubleToString(deviationPercent, 2) + "%), threshold: " + DoubleToString(updateThreshold * channelWidth, _Digits) + " (" + IntegerToString(UpdateThresholdPercent) + "%) - recreating channel"); //--- Log breakout CreateChannelIfTrend(); //--- Recreate channel return; //--- Return } else { //--- No breakout // Extend right by one bar if within datetime new_time2 = current_time2 + (datetime)period_sec; //--- Calc new time2 current_right_x += 1.0; //--- Increment x double new_price_middle = intercept_global + slope_global * current_right_x; //--- Calc new middle double new_price_upper = new_price_middle + Deviations * stdDev_global; //--- Calc new upper double new_price_lower = new_price_middle - Deviations * stdDev_global; //--- Calc new lower // Move channels ObjectMove(0, channelName + "_um", 1, new_time2, new_price_upper); //--- Move um ObjectMove(0, channelName + "_ml", 1, new_time2, new_price_middle); //--- Move ml // Move trendlines ObjectMove(0, channelName + "_upper", 1, new_time2, new_price_upper); //--- Move upper ObjectMove(0, channelName + "_middle", 1, new_time2, new_price_middle); //--- Move middle ObjectMove(0, channelName + "_lower", 1, new_time2, new_price_lower); //--- Move lower UpdateLabels(new_time2); //--- Update labels ChartRedraw(0); //--- Redraw chart }
ここではチャネルの適応的な挙動を処理します。つまり、価格の動きに応じてバーごとに延長するか、あるいは現在の回帰から大きく乖離した場合に完全に再構築するかを判断します。まずチャネル全体の幅をchannelUpperとchannelLowerの差として計算し、参照用にポイントへ変換します。次にUpdateThresholdPercentを100で割って更新判定の閾値を求めます。その後、直近の終値が最も近い境界からどれだけ乖離しているかをMathMaxで求め、この乖離をチャネル全体幅に対するパーセンテージとして表現します。この乖離率が閾値(たとえばデフォルトで幅の30パーセント)を超えた場合、有意なブレイクアウトまたは現在の回帰の限界とみなします。この場合、価格での乖離量とパーセンテージ、および閾値をログ出力し、CreateChannelIfTrendを即座に呼び出して最新データで回帰を再計算し、新しいトレンドにより適合するチャネルを再構築します。その後はチャネルが再生成されているため早期returnします。この再構築ロジックは任意に変更可能であり、ここでは一例として採用しています。
一方で価格がチャネル内に収まっている場合(乖離が閾値以下の場合)は、連続性を保つため既存チャネルを1バー分右へ延長します。「current_time2 + period_sec」で右端時間を1バー進めてnew_time2とし、current_right_xを1.0増加させます。次に保存済みのグローバル値を使って新しい中央、上、下の価格をプロジェクションします。その後すべての構成要素について右側アンカーポイントインデックス1を新しい時間と価格へ移動します。対象は_umの上部中央フィル、_mlの中央下部フィル、および_upper、_middle、_lowerの3本のトレンドラインです。最後にUpdateLabelsを新しい右端時間で呼び出してテキストラベルを再配置し、チャートを再描画します。この延長処理により、トレンド中は不要な再計算をおこなわずにチャネルが滑らかに価格へ追従します。一方で価格が想定レンジを大きく外れた場合には、新しい回帰へ即座に切り替わります。ブレイクアウトが発生した場合は、ログ出力によってその状況を確認できます。

チャネルを完全に管理できるようになったので、次は価格がチャネル自体からブレイクアウトしたときにシグナルを生成できます。加えて、中央ラインをクロスした場合には既存ポジションを決済する処理もおこないます。この中央ラインクロスによる決済は任意の管理ロジックであり、ポジションをストップロスやテイクプロフィットでのみ完全に決済したい場合は省略可能です。
// Count existing positions int buyCount = 0, sellCount = 0; //--- Init counts int total = PositionsTotal(); //--- Get total positions for (int i = total - 1; i >= 0; i--) { //--- Iterate reverse ulong ticket = PositionGetTicket(i); //--- Get ticket if (PositionGetString(POSITION_SYMBOL) == _Symbol && PositionGetInteger(POSITION_MAGIC) == MagicNumber) { //--- Check symbol magic if (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY) { //--- Check buy buyCount++; //--- Increment buy } else if (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL) { //--- Check sell sellCount++; //--- Increment sell } } } // Close logic: Close all buys if crossed above middle, all sells if below if (closePrevious > channelMiddle) { //--- Check close above middle for (int i = PositionsTotal() - 1; i >= 0; i--) { //--- Iterate reverse ulong ticket = PositionGetTicket(i); //--- Get ticket if (PositionGetString(POSITION_SYMBOL) == _Symbol && PositionGetInteger(POSITION_MAGIC) == MagicNumber && PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY) { //--- Check buy obj_Trade.PositionClose(ticket, Slippage); //--- Close position Print("Closing Buy: Price " + DoubleToString(closePrevious, _Digits) + " crossed above middle channel " + DoubleToString(channelMiddle, _Digits)); //--- Log close } } } if (closePrevious < channelMiddle) { //--- Check close below middle for (int i = PositionsTotal() - 1; i >= 0; i--) { //--- Iterate reverse ulong ticket = PositionGetTicket(i); //--- Get ticket if (PositionGetString(POSITION_SYMBOL) == _Symbol && PositionGetInteger(POSITION_MAGIC) == MagicNumber && PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL) { //--- Check sell obj_Trade.PositionClose(ticket, Slippage); //--- Close position Print("Closing Sell: Price " + DoubleToString(closePrevious, _Digits) + " crossed below middle channel " + DoubleToString(channelMiddle, _Digits)); //--- Log close } } } // Open on clear breakout if room (with inverse option) bool buySignal = (close2 >= lower2) && (closePrevious < channelLower); //--- Buy signal bool sellSignal = (close2 <= upper2) && (closePrevious > channelUpper); //--- Sell signal if (TradeDirection == Inverse) { //--- Check inverse bool temp = buySignal; //--- Temp buy buySignal = sellSignal; //--- Swap buy sellSignal = temp; //--- Swap sell } double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK); //--- Get ask double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID); //--- Get bid if (buySignal && buyCount < MaxBuys) { //--- Check buy signal // Buy double sl = (StopLossPips == 0) ? 0 : NormalizeDouble(ask - StopLossPips * _Point, _Digits); //--- Calc SL double tp = (TakeProfitPips == 0) ? 0 : NormalizeDouble(ask + TakeProfitPips * _Point, _Digits); //--- Calc TP if (obj_Trade.Buy(Lots, _Symbol, 0, sl, tp, "LRC Buy")) { //--- Open buy Print("Buy signal: Price " + DoubleToString(closePrevious, _Digits) + " broke below lower channel from inside"); //--- Log signal DrawArrow(true, previousTime, closePrevious); //--- Draw arrow } else { //--- Failed Print("Buy order failed: " + obj_Trade.ResultRetcodeDescription()); //--- Log failure } } if (sellSignal && sellCount < MaxSells) { //--- Check sell signal // Sell double sl = (StopLossPips == 0) ? 0 : NormalizeDouble(bid + StopLossPips * _Point, _Digits); //--- Calc SL double tp = (TakeProfitPips == 0) ? 0 : NormalizeDouble(bid - TakeProfitPips * _Point, _Digits); //--- Calc TP if (obj_Trade.Sell(Lots, _Symbol, 0, sl, tp, "LRC Sell")) { //--- Open sell Print("Sell signal: Price " + DoubleToString(closePrevious, _Digits) + " broke above upper channel from inside"); //--- Log signal DrawArrow(false, previousTime, closePrevious); //--- Draw arrow } else { //--- Failed Print("Sell order failed: " + obj_Trade.ResultRetcodeDescription()); //--- Log failure } }
次に、新しいバーごとのポジション管理と取引実行に移ります。まず、このプログラムに属する買いおよび売りポジションが現在いくつ開かれているかをカウントします。PositionsTotalを逆順にループし、PositionGetTicketで各チケットを取得し、銘柄とMagicNumberが一致するかを確認します。POSITION_TYPE_BUYの場合はbuyCountを、POSITION_TYPE_SELLの場合はsellCountをインクリメントすることで、新規エントリーをおこなう前の正確な制限を把握できます。次に、ミドルラインクロスエグジットロジックを実装します。前のバーの終値("closePrevio次に、ミドルラインクロスによる決済ロジックを実装します。前バーの終値(closePrevious)がchannelMiddleより上の場合、すべての買いポジションを直ちに決済します。再度ループしてタイプを確認し、obj_Trade.PositionCloseにチケットと許容Slippageを指定して決済し、その理由をログに記録します。同様に、前バーの終値がchannelMiddleより下の場合は、すべての売りポジションを同じ手順で決済し、ログに残します。この方法により、価格が回帰線自体を越えた瞬間に利益を確保するか、損失を切ることが可能です。偏差がどれだけ離れているかは関係ありません。
次に、チャネル内からのクリーンなブレイクアウトに基づくエントリーシグナルを定義します。買いシグナルは、2本前のバーの終値(close2)が当時の下限バンド(lower2)以上であり、かつ前バーの終値が現在の下限バンド(channelLower)より下回った場合に発生します。これは価格がチャネルから明確に下方に抜けたことを意味します。売りシグナルはその逆条件で発生します。TradeDirectionがInverseの場合は、2つのシグナルを入れ替えるだけで、トレンド継続のプルバック戦略から平均回帰のフェードトレードに即座に変換できます。
次に、現在の売り気配値と買い気配値を取得し、買いシグナルが出ておりMaxBuysの制限内である場合は、ストップロスとテイクプロフィットを計算して、obj_Trade.Buyで市場注文を送信します。ロット数は固定、価格指定なし(市場成行)、計算したSL/TP、コメントは「LRC Buy」です。成功した場合はシグナルの詳細をログに記録し、DrawArrowで前バーの時間と終値に緑色の上向き矢印を描画します。失敗した場合は、リターンコードの説明をログに残します。売りシグナルも同様の手順で処理します。コンパイルすると、次の結果が得られます。

この可視化から、線形回帰チャネルを定義し、必要に応じて更新し、ブレイクアウトでポジションを開くことが確認できます。これにより、目的を達成しています。残っている作業は、このプログラムのバックテストをおこなうことです。バックテストについては次のセクションで扱います。
バックテスト
徹底的なバックテストによって、次の結果が得られました。
バックテストグラフ

バックテストレポート

結論
MQL5で適応型の線形回帰チャネルシステムを開発しました。このシステムは、ユーザーが設定した期間に基づいて回帰線と標準偏差バンドを計算します。システムは、傾きの絶対値が最小閾値を超えた場合にのみ作動します。価格がチャネル内にある限り、チャネルはバーごとに自動的に拡張されます。また、価格がチャネル幅の設定可能な割合を超えて移動した場合は、チャネルを完全に再作成します。このシステムは、通常(プルバック)モードと逆(フェード)モードの両方をサポートしています。チャネル内からのクリーンなブレイクアウト時にポジションを開きます。視覚的には、塗りつぶされた二色ゾーン、上限、中間、下限のソリッドトレンドライン、移動ラベル、およびエントリー矢印を表示します。
免責条項:本記事は教育目的のみを意図したものです。取引には重大な財務リスクが伴い、市場の変動によって損失が生じる可能性があります。本プログラムを実際の市場で運用する前に、十分なバックテストと慎重なリスク管理が不可欠です。
この適応型線形回帰チャネル戦略は、動的な更新、通常および逆モード、中間線クロスでの決済を提供します。チャネルに基づくシグナルでトレンド相場を取引する準備が整っています。戦略はさらに最適化して活用することが可能です。取引をお楽しみください。
MetaQuotes Ltdにより英語から翻訳されました。
元の記事: https://www.mql5.com/en/articles/20347
警告: これらの資料についてのすべての権利はMetaQuotes Ltd.が保有しています。これらの資料の全部または一部の複製や再プリントは禁じられています。
この記事はサイトのユーザーによって執筆されたものであり、著者の個人的な見解を反映しています。MetaQuotes Ltdは、提示された情報の正確性や、記載されているソリューション、戦略、または推奨事項の使用によって生じたいかなる結果についても責任を負いません。
MQL5におけるARIMA予測指標
他言語の実用モジュールをMQL5で実装する(第04回):Pythonのtime、date、datetimeモジュール
プライスアクション分析ツールキットの開発(第53回):サポート・レジスタンスゾーン発見のためのPattern Density Heatmap
古典的な戦略を再構築する(第13回):クロスオーバー戦略を新たな次元へ(その2)
- 無料取引アプリ
- 8千を超えるシグナルをコピー
- 金融ニュースで金融マーケットを探索