English
preview
MQL5での取引戦略の自動化(第40回):カスタムレベルを使ったフィボナッチリトレースメント取引

MQL5での取引戦略の自動化(第40回):カスタムレベルを使ったフィボナッチリトレースメント取引

MetaTrader 5トレーディング |
25 4
Allan Munene Mutiiria
Allan Munene Mutiiria

はじめに

前回の記事(第39回)では、MetaQuotes Language 5 (MQL5)で統計的平均回帰システムを開発しました。このシステムは、平均、分散、歪度、尖度、ジャック=ベラ統計量などの価格データを分析し、適応型閾値と上位時間足の確認を伴う信頼区間に基づいて回帰シグナルを生成し、エクイティベースのポジションサイジング、トレーリングストップ、部分決済、時間ベースのエグジットで取引を管理しながら、リアルタイム監視のためのオンチャートダッシュボードを提供しました。第40回では、カスタムレベルを備えたフィボナッチリトレースメント取引システムを開発します。

このシステムは、日足の値幅またはルックバック配列を用いてリトレースメントレベルを計算し、終値と始値の比較に基づいて強気または弱気のセットアップを識別し、50%や61.8%といった指定レベルの価格のクロス時にエントリーをトリガーし、最大取引回数の制限を設けます。また、新しいフィボナッチ計算時の任意の決済、利益閾値到達後のポイントベースのトレーリングストップ、値幅の割合として設定されるSL/TPバッファも含まれます。本記事では以下のトピックを扱います。

  1. フィボナッチリトレースメント戦略の理解
  2. MQL5での実装
  3. バックテスト
  4. 結論

記事を読み終える頃には、フィボナッチリトレースメント取引戦略の実用的なMQL5戦略が完成し、自由にカスタマイズできる状態になります。それでは始めましょう。


フィボナッチリトレースメント戦略の理解

フィボナッチリトレースメント戦略は、過去の価格スイングにフィボナッチ数列から導かれる主要な比率を適用することで、潜在的なサポートおよびレジスタンスレベルを特定する手法です。これにより、価格が動きの一部を押し戻した後に反転または継続する可能性が高いトレンド相場において、押し目を予測するのに役立ちます。

強気セットアップでは、安値から高値への上昇スイングの後、50%や61.8%といったリトレースメントレベルが押し目局面における潜在的な買いゾーンとして機能し、その後の上方向への反発を期待します。一方、弱気セットアップでは、高値から安値への下降スイングの後、これらのレベルが上昇修正局面における売りゾーンとして機能し、下降トレンドの再開を見込みます。

エントリー精度を高めるために、これらのレベルでの価格のクロスを確認し、リスク調整のためにスイング値幅に基づいたバッファを取引レベルに適用し、過度なエクスポージャーを避けるために各レベルごとの取引回数を制限し、価格が有利に動いた際に利益を保護するためのトレーリングストップを組み込みます。また、必要に応じて新しいフィボナッチ計算時にポジションを決済します。これらの要素を組み合わせることで、トレンド内の反転ポイントを狙うことができます。以下で、想定される弱気リトレースメントセットアップのサンプルをご覧ください。

弱気リトレースメントセットアップ

本記事では、日足またはルックバック配列を用いてフィボナッチレベルを計算します。これは、任意の戦略に切り替えることも可能です。終値と始値の比較から強気/弱気の方向を判断し、50%や61.8%など、任意に設定可能なカスタム比率のレベルを価格がクロスした際にエントリーをトリガーします。これらは、一般的かつ重要と考えられる代表的なレベルです。さらに、最大取引回数の制限を設け、値幅ベースのバッファを任意で適用したSL/TPを設定し、利益閾値到達後にポイントベースのトレーリングストップを有効化し、選択に応じて新しいフィボナッチ計算時に決済し、色付きオブジェクトや情報ラベルで可視化することで、柔軟なリトレースメント取引システムを構築します。


MQL5での実装

MQL5でプログラムを作成するには、まずMetaEditorを開き、ナビゲーターで[Experts]フォルダを探します。[新規]タブをクリックして指示に従い、ファイルを作成します。ファイルが作成されたら、コーディング環境で、まずプログラム全体で使用する入力パラメータグローバル変数をいくつか宣言する必要があります。

//+------------------------------------------------------------------+
//|                                 Fibonacci Retracement Ratios.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"
#property strict

#include <Trade\Trade.mqh>                                        // For trade execution

//+------------------------------------------------------------------+
//| Enums                                                            |
//+------------------------------------------------------------------+
enum CloseOnNewEnum {                                             // Define enum for closing on new Fibonacci
   CloseOnNew_No  = 0,                                            // No
   CloseOnNew_Yes = 1                                             // Yes
};
enum TrailingTypeEnum {                                           // Define enum for trailing stop types
   Trailing_None   = 0,                                           // None
   Trailing_Points = 2                                            // By Points
};

//+------------------------------------------------------------------+
//| Input Parameters                                                 |
//+------------------------------------------------------------------+
input bool   UseDailyApproach     = true;                         // Use daily candle (true) or array (false)
input string fibLevelsStr         = "50,61.8";                    // Comma-separated Fib levels for entry (e.g., 50,61.8)
input int    maxTradesPerLevel    = 1;                            // Max trades per level per Fib period (0=unlimited)
input CloseOnNewEnum CloseOnNewFib = CloseOnNew_No;               // Close trades on new Fib calc
input TrailingTypeEnum TrailingType = Trailing_None;              // Trailing Stop Type
input double Trailing_Stop_Pips   = 30.0;                         // Trailing Stop in Pips (for Points type)
input double Min_Profit_To_Trail_Pips = 50.0;                     // Min Profit to Start Trailing in Pips
input int    LookbackSize         = 100;                          // Number of candles for array approach
input double LotSize              = 0.1;                          // Trade lot size
input int    MagicNumber          = 12345;                        // Magic number for trades
input bool   IncludeCurrentBar    = false;                        // Include current bar in array calcs for updates
input double SlBufferPercent      = 0.0;                          // SL buffer percent of range (0=no buffer)
input double TpBufferPercent      = 0.0;                          // TP buffer percent of range (0=no buffer)

まず、注文実行およびポジション管理機能を有効にするために、「#include <Trade\Trade.mqh>」でTradeライブラリをインクルードします。ユーザーオプション用に2つの列挙型を定義します。CloseOnNewEnumは、新しいフィボナッチ計算時に取引を決済しないCloseOnNew_Noと、決済するCloseOnNew_Yesを持ちます。TrailingTypeEnumは、トレーリングストップを無効にするTrailing_Noneと、ポイントベースで調整するTrailing_Pointsを提供します。

次に、カスタマイズ用の入力パラメータを設定します。UseDailyApproachはデフォルトでtrueとし日足値幅を使用し、falseの場合は配列ベースのルックバックを使用します。fibLevelsStrは「50,61.8」とし、エントリー用のリトレースメントレベルをカンマ区切りで指定できます。maxTradesPerLevelは1に設定され、各期間・各レベルごとの取引回数を制限します(0は無制限)。CloseOnNewFibは列挙型を使用して再計算時の決済を決定し、TrailingTypeはトレーリングストップモードを選択します。

トレーリングの詳細として、Trailing_Stop_Pipsは30.0で距離を設定し、Min_Profit_To_Trail_Pipsは50.0で開始する利益閾値を設定します。LookbackSizeは100で配列モードのローソク足数を定義し、LotSizeは0.1でポジションサイズを設定します。MagicNumberは12345で取引識別用に使用し、IncludeCurrentBarはfalseで、形成中のバーを計算に含めるかどうかを指定できます。SlBufferPercentとTpBufferPercentはともに0.0で、ストップおよび利益に対する値幅ベースの調整をおこないます(値を大きくするとバッファが追加されます)。コンパイルすると、以下の入力パラメータが表示されます。

入力のカスタマイズ

入力の設定が完了したら、プログラム全体で使用するグローバル変数を作成していきます。

//+------------------------------------------------------------------+
//| Global Variables                                                 |
//+------------------------------------------------------------------+
CTrade obj_Trade;                                                 //--- Trade object
int    barsTotal;                                                 //--- For daily approach
#define FIB_OBJ "Fibonacci Retracement"                           //--- Define Fibonacci object name
// Persistent variables for both approaches
static double storedEntryLvls[];                                  //--- Array of entry levels
static int    storedTradesCount[];                                //--- Trades count per level
static double storedSl = 0.0;                                     //--- Stored stop loss
static double storedTp = 0.0;                                     //--- Stored take profit
static string storedInfo = "";                                    //--- Stored information string
static bool   storedIsBullish = false;                            //--- Stored bullish flag
static double fibLevels[];                                        //--- Parsed Fibonacci levels (original order)
static string lastShownInfo = "";                                 //--- To detect changes and avoid unnecessary updates
// For array approach
static bool   fibCalculated = false;                              //--- Fibonacci calculated flag
static double currentHigh = 0.0;                                  //--- Current high
static double currentLow = 0.0;                                   //--- Current low
static string fibName = "Fib_Array";                              //--- Fibonacci name for array

次に、グローバル変数を宣言します。まず、obj_Tradeを注文処理用のCTradeインスタンスとして定義し、barsTotalを日足アプローチにおける日足数の追跡に使用します。また、メインのフィボナッチオブジェクト名としてFIB_OBJを「Fibonacci Retracement」と定義します。日足方式と配列方式の両方でデータを保持するために、static配列storedEntryLvls[]を使用して計算されたエントリー価格を保存し、storedTradesCount[]で各レベルごとの取引回数を管理します。double型のstoredSlとstoredTpはストップロスおよびテイクプロフィット用として0.0で初期化します。storedInfoは表示用テキストとして空文字列で定義し、storedIsBullishは方向判定用フラグとしてfalseに設定します。fibLevels[]は解析済みの比率を格納し、lastShownInfoは情報の変更を検出して再描画を最小化するために空文字列で定義します。

配列方式専用として、staticのfibCalculatedはレベルが設定済みかどうかを示すためfalseで初期化します。currentHighとcurrentLowはブレイク判定用の高値および安値を保存するため0.0で定義し、fibNameはオブジェクト識別子として「Fib_Array」とします。これで実装ロジックを開始する準備が整いました。まずはロジックを初期化するOnInitイベントハンドラから実装します。

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit() {
   obj_Trade.SetExpertMagicNumber(MagicNumber);                   //--- Set magic number for trade object
   // Force initial calculation for daily approach
   barsTotal = 0;                                                 //--- Ensure first tick updates
   // Parse fibLevelsStr into fibLevels array (from MQL5 docs: StringSplit)
   string tempLevels[];                                           //--- Temporary levels array
   ushort commaSep = StringGetCharacter(",", 0);                  //--- Get comma separator
   int numLevels = StringSplit(fibLevelsStr, commaSep, tempLevels); //--- Split string into levels
   ArrayResize(fibLevels, numLevels);                             //--- Resize fibLevels array
   for (int i = 0; i < numLevels; i++) {                          //--- Iterate through levels
      fibLevels[i] = StringToDouble(tempLevels[i]);               //--- Convert to double
   }
   ArrayResize(storedEntryLvls, numLevels);                       //--- Resize storedEntryLvls
   ArrayResize(storedTradesCount, numLevels);                     //--- Resize storedTradesCount
   // Clean up old labels
   ObjectsDeleteAll(0, "InfoLabel_", -1, OBJ_LABEL);              //--- Delete all info labels
   lastShownInfo = "";                                            //--- Reset last shown info
   // Clean up old Fib object for array
   ObjectDelete(0, fibName);                                      //--- Delete Fibonacci object
   fibCalculated = false;                                         //--- Reset calculated flag
   return(INIT_SUCCEEDED);                                        //--- Return success
}

OnInitイベントハンドラでは、まずobj_Trade.SetExpertMagicNumberをMagicNumberとともに呼び出して、注文を識別するための設定をおこないます。日足アプローチでは、最初のティックで初期更新がおこなわれるように、barsTotalを0に設定します。次に、fibLevelsStrをfibLevels配列に解析します。StringSplitを使用して文字列をカンマで分割し、区切り文字にはStringGetCharacterを使用します。分割結果はtempLevelsに格納し、取得した件数に合わせてfibLevelsのサイズを変更します。その後、ループ内で各要素をStringToDoubleによってdouble型へ変換します。続いて、レベル数に合わせてstoredEntryLvlsおよびstoredTradesCountのサイズを変更し、各レベルの追跡ができるようにします。

クリーンアップとして、ObjectsDeleteAllを使用し、接頭辞「InfoLabel_」およびタイプOBJ_LABELを指定して、すべての情報ラベルを削除します。lastShownInfoを空文字にリセットし、ObjectDeleteを使ってfibNameという名前の古いフィボナッチオブジェクトを削除し、fibCalculatedをfalseに設定します。最後に、初期化が正常に完了したことを示すためINIT_SUCCEEDEDを返します。これでティックイベントハンドラへ進み、最初のロジックを定義して処理を開始できます。ここでは日足アプローチを使用します。

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick() {
   if (UseDailyApproach) {                                        //--- Check daily approach
      // Daily approach logic remains the same
      int bars = iBars(_Symbol, PERIOD_D1);                       //--- Get daily bars
      if (barsTotal != bars && TimeCurrent() > StringToTime("00:05")) { //--- Check new bar
         barsTotal = bars;                                        //--- Update bars total
         ObjectDelete(0, FIB_OBJ);                                //--- Delete Fib object
         double openPrice = iOpen(_Symbol, PERIOD_D1, 1);         //--- Get open price
         double closePrice = iClose(_Symbol, PERIOD_D1, 1);       //--- Get close price
         double high = iHigh(_Symbol, PERIOD_D1, 1);              //--- Get high
         double low = iLow(_Symbol, PERIOD_D1, 1);                //--- Get low
         datetime startingTime = iTime(_Symbol, PERIOD_D1, 1);    //--- Get start time
         datetime endingTime = iTime(_Symbol, PERIOD_D1, 0) - 1;  //--- Get end time
         double range = high - low;                               //--- Calc range
         storedIsBullish = (closePrice > openPrice);              //--- Set bullish flag
         string levelsList = "";                                  //--- Init levels list
         for (int i = 0; i < ArraySize(fibLevels); i++) {         //--- Iterate levels
            storedTradesCount[i] = 0;                             //--- Reset count
            if (storedIsBullish) {                                //--- Check bullish
               storedEntryLvls[i] = NormalizeDouble(high - range * fibLevels[i] / 100, _Digits); //--- Calc entry
            } else {                                              //--- Handle bearish
               storedEntryLvls[i] = NormalizeDouble(low + range * fibLevels[i] / 100, _Digits); //--- Calc entry
            }
            levelsList += DoubleToString(fibLevels[i], 1) + ": " + DoubleToString(storedEntryLvls[i], _Digits) + "\n"; //--- Add to list
         }
         if (storedIsBullish) {                                   //--- Check bullish
            // Bullish: Fibo from low to high for correct 0% at high, 100% at low, green
            ObjectCreate(0, FIB_OBJ, OBJ_FIBO, 0, startingTime, low, endingTime, high); //--- Create Fib
            ObjectSetInteger(0, FIB_OBJ, OBJPROP_COLOR, clrGreen); //--- Set color
            for (int i = 0; i < ObjectGetInteger(0, FIB_OBJ, OBJPROP_LEVELS); i++) { //--- Iterate levels
               ObjectSetInteger(0, FIB_OBJ, OBJPROP_LEVELCOLOR, i, clrGreen); //--- Set level color
            }
            storedSl = NormalizeDouble(low - range * (SlBufferPercent / 100), _Digits); //--- Calc SL
            storedTp = NormalizeDouble(high + range * (TpBufferPercent / 100), _Digits); //--- Calc TP
            storedInfo = "Daily Approach - Bullish\n" +           //--- Set info
                         "Open: " + DoubleToString(openPrice, _Digits) + "\n" +
                         "Close: " + DoubleToString(closePrice, _Digits) + "\n" +
                         "Buy Entries:\n" + levelsList +
                         "SL: " + DoubleToString(storedSl, _Digits) + "\n" +
                         "TP: " + DoubleToString(storedTp, _Digits);
            Print("New daily bar: Bullish Fibonacci levels calculated. Entries: ", levelsList); //--- Log
         } else {                                                 //--- Handle bearish
            // Bearish: Fibo from high to low for correct 0% at low, 100% at high, red
            ObjectCreate(0, FIB_OBJ, OBJ_FIBO, 0, startingTime, high, endingTime, low); //--- Create Fib
            ObjectSetInteger(0, FIB_OBJ, OBJPROP_COLOR, clrRed);  //--- Set color
            for (int i = 0; i < ObjectGetInteger(0, FIB_OBJ, OBJPROP_LEVELS); i++) { //--- Iterate levels
               ObjectSetInteger(0, FIB_OBJ, OBJPROP_LEVELCOLOR, i, clrRed); //--- Set level color
            }
            storedSl = NormalizeDouble(high + range * (SlBufferPercent / 100), _Digits); //--- Calc SL
            storedTp = NormalizeDouble(low - range * (TpBufferPercent / 100), _Digits); //--- Calc TP
            storedInfo = "Daily Approach - Bearish\n" +           //--- Set info
                         "Open: " + DoubleToString(openPrice, _Digits) + "\n" +
                         "Close: " + DoubleToString(closePrice, _Digits) + "\n" +
                         "Sell Entries:\n" + levelsList +
                         "SL: " + DoubleToString(storedSl, _Digits) + "\n" +
                         "TP: " + DoubleToString(storedTp, _Digits);
            Print("New daily bar: Bearish Fibonacci levels calculated. Entries: ", levelsList); //--- Log
         }
      }
   }
   // Redraw chart objects
   ChartRedraw();                                                 //--- Redraw chart
}

OnTickイベントハンドラでは、UseDailyApproachが有効な場合、iBarsを使って_SymbolPERIOD_D1マクロで日足の数を取得します。新しい日足が形成され、かつTimeCurrentStringToTimeで現在時刻が00:05を過ぎている場合、barsTotalを更新し、ObjectDeleteで既存のFIB_OBJオブジェクトを削除します。そして、iOpenで前日の始値、iCloseで終値、iHighで高値、iLowで安値、開始時刻と終了時刻から1秒引いて描画に適切に調整します。値幅は高値から安値を差し引いて計算し、終値が始値を上回ればstoredIsBullishをtrueに設定します。その後、ArraySizeでfibLevelsのサイズをループしてstoredTradesCount[i]を0にリセットし、NormalizeDoubleで正規化したエントリーレベルを計算します(強気の場合は値幅×レベルパーセントを高値から引き、弱気の場合は安値に足します)。levelsList文字列も作成して表示用に準備します。

強気の場合、OBJ_FIBOとしてFIB_OBJを作成し、低値から高値までstartTimeからendTimeにかけてアンカーし、ObjectSetIntegerOBJPROP_COLORを緑に設定します。ObjectGetIntegerのOBJPROP_LEVELSでレベル数を取得し、各レベルのOBJPROP_LEVELCOLORも緑に設定します。storedSlは値幅のバッファパーセント分低値下に、storedTpは高値上に設定します。storedInfoにはアプローチタイプ、始値/終値、エントリーリスト、取引レベルをフォーマットして計算をログに残します。弱気の場合は同様に処理を反転させ、高値から低値にFibをアンカーし、色を赤に設定し、SLを高値上、TPを安値下に設定して情報を更新しログに残します。最後にChartRedrawでチャートを再描画します。常に各マイルストーンでコードをコンパイルしてテストすることは良いプログラミングの習慣です。コンパイルすると次のようになります。

確認されたフィボナッチリトレースメントレベル

画像から、値幅を計算し、方向を判定し、それぞれのFibonacciオブジェクトを描画していることがわかります。次に、指定されたレベルにリトレースした際に、対応するポジションを追跡して建てる必要があります。以下がそのロジックです。

//+------------------------------------------------------------------+
//| Display info using labels without flicker                        |
//+------------------------------------------------------------------+
void ShowLabels(string info) {
   if (info == lastShownInfo) return;                             //--- Skip if no change
   lastShownInfo = info;                                          //--- Update last info
   // Split info into lines
   string lines[];                                                //--- Lines array
   ushort nlSep = StringGetCharacter("\n", 0);                    //--- Get newline sep
   int numLines = StringSplit(info, nlSep, lines);                //--- Split into lines
   int y = 10;                                                    //--- Starting Y
   for (int i = 0; i < numLines; i++) {                           //--- Iterate lines
      string name = "InfoLabel_" + IntegerToString(i);            //--- Label name
      if (ObjectFind(0, name) < 0) {                              //--- Check exists
         ObjectCreate(0, name, OBJ_LABEL, 0, 0, 0);               //--- Create label
         ObjectSetInteger(0, name, OBJPROP_CORNER, CORNER_LEFT_UPPER); //--- Set corner
         ObjectSetInteger(0, name, OBJPROP_XDISTANCE, 10);        //--- Set X distance
         ObjectSetInteger(0, name, OBJPROP_FONTSIZE, 8);          //--- Set font size
      }
      ObjectSetInteger(0, name, OBJPROP_YDISTANCE, y);            //--- Set Y distance
      ObjectSetString(0, name, OBJPROP_TEXT, lines[i]);           //--- Set text
      y += 15;                                                    //--- Increment Y
   }
   // Delete extra labels if numLines decreased
   for (int i = numLines; ; i++) {                                //--- Iterate extras
      string name = "InfoLabel_" + IntegerToString(i);            //--- Label name
      if (ObjectFind(0, name) < 0) break;                         //--- Break if none
      ObjectDelete(0, name);                                      //--- Delete label
   }
}

// Display info every tick using labels (but only update if changed)
ShowLabels(storedInfo);                                           //--- Show labels
// Entry logic: Checked every tick using stored levels (no existing position)
if (PositionsTotal() == 0) {                                      //--- Check no positions
   double close1 = iClose(_Symbol, _Period, 1);                   //--- Get close 1
   double close2 = iClose(_Symbol, _Period, 2);                   //--- Get close 2
   for (int i = 0; i < ArraySize(storedEntryLvls); i++) {         //--- Iterate levels
      // Only enter on levels 0 < fib <=100 (retracements), ignore 0/100/extensions for entry
      if (fibLevels[i] <= 0 || fibLevels[i] > 100.0) continue;    //--- Skip invalid
      if ((maxTradesPerLevel == 0 || storedTradesCount[i] < maxTradesPerLevel) && //--- Check count
          ((storedIsBullish && close1 > storedEntryLvls[i] && close2 <= storedEntryLvls[i]) || //--- Buy cross
           (!storedIsBullish && close1 < storedEntryLvls[i] && close2 >= storedEntryLvls[i]))) { //--- Sell cross
         string levelStr = DoubleToString(fibLevels[i], 1);       //--- Level string
         ulong ticket = 0;                                        //--- Init ticket
         if (storedIsBullish) {                                   //--- Check buy
            Print("Buy signal triggered at ", close1, " crossing level ", levelStr, " (", storedEntryLvls[i], ")"); //--- Log
            obj_Trade.Buy(LotSize, _Symbol, 0, storedSl, storedTp, "Fibo Buy at " + levelStr); //--- Open buy
            ticket = obj_Trade.ResultDeal();                      //--- Get deal
         } else {                                                 //--- Handle sell
            Print("Sell signal triggered at ", close1, " crossing level ", levelStr, " (", storedEntryLvls[i], ")"); //--- Log
            obj_Trade.Sell(LotSize, _Symbol, 0, storedSl, storedTp, "Fibo Sell at " + levelStr); //--- Open sell
            ticket = obj_Trade.ResultDeal();                      //--- Get deal
         }
         storedTradesCount[i]++;                                  //--- Increment count
         break;                                                   //--- Break loop
      }
   }
}

まず、ShowLabels関数を定義して、ラベルを使いチャート上に戦略情報を表示します。不要な再描画を避けるため、引数として受け取った文字列infoがlastShownInfoと同じ場合はすぐに返し、そうでなければlastShownInfoを更新します。infoを改行文字でStringSplitし、各文字をStringGetCharacterで処理してlines配列に分割します。その後、numLines分ループしてInfoLabel_+インデックス名のラベルを作成または更新します。ObjectFindで既存ラベルを確認し、新しい場合はObjectCreateOBJ_LABELとして左上隅に配置、X距離10、フォントサイズ8に設定します。Y距離は10から始め、15ずつ増加させ、ObjectSetStringでlines[i]をOBJPROP_TEXTに設定します。行数が減った場合の余分なラベルは、numLinesから上方向にループしてObjectDeleteで削除し、ObjectFindで見つからなくなるまで続けます。

その後、先ほど定義したロジックのすぐ下のOnTick関数内で、storedInfoを引数としてShowLabels関数を呼び出し、変化があった場合のみ表示を更新します。エントリーロジックでは、PositionsTotalが0でポジションが存在しない場合、iCloseで直近2本分の終値(シフト1と2)を取得します。次にstoredEntryLvlsのサイズ分ループし、0~100の範囲外のレベルはスキップして実際のリトレースメントのみ処理します。エントリーが許可されている場合(無制限またはmaxTradesPerLevel以下)、かつクロスが発生した場合(強気買いならclose1がレベル上でclose2がレベル以下、弱気売りならclose1がレベル下でclose2がレベル以上)、levelStrを小数第1位に丸めてDoubleToStringでフォーマットし、チケットを初期化、シグナルをログに記録します。その後obj_Trade.BuyまたはSellでLotSize、銘柄、成行価格0、storedSl、storedTp、コメントにレベルを含めて注文を出し、結果を取得してstoredTradesCount[i]を増加させ、同一ティックで複数エントリーしないようbreakします。コンパイルすると、次の結果が得られます。

エントリー確認済み

これで日足アプローチロジックでのエントリーを確認できました。次に別のアプローチに進みます。このプロジェクトでは、任意の取引アプローチに切り替えたりカスタマイズできることを示すために、2つのロジックを選択しました。この配列アプローチはシグナルを多く出すために動的で、分析は1回だけおこない、価格が前回のセットアップ内にある間は待機します。分析は前回セットアップをブレイクした後にのみ行い、通常は価格が0~100レベルを突破したタイミングです。なお100を超えるレベルも存在するため、ブレイクをシグナルする関数が必要になります。

//+------------------------------------------------------------------+
//| Check if price breaches the current Fib extremes                 |
//+------------------------------------------------------------------+
bool IsBreach() {
   if (!fibCalculated) return false;                              //--- Return false if not calculated
   double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);            //--- Get bid price
   if (storedIsBullish) {                                         //--- Check bullish
      // For bullish, 0% is high, 100% is low
      return (bid > currentHigh || bid < currentLow);             //--- Check breach
   } else {                                                       //--- Handle bearish
      // For bearish, 0% is low, 100% is high
      return (bid > currentLow || bid < currentHigh);             //--- Check breach
   }
}

ここでは、IsBreach関数を実装して、SymbolInfoDoubleで取得した現在のBid価格(SYMBOL_BID)が、保存されたFibの極値を超えたかどうかを検出します。fibCalculatedがtrueでない場合は、早期にfalseを返します。強気セットアップ(0%が高値、100%が安値)の場合は、BidがcurrentHighを上回るか、currentLowを下回るかをチェックします。弱気セットアップ(アンカーが逆)の場合は、BidがcurrentLowを上回るか、currentHighを下回るかを確認します。いずれかの場合、配列モードで再計算がトリガーされます。これで、日足アプローチと同じロジックを持つ配列アプローチを組み込むことができます。

else {                                                          //--- Array approach
   // Array approach: Calculate only when not calculated or breached
   if (!fibCalculated || IsBreach()) {                          //--- Check recalc
      if (fibCalculated) {                                      //--- Check calculated
         // Invalidate and forget previous
         ObjectDelete(0, fibName);                              //--- Delete Fib
         fibCalculated = false;                                 //--- Reset flag
      }
      int startShift = IncludeCurrentBar ? 0 : 1;               //--- Set start shift
      int copyCount = IncludeCurrentBar ? LookbackSize : LookbackSize; //--- Set copy count
      double high[], low[];                                     //--- High and low arrays
      ArraySetAsSeries(high, true);                             //--- Set as series
      ArraySetAsSeries(low, true);                              //--- Set as series
      if (CopyHigh(_Symbol, _Period, startShift, copyCount, high) <= 0) return; //--- Copy high
      if (CopyLow(_Symbol, _Period, startShift, copyCount, low) <= 0) return; //--- Copy low
      int highestCandle = ArrayMaximum(high, 0, copyCount);     //--- Get highest
      int lowestCandle = ArrayMinimum(low, 0, copyCount);       //--- Get lowest
      MqlRates pArray[];                                        //--- Rates array
      ArraySetAsSeries(pArray, true);                           //--- Set as series
      int pData = CopyRates(_Symbol, _Period, startShift, copyCount, pArray); //--- Copy rates
      if (pData <= 0) return;                                   //--- Check data
      double highVal = pArray[highestCandle].high;              //--- Get high val
      double lowVal = pArray[lowestCandle].low;                 //--- Get low val
      double range = highVal - lowVal;                          //--- Calc range
      int oldestShift = IncludeCurrentBar ? (LookbackSize - 1) : LookbackSize; //--- Oldest shift
      double openCandle = iOpen(_Symbol, _Period, oldestShift); //--- Get open
      double closeCandle = iClose(_Symbol, _Period, IncludeCurrentBar ? 0 : 1); //--- Get close
      storedIsBullish = (closeCandle > openCandle);             //--- Set bullish
      string levelsList = "";                                   //--- Init list
      for (int i = 0; i < ArraySize(fibLevels); i++) {          //--- Iterate levels
         storedTradesCount[i] = 0;                              //--- Reset count
         if (storedIsBullish) {                                 //--- Check bullish
            storedEntryLvls[i] = NormalizeDouble(highVal - range * fibLevels[i] / 100, _Digits); //--- Calc entry
         } else {                                               //--- Handle bearish
            storedEntryLvls[i] = NormalizeDouble(lowVal + range * fibLevels[i] / 100, _Digits); //--- Calc entry
         }
         levelsList += DoubleToString(fibLevels[i], 1) + ": " + DoubleToString(storedEntryLvls[i], _Digits) + "\n"; //--- Add to list
      }
      if (storedIsBullish) {                                    //--- Check bullish
         // Bullish: Anchor from low to high
         datetime time1 = pArray[lowestCandle].time;            //--- Time1
         double price1 = lowVal;                                //--- Price1
         datetime time2 = pArray[highestCandle].time;           //--- Time2
         double price2 = highVal;                               //--- Price2
         ObjectCreate(0, fibName, OBJ_FIBO, 0, time1, price1, time2, price2); //--- Create Fib
         ObjectSetInteger(0, fibName, OBJPROP_COLOR, clrGreen); //--- Set color
         for (int i = 0; i < ObjectGetInteger(0, fibName, OBJPROP_LEVELS); i++) { //--- Iterate levels
            ObjectSetInteger(0, fibName, OBJPROP_LEVELCOLOR, i, clrGreen); //--- Set level color
         }
         storedSl = NormalizeDouble(lowVal - range * (SlBufferPercent / 100), _Digits); //--- Calc SL
         storedTp = NormalizeDouble(highVal + range * (TpBufferPercent / 100), _Digits); //--- Calc TP
         storedInfo = "Array Approach - Bullish\n" +            //--- Set info
                      "Array Open: " + DoubleToString(openCandle, _Digits) + "\n" +
                      "Array Close: " + DoubleToString(closeCandle, _Digits) + "\n" +
                      "Buy Entries:\n" + levelsList +
                      "SL: " + DoubleToString(storedSl, _Digits) + "\n" +
                      "TP: " + DoubleToString(storedTp, _Digits);
      } else {                                                  //--- Handle bearish
         // Bearish: Anchor from high to low
         datetime time1 = pArray[highestCandle].time;           //--- Time1
         double price1 = highVal;                               //--- Price1
         datetime time2 = pArray[lowestCandle].time;            //--- Time2
         double price2 = lowVal;                                //--- Price2
         ObjectCreate(0, fibName, OBJ_FIBO, 0, time1, price1, time2, price2); //--- Create Fib
         ObjectSetInteger(0, fibName, OBJPROP_COLOR, clrRed);   //--- Set color
         for (int i = 0; i < ObjectGetInteger(0, fibName, OBJPROP_LEVELS); i++) { //--- Iterate levels
            ObjectSetInteger(0, fibName, OBJPROP_LEVELCOLOR, i, clrRed); //--- Set level color
         }
         storedSl = NormalizeDouble(highVal + range * (SlBufferPercent / 100), _Digits); //--- Calc SL
         storedTp = NormalizeDouble(lowVal - range * (TpBufferPercent / 100), _Digits); //--- Calc TP
         storedInfo = "Array Approach - Bearish\n" +            //--- Set info
                      "Array Open: " + DoubleToString(openCandle, _Digits) + "\n" +
                      "Array Close: " + DoubleToString(closeCandle, _Digits) + "\n" +
                      "Sell Entries:\n" + levelsList +
                      "SL: " + DoubleToString(storedSl, _Digits) + "\n" +
                      "TP: " + DoubleToString(storedTp, _Digits);
      }
      currentHigh = storedIsBullish ? highVal : lowVal;         //--- Set current high
      currentLow = storedIsBullish ? lowVal : highVal;          //--- Set current low
      fibCalculated = true;                                     //--- Set calculated
   }
   // Display info using labels (but only update if changed)
   ShowLabels(storedInfo);                                        //--- Show labels
   // Entry logic: Checked every tick using stored levels (no existing position)
   if (PositionsTotal() == 0) {                                   //--- Check no positions
      double close1 = iClose(_Symbol, _Period, 1);                //--- Get close 1
      double close2 = iClose(_Symbol, _Period, 2);                //--- Get close 2
      for (int i = 0; i < ArraySize(storedEntryLvls); i++) {      //--- Iterate levels
         if (fibLevels[i] <= 0 || fibLevels[i] > 100.0) continue; //--- Skip invalid
         if ((maxTradesPerLevel == 0 || storedTradesCount[i] < maxTradesPerLevel) && //--- Check count
             ((storedIsBullish && close1 > storedEntryLvls[i] && close2 <= storedEntryLvls[i]) || //--- Buy cross
              (!storedIsBullish && close1 < storedEntryLvls[i] && close2 >= storedEntryLvls[i]))) { //--- Sell cross
            string levelStr = DoubleToString(fibLevels[i], 1);     //--- Level string
            ulong ticket = 0;                                      //--- Init ticket
            if (storedIsBullish) {                                 //--- Check buy
               Print("Buy signal triggered (Array) at ", close1, " crossing level ", levelStr, " (", storedEntryLvls[i], ")"); //--- Log
               obj_Trade.Buy(LotSize, _Symbol, 0, storedSl, storedTp, "Fibo Buy Array at " + levelStr); //--- Open buy
               ticket = obj_Trade.ResultDeal();                        //--- Get deal
            } else {                                               //--- Handle sell
               Print("Sell signal triggered (Array) at ", close1, " crossing level ", levelStr, " (", storedEntryLvls[i], ")"); //--- Log
               obj_Trade.Sell(LotSize, _Symbol, 0, storedSl, storedTp, "Fibo Sell Array at " + levelStr); //--- Open sell
               ticket = obj_Trade.ResultDeal();                        //--- Get deal
            }
            storedTradesCount[i]++;                                //--- Increment count
            break;                                                 //--- Break loop
         }
      }
   }
}

非日足モードでは、配列アプローチを使用します。フィボナッチレベルは、まだ計算されていない場合、あるいはIsBreachで価格のブレイクアウトが検出された場合のみ再計算されます。すでに設定済みの場合は、ObjectDeleteで古いFibオブジェクト(fibName)を削除し、fibCalculatedをfalseにリセットします。開始シフトとコピーする本数はIncludeCurrentBarに依存します。trueの場合はシフト0で全履歴を使用し、falseの場合はシフト1で完了済みバーのみを使用します。高値と安値の配列はArraySetAsSeriesでシリーズとして設定し、CopyHighCopyLowで値を埋めます。失敗した場合は早期に返します。その後、ArrayMaximumとArrayMinimumで、インデックス0からcountまでの範囲で最高値と最安値のローソク足を見つけます。

正確な値と時刻を取得するために、CopyRatesでpArrayにレートをコピーし、データ不足の場合は返します。そして、高値のローソク足からhighVal、安値のローソク足からlowValを抽出し、値幅を計算します。方向性は、最も古いシフトの始値をiOpenで、最新シフトの終値をiCloseで取得し、終値が始値を上回ればstoredIsBullishをtrueに設定します。その後、ArraySizeでfibLevelsのサイズ分ループし、取引数をリセット、正規化されたエントリーを計算します(強気の場合はhighValから値幅×パーセントを引き、弱気の場合はlowValに加える)とlevelsListを組み立てます。描画と取引は、日足モードと同様のアプローチを使用します。コンパイルすると、次の結果が得られます。

配列アプローチロジック確認

これで配列アプローチを使用し、ポジションを開始していることが確認できます。残りは、新しいシグナルが出たときにポジションを決済し、有利に動いたものはトレーリングすることで管理することです。

//+------------------------------------------------------------------+
//| Close all positions with matching magic and symbol               |
//+------------------------------------------------------------------+
void CloseAllPositions() {
   for (int i = PositionsTotal() - 1; i >= 0; i--) {              //--- Iterate positions reverse
      if (PositionGetTicket(i) > 0 && PositionGetInteger(POSITION_MAGIC) == MagicNumber && PositionGetString(POSITION_SYMBOL) == _Symbol) { //--- Check position
         obj_Trade.PositionClose(PositionGetTicket(i));                //--- Close position
      }
   }
}

//+------------------------------------------------------------------+
//| Apply Points Trailing Stop (from reference)                      |
//+------------------------------------------------------------------+
void ApplyPointsTrailing() {
   double point = _Point;                                         //--- Get point value
   for (int i = PositionsTotal() - 1; i >= 0; i--) {              //--- Iterate positions reverse
      if (PositionGetTicket(i) > 0) {                             //--- Check valid ticket
         if (PositionGetString(POSITION_SYMBOL) == _Symbol && PositionGetInteger(POSITION_MAGIC) == MagicNumber) { //--- Check symbol and magic
            double sl = PositionGetDouble(POSITION_SL);              //--- Get SL
            double tp = PositionGetDouble(POSITION_TP);              //--- Get TP
            double openPrice = PositionGetDouble(POSITION_PRICE_OPEN); //--- Get open price
            ulong ticket = PositionGetInteger(POSITION_TICKET);      //--- Get ticket
            if (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY) { //--- Check buy
               double newSL = NormalizeDouble(SymbolInfoDouble(_Symbol, SYMBOL_BID) - Trailing_Stop_Pips * point, _Digits); //--- Calc new SL
               if (newSL > sl && SymbolInfoDouble(_Symbol, SYMBOL_BID) - openPrice > Min_Profit_To_Trail_Pips * point) { //--- Check conditions
                  obj_Trade.PositionModify(ticket, newSL, tp);           //--- Modify position
               }
            } else if (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL) { //--- Check sell
               double newSL = NormalizeDouble(SymbolInfoDouble(_Symbol, SYMBOL_ASK) + Trailing_Stop_Pips * point, _Digits); //--- Calc new SL
               if (newSL < sl && openPrice - SymbolInfoDouble(_Symbol, SYMBOL_ASK) > Min_Profit_To_Trail_Pips * point) { //--- Check conditions
                  obj_Trade.PositionModify(ticket, newSL, tp);           //--- Modify position
               }
            }
         }
      }
   }
}

これらは、ポジション管理ロジックを実現するために必要な関数です。あとは、必要な箇所でそれぞれ呼び出すだけで済みます。

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick() {
   // Points trailing can run anytime
   if (TrailingType == Trailing_Points && PositionsTotal() > 0) { //--- Check trailing
      ApplyPointsTrailing();                                      //--- Apply trailing
   }
   //--- call where necessary
         if (CloseOnNewFib == CloseOnNew_Yes) {                   //--- Check close on new
            CloseAllPositions();                                  //--- Close positions
         }
}

必要な箇所でOnTick関数内から関数を呼び出した後、次におこなうべきことは、チャートを終了するときに作成したオブジェクトを削除することです。

//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason) {
   ObjectsDeleteAll(0, "InfoLabel_", -1, OBJ_LABEL);              //--- Delete all info labels
   ObjectDelete(0, FIB_OBJ);                                      //--- Delete daily Fibonacci
   ObjectDelete(0, fibName);                                      //--- Delete array Fibonacci
}

OnDeinit関数では、ObjectsDeleteAll関数を使い、接頭辞「InfoLabel_」とタイプOBJ_LABELを指定して、すべてのサブウィンドウから情報ラベルを削除します。その後、ObjectDeleteで日足用のFIB_OBJと配列用のfibNameを削除し、チャート上の表示をクリアします。コンパイルすると、次の結果が得られます。

コンパイルのGIF

これで、必要に応じてトレーリングストップを適用することで、ポジションをデフォルトで管理できることが確認でき、目的を達成しています。残っている作業は、このプログラムのバックテストをおこなうことです。バックテストについては次のセクションで扱います。


バックテスト

徹底的なバックテストによって、次の結果が得られました。

バックテストグラフ

グラフ

バックテストレポート

レポート


結論

MQL5で、日足または履歴配列を使ってフィボナッチリトレースメントレベルを計算し、終値と始値を比較して強気と弱気のセットアップを判定し、カスタム比率のクロス時に買いと売りを実行してレベルごとの取引制限を適用するフィボナッチリトレースメント取引システムを開発しました。再計算時にはポジションをオプションで決済でき、利益閾値到達後にはポイントベースのトレーリングストップを適用します。また、値幅バッファを考慮したエントリーレベルを設定し、チャート上に可視化と情報ラベルを表示して補助しています。

免責条項:本記事は教育目的のみを意図したものです。取引には重大な財務リスクが伴い、市場の変動によって損失が生じる可能性があります。本プログラムを実際の市場で運用する前に、十分なバックテストと慎重なリスク管理が不可欠です。

このフィボナッチリトレースメント戦略を用いることで、プルバックのチャンスを効率的に取引でき、さらに最適化して取引戦略を発展させる準備が整います。取引をお楽しみください。

MetaQuotes Ltdにより英語から翻訳されました。
元の記事: https://www.mql5.com/en/articles/20221

添付されたファイル |
最後のコメント | ディスカッションに移動 (4)
Israr Hussain Shah
Israr Hussain Shah | 17 11月 2025 において 15:47
わあ、これはとてもいい。これを私の取引戦略に使って いる。
Allan Munene Mutiiria
Allan Munene Mutiiria | 18 11月 2025 において 06:04
Israr Hussain Shah 取引戦略として使って いる。
親切なフィードバックをありがとう。ようこそ。
Lesley Malabi Barasa
Lesley Malabi Barasa | 19 11月 2025 において 07:12
キャンパスで学んだことを自分のトレーディング 戦略に応用するために、幾何学的ブラウン運動と微積分についても教えてほしい。
Allan Munene Mutiiria
Allan Munene Mutiiria | 19 11月 2025 において 07:29
Lesley Malabi Barasa トレーディング 戦略に応用できるなんて、本当に驚きです。
親切なフィードバックをありがとう。もちろんです。
MQL5で自己最適化エキスパートアドバイザーを構築する(第17回):アンサンブルインテリジェンス MQL5で自己最適化エキスパートアドバイザーを構築する(第17回):アンサンブルインテリジェンス
すべてのアルゴリズム取引戦略は、その複雑さに関係なく、構築や維持が困難です。これは初心者と専門家の双方に共通する課題です。本記事では、教師ありモデルと人間の直感を組み合わせるアンサンブルフレームワークを紹介し、それぞれの限界を相互に補完する方法を提案します。移動平均チャネル戦略とリッジ回帰モデルを同じテクニカル指標上で整合させることで、集中管理、より速い自己修正、そして本来は収益性のなかったシステムからの利益創出を実現します。
MQL5取引ツール(第10回):視覚的なレベルとパフォーマンス指標を備えた戦略追跡システムの構築 MQL5取引ツール(第10回):視覚的なレベルとパフォーマンス指標を備えた戦略追跡システムの構築
移動平均線のクロスオーバーシグナルを検知し、長期移動平均線でフィルタリングした上で、利益確定(TP)や損切り(SL)をポイント単位で設定して取引をシミュレーションまたは実行し、結果をモニタリングするMQL5戦略トラッカーシステムを開発します。
MQL5でのAI搭載取引システムの構築(第5回):チャットポップアップを備えた折りたたみ可能なサイドバーの追加 MQL5でのAI搭載取引システムの構築(第5回):チャットポップアップを備えた折りたたみ可能なサイドバーの追加
連載第5回では、ChatGPT統合型エキスパートアドバイザー(EA)に折りたたみ可能なサイドバーを追加し、ナビゲーションを改善します。これにより、大小の履歴ポップアップからチャットをスムーズに選択できるようになり、従来の複数行入力処理、暗号化されたチャットの保存機能、チャートデータからのAIによる取引シグナル生成も維持されます。
取引戦略の開発:トリプルサイン平均回帰法 取引戦略の開発:トリプルサイン平均回帰法
新しい数学的指標であるTriple Sine Oscillator (TSO)に基づいて構築された「トリプルサイン平均回帰法」取引戦略を紹介します。TSOは、−1から+1の間で振動する正弦の三乗関数から導出されており、買われ過ぎおよび売られ過ぎの市場状況を特定するのに適しています。本記事では、数学的関数を実践的な取引ツールへと応用できることを示しています。