MQL5での取引戦略の自動化(第40回):カスタムレベルを使ったフィボナッチリトレースメント取引
はじめに
前回の記事(第39回)では、MetaQuotes Language 5 (MQL5)で統計的平均回帰システムを開発しました。このシステムは、平均、分散、歪度、尖度、ジャック=ベラ統計量などの価格データを分析し、適応型閾値と上位時間足の確認を伴う信頼区間に基づいて回帰シグナルを生成し、エクイティベースのポジションサイジング、トレーリングストップ、部分決済、時間ベースのエグジットで取引を管理しながら、リアルタイム監視のためのオンチャートダッシュボードを提供しました。第40回では、カスタムレベルを備えたフィボナッチリトレースメント取引システムを開発します。
このシステムは、日足の値幅またはルックバック配列を用いてリトレースメントレベルを計算し、終値と始値の比較に基づいて強気または弱気のセットアップを識別し、50%や61.8%といった指定レベルの価格のクロス時にエントリーをトリガーし、最大取引回数の制限を設けます。また、新しいフィボナッチ計算時の任意の決済、利益閾値到達後のポイントベースのトレーリングストップ、値幅の割合として設定されるSL/TPバッファも含まれます。本記事では以下のトピックを扱います。
記事を読み終える頃には、フィボナッチリトレースメント取引戦略の実用的な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を使って_SymbolとPERIOD_D1マクロで日足の数を取得します。新しい日足が形成され、かつTimeCurrentとStringToTimeで現在時刻が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にかけてアンカーし、ObjectSetIntegerでOBJPROP_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で既存ラベルを確認し、新しい場合はObjectCreateでOBJ_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でシリーズとして設定し、CopyHighとCopyLowで値を埋めます。失敗した場合は早期に返します。その後、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を削除し、チャート上の表示をクリアします。コンパイルすると、次の結果が得られます。

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

バックテストレポート

結論
MQL5で、日足または履歴配列を使ってフィボナッチリトレースメントレベルを計算し、終値と始値を比較して強気と弱気のセットアップを判定し、カスタム比率のクロス時に買いと売りを実行してレベルごとの取引制限を適用するフィボナッチリトレースメント取引システムを開発しました。再計算時にはポジションをオプションで決済でき、利益閾値到達後にはポイントベースのトレーリングストップを適用します。また、値幅バッファを考慮したエントリーレベルを設定し、チャート上に可視化と情報ラベルを表示して補助しています。
免責条項:本記事は教育目的のみを意図したものです。取引には重大な財務リスクが伴い、市場の変動によって損失が生じる可能性があります。本プログラムを実際の市場で運用する前に、十分なバックテストと慎重なリスク管理が不可欠です。
このフィボナッチリトレースメント戦略を用いることで、プルバックのチャンスを効率的に取引でき、さらに最適化して取引戦略を発展させる準備が整います。取引をお楽しみください。
MetaQuotes Ltdにより英語から翻訳されました。
元の記事: https://www.mql5.com/en/articles/20221
警告: これらの資料についてのすべての権利はMetaQuotes Ltd.が保有しています。これらの資料の全部または一部の複製や再プリントは禁じられています。
この記事はサイトのユーザーによって執筆されたものであり、著者の個人的な見解を反映しています。MetaQuotes Ltdは、提示された情報の正確性や、記載されているソリューション、戦略、または推奨事項の使用によって生じたいかなる結果についても責任を負いません。
MQL5で自己最適化エキスパートアドバイザーを構築する(第17回):アンサンブルインテリジェンス
MQL5取引ツール(第10回):視覚的なレベルとパフォーマンス指標を備えた戦略追跡システムの構築
MQL5でのAI搭載取引システムの構築(第5回):チャットポップアップを備えた折りたたみ可能なサイドバーの追加
取引戦略の開発:トリプルサイン平均回帰法
- 無料取引アプリ
- 8千を超えるシグナルをコピー
- 金融ニュースで金融マーケットを探索