MQL5における取引戦略の自動化(第47回):ヘッジ機能を備えたNick Rypock Trailing Reverse (NRTR)
はじめに
前回の記事(第46回)では、MetaQuotes Language 5 (MQL5)を使用して、流動性スイープ取引システムを開発しました。このシステムでは流動性ゾーンを特定し、Break of Structure (BoS)を検出し、カスタマイズ可能なリスクパラメータと視覚的インジケータを用いて取引を実行します。第47回では、Nick Rypock Trailing Reverse (NRTR)システムを構築します。このシステムは、チャネルベースの反転シグナルを利用してトレンドフォロー型のエントリーを実行し、さらにヘッジ機能、動的トレーリングストップ、および各種リスク管理機能を統合します。本記事では以下のトピックを扱います。
記事を読み終える頃には、ヘッジ機能を備えたNRTRベースのトレンド転換売買システムを実装したMQL5プログラムが完成し、自由に拡張およびカスタマイズできる状態になります。それでは始めましょう。
Nick Rypock Trailing Reverse (NRTR)戦略とその構成要素の考察
Nick Rypock Trailing Reverse (NRTR)戦略は、NRTRチャネル内における動的なサポートレベルの形成を追跡することで、潜在的なトレンド反転を検出します。NRTRチャネルは、ATRの値にユーザー指定の係数を乗じて算出された上下の境界線によって構成され、市場ボラティリティに応じて動的に変化します。エントリーシグナルは、新たなサポートレベルが出現した際に生成されます。買いサポート(ロングチャネル)の開始時には買いポジションを、売りサポート(ショートチャネル)の開始時には売りポジションを建てることで、新たに形成されるトレンドを追従します。ヘッジ機能を有効にした場合は、買いポジションと売りポジションを同時に保有できます。リスク管理機能としては、口座残高、エクイティ、または余剰証拠金に応じてロットサイズを自動計算し、1回の取引あたりのリスクを制限します。また、固定値またはATRベースのストップロス/テイクプロフィット設定により、市場ボラティリティに応じた保護機能を提供します。
本システムでは、MQL5で入手可能な無料のNRTRチャネルインジケータを利用し、サポートレベルの切り替わりを検出して売買シグナルを生成します。さらに、ポジション数制限およびヘッジ設定を考慮したエントリーを管理をし、リスクパラメータに基づいてロットサイズやストップ水準を動的に算出します。また、トレーリングストップ機能および仮想決済機能を実装することで、保有ポジションを継続的に監視します。その結果、トレンド転換を捉える能力と厳格なリスク管理を両立した反転売買システムを実現します。要するに、本フレームワークはNick Rypock Trailing Reverse (NRTR)の考え方を中心としたボラティリティ適応型の取引手法を提供し、柔軟なリスク管理機能によって一貫した運用を可能にします。以下は、この戦略の構成を示した図です。

MQL5での実装
MQL5でプログラムを作成するには、まずMetaEditorを開き、ナビゲーターで[Experts]フォルダを探します。[新規]タブをクリックして指示に従い、ファイルを作成します。ファイルが作成されたら、コーディング環境で、まずプログラム全体で使用する入力パラメータとグローバル変数をいくつか宣言する必要があります。
//+------------------------------------------------------------------+ //| NRTR - Nick Rypock Trailing Reverse - EA.mq5 | //| Copyright 2026, Allan Munene Mutiiria. | //| https://t.me/Forex_Algo_Trader | //+------------------------------------------------------------------+ #property copyright "Copyright 2026, Allan Munene Mutiiria." #property link "https://t.me/Forex_Algo_Trader" #property version "1.00" #include <Trade\Trade.mqh> #include <Trade\PositionInfo.mqh> #include <Trade\SymbolInfo.mqh> //+------------------------------------------------------------------+ //| Enumerations | //+------------------------------------------------------------------+ enum ENUM_RISK_BASE { RISK_BASE_EQUITY = 1, // Equity RISK_BASE_BALANCE = 2, // Balance RISK_BASE_FREEMARGIN = 3 // Free Margin }; enum ENUM_RISK_DEFAULT_SIZE { RISK_DEFAULT_FIXED = 1, // Fixed RISK_DEFAULT_AUTO = 2 // Auto }; enum ENUM_MODE_SL { SL_FIXED = 0, // Fixed SL_AUTO = 1 // Auto }; enum ENUM_MODE_TP { TP_FIXED = 0, // Fixed TP_AUTO = 1 // Auto }; enum ENUM_TRAILING_TYPE { TRAILING_NONE = 0, // None TRAILING_POINTS = 1, // Points TRAILING_SUPPORT_LEVELS = 2 // Support Levels }; //+------------------------------------------------------------------+ //| Inputs | //+------------------------------------------------------------------+ input group "Risk Management Settings" input ENUM_RISK_DEFAULT_SIZE RiskDefaultSize = RISK_DEFAULT_FIXED; // Default Risk Size input double DefaultLotSize = 0.01; // Default Lot Size input ENUM_RISK_BASE RiskBase = RISK_BASE_BALANCE; // Risk Base input int MaxRiskPerTrade = 5; // Max Risk Per Trade (%) double MinLotSize = 0.01; // Min Lot Size double MaxLotSize = 100; // Max Lot Size input int MaxPositions = 1; // Max Positions input bool HedgeMode = true; // Hedge Mode bool CloseOnReversalSignal = false; // Close On Reversal Signal input group "Stop-Loss and Take-Profit Settings" input int DefaultStopLossPoints = 300; // Default Stop Loss (Points) input int DefaultTakeProfitPoints = 300; // Default Take Profit (Points) input bool CloseOnStopLossHit = false; // Close On Stop Loss Hit input bool CloseOnTakeProfitHit = false; // Close On Take Profit Hit input group "Additional Settings" input int MagicNumber = 1234567890; // Magic Number input group "Trailing Stop Loss" input ENUM_TRAILING_TYPE TrailingType = TRAILING_POINTS; // Trailing Type input ushort TrailingFrequencySeconds = 10; // Trailing Frequency (Seconds) input ushort SignalCheckFrequencySeconds = 10; // Signal Check Frequency (Seconds) input ushort TrailingStopPoints = 120; // Trailing Stop (Points) input ushort TrailingStepPoints = 100; // Trailing Step (Points) input ushort BreakEvenPoints = 10; // Break Even (Points) input ushort BreakEvenTriggerPoints = 30; // Break Even Trigger (Points) input group "NRTR Channel Settings" input int NRTR_ATR_Period = 40; // NRTR ATR Period input double NRTR_Multiplier = 2.0; // NRTR Multiplier input bool NRTR_Show_Price_Label = true; // Show Price Label //+------------------------------------------------------------------+ //| Global Variables | //+------------------------------------------------------------------+ double CurrentStopLossPoints = DefaultStopLossPoints; //--- Initialize current SL points double CurrentTakeProfitPoints = DefaultTakeProfitPoints; //--- Initialize current TP points double CurrentTrailingStopPoints = TrailingStopPoints; //--- Initialize current trailing stop points double CurrentTrailingStepPoints = TrailingStepPoints; //--- Initialize current trailing step points bool PrintLog = false; //--- Set print log flag datetime LastTrailingTime = 0; //--- Initialize last trailing time ENUM_MODE_SL StopLossMode = SL_FIXED; //--- Set SL mode ENUM_MODE_TP TakeProfitMode = TP_FIXED; //--- Set TP mode double AtrMultiplierForStopLoss = 2; //--- Set ATR multiplier for SL double AtrMultiplierForTakeProfit = 3; //--- Set ATR multiplier for TP CTrade TradeObject; //--- Declare trade object CSymbolInfo SymbolInfo; //--- Declare symbol info int TrendIndicatorHandle = -1; //--- Initialize trend handle double CurrentTrendValue, PreviousTrendValue; //--- Declare trend values double CurrentTrendDirection, PreviousTrendDirection; //--- Declare trend directions double CurrentUpSupport, PreviousUpSupport; //--- Declare up supports double CurrentDownSupport, PreviousDownSupport; //--- Declare down supports double CurrentNrtrAtr, PreviousNrtrAtr; //--- Declare NRTR ATR values
まず、取引操作、ポジション情報、および銘柄情報を扱うためのクラスを利用できるようにするため、「#include <Trade\Trade.mqh>」、「#include <Trade\PositionInfo.mqh>」、「#include <Trade\SymbolInfo.mqh>」といった重要なMQL5ライブラリをインクルードします。次に、ユーザー設定を体系的に管理するために、いくつかの列挙型を定義します。ENUM_RISK_BASE列挙型では、リスク計算の基準として「エクイティ」「残高」「余剰証拠金」を選択できるようにします。また、ENUM_RISK_DEFAULT_SIZEを作成し、ロットサイズを「固定」または「自動」で設定できるようにします。ストップロスおよびテイクプロフィットについては、ENUM_MODE_SLとENUM_MODE_TPを定義し、それぞれ「固定モード」または「自動モード」を選択できるようにします。さらに、トレーリングストップの種類を指定するためにENUM_TRAILING_TYPEを用意し、「なし」「ポイントベース」「サポートレベル連動」の各方式を選択できるようにします。
続いて、ユーザー入力項目をグループ化し、設定の使いやすさを向上させます。リスク管理セクションでは、デフォルトのロットサイズ計算方式を指定するRiskDefaultSize、固定ロット値を設定するDefaultLotSize、リスク計算の基準を選択するRiskBase、1回の取引あたりの最大リスク率を設定するMaxRiskPerTradeを用意します。また、最小ロットサイズと最大ロットサイズはグローバル変数として定義し、同時保有可能なポジション数を制限するMaxPositions、ヘッジを許可するHedgeMode、および反対シグナル発生時にポジションを決済するCloseOnReversalSignalを追加します。ストップロスおよびテイクプロフィットの設定では、ポイント単位で指定するDefaultStopLossPointsとDefaultTakeProfitPointsを用意し、それぞれのレベル到達時に自動決済するかどうかを設定するブール型オプションも追加します。
追加設定セクションでは、取引を識別するための一意の番号としてMagicNumberを設定します。また、トレーリングストップ関連のパラメータとして、TrailingType、トレーリング更新間隔(秒)、シグナル確認間隔(秒)、TrailingStopPoints、TrailingStepPoints、BreakEvenPoints、BreakEvenTriggerPointsを用意します。NRTRチャネル設定グループでは、インジケータの動作をプログラムから直接カスタマイズできるように、NRTR_ATR_Period、NRTR_Multiplier、NRTR_Show_Price_Labelを設定項目として追加します。
最後に、主要な値を管理するためのグローバル変数を宣言します。これには、現在使用するストップロス値やトレーリングストップ値、ログ出力フラグ、最後にトレーリング処理を実行した時刻、ストップロスおよびテイクプロフィットのモード、動的レベル計算用のATR倍率などが含まれます。また、CTradeおよびCSymbolInfoクラスのインスタンスも生成します。さらに、トレンドインジケータのハンドルは初期値として-1に設定し、現在および直前のトレンド値、方向、サポートレベル、NRTRのATR値を保持するためのdouble型変数を宣言します。次のステップでは、インジケータの初期化処理を実装します。コード全体の可読性、保守性、および拡張性を高めるため、必要に応じて関数を作成し、機能ごとに処理を分割したモジュール構造で実装を進めていきます。
//+------------------------------------------------------------------+ //| Initialize expert | //+------------------------------------------------------------------+ int OnInit() { if (!PreInitChecks()) return INIT_FAILED; //--- Perform pre-init checks SymbolInfo.Name(Symbol()); //--- Set symbol name if (!InitIndicatorHandles()) return INIT_FAILED; //--- Initialize indicator handles InitTradeObject(); //--- Initialize trade object return INIT_SUCCEEDED; //--- Return success } //+------------------------------------------------------------------+ //| Perform pre-init checks | //+------------------------------------------------------------------+ bool PreInitChecks() { if (MaxLotSize < MinLotSize) { //--- Check lot sizes Print("MaxLotSize cannot be less than MinLotSize"); //--- Print error return false; //--- Return failure } return true; //--- Return success } //+------------------------------------------------------------------+ //| Initialize indicator handles | //+------------------------------------------------------------------+ bool InitIndicatorHandles() { TrendIndicatorHandle = iCustom(_Symbol, _Period, "Free Indicators\\NRTR Channel", NRTR_ATR_Period, NRTR_Multiplier, NRTR_Show_Price_Label); //--- Create NRTR handle if (TrendIndicatorHandle == INVALID_HANDLE) { //--- Check handle PrintFormat("Error creating NRTR handle - %d", GetLastError()); //--- Print error return false; //--- Return failure } return true; //--- Return success } //+------------------------------------------------------------------+ //| Initialize trade object | //+------------------------------------------------------------------+ void InitTradeObject() { TradeObject.SetExpertMagicNumber(MagicNumber); //--- Set magic number }
OnInitイベントハンドラでは、まずPreInitChecksを呼び出して初期条件を検証します。このチェックに失敗した場合はINIT_FAILEDを返し、不正な設定のままプログラムが続行されないようにします。次に、現在の銘柄を使用してSymbolInfoオブジェクトに銘柄名を設定します。その後、InitIndicatorHandlesを呼び出してNRTRチャネルインジケータを読み込み、エラーが発生した場合はINIT_FAILEDを返します。続いて、InitTradeObjectを呼び出して取引オブジェクトを初期化し、最後にINIT_SUCCEEDEDを返して初期化が正常に完了したことを示します。
使用した関数について説明します。PreInitChecks関数は、MaxLotSizeがMinLotSize未満でないことを確認します。この条件に違反している場合はエラーメッセージを出力してfalseを返し、問題がなければtrueを返して初期化処理を続行できるようにします。InitIndicatorHandles内では、銘柄、時間足、インジケータパス、およびNRTR_ATR_Period、NRTR_Multiplier、NRTR_Show_Price_Labelなどのパラメータを指定して、iCustomを使用してTrendIndicatorHandleを作成します。ハンドルがINVALID_HANDLEの場合は、GetLastErrorを使用してエラー内容を出力し、falseを返します。それ以外の場合はtrueを返します。InitTradeObject関数では、プログラム固有の取引を識別するために、TradeObjectのマジックナンバーをMagicNumberに設定します。シグナルの主な生成元であるインジケータが正常に読み込まれていることを確認するため、プログラムを実行して検証することが非常に重要です。

今回の場合、インジケータは正常に読み込まれています。これでインジケータバッファの読み取りとシグナル生成ロジックの定義を進めることができます。まずはデータ取得ロジックから始めます。
//+------------------------------------------------------------------+ //| Fetch indicator data | //+------------------------------------------------------------------+ bool FetchIndicatorData() { double atrBuffer[]; //--- Declare ATR buffer as dynamic double trendBuffer[]; //--- Declare trend buffer as dynamic double upSupportBuffer[]; //--- Declare up support buffer as dynamic double downSupportBuffer[]; //--- Declare down support buffer as dynamic ArrayResize(atrBuffer, 2); //--- Resize to 2 ArrayResize(trendBuffer, 2); //--- Resize to 2 ArrayResize(upSupportBuffer, 2); //--- Resize to 2 ArrayResize(downSupportBuffer, 2); //--- Resize to 2 ArraySetAsSeries(atrBuffer, true); //--- Set as series ArraySetAsSeries(trendBuffer, true); //--- Set as series ArraySetAsSeries(upSupportBuffer, true); //--- Set as series ArraySetAsSeries(downSupportBuffer, true); //--- Set as series int copyCount; //--- Declare copy count bool dataReady = false; //--- Set data ready flag int maxAttempts = 5; //--- Set max attempts int delayMs = 200; //--- Set delay ms int attempt = 0; //--- Initialize attempt while (!dataReady && attempt < maxAttempts) { //--- Loop until ready dataReady = true; //--- Assume ready copyCount = CopyBuffer(TrendIndicatorHandle, 5, 1, 2, atrBuffer); //--- Copy ATR if (copyCount < 2 || atrBuffer[0] == EMPTY_VALUE) { //--- Check ATR copy dataReady = false; //--- Set not ready } else { //--- Set ATR values CurrentNrtrAtr = atrBuffer[0]; //--- Set current ATR (recent) PreviousNrtrAtr = atrBuffer[1]; //--- Set previous ATR (older) } copyCount = CopyBuffer(TrendIndicatorHandle, 4, 1, 2, trendBuffer); //--- Copy trend if (copyCount < 2) { //--- Check trend copy dataReady = false; //--- Set not ready } else { //--- Set trend directions CurrentTrendDirection = trendBuffer[0]; //--- Set current direction (recent) PreviousTrendDirection = trendBuffer[1]; //--- Set previous direction (older) } copyCount = CopyBuffer(TrendIndicatorHandle, 1, 1, 2, upSupportBuffer); //--- Copy up support if (copyCount < 2) { //--- Check up copy dataReady = false; //--- Set not ready } else { //--- Set up supports CurrentUpSupport = upSupportBuffer[0]; //--- Set current up (recent) PreviousUpSupport = upSupportBuffer[1]; //--- Set previous up (older) } copyCount = CopyBuffer(TrendIndicatorHandle, 2, 1, 2, downSupportBuffer); //--- Copy down support if (copyCount < 2) { //--- Check down copy dataReady = false; //--- Set not ready } else { //--- Set down supports CurrentDownSupport = downSupportBuffer[0]; //--- Set current down (recent) PreviousDownSupport = downSupportBuffer[1]; //--- Set previous down (older) } if (dataReady) { //--- Check ready CurrentTrendValue = (CurrentTrendDirection > 0) ? CurrentUpSupport : ((CurrentTrendDirection < 0) ? CurrentDownSupport : EMPTY_VALUE); //--- Set current trend value PreviousTrendValue = (PreviousTrendDirection > 0) ? PreviousUpSupport : ((PreviousTrendDirection < 0) ? PreviousDownSupport : EMPTY_VALUE); //--- Set previous trend value if (CurrentTrendValue == EMPTY_VALUE || PreviousTrendValue == EMPTY_VALUE) dataReady = false; //--- Check values } attempt++; //--- Increment attempt Sleep(delayMs); //--- Delay } if (!dataReady) { //--- Check final ready Print("Failed to fetch indicator data"); //--- Print error return false; //--- Return failure } return true; //--- Return success }
FetchIndicatorData関数を定義し、NRTRチャネルインジケータのバッファから値を取得して保存します。まず、atrBuffer、trendBuffer、upSupportBuffer、downSupportBufferの動的配列を宣言します。各配列はArrayResizeを使用して2要素分のサイズに設定し、ArraySetAsSeriesを使用して時系列配列として設定することで、最新のデータがインデックス0に配置されるようにします。次に、コピー件数を格納する変数、データ準備完了フラグ、最大再試行回数を5回、待機時間を200ミリ秒、そして0から開始する試行回数カウンターを初期化します。データの準備が完了するか、再試行回数の上限に達するまで継続するwhileループ内で、まずデータが利用可能であると仮定します。その後、CopyBufferを使用して、インジケータの各バッファから直近2本分のバーのデータを取得します。ATR値にはバッファ5、トレンド方向にはバッファ4、上側サポートにはバッファ1、下側サポートにはバッファ2を使用します。
いずれかのコピー結果が2未満であった場合、または最新のATR値がEMPTY_VALUEであった場合は、データ未準備として扱います。それ以外の場合は、現在の値(最新、インデックス0)と前回の値(1本前、インデックス1)を、CurrentNrtrAtr、PreviousNrtrAtr、CurrentTrendDirection、PreviousTrendDirection、CurrentUpSupport、PreviousUpSupport、CurrentDownSupport、PreviousDownSupportといったグローバル変数に格納します。すべてのバッファのコピーが成功した場合は、トレンド方向が正であればCurrentTrendValueにCurrentUpSupportを、負であればCurrentDownSupportを、それ以外の場合はEMPTY_VALUEを設定します。同様に、PreviousTrendValueも設定します。いずれかのトレンド値がEMPTY_VALUEである場合は、データ未準備として再設定します。
その後、試行回数カウンターを増加させ、再試行前にSleepで待機します。再試行後もデータを取得できない場合は、エラーメッセージを出力してfalseを返します。データ取得に成功した場合は、trueを返して正常にデータを取得できたことを示します。データの取得後は、シグナル生成のための分析処理に進むことができます。まずは、いくつかのヘルパー関数を定義します。
//+------------------------------------------------------------------+ //| Count open positions | //+------------------------------------------------------------------+ int CountOpenPositions() { int count = 0; //--- Initialize count int totalPositions = PositionsTotal(); //--- Get total positions for (int i = 0; i < totalPositions; i++) { //--- Loop positions if (PositionGetSymbol(i) != Symbol()) continue; //--- Skip wrong symbol if (MagicNumber != 0 && PositionGetInteger(POSITION_MAGIC) != MagicNumber) continue; //--- Skip wrong magic count++; //--- Increment count } return count; //--- Return count } //+------------------------------------------------------------------+ //| Count open positions by type | //+------------------------------------------------------------------+ int CountOpenPositionsByType(ENUM_POSITION_TYPE positionType) { int count = 0; //--- Initialize count int totalPositions = PositionsTotal(); //--- Get total positions for (int i = 0; i < totalPositions; i++) { //--- Loop positions if (PositionGetSymbol(i) != Symbol()) continue; //--- Skip wrong symbol if (MagicNumber != 0 && PositionGetInteger(POSITION_MAGIC) != MagicNumber) continue; //--- Skip wrong magic if (PositionGetInteger(POSITION_TYPE) == positionType) count++; //--- Increment if match } return count; //--- Return count } //+------------------------------------------------------------------+ //| Open buy position | //+------------------------------------------------------------------+ bool OpenBuyPosition(string positionType) { double askPrice = SymbolInfoDouble(Symbol(), SYMBOL_ASK); //--- Get ask price double stopLossPrice = CalculateStopLoss(ORDER_TYPE_BUY, askPrice); //--- Calculate SL double takeProfitPrice = CalculateTakeProfit(ORDER_TYPE_BUY, askPrice); //--- Calculate TP double positionSize = CalculatePositionSize(stopLossPrice, askPrice); //--- Calculate size string orderComment = positionType + " Buy"; //--- Set comment if (!TradeObject.Buy(positionSize, Symbol(), 0, stopLossPrice, takeProfitPrice, orderComment)) { //--- Open buy PrintFormat("Failed to open BUY: %d", TradeObject.ResultRetcode()); //--- Print error return false; //--- Return failure } PrintFormat("%s Buy Position Opened Successfully", positionType); //--- Print success return true; //--- Return success } //+------------------------------------------------------------------+ //| Open sell position | //+------------------------------------------------------------------+ bool OpenSellPosition(string positionType) { double bidPrice = SymbolInfoDouble(Symbol(), SYMBOL_BID); //--- Get bid price double stopLossPrice = CalculateStopLoss(ORDER_TYPE_SELL, bidPrice); //--- Calculate SL double takeProfitPrice = CalculateTakeProfit(ORDER_TYPE_SELL, bidPrice); //--- Calculate TP double positionSize = CalculatePositionSize(stopLossPrice, bidPrice); //--- Calculate size string orderComment = positionType + " Sell"; //--- Set comment if (!TradeObject.Sell(positionSize, Symbol(), 0, stopLossPrice, takeProfitPrice, orderComment)) { //--- Open sell PrintFormat("Failed to open SELL: %d", TradeObject.ResultRetcode()); //--- Print error return false; //--- Return failure } PrintFormat("%s Sell Position Opened Successfully", positionType); //--- Print success return true; //--- Return success } //+------------------------------------------------------------------+ //| Close all buy positions | //+------------------------------------------------------------------+ void CloseAllBuyPositions() { int total = PositionsTotal(); //--- Get total positions for (int i = total - 1; i >= 0; i--) { //--- Loop backward if (PositionGetSymbol(i) != Symbol()) continue; //--- Skip wrong symbol if (PositionGetInteger(POSITION_TYPE) != POSITION_TYPE_BUY) continue; //--- Skip non-buy if (PositionGetInteger(POSITION_MAGIC) != MagicNumber) continue; //--- Skip wrong magic TradeObject.PositionClose(PositionGetInteger(POSITION_TICKET)); //--- Close position } Print("All Buy Positions Closed"); //--- Print closed } //+------------------------------------------------------------------+ //| Close all sell positions | //+------------------------------------------------------------------+ void CloseAllSellPositions() { int total = PositionsTotal(); //--- Get total positions for (int i = total - 1; i >= 0; i--) { //--- Loop backward if (PositionGetSymbol(i) != Symbol()) continue; //--- Skip wrong symbol if (PositionGetInteger(POSITION_TYPE) != POSITION_TYPE_SELL) continue; //--- Skip non-sell if (PositionGetInteger(POSITION_MAGIC) != MagicNumber) continue; //--- Skip wrong magic TradeObject.PositionClose(PositionGetInteger(POSITION_TICKET)); //--- Close position } Print("All Sell Positions Closed"); //--- Print closed } //+------------------------------------------------------------------+ //| Close all positions | //+------------------------------------------------------------------+ void CloseAllPositions() { int total = PositionsTotal(); //--- Get total positions for (int i = total - 1; i >= 0; i--) { //--- Loop backward if (PositionGetSymbol(i) != Symbol()) continue; //--- Skip wrong symbol if (PositionGetInteger(POSITION_MAGIC) != MagicNumber) continue; //--- Skip wrong magic TradeObject.PositionClose(PositionGetInteger(POSITION_TICKET)); //--- Close position } Print("All Positions Closed"); //--- Print closed } //+------------------------------------------------------------------+ //| Calculate stop loss | //+------------------------------------------------------------------+ double CalculateStopLoss(ENUM_ORDER_TYPE orderType, double entryPrice) { double stopLossPrice = 0; //--- Initialize SL price if (StopLossMode == SL_FIXED) { //--- Check fixed mode if (DefaultStopLossPoints == 0) return 0; //--- Return zero if none double pointValue = SymbolInfoDouble(Symbol(), SYMBOL_POINT); //--- Get point value if (orderType == ORDER_TYPE_BUY) stopLossPrice = entryPrice - DefaultStopLossPoints * pointValue; //--- Set buy SL if (orderType == ORDER_TYPE_SELL) stopLossPrice = entryPrice + DefaultStopLossPoints * pointValue; //--- Set sell SL } else { //--- Handle auto mode if (orderType == ORDER_TYPE_BUY) stopLossPrice = entryPrice - PreviousNrtrAtr * AtrMultiplierForStopLoss; //--- Set buy SL if (orderType == ORDER_TYPE_SELL) stopLossPrice = entryPrice + PreviousNrtrAtr * AtrMultiplierForStopLoss; //--- Set sell SL } return NormalizeDouble(stopLossPrice, _Digits); //--- Normalize SL } //+------------------------------------------------------------------+ //| Calculate take profit | //+------------------------------------------------------------------+ double CalculateTakeProfit(ENUM_ORDER_TYPE orderType, double entryPrice) { double takeProfitPrice = 0; //--- Initialize TP price if (TakeProfitMode == TP_FIXED) { //--- Check fixed mode if (DefaultTakeProfitPoints == 0) return 0; //--- Return zero if none double pointValue = SymbolInfoDouble(Symbol(), SYMBOL_POINT); //--- Get point value if (orderType == ORDER_TYPE_BUY) takeProfitPrice = entryPrice + DefaultTakeProfitPoints * pointValue; //--- Set buy TP if (orderType == ORDER_TYPE_SELL) takeProfitPrice = entryPrice - DefaultTakeProfitPoints * pointValue; //--- Set sell TP } else { //--- Handle auto mode if (orderType == ORDER_TYPE_BUY) takeProfitPrice = entryPrice + PreviousNrtrAtr * AtrMultiplierForTakeProfit; //--- Set buy TP if (orderType == ORDER_TYPE_SELL) takeProfitPrice = entryPrice - PreviousNrtrAtr * AtrMultiplierForTakeProfit; //--- Set sell TP } return NormalizeDouble(takeProfitPrice, _Digits); //--- Normalize TP } //+------------------------------------------------------------------+ //| Calculate position size | //+------------------------------------------------------------------+ double CalculatePositionSize(double stopLossPrice, double entryPrice) { double size = DefaultLotSize; //--- Set default size if (RiskDefaultSize == RISK_DEFAULT_AUTO) { //--- Check auto risk if (stopLossPrice == 0) stopLossPrice = 200; //--- Set default SL if zero double riskBaseAmount = 0; //--- Initialize base amount double tickValue = SymbolInfoDouble(Symbol(), SYMBOL_TRADE_TICK_VALUE); //--- Get tick value if (RiskBase == RISK_BASE_BALANCE) riskBaseAmount = AccountInfoDouble(ACCOUNT_BALANCE); //--- Set balance base else if (RiskBase == RISK_BASE_EQUITY) riskBaseAmount = AccountInfoDouble(ACCOUNT_EQUITY); //--- Set equity base else if (RiskBase == RISK_BASE_FREEMARGIN) riskBaseAmount = AccountInfoDouble(ACCOUNT_MARGIN_FREE); //--- Set free margin base double stopLossDistancePoints = MathAbs(entryPrice - stopLossPrice) / SymbolInfoDouble(Symbol(), SYMBOL_POINT); //--- Compute distance size = (riskBaseAmount * MaxRiskPerTrade / 100) / (stopLossDistancePoints * tickValue); //--- Compute size } double lotStep = SymbolInfoDouble(Symbol(), SYMBOL_VOLUME_STEP); //--- Get lot step double maxLot = SymbolInfoDouble(Symbol(), SYMBOL_VOLUME_MAX); //--- Get max lot double minLot = SymbolInfoDouble(Symbol(), SYMBOL_VOLUME_MIN); //--- Get min lot size = MathFloor(size / lotStep) * lotStep; //--- Step size if (size > MaxLotSize) size = MaxLotSize; //--- Clamp max size if (size > maxLot) size = maxLot; //--- Clamp symbol max if (size < MinLotSize || size < minLot) size = 0; //--- Clamp min size return size; //--- Return size }
ヘルパー関数として、CountOpenPositions関数を作成し、現在の銘柄およびマジックナンバーに一致するすべてのオープンポジションをカウントします。関数内ではカウンターを初期化し、PositionsTotalで総ポジション数を取得し、0からその合計までループします。PositionGetSymbolを使用して銘柄が一致しないものをスキップし、PositionGetIntegerでPOSITION_MAGICを取得してMagicNumberが設定されている場合のみフィルタリングし、有効なものをカウントして最後に返します。同様に、CountOpenPositionsByType関数では、特定のポジションタイプ(買いまたは売りなど)のポジション数をカウントします。同じループ構造を使用しますが、PositionGetIntegerでPOSITION_TYPEを取得し、入力されたpositionTypeと一致するかを確認する条件を追加します。
取引のオープン処理として、OpenBuyPosition関数を定義します。この関数はpositionType文字列をコメントとして受け取ります。SymbolInfoDoubleでSYMBOL_ASKを取得してアスク価格を取得し、専用関数でストップロスとテイクプロフィットを計算し、ロットサイズを決定します。コメントは「positionType + ' Buy'」として作成します。TradeObject.Buyで買い注文を実行し、失敗した場合はretcodeを出力してエラーを表示し、成功時は結果を出力して結果を返します。OpenSellPositionも同様で、SYMBOL_BIDを使用してビッド価格を取得し、ストップを計算し、TradeObject.Sellを実行し、コメントは'Sell'とします。
決済処理として、CloseAllBuyPositionsは「PositionsTotal - 1」から0へ逆方向にループし、安全に削除します。銘柄が一致しないもの、POSITION_TYPE_BUYでないもの、またマジックが一致しないものをスキップし、有効なもののみPOSITION_TICKETを使用してTradeObject.PositionCloseで決済し、結果を出力します。CloseAllSellPositionsも同様に売りポジションに対してPOSITION_TYPE_SELLを確認します。CloseAllPositionsはタイプフィルタなしで、銘柄とマジックに一致するすべてのポジションを決済し、結果を出力します。
CalculateStopLossでは、注文タイプとエントリー価格に基づいてストップロス価格を計算します。StopLossModeが固定モードの場合、ポイントが0であれば0を返し、それ以外ではポイント値を取得し、買いの場合は価格からポイント分を引き、売りの場合は加算します。自動モードではPreviousNrtrAtrとAtrMultiplierForStopLossを使用して調整し、Digitsに基づいて正規化します。CalculateTakeProfitも同様に処理し、TakeProfitModeとAtrMultiplierForTakeProfitを使用して自動調整します。CalculatePositionSizeでは、まずデフォルトロットを設定しますが、自動リスクの場合はストップロスが0であればデフォルト距離を設定します。次にリスク基準額を決定し、ティックバリューを取得します。距離をポイントで計算し、ロットサイズを「risk amount / (distance × tick value)」として算出します。その後、MathFloorでロットステップに合わせて調整し、最小ロットと最大ロット(銘柄制限を含む)の間に収めて最終サイズを返します。これらの関数を使用することで、シグナルのチェックおよびポジションのオープン処理をスムーズに実行できるようになります。
//+------------------------------------------------------------------+ //| Check for entry signals | //+------------------------------------------------------------------+ void CheckForEntrySignals() { bool buySignal = (CurrentUpSupport != EMPTY_VALUE) && (PreviousUpSupport == EMPTY_VALUE); //--- Check buy signal (start of long support) bool sellSignal = (CurrentDownSupport != EMPTY_VALUE) && (PreviousDownSupport == EMPTY_VALUE); //--- Check sell signal (start of short support) if (buySignal) { //--- Handle buy signal string currUpStr = DoubleToString(CurrentUpSupport, _Digits); string prevUpStr = (PreviousUpSupport == EMPTY_VALUE) ? "EMPTY" : DoubleToString(PreviousUpSupport, _Digits); PrintFormat("Buy Signal Detected: Start of Long Support (Buffer 1 Recent: %s, Older: %s)", currUpStr, prevUpStr); //--- Print buy signal if ((CountOpenPositionsByType(POSITION_TYPE_BUY) == 0 && HedgeMode) || CountOpenPositions() < MaxPositions) { //--- Check open buys OpenBuyPosition("Initial Signal"); //--- Open buy } else { //--- Handle rejected Print("Buy Trade Rejected: Maximum positions reached or hedge mode restricts"); //--- Print rejected } } if (sellSignal) { //--- Handle sell signal string currDownStr = DoubleToString(CurrentDownSupport, _Digits); string prevDownStr = (PreviousDownSupport == EMPTY_VALUE) ? "EMPTY" : DoubleToString(PreviousDownSupport, _Digits); PrintFormat("Sell Signal Detected: Start of Short Support (Buffer 2 Recent: %s, Older: %s)", currDownStr, prevDownStr); //--- Print sell signal if ((CountOpenPositionsByType(POSITION_TYPE_SELL) == 0 && HedgeMode) || CountOpenPositions() < MaxPositions) { //--- Check open sells OpenSellPosition("Initial Signal"); //--- Open sell } else { //--- Handle rejected Print("Sell Trade Rejected: Maximum positions reached or hedge mode restricts"); //--- Print rejected } } }
ここではCheckForEntrySignals関数を実装し、NRTRチャネルの遷移に基づいてエントリーシグナルを検出し、取引を実行します。関数内では、まず買いシグナルをCurrentUpSupportがEMPTY_VALUEではなく、かつPreviousUpSupportがEMPTY_VALUEである場合に成立と定義します。これは買いサポートレベルの開始を示します。同様に、売りシグナルはCurrentDownSupportが有効であり、かつPreviousDownSupportがEMPTY_VALUEである場合に発生し、売りサポートの開始を示します。
買いシグナルが検出された場合、サポート値を文字列に変換し(EMPTY_VALUEの場合は前回値をEMPTYとして扱う)、バッファ1からの検出であることを示す詳細メッセージをフォーマットして出力します。その後、条件を確認します。ヘッジが有効であり、かつ買いポジションが存在しない場合、または総ポジション数がMaxPositions未満の場合にOpenBuyPositionをInitial Signalとして呼び出します。条件を満たさない場合は拒否メッセージを出力します。売りシグナルの場合も同様の処理をおこないます。バッファ2からの検出として詳細をフォーマットして出力し、ヘッジ条件により既存の売りポジションがない場合、またはポジション数が上限未満の場合にOpenSellPositionを呼び出します。条件を満たさない場合は拒否メッセージを出力します。この関数をティックイベントハンドラー内で呼び出すことで、シグナル検出と取引実行の結果を得ることができます。
//+------------------------------------------------------------------+ //| Handle tick event | //+------------------------------------------------------------------+ void OnTick() { ProcessEachTick(); //--- Process tick } //+------------------------------------------------------------------+ //| Process each tick | //+------------------------------------------------------------------+ void ProcessEachTick() { if (!FetchIndicatorData()) return; //--- Fetch indicator data static datetime lastBarTime = WRONG_VALUE; //--- Initialize last bar time datetime currentBarTime = iTime(Symbol(), Period(), 0); //--- Get current bar time static int newBarTicks = 0; //--- Initialize new bar ticks if (currentBarTime == lastBarTime) { //--- Check same bar newBarTicks++; //--- Increment ticks if (newBarTicks > 1) return; //--- Skip if more than 1 } else { //--- New bar newBarTicks = 0; //--- Reset ticks lastBarTime = currentBarTime; //--- Update last time } CheckForEntrySignals(); //--- Check entry signals }
OnTickイベントハンドラーでは、各価格更新時の処理をProcessEachTickの呼び出しに集約して扱います。ProcessEachTick内では、まずFetchIndicatorDataを使用して最新のインジケータデータを取得し、失敗した場合は早期に処理を終了して、必ず有効な情報に基づいて判断が下されるようにします。次に、新しいバーのみを効率的に処理するため、WRONG_VALUEで初期化された静的変数lastBarTimeを使用し、iTimeを用いて銘柄と時間足における現在のバーの開始時刻を取得します。また、同一バー内のティック数を管理するために、静的変数newBarTicksを0で初期化します。
現在のバー時間がlastBarTimeと一致する場合、すなわち同一バーである場合はnewBarTicksをインクリメントし、1を超える場合はそれ以上の処理をおこなわずに終了し、同一バー内での過剰なチェックを抑制します。一方、新しいバーの場合はnewBarTicksを0にリセットし、lastBarTimeを更新した上で処理を継続します。その後、CheckForEntrySignalsを呼び出し、更新されたデータに基づいてエントリーシグナルの評価および取引の実行をおこないます。コンパイルすると、次の結果が得られます。

画像から、シグナル生成に基づいて取引が実行されていることが確認できます。現在残っているのはエグジット戦略の処理であり、反対シグナルでの決済および仮想決済の処理を実装する段階です。
//+------------------------------------------------------------------+ //| Check for exit signals | //+------------------------------------------------------------------+ void CheckForExitSignals() { bool exitBuySignal = false; //--- Initialize buy exit bool exitSellSignal = false; //--- Initialize sell exit if (CloseOnReversalSignal) { //--- Check reversal close exitBuySignal = (CurrentDownSupport != EMPTY_VALUE) && (PreviousDownSupport == EMPTY_VALUE); //--- Set buy exit (start of short) exitSellSignal = (CurrentUpSupport != EMPTY_VALUE) && (PreviousUpSupport == EMPTY_VALUE); //--- Set sell exit (start of long) } if (exitBuySignal) { //--- Handle buy exit string currDownStr = DoubleToString(CurrentDownSupport, _Digits); string prevDownStr = (PreviousDownSupport == EMPTY_VALUE) ? "EMPTY" : DoubleToString(PreviousDownSupport, _Digits); PrintFormat("Exit Buy Signal Detected (Reversal): Start of Short Support (Buffer 2 Recent: %s, Older: %s)", currDownStr, prevDownStr); //--- Print buy exit CloseAllBuyPositions(); //--- Close buys } if (exitSellSignal) { //--- Handle sell exit string currUpStr = DoubleToString(CurrentUpSupport, _Digits); string prevUpStr = (PreviousUpSupport == EMPTY_VALUE) ? "EMPTY" : DoubleToString(PreviousUpSupport, _Digits); PrintFormat("Exit Sell Signal Detected (Reversal): Start of Long Support (Buffer 1 Recent: %s, Older: %s)", currUpStr, prevUpStr); //--- Print sell exit CloseAllSellPositions(); //--- Close sells } } //+------------------------------------------------------------------+ //| Close on virtual take profit | //+------------------------------------------------------------------+ void CloseOnVirtualTakeProfit(ulong ticket) { if (!PositionSelectByTicket(ticket)) return; //--- Select position double entryPrice = PositionGetDouble(POSITION_PRICE_OPEN); //--- Get entry price double virtualTakeProfit = 0; //--- Initialize virtual TP double ask = SymbolInfoDouble(Symbol(), SYMBOL_ASK); //--- Get ask double bid = SymbolInfoDouble(Symbol(), SYMBOL_BID); //--- Get bid if (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY) { //--- Check buy virtualTakeProfit = entryPrice + DefaultTakeProfitPoints * SymbolInfoDouble(Symbol(), SYMBOL_POINT); //--- Set buy TP if (virtualTakeProfit <= bid && CloseOnTakeProfitHit) { //--- Check hit TradeObject.PositionClose(ticket); //--- Close position PrintFormat("Position Closed on Virtual TP: Ticket %llu, Price Hit %.5f", ticket, virtualTakeProfit); //--- Print closed } } else { //--- Handle sell virtualTakeProfit = entryPrice - DefaultTakeProfitPoints * SymbolInfoDouble(Symbol(), SYMBOL_POINT); //--- Set sell TP if (virtualTakeProfit >= ask && CloseOnTakeProfitHit) { //--- Check hit TradeObject.PositionClose(ticket); //--- Close position PrintFormat("Position Closed on Virtual TP: Ticket %llu, Price Hit %.5f", ticket, virtualTakeProfit); //--- Print closed } } } //+------------------------------------------------------------------+ //| Close on virtual stop loss | //+------------------------------------------------------------------+ void CloseOnVirtualStopLoss(ulong ticket) { if (!PositionSelectByTicket(ticket)) return; //--- Select position double entryPrice = PositionGetDouble(POSITION_PRICE_OPEN); //--- Get entry price double virtualStopLoss = 0; //--- Initialize virtual SL double ask = SymbolInfoDouble(Symbol(), SYMBOL_ASK); //--- Get ask double bid = SymbolInfoDouble(Symbol(), SYMBOL_BID); //--- Get bid if (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY) { //--- Check buy virtualStopLoss = entryPrice - DefaultStopLossPoints * SymbolInfoDouble(Symbol(), SYMBOL_POINT); //--- Set buy SL if (virtualStopLoss >= ask && CloseOnStopLossHit) { //--- Check hit TradeObject.PositionClose(ticket); //--- Close position PrintFormat("Position Closed on Virtual SL: Ticket %llu, Price Hit %.5f", ticket, virtualStopLoss); //--- Print closed } } else { //--- Handle sell virtualStopLoss = entryPrice + DefaultStopLossPoints * SymbolInfoDouble(Symbol(), SYMBOL_POINT); //--- Set sell SL if (virtualStopLoss <= bid && CloseOnStopLossHit) { //--- Check hit TradeObject.PositionClose(ticket); //--- Close position PrintFormat("Position Closed on Virtual SL: Ticket %llu, Price Hit %.5f", ticket, virtualStopLoss); //--- Print closed } } } //+------------------------------------------------------------------+ //| Handle virtual closures | //+------------------------------------------------------------------+ void HandleVirtualClosures() { int total = PositionsTotal(); //--- Get total positions for (int i = total - 1; i >= 0; i--) { //--- Loop backward if (PositionGetSymbol(i) == "") continue; //--- Skip invalid ulong ticket = PositionGetInteger(POSITION_TICKET); //--- Get ticket if (PositionGetString(POSITION_SYMBOL) != Symbol() || PositionGetInteger(POSITION_MAGIC) != MagicNumber) continue; //--- Skip wrong symbol/magic if (CloseOnStopLossHit) CloseOnVirtualStopLoss(ticket); //--- Check virtual SL if (CloseOnTakeProfitHit) CloseOnVirtualTakeProfit(ticket); //--- Check virtual TP } }
まず、CheckForExitSignals関数を開発し、既存ポジションを決済する必要がある条件を監視します。この処理では、まず買いと売りのエグジット用フラグをfalseで初期化します。CloseOnReversalSignalが有効な場合、売りサポートの開始(現在のダウンサポートが有効で、前回がEMPTY_VALUE)を検出したときにexitBuySignalをtrueに設定します。同様に、買いサポートの開始を検出した場合にはexitSellSignalを設定します。エグジットの買いシグナルが発生した場合、ダウンサポートの値を文字列として整形し(EMPTY_VALUEは「EMPTY」として扱う)、バッファ2からの詳細メッセージを出力し、すべての買いポジションを決済するためにCloseAllBuyPositionsを呼び出します。同様に、売りのエグジットシグナルが発生した場合は、バッファ1の情報を出力し、CloseAllSellPositionsを呼び出して売りポジションをすべて決済します。
次に、CloseOnVirtualTakeProfit関数では、指定されたチケットのポジションに対して仮想テイクプロフィットを管理します。まずPositionSelectByTicketでポジションを選択し、失敗した場合は処理を終了します。その後エントリー価格を取得します。買いポジションの場合は、DefaultTakeProfitPointsにポイント値を掛けたものをエントリー価格に加算してvirtualTakeProfitを計算します。売りポジションの場合は減算します。その後、AskとBid価格を取得し、買いの場合はTP <= Bid、売りの場合はTP >= Askを満たしたときに、かつCloseOnTakeProfitHitが有効であればTradeObject.PositionCloseを実行してポジションを決済し、チケット番号とヒット価格を出力します。
同様に、CloseOnVirtualStopLoss関数では仮想ストップロスを処理します。ポジション選択後にエントリー価格を取得し、買いの場合はポイント分を引き、売りの場合は加算してvirtualStopLossを計算します。AskとBidを用いて、買いではSL >= Ask、売りではSL <= Bidの条件を満たし、かつCloseOnStopLossHitが有効な場合にポジションを決済し、詳細を出力します。HandleVirtualClosures関数では、すべてのポジションを安全に処理するために「PositionsTotal - 1」から0へ逆方向にループします。各ポジションについて、まず銘柄が無効であればスキップします。その後チケットを取得し、銘柄またはマジックナンバーが一致しない場合は処理を継続します。CloseOnStopLossHitが有効であればCloseOnVirtualStopLossを呼び出し、CloseOnTakeProfitHitが有効であればCloseOnVirtualTakeProfitを呼び出して、すべてのオープンポジションに対して仮想決済処理を適用します。さらに、トレーリングタイプが有効な場合には、ポイントベースまたはサポートレベルベースのいずれかに応じてトレーリング処理を実装する必要があります。
//+------------------------------------------------------------------+ //| Adjust to break even | //+------------------------------------------------------------------+ bool AdjustToBreakEven(ulong ticket) { if (TrailingType == TRAILING_NONE) return false; //--- Check trailing type if (!PositionSelectByTicket(ticket)) return false; //--- Select position MqlTradeRequest request = {}; //--- Declare request MqlTradeResult result = {}; //--- Declare result request.action = TRADE_ACTION_SLTP; //--- Set action SLTP request.position = ticket; //--- Set position ticket long positionType = PositionGetInteger(POSITION_TYPE); //--- Get type double entryPrice = PositionGetDouble(POSITION_PRICE_OPEN); //--- Get entry price double currentStopLoss = PositionGetDouble(POSITION_SL); //--- Get current SL double currentTakeProfit = PositionGetDouble(POSITION_TP); //--- Get current TP string symbol = PositionGetString(POSITION_SYMBOL); //--- Get symbol double point = SymbolInfoDouble(symbol, SYMBOL_POINT); //--- Get point double currentPrice = (positionType == POSITION_TYPE_BUY) ? SymbolInfoDouble(symbol, SYMBOL_BID) : SymbolInfoDouble(symbol, SYMBOL_ASK); //--- Get current price double currentProfitPoints = (positionType == POSITION_TYPE_BUY) ? (currentPrice - entryPrice) / point : (entryPrice - currentPrice) / point; //--- Compute profit points double newStopLoss = 0; //--- Initialize new SL if (currentProfitPoints <= 0) return false; //--- Skip if no profit switch (TrailingType) { //--- Switch trailing type case TRAILING_POINTS: { //--- Handle points if (currentProfitPoints >= BreakEvenTriggerPoints) { //--- Check BE trigger double bePoints = BreakEvenPoints; //--- Set BE points newStopLoss = (positionType == POSITION_TYPE_BUY) ? NormalizeDouble(entryPrice + (bePoints * point), _Digits) : NormalizeDouble(entryPrice - (bePoints * point), _Digits); //--- Compute new SL if ((positionType == POSITION_TYPE_BUY && newStopLoss > currentStopLoss) || (positionType == POSITION_TYPE_SELL && newStopLoss < currentStopLoss)) { //--- Check improvement if (NormalizeDouble(newStopLoss, _Digits) != NormalizeDouble(currentStopLoss, _Digits)) { //--- Check different request.sl = newStopLoss; //--- Set SL request.tp = currentTakeProfit; //--- Set TP if (OrderSend(request, result)) { //--- Send order PrintFormat("Breakeven SL Adjusted for %s Position: Ticket %llu, New SL = %.5f (Triggered at %.0f points profit)", (positionType == POSITION_TYPE_BUY) ? "Buy" : "Sell", ticket, newStopLoss, currentProfitPoints); //--- Print adjusted return true; //--- Return success } } } } if (currentProfitPoints >= CurrentTrailingStopPoints) { //--- Check trailing trigger double trailPoints = currentProfitPoints - CurrentTrailingStepPoints; //--- Compute trail points newStopLoss = (positionType == POSITION_TYPE_BUY) ? NormalizeDouble(currentPrice - (CurrentTrailingStopPoints * point), _Digits) : NormalizeDouble(currentPrice + (CurrentTrailingStopPoints * point), _Digits); //--- Compute new SL if ((positionType == POSITION_TYPE_BUY && newStopLoss > currentStopLoss) || (positionType == POSITION_TYPE_SELL && newStopLoss < currentStopLoss)) { //--- Check improvement if (NormalizeDouble(newStopLoss, _Digits) != NormalizeDouble(currentStopLoss, _Digits)) { //--- Check different request.sl = newStopLoss; //--- Set SL request.tp = currentTakeProfit; //--- Set TP if (OrderSend(request, result)) { //--- Send order PrintFormat("Trailing SL Adjusted for %s Position: Ticket %llu, New SL = %.5f (Current Profit = %.0f points)", (positionType == POSITION_TYPE_BUY) ? "Buy" : "Sell", ticket, newStopLoss, currentProfitPoints); //--- Print adjusted return true; //--- Return success } } } } break; } case TRAILING_SUPPORT_LEVELS: { //--- Handle support levels double currentSupport = (positionType == POSITION_TYPE_BUY) ? CurrentUpSupport : CurrentDownSupport; //--- Get current support double previousSupport = (positionType == POSITION_TYPE_BUY) ? PreviousUpSupport : PreviousDownSupport; //--- Get previous support if (currentSupport == EMPTY_VALUE) return false; //--- Skip if invalid support if (currentProfitPoints >= BreakEvenTriggerPoints) { //--- Check BE trigger newStopLoss = currentSupport; //--- Set new SL to support bool isProfitable = (positionType == POSITION_TYPE_BUY && newStopLoss >= entryPrice) || (positionType == POSITION_TYPE_SELL && newStopLoss <= entryPrice); //--- Ensure beyond entry if (isProfitable && ((positionType == POSITION_TYPE_BUY && newStopLoss > currentStopLoss) || (positionType == POSITION_TYPE_SELL && newStopLoss < currentStopLoss))) { //--- Check improvement and profitable if (NormalizeDouble(newStopLoss, _Digits) != NormalizeDouble(currentStopLoss, _Digits)) { //--- Check different request.sl = newStopLoss; //--- Set SL request.tp = currentTakeProfit; //--- Set TP if (OrderSend(request, result)) { //--- Send order PrintFormat("Breakeven SL Adjusted to Support Level for %s Position: Ticket %llu, New SL = %.5f (Triggered at %.0f points profit)", (positionType == POSITION_TYPE_BUY) ? "Buy" : "Sell", ticket, newStopLoss, currentProfitPoints); //--- Print adjusted return true; //--- Return success } } } } if ((positionType == POSITION_TYPE_BUY && currentSupport > previousSupport) || (positionType == POSITION_TYPE_SELL && currentSupport < previousSupport)) { //--- Check support moved newStopLoss = currentSupport; //--- Set new SL to support bool isProfitable = (positionType == POSITION_TYPE_BUY && newStopLoss >= entryPrice) || (positionType == POSITION_TYPE_SELL && newStopLoss <= entryPrice); //--- Ensure beyond entry if (isProfitable && ((positionType == POSITION_TYPE_BUY && newStopLoss > currentStopLoss) || (positionType == POSITION_TYPE_SELL && newStopLoss < currentStopLoss))) { //--- Check improvement and profitable if (NormalizeDouble(newStopLoss, _Digits) != NormalizeDouble(currentStopLoss, _Digits)) { //--- Check different request.sl = newStopLoss; //--- Set SL request.tp = currentTakeProfit; //--- Set TP if (OrderSend(request, result)) { //--- Send order PrintFormat("SL Trailed to Support Level for %s Position: Ticket %llu, New SL = %.5f (Support Moved from %.5f to %.5f)", (positionType == POSITION_TYPE_BUY) ? "Buy" : "Sell", ticket, newStopLoss, previousSupport, currentSupport); //--- Print trailed return true; //--- Return success } } } } break; } default: break; } return false; //--- Return no adjustment } //+------------------------------------------------------------------+ //| Adjust all stop losses | //+------------------------------------------------------------------+ void AdjustAllStopLosses() { for (int i = PositionsTotal() - 1; i >= 0; i--) { //--- Loop backward if (PositionGetSymbol(i) == "") continue; //--- Skip invalid ulong ticket = PositionGetInteger(POSITION_TICKET); //--- Get ticket AdjustToBreakEven(ticket); //--- Adjust to BE } }
ここでは、AdjustToBreakEven関数を構築し、チケットで識別される特定のポジションに対してトレーリングおよびブレークイーブン調整を管理します。TrailingTypeがTRAILING_NONEの場合、またはPositionSelectByTicketでポジション選択に失敗した場合は、即座にfalseを返します。次に、MqlTradeRequestおよびMqlTradeResultの構造体を準備し、ストップロスとテイクプロフィットを変更するためにTRADE_ACTION_SLTPをrequest.actionに設定し、ポジションチケットを割り当てます。ポジションタイプ、エントリー価格、現在のストップロス、テイクプロフィット、銘柄、およびポイント値を取得します。現在価格は買いの場合はBid、売りの場合はAskを使用し、エントリーからの差分をポイント換算したcurrentProfitPointsを計算します。利益が発生していない場合は処理をスキップしfalseを返します。
TrailingTypeに対するswitch処理では、まずTRAILING_POINTSの場合を扱います。利益がBreakEvenTriggerPointsに到達しているかを確認し、到達している場合はブレークイーブン用の新しいストップロスを計算します。買いポジションでは「entry + BreakEvenPoints * point」、売りポジションでは「entry - BreakEvenPoints * point」とし、正規化します。現在のストップロスよりも有利であり(買いなら高い、売りなら低い)、かつ差分がある場合のみ、リクエストにストップロスとテイクプロフィットを設定し、OrderSendで送信します。成功した場合はログを出力し、trueを返します。同じくポイントモード内で、利益がCurrentTrailingStopPointsを超えている場合にはトレーリングストップを計算します。現在価格から「CurrentTrailingStopPoints * point」を減算または加算し、より有利であることと差分があることを確認した上で更新リクエストを送信し、成功時にログを出力してtrueを返します。
TRAILING_SUPPORT_LEVELSの場合、現在および前回のサポート値をポジションタイプに応じて取得し、現在値がEMPTYであればスキップします。利益がブレークイーブン条件に達している場合は、ストップロスを現在のサポートに設定します。このとき、エントリー価格以上であること(買いの場合)または以下であること(売りの場合)を確認し、改善されておりかつ差分がある場合のみ更新リクエストを送信し、成功時に詳細を出力します。サポートが有利方向に移動した場合(買いなら上昇、売りなら下降)にも同様に更新し、現在のサポートが利益圏内かつ改善されていることを確認したうえでストップロスを更新し、変更内容をログ出力してtrueを返します。どの条件にも該当しない場合はbreakし、調整されなかった場合はfalseを返します。AdjustAllStopLosses関数では、すべてのポジションを安全に処理するために「PositionsTotal- 1から0へ逆方向にループします。各ポジションについて無効な銘柄をスキップし、チケットを取得した後、各ポジションに対してAdjustToBreakEvenを呼び出し、すべてのオープントレードに対して調整処理を適用します。これにより、これらすべての関数を管理関数内で呼び出し、ティックイベントごとのポジション管理処理を完成させることができます。最終的な関数は以下のようになります。
//+------------------------------------------------------------------+ //| Process each tick | //+------------------------------------------------------------------+ void ProcessEachTick() { if (!FetchIndicatorData()) return; //--- Fetch indicator data int positionCount = CountOpenPositions(); //--- Count positions if (positionCount > 0) { //--- Check positions exist CheckForExitSignals(); //--- Check exit signals } static datetime lastBarTime = WRONG_VALUE; //--- Initialize last bar time datetime currentBarTime = iTime(Symbol(), Period(), 0); //--- Get current bar time static int newBarTicks = 0; //--- Initialize new bar ticks if (currentBarTime == lastBarTime) { //--- Check same bar newBarTicks++; //--- Increment ticks if (newBarTicks > 1) return; //--- Skip if more than 1 } else { //--- New bar newBarTicks = 0; //--- Reset ticks lastBarTime = currentBarTime; //--- Update last time } CheckForEntrySignals(); //--- Check entry signals datetime currentTime = TimeCurrent(); //--- Get current time if (currentTime - LastTrailingTime >= TrailingFrequencySeconds) { //--- Check trailing time AdjustAllStopLosses(); //--- Adjust SLs LastTrailingTime = currentTime; //--- Update trailing time } HandleVirtualClosures(); //--- Handle virtual closures }
各関数を条件に応じて呼び出すことで、コンパイル時に以下の結果が得られます。

画像から、NRTRシグナルのセットアップを検出し、取引し、管理していることが確認できます。これにより、目的は達成されています。残っている作業は、このプログラムのバックテストをおこなうことです。バックテストについては次のセクションで扱います。
バックテスト
バックテストを実施した結果、以下の結果が得られました。
バックテストグラフ

バックテストレポート

結論
MQL5でNick Rypock Trailing Reverse(NRTR)取引システムを構築しました。このシステムは、動的チャネルを通じて反転シグナルを検出し、買いと売りの両方に対応したヘッジ付きトレンドフォローエントリーをサポートし、ポジション制限によってリスクエクスポージャーを制御します。また、エクイティ、残高、余剰証拠金に基づく自動ロット計算や、固定またはATR調整型のストップロスおよびテイクプロフィットなどのリスク管理機能も追加しました。
免責条項:本記事は教育目的のみを意図したものです。取引には重大な財務リスクが伴い、市場の変動によって損失が生じる可能性があります。本プログラムを実際の市場で運用する前に、十分なバックテストと慎重なリスク管理が不可欠です。
このNRTRリバーサル戦略により、トレンド機会を捉えつつヘッジとトレーリング保護を備えた運用が可能になり、さらなる改善にも対応できる設計となっています。取引をお楽しみください。
MetaQuotes Ltdにより英語から翻訳されました。
元の記事: https://www.mql5.com/en/articles/21096
警告: これらの資料についてのすべての権利はMetaQuotes Ltd.が保有しています。これらの資料の全部または一部の複製や再プリントは禁じられています。
この記事はサイトのユーザーによって執筆されたものであり、著者の個人的な見解を反映しています。MetaQuotes Ltdは、提示された情報の正確性や、記載されているソリューション、戦略、または推奨事項の使用によって生じたいかなる結果についても責任を負いません。
ダイナミックマルチペアEAの形成(第6回):高頻度銘柄切り替えのための適応型スプレッド感度制御
MQL5でカスタムインジケータを作成する(第6回):平滑化、色相シフト、マルチタイムフレーム対応を備えたRSI計算の拡張
初心者からエキスパートへ:サプライ&デマンドゾーンの統計的検証
MQL5取引ツール(第14回):アンチエイリアシングと角丸スクロールバーを備えたピクセルパーフェクトなスクロール対応テキストキャンバス
- 無料取引アプリ
- 8千を超えるシグナルをコピー
- 金融ニュースで金融マーケットを探索