English Deutsch
preview
MQL5での取引戦略の自動化(第14回):MACD-RSI統計手法を用いた取引レイヤリング戦略

MQL5での取引戦略の自動化(第14回):MACD-RSI統計手法を用いた取引レイヤリング戦略

MetaTrader 5トレーディング |
82 0
Allan Munene Mutiiria
Allan Munene Mutiiria

はじめに

前回の記事(第13回)では、MetaQuotes Language 5 (MQL5)を使用して三尊天井パターンを自動化し、市場の反転ポイントを捉える取引アルゴリズムを構築しました。今回の第14回では、トレンド市場におけるポジションの動的スケーリングを目的とした取引レイヤリング戦略に焦点を当てます。この戦略では、MACD(移動平均収束拡散法)Relative Strength Index(相対力指数)(RSI)をベースに、統計的な手法を加えることで、より洗練された判断を可能にします。次のトピックについて説明します。

  1. 戦略アーキテクチャ
  2. MQL5での実装
  3. バックテスト
  4. 結論

この記事を読み終えるまでには、精度の高い取引レイヤリングを実現する堅牢なエキスパートアドバイザー(EA)が完成していることでしょう。


戦略アーキテクチャ

本記事で取り上げる取引レイヤリング戦略は、持続的な市場トレンドを活かして、価格が有利な方向に動くたびに段階的にポジションを追加していく「カスケード方式」を採用しています。これは、単一のエントリーで一定の利確を目指す従来の戦略とは異なり、モメンタムを活用して利益を拡大しつつ、リスクを管理することを目的としています。利益が一定水準に達するたびに新たなポジションを追加し、複利的な利益成長を狙います。この戦略の中核には、広く知られる2つのテクニカル指標であるMACD(移動平均収束拡散)とRSI(相対力指数)を組み合わせた統計的な判断フィルターがあり、タイミングの正確さと信頼性を両立させた設計となっています。そのため、明確な方向性を持つ市場では特に効果を発揮します。

MACDとRSIの強みを活用して、取引シグナルの強固な基盤を確立し、レイヤリングプロセスをいつ開始するかについての明確なルールを設定します。計画には、MACDを使用してトレンドの方向と強さを確認し、市場が一貫したバイアスを示している場合にのみ取引を開始することが含まれます。一方、RSIは、極端な価格レベルからのシフトを検出することで最適なエントリーの瞬間を正確に特定します。これらのインジケーターを統合することで、最初の取引を開始する信頼性の高いトリガーの仕組みを作成することを目指しています。このメカニズムは、その後、カスケードシーケンスの開始点として機能し、トレンドの進行に合わせてポジションを構築できるようになります。ここに戦略の視覚化があります。

戦略設計図

次に、この戦略をさらに強化するために統計的手法を導入し、エントリー精度を高めてレイヤリングプロセスを最適化します。具体的には、RSI(相対力指数)の過去の動きを分析するなど、統計的フィルターを活用してシグナルを検証し、統計的に有意な条件下でのみ取引を実行するようにします。この計画はさらに進み、レイヤリングルールの定義へと展開していきます。ここでは、利益目標に達した際にどのように新たな取引を追加していくかを明確にし、それに応じてリスクレベルを調整することで利益を保護する仕組みを構築します。最終的には、市場のモメンタムに適応しながらも、規律ある実行を維持する動的な戦略を目指します。では、さっそく始めましょう。


MQL5での実装

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

//+------------------------------------------------------------------+
//|                                  MACD-RSI LAYERING STRATEGY.mq5  |
//|      Copyright 2025, ALLAN MUNENE MUTIIRIA. #@Forex Algo-Trader. |
//|                           https://youtube.com/@ForexAlgo-Trader? |
//+------------------------------------------------------------------+
#property copyright "Copyright 2025, ALLAN MUNENE MUTIIRIA. #@Forex Algo-Trader"
#property link      "https://youtube.com/@ForexAlgo-Trader?"
#property description "MACD-RSI-based layering strategy with adjustable Risk:Reward and visual levels"
#property version   "1.0"

#include <Trade\Trade.mqh>//---- Includes the Trade.mqh library for trading operations
CTrade obj_Trade;//---- Declares a CTrade object for executing trade operations

int rsiHandle = INVALID_HANDLE;//---- Initializes RSI indicator handle as invalid
double rsiValues[];//---- Declares an array to store RSI values

int handleMACD = INVALID_HANDLE;//---- Initializes MACD indicator handle as invalid
double macdMAIN[];//---- Declares an array to store MACD main line values
double macdSIGNAL[];//---- Declares an array to store MACD signal line values

double takeProfitLevel = 0;//---- Initializes the take profit level variable
double stopLossLevel = 0;//---- Initializes the stop loss level variable
bool buySequenceActive = false;//---- Flag to track if a buy sequence is active
bool sellSequenceActive = false;//---- Flag to track if a sell sequence is active

// Inputs with clear names
input int stopLossPoints = 300;        // Initial Stop Loss (points)
input double tradeVolume = 0.01;       // Trade Volume (lots)
input int minStopLossPoints = 100;     // Minimum SL for cascading orders (points)
input int rsiLookbackPeriod = 14;      // RSI Lookback Period
input double rsiOverboughtLevel = 70.0; // RSI Overbought Threshold
input double rsiOversoldLevel = 30.0;   // RSI Oversold Threshold
input bool useStatisticalFilter = true; // Enable Statistical Filter
input int statAnalysisPeriod = 20;      // Statistical Analysis Period (bars)
input double statDeviationFactor = 1.0; // Statistical Deviation Factor
input double riskRewardRatio = 1.0;     // Risk:Reward Ratio

// Object names for visualization
string takeProfitLineName = "TakeProfitLine";//---- Name of the take profit line object for chart visualization
string takeProfitTextName = "TakeProfitText";//---- Name of the take profit text object for chart visualization


まず、Trade.mqhライブラリを組み込み、すべての取引操作を処理するために「obj_Trade」という名前のCTradeオブジェクトを宣言して、取引階層化戦略に不可欠なフレームワークを設定します。主要なインジケーターハンドル(RSIの場合はrsiHandle、MACDの場合はhandleMACD)を初期化します。どちらも最初はINVALID_HANDLEに設定されます。それぞれのデータを格納するためのrsiValues、macdMAIN、macdSIGNALなどの配列も初期化します。戦略の状態を追跡するために、取引レベル用のtakeProfitLevelやstopLossLevelなどの変数と、買いまたは売りの階層化シーケンスが進行中かどうかを監視するためのブールフラグ「buySequenceActive」や「sellSequenceActive」を定義し、システムがいつ取引をカスケードするかを認識できるようにします。

次に、戦略に柔軟性を持たせるために、ユーザーが設定可能な入力項目を定義します。具体的には、初期ストップロスの距離を指定するstopLossPoints、取引ロット数を指定するtradeVolume、カスケード取引(段階的エントリー)でよりタイトなストップを使うためのminStopLossPointsなどです。インジケーターに関しては、RSIの計算期間を定義するrsiLookbackPeriod、エントリーの閾値となるrsiOverboughtLevelとrsiOversoldLevel、リスクに対する利益目標をコントロールするriskRewardRatioを設定します。さらに統計的手法を取り入れるために、統計的フィルターを有効にするスイッチとしてuseStatisticalFilterを追加し、RSIの統計的な挙動をもとにシグナルの精度を高めるためのstatAnalysisPeriodおよびstatDeviationFactorを導入します。これにより、市場の有意な乖離と一致する取引のみを選別できるようになります。

最後に、視覚的なフィードバックを提供するために、チャート上の利確ラインやラベルに名前をつけるtakeProfitLineNameおよびtakeProfitTextNameを定義します。これにより、トレーダーがリアルタイムで水準を把握しやすくなります。プログラムをコンパイルすると、次の出力が表示されます。

ユーザー入力

次に、初期化プロパティを処理する初期化(OnInit)イベントハンドラに進みます。

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit(){//---- Expert advisor initialization function
   rsiHandle = iRSI(_Symbol, _Period, rsiLookbackPeriod, PRICE_CLOSE);//---- Creates RSI indicator handle with specified parameters
   handleMACD = iMACD(_Symbol,_Period,12,26,9,PRICE_CLOSE);//---- Creates MACD indicator handle with standard 12,26,9 settings
   
   if(rsiHandle == INVALID_HANDLE){//---- Checks if RSI handle creation failed
      Print("UNABLE TO LOAD RSI, REVERTING NOW");//---- Prints error message if RSI failed to load
      return(INIT_FAILED);//---- Returns initialization failure code
   }
   if(handleMACD == INVALID_HANDLE){//---- Checks if MACD handle creation failed
      Print("UNABLE TO LOAD MACD, REVERTING NOW");//---- Prints error message if MACD failed to load
      return(INIT_FAILED);//---- Returns initialization failure code
   }
   
   ArraySetAsSeries(rsiValues, true);//---- Sets RSI values array as a time series (latest data at index 0)
   
   ArraySetAsSeries(macdMAIN,true);//---- Sets MACD main line array as a time series
   ArraySetAsSeries(macdSIGNAL,true);//---- Sets MACD signal line array as a time series

   return(INIT_SUCCEEDED);//---- Returns successful initialization code
}

ここでは、OnInit関数において取引のレイヤリング戦略を実装し始めます。この関数は、EAが市場データと連動する前に主要コンポーネントを初期化する出発点となります。まず、RSIインジケーターを設定します。iRSI関数を使用してrsiHandleにハンドルを割り当てます。引数には、現在のチャートの銘柄を示す_Symbol、時間枠を示す_Period、RSIの計算期間を定義するrsiLookbackPeriod、終値を使うPRICE_CLOSEを指定します。次に、MACDの準備をおこない、iMACD関数を使ってhandleMACDにハンドルを保存します。ここでは、一般的な設定値である12(ファーストEMA)、26(スローEMA)、9(シグナルライン)をPRICE_CLOSEに対して適用し、トレンド分析用のインジケーターとして構成します。

戦略の堅牢性を確保するため、rsiHandleがINVALID_HANDLEに等しいかどうかをチェックし、もしそうであれば、Print関数を使って「UNABLE TO LOAD RSI, REVERTING NOW」とログに出力し、INIT_FAILEDを返して初期化を中断します。同様に、handleMACDに対しても「UNABLE TO LOAD MACD, REVERTING NOW」とエラーメッセージを出力し、同じくINIT_FAILEDを返します。両方のインジケーターが正しくロードされたことを確認できたら、RSIとMACDのデータを保持する配列であるrsiValues、macdMAIN、macdSIGNALをArraySetAsSeries関数でtrueに設定し、最新データがインデックス0に来るように整えます。最後に、INIT_SUCCEEDEDを返すことで、EAが取引の準備が整ったことを示します。次に、OnTickイベントハンドラに移動して、実際の取引ロジックを定義します。

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick(){//---- Function called on each price tick
   double askPrice = NormalizeDouble(SymbolInfoDouble(_Symbol, SYMBOL_ASK), _Digits);//---- Gets and normalizes current ask price
   double bidPrice = NormalizeDouble(SymbolInfoDouble(_Symbol, SYMBOL_BID), _Digits);//---- Gets and normalizes current bid price
   
   if(CopyBuffer(rsiHandle, 0, 1, 3, rsiValues) < 3){//---- Copies 3 RSI values into array, checks if successful
      Print("INSUFFICIENT RSI DATA FOR ANALYSIS, SKIPPING TICK");//---- Prints error if insufficient RSI data
      return;//---- Exits function if data copy fails
   }
   
   if (!CopyBuffer(handleMACD,MAIN_LINE,0,3,macdMAIN))return;//---- Copies 3 MACD main line values, exits if fails
   if (!CopyBuffer(handleMACD,SIGNAL_LINE,0,3,macdSIGNAL))return;//---- Copies 3 MACD signal line values, exits if fails
}

ここでは、OnTick関数により、取引レイヤリング戦略を強化します。この関数は価格が更新されるたびに呼び出され、EAにリアルタイムな意思決定をおこなわせます。まず、現在の市場価格を取得します。NormalizeDouble関数を使用して、askPriceをSymbolInfoDouble関数から取得します。対象は_Symbol、タイプはSYMBOL_ASKで、_Digitsの精度に調整します。同様に、bidPriceはSYMBOL_BIDを使って取得します。これにより、取引計算のための正確な価格情報が確保され、最新の市場状況に基づいてレイヤリングロジックを発動できる基盤が整います。

次に、インジケーターデータを取得してシグナルを分析します。まずRSIの取得にはCopyBuffer関数を使い、rsiHandleからrsiValuesに3つの値を読み込みます(バッファ0、インデックス1から開始)。もし3未満の値しか取得できなかった場合、Print関数で「INSUFFICIENT RSI DATA FOR ANALYSIS, SKIPPING TICK」とログに出力し、returnでこのティックをスキップします。これにより、不完全なデータに基づいた判断を防ぎます。MACDについても同様に、CopyBuffer関数を使用して、handleMACDからmacdMAIN (MAIN_LINE)とmacdSIGNAL (SIGNAL_LINE)をそれぞれ3つずつ取得します。どちらかのデータが取得できなかった場合はreturnし、完全なRSIおよびMACDデータが揃った状態でのみ処理を進めるようにします。しかし、ここに統計的データも取り込む必要があります。そのため、次にそのための関数定義をおこないます。

//+------------------------------------------------------------------+
//| Calculate RSI Average                                            |
//+------------------------------------------------------------------+
double CalculateRSIAverage(int bars){//---- Function to calculate RSI average
   double sum = 0;//---- Initializes sum variable
   double buffer[];//---- Declares buffer array for RSI values
   ArraySetAsSeries(buffer, true);//---- Sets buffer as time series
   if(CopyBuffer(rsiHandle, 0, 0, bars, buffer) < bars) return 0;//---- Copies RSI values, returns 0 if fails
   
   for(int i = 0; i < bars; i++){//---- Loops through specified number of bars
      sum += buffer[i];//---- Adds each RSI value to sum
   }
   return sum / bars;//---- Returns average RSI value
}

//+------------------------------------------------------------------+
//| Calculate RSI STDDev                                             |
//+------------------------------------------------------------------+
double CalculateRSIStandardDeviation(int bars){//---- Function to calculate RSI standard deviation
   double average = CalculateRSIAverage(bars);//---- Calculates RSI average
   double sumSquaredDiff = 0;//---- Initializes sum of squared differences
   double buffer[];//---- Declares buffer array for RSI values
   ArraySetAsSeries(buffer, true);//---- Sets buffer as time series
   if(CopyBuffer(rsiHandle, 0, 0, bars, buffer) < bars) return 0;//---- Copies RSI values, returns 0 if fails
   
   for(int i = 0; i < bars; i++){//---- Loops through specified number of bars
      double diff = buffer[i] - average;//---- Calculates difference from average
      sumSquaredDiff += diff * diff;//---- Adds squared difference to sum
   }
   return MathSqrt(sumSquaredDiff / bars);//---- Returns standard deviation
}

ここでは、RSIデータの統計分析を導入するために、2つの主要な関数、CalculateRSIAverageとCalculateRSIStandardDeviationを実装し、シグナルの精度を強化します。CalculateRSIAverageでは、入力としてbarsを受け取り、最初にsumを0で初期化し、buffer配列を宣言します。配列はArraySetAsSeries関数を使って時系列(インデックス0が最新)として設定します。続いてCopyBuffer関数を用い、rsiHandleからbars本のRSI値をbufferに読み込みます。もしコピーできた値がbars未満であれば0を返します。その後、ループを使ってbuffer[i]をsumに加算していき、最終的にsum / barsを返して、統計フィルターに使用するRSIの平均値とします。

次に、CalculateRSIStandardDeviationでは、同じbars期間に対してRSIの標準偏差を計算します。まずCalculateRSIAverageを呼び出してその結果をaverageに格納し、差の二乗を格納するためのsumSquaredDiffを0で初期化します。再びbuffer配列を宣言し、ArraySetAsSeriesで時系列に設定し、CopyBufferを使ってrsiHandleからRSI値を取得します。コピーに失敗した場合は0を返してデータの整合性を確保します。続くループでは、「diff = buffer[i] - average」として各値と平均の差を求め、その「diff * diff」をsumSquaredDiffに加算していきます。そしてMathSqrt(sumSquaredDiff / bars)を返すことで、取引レイヤリング判断の精度向上に使える統計的指標(標準偏差)を提供します。このデータを用いた統計分析は、あいまいさを避けるため1バーごとに1回だけ実行する必要があります。そこで、これを管理する関数を次に定義します。

//+------------------------------------------------------------------+
//| Is New Bar                                                       |
//+------------------------------------------------------------------+
bool IsNewBar(){//---- Function to detect a new bar
   static int previousBarCount = 0;//---- Stores the previous bar count
   int currentBarCount = iBars(_Symbol, _Period);//---- Gets current number of bars
   if(previousBarCount == currentBarCount) return false;//---- Returns false if no new bar
   previousBarCount = currentBarCount;//---- Updates previous bar count
   return true;//---- Returns true if new bar detected
}

ここでは、戦略にIsNewBar関数を追加し、新しいバーを検出できるようにします。この関数では、static変数previousBarCountを0で初期化し、直近のバー数を記録します。そして、iBars関数を使って_Symbol_Periodに対するcurrentBarCountを取得します。バー数が変わっていなければfalseを返し、新しいバーが形成された場合はpreviousBarCountを更新してtrueを返します。この機能を利用することで、取引ルールを定義できるようになります。

// Calculate statistical measures if enabled
double rsiAverage = useStatisticalFilter ? CalculateRSIAverage(statAnalysisPeriod) : 0;//---- Calculates RSI average if filter enabled
double rsiStdDeviation = useStatisticalFilter ? CalculateRSIStandardDeviation(statAnalysisPeriod) : 0;//---- Calculates RSI std dev if filter enabled

統計的な強化を実装するために、useStatisticalFilterがtrueの場合はCalculateRSIAverage関数をstatAnalysisPeriodで呼び出してrsiAverageを計算し、それ以外の場合は0を設定します。同様に、CalculateRSIStandardDeviation関数を使ってrsiStdDeviationを計算するか、フィルターが無効な場合は0を代入します。これにより、統計的フィルタが有効なときに、精度の高いシグナル判定が可能になります。続いて、この結果を使って取引条件を定義していきます。まずは買い条件から始めます。

if(PositionsTotal() == 0 && IsNewBar()){//---- Checks for no positions and new bar
   // Buy Signal
   bool buyCondition = rsiValues[1] <= rsiOversoldLevel && rsiValues[0] > rsiOversoldLevel;//---- Checks RSI crossing above oversold
   if(useStatisticalFilter){//---- Applies statistical filter if enabled
      buyCondition = buyCondition && (rsiValues[0] < (rsiAverage - statDeviationFactor * rsiStdDeviation));//---- Adds statistical condition
   }
   
   buyCondition = macdMAIN[0] < 0 && macdSIGNAL[0] < 0;//---- Confirms MACD below zero for buy signal
}

買いシグナルのロジックを定義するにあたり、まずPositionsTotalが0であることを確認し、IsNewBar関数を使って新しいバーが形成されたことをチェックします。これにより、取引がポジション未保有かつバー始値でのみ発動するように制御されます。次に、buyConditionを判定します。これは、rsiValues[1]がrsiOversoldLevelを下回っており、rsiValues[0]がそれを上抜いた場合にtrueとします。さらに、useStatisticalFilterが有効な場合は、rsiValues[0]が「rsiAverage - statDeviationFactor * rsiStdDeviation」未満であることも条件に加え、統計的な精度を強化します。最後に、macdMAIN[0]とmacdSIGNAL[0]の両方がゼロ未満であることを確認し、MACDがベアリッシュなゾーンにあることをもってトレンド方向を検証します。これらの条件をすべて満たした場合、ポジションをオープンし、トラッキング用の変数を初期化し、チャート上に確定したレベル、特にテイクプロフィット)水準を描画します。そのために、これらの水準を描画するカスタム関数が必要になります。

//+------------------------------------------------------------------+
//| Draw TrendLine                                                   |
//+------------------------------------------------------------------+
void DrawTradeLevelLine(double price, bool isBuy){//---- Function to draw take profit line on chart
   // Delete existing objects first
   DeleteTradeLevelObjects();//---- Removes existing trade level objects
   
   // Create horizontal line
   ObjectCreate(0, takeProfitLineName, OBJ_HLINE, 0, 0, price);//---- Creates a horizontal line at specified price
   ObjectSetInteger(0, takeProfitLineName, OBJPROP_COLOR, clrBlue);//---- Sets line color to blue
   ObjectSetInteger(0, takeProfitLineName, OBJPROP_WIDTH, 2);//---- Sets line width to 2
   ObjectSetInteger(0, takeProfitLineName, OBJPROP_STYLE, STYLE_SOLID);//---- Sets line style to solid
   
   // Create text, above for buy, below for sell with increased spacing
   datetime currentTime = TimeCurrent();//---- Gets current time
   double textOffset = 30.0 * _Point;//---- Sets text offset distance from line
   double textPrice = isBuy ? price + textOffset : price - textOffset;//---- Calculates text position based on buy/sell
   
   ObjectCreate(0, takeProfitTextName, OBJ_TEXT, 0, currentTime + PeriodSeconds(_Period) * 5, textPrice);//---- Creates text object
   ObjectSetString(0, takeProfitTextName, OBJPROP_TEXT, DoubleToString(price, _Digits));//---- Sets text to price value
   ObjectSetInteger(0, takeProfitTextName, OBJPROP_COLOR, clrBlue);//---- Sets text color to blue
   ObjectSetInteger(0, takeProfitTextName, OBJPROP_FONTSIZE, 10);//---- Sets text font size to 10
   ObjectSetInteger(0, takeProfitTextName, OBJPROP_ANCHOR, isBuy ? ANCHOR_BOTTOM : ANCHOR_TOP);//---- Sets text anchor based on buy/sell
}

ここでは、テイクプロフィットの水準を視覚的に表示するために、DrawTradeLevelLine関数を実装します。まず、既存のオブジェクトを消去するためにDeleteTradeLevelObjectsを呼び出します。次に、ObjectCreate関数を使って水平方向のラインをpriceの位置に描画します。ラインはtakeProfitLineNameという名前で、種類はOBJ_HLINEとし、ObjectSetIntegerを用いて色はclrBlue、太さは2、スタイルはSTYLE_SOLIDに設定します。その後、テキストラベルを追加します。まずTimeCurrentでcurrentTimeを取得し、textOffsetを「30.0 * _Point」に設定します。ラベルの表示位置であるtextPriceは、isBuyの値に応じてpriceの上か下に計算されます。テキストはObjectCreateで、名前はtakeProfitTextName、種類はOBJ_TEXTとして作成します。テキストの内容はObjectSetStringで設定し、DoubleToStringを使って_Digits桁のpriceを表示します。さらにObjectSetIntegerを使って、色はclrBlue、フォントサイズは10、アンカー位置はisBuyに応じてANCHOR_BOTTOMまたはANCHOR_TOPを設定します。これにより、チャート上の視認性が向上します。この関数を使用してターゲットレベルを視覚化できるようになります。

if(buyCondition){//---- Executes if buy conditions are met
   Print("BUY SIGNAL - RSI: ", rsiValues[0],//---- Prints buy signal details
         useStatisticalFilter ? " Avg: " + DoubleToString(rsiAverage, 2) + " StdDev: " + DoubleToString(rsiStdDeviation, 2) : "");
   stopLossLevel = askPrice - stopLossPoints * _Point;//---- Calculates stop loss level for buy
   takeProfitLevel = askPrice + (stopLossPoints * riskRewardRatio) * _Point;//---- Calculates take profit level for buy
   obj_Trade.Buy(tradeVolume, _Symbol, askPrice, stopLossLevel, 0,"Signal Position");//---- Places buy order
   buySequenceActive = true;//---- Activates buy sequence flag
   DrawTradeLevelLine(takeProfitLevel, true);//---- Draws take profit line for buy
}

ここでは、buyConditionがtrueのときに買いをエントリーします。まず、Print関数を使って「BUY SIGNAL - RSI: 」とrsiValues[0]をログに出力します。useStatisticalFilterが有効な場合は、DoubleToString関数を使ってrsiAverageおよびrsiStdDeviationを付加し、詳細なフィードバックを表示します。次に、stopLossLevelを「askPrice - stopLossPoints * _Point」で計算し、takeProfitLevelを「askPrice + stopLossPoints * riskRewardRatio * _Point」で算出します。そして、obj_Trade.Buyメソッドを使用して、tradeVolume、_Symbol、askPrice、stopLossLevel、takeProfitLevelを指定し、「Signal Position」というラベルで買い注文を出します。最後に、buySequenceActiveをtrueに設定し、DrawTradeLevelLine関数をtakeProfitLevelとtrueを引数に呼び出して、買いポジションのテイクプロフィットラインをチャートに描画します。プログラムを実行すると、次の結果が得られます。

確定した買いポジション

画像から確認できるように、すべての買いシグナル条件が満たされており、次のレベルも自動的にチャート上にマークされています。したがって、売りシグナルに対しても同様の処理を続けることができます。

// Sell Signal
bool sellCondition = rsiValues[1] >= rsiOverboughtLevel && rsiValues[0] < rsiOverboughtLevel;//---- Checks RSI crossing below overbought
if(useStatisticalFilter){//---- Applies statistical filter if enabled
   sellCondition = sellCondition && (rsiValues[0] > (rsiAverage + statDeviationFactor * rsiStdDeviation));//---- Adds statistical condition
}

sellCondition = macdMAIN[0] > 0 && macdSIGNAL[0] > 0;//---- Confirms MACD above zero for sell signal

if(sellCondition){//---- Executes if sell conditions are met
   Print("SELL SIGNAL - RSI: ", rsiValues[0],//---- Prints sell signal details
         useStatisticalFilter ? " Avg: " + DoubleToString(rsiAverage, 2) + " StdDev: " + DoubleToString(rsiStdDeviation, 2) : "");
   stopLossLevel = bidPrice + stopLossPoints * _Point;//---- Calculates stop loss level for sell
   takeProfitLevel = bidPrice - (stopLossPoints * riskRewardRatio) * _Point;//---- Calculates take profit level for sell
   obj_Trade.Sell(tradeVolume, _Symbol, bidPrice, stopLossLevel, 0,"Signal Position");//---- Places sell order
   sellSequenceActive = true;//---- Activates sell sequence flag
   DrawTradeLevelLine(takeProfitLevel, false);//---- Draws take profit line for sell
}

ここでは買いとは逆のロジックを実行します。rsiValues[1]がrsiOverboughtLevelを上回り、rsiValues[0]がそれを下回った場合にsellConditionを設定します。さらに、useStatisticalFilterが有効な場合は、rsiValues[0]が「rsiAverage + statDeviationFactor * rsiStdDeviation」を上回っているかどうかを確認します。そしてmacdMAIN[0]およびmacdSIGNAL[0]がともにゼロより上であることを確認条件とします。これらの条件がすべて満たされた場合、Print関数を使って「SELL SIGNAL - RSI: 」というメッセージとrsiValues[0]および統計値(DoubleToStringでフォーマット)を出力します。次に、stopLossLevelを「bidPrice + stopLossPoints * _Point」として設定し、takeProfitLevelを「bidPrice - stopLossPoints * riskRewardRatio * _Point」として設定します。その後、obj_Trade.Sellを呼び出し、DrawTradeLevelLineをfalseで実行して、ビジュアル的なラインを描画し、sellSequenceActiveを有効にします。ポジションが建てられたら、次は勝っているポジションをトレンドに従ってカスケードさせる必要があります。つまり、トレンドに追従しながらポジションを修正していきます。以下に取引を修正するための関数を示します。

//+------------------------------------------------------------------+
//| Modify Trades                                                    |
//+------------------------------------------------------------------+
void ModifyTrades(ENUM_POSITION_TYPE positionType, double newStopLoss){//---- Function to modify open trades
   for(int i = 0; i < PositionsTotal(); i++){//---- Loops through all open positions
      ulong ticket = PositionGetTicket(i);//---- Gets ticket number of position
      if(ticket > 0 && PositionSelectByTicket(ticket)){//---- Checks if ticket is valid and selectable
         ENUM_POSITION_TYPE type = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE);//---- Gets position type
         if(type == positionType){//---- Checks if position matches specified type
            obj_Trade.PositionModify(ticket, newStopLoss, PositionGetDouble(POSITION_TP));//---- Modifies position with new stop loss
         }
      }
   }
}

ここでは、ModifyTrades関数を実装してオープン中の取引を更新します。関数はpositionTypeとnewStopLossを引数として受け取り、PositionsTotal関数を使って全ポジションに対して0からiまでのforループで処理をおこないます。各ポジションについては、まずPositionGetTicket関数でticket番号を取得し、それが有効かどうかをPositionSelectByTicketで確認します。次に、PositionGetIntegerを使ってポジションのタイプをENUM_POSITION_TYPEとして取得し、それがpositionTypeと一致するかを確認します。一致した場合は、obj_Trade.PositionModifyを使用して、そのポジションのストップロスをnewStopLossに変更します。テイクプロフィットについてはPositionGetDoubleでPOSITION_TPを取得し、その値をそのまま使用することで、正確な取引管理をおこないます。この関数を活用することで、トレンドに従ってポジションをカスケードさせることが可能になります。

else {//---- Handles cascading logic when positions exist
   // Cascading Buy Logic
   if(buySequenceActive && askPrice >= takeProfitLevel){//---- Checks if buy sequence active and price hit take profit
      double previousTakeProfit = takeProfitLevel;//---- Stores previous take profit level
      takeProfitLevel = previousTakeProfit + (stopLossPoints * riskRewardRatio) * _Point;//---- Sets new take profit level
      stopLossLevel = askPrice - minStopLossPoints * _Point;//---- Sets new stop loss level
      obj_Trade.Buy(tradeVolume, _Symbol, askPrice, stopLossLevel, 0,"Cascade Position");//---- Places new buy order
      ModifyTrades(POSITION_TYPE_BUY, stopLossLevel);//---- Modifies existing buy trades with new stop loss
      Print("CASCADING BUY - New TP: ", takeProfitLevel, " New SL: ", stopLossLevel);//---- Prints cascading buy details
      DrawTradeLevelLine(takeProfitLevel, true);//---- Updates take profit line for buy
   }
   // Cascading Sell Logic
   else if(sellSequenceActive && bidPrice <= takeProfitLevel){//---- Checks if sell sequence active and price hit take profit
      double previousTakeProfit = takeProfitLevel;//---- Stores previous take profit level
      takeProfitLevel = previousTakeProfit - (stopLossPoints * riskRewardRatio) * _Point;//---- Sets new take profit level
      stopLossLevel = bidPrice + minStopLossPoints * _Point;//---- Sets new stop loss level
      obj_Trade.Sell(tradeVolume, _Symbol, bidPrice, stopLossLevel, 0,"Cascade Position");//---- Places new sell order
      ModifyTrades(POSITION_TYPE_SELL, stopLossLevel);//---- Modifies existing sell trades with new stop loss
      Print("CASCADING SELL - New TP: ", takeProfitLevel, " New SL: ", stopLossLevel);//---- Prints cascading sell details
      DrawTradeLevelLine(takeProfitLevel, false);//---- Updates take profit line for sell
   }
}

ここでは、ポジションが存在する場合のカスケードロジックを処理します。まず、buySequenceActiveがtrueであり、askPriceがtakeProfitLevelに達したかどうかを確認します。達していれば、previousTakeProfitを保存し、新しいtakeProfitLevelを「stopLossPoints * riskRewardRatio * _Point」ぶん加算して設定します。また、stopLossLevelは「askPrice - minStopLossPoints * _Point」として設定します。その後、obj_Trade.Buyを使って新しい買い注文を出し、ModifyTrades関数でPOSITION_TYPE_BUYのストップロスを更新します。Print関数で「CASCADING BUY」の詳細をログ出力し、DrawTradeLevelLineで買いラインを更新します。

売りに関しては、sellSequenceActiveがtrueであり、bidPriceがtakeProfitLevelに到達したかどうかを確認します。条件を満たした場合は、previousTakeProfitから一定値を引いて新しいtakeProfitLevelを設定し、stopLossLevelは「bidPrice + minStopLossPoints * _Point」とします。その後、obj_Trade.Sellを実行し、ModifyTrades関数でPOSITION_TYPE_SELLのストップロスを更新します。Print関数で情報を出力し、DrawTradeLevelLineで売りラインを更新します。このロジックを実行することで、以下のような結果が得られます。

ポジションカスケード

画像から、ポジションがカスケードされ、すべてのポジションに対してストップロスが正しく更新されていることを確認できます。次に必要な作業は、システムが不要になったときに追加したオブジェクトを整理するクリーンアップ処理をおこなうことです。この処理は、OnDeinitイベントハンドラを通じて実現できますが、まずはクリーンアップ用の関数を用意する必要があります。

//+------------------------------------------------------------------+
//| Delete Level Objects                                             |
//+------------------------------------------------------------------+
void DeleteTradeLevelObjects(){//---- Function to delete trade level objects
   ObjectDelete(0, takeProfitLineName);//---- Deletes take profit line object
   ObjectDelete(0, takeProfitTextName);//---- Deletes take profit text object
}

ここでは、チャート上の視覚的要素を整理するために、DeleteTradeLevelObjects関数を実装します。この関数では、ObjectDelete関数を使用して、takeProfitLineNameというラインオブジェクトと、takeProfitTextNameというテキストオブジェクトを削除します。これにより、古いテイクプロフィットのラインが新たに描画される前に確実に消去されるようになります。この関数は、OnDeinitイベントハンドラ内で呼び出すようにします。

//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason){//---- Expert advisor deinitialization function
   DeleteTradeLevelObjects();//---- Removes trade level visualization objects from chart
}

ここでは、チャート上のオブジェクトを削除する関数を呼び出すだけで、プログラムを終了する際にチャートをクリーンな状態に保つことができます。これにより、目的である整理処理を達成します。残っている作業は、このプログラムのバックテストをおこなうことです。バックテストについては次のセクションで扱います。


バックテスト

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

バックテストグラフ

グラフ

バックテストレポート

レポート


結論

まとめとして、私たちはMQL5で取引レイヤリング戦略を成功裏に構築しました。MACDRSIを統計的手法と組み合わせて、トレンド市場での動的なポジションスケーリングを自動化しています。本プログラムは堅牢なシグナル検出、カスケード取引ロジック、視覚的なテイクプロフィットレベルを備えており、モメンタムの変化に精確に適応します。この基盤をもとに、rsiLookbackPeriodの最適化やriskRewardRatioの調整など、さらなる改善を加えることが可能です。

免責条項:本記事は教育目的のみを意図したものです。取引には重大な金銭的リスクが伴い、市場の動きは不安定である場合があります。実際の運用前には入念なバックテストとリスク管理が不可欠です。

この解説を通じて、あなたの自動化スキルを磨き、戦略を洗練させる助けになれば幸いです。ぜひ試行錯誤し、最適化を重ねてください。取引をお楽しみください。

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

EAのサンプル EAのサンプル
一般的なMACDを使ったEAを例として、MQL4開発の原則を紹介します。
MQL5で取引管理者パネルを作成する(第9回):コード編成(V):AnalyticsPanelクラス MQL5で取引管理者パネルを作成する(第9回):コード編成(V):AnalyticsPanelクラス
この議論では、リアルタイムの市場データや取引口座情報の取得方法、さまざまな計算の実行、そしてその結果をカスタムパネルに表示する方法について探ります。これを実現するために、パネル作成を含むこれらすべての機能をカプセル化したAnalyticsPanelクラスの開発にさらに深く取り組みます。この取り組みは、モジュラー設計の原則とコード構造のベストプラクティスを用い、高度な機能を導入するNew Admin Panel EAの継続的な拡張の一環です。
エラー 146 (「トレードコンテキスト ビジー」) と、その対処方法 エラー 146 (「トレードコンテキスト ビジー」) と、その対処方法
この記事では、MT4において複数のEAの衝突をさける方法を扱います。ターミナルの操作、MQL4の基本的な使い方がわかる人にとって、役に立つでしょう。
MQL5入門(第15回):初心者のためのカスタムインジケーター作成ガイド(IV) MQL5入門(第15回):初心者のためのカスタムインジケーター作成ガイド(IV)
この記事では、MQL5でプライスアクションインジケーターを構築する方法を学びます。具体的には、トレンド分析において重要なポイントである、安値(L)、高値(H)、安値切り上げ(HL)、高値更新(HH)、安値更新(LL)、高値切り下げ(LH)といった構造の把握に焦点を当てます。また、プレミアムゾーンとディスカウントゾーンの識別、50%リトレースメントレベルの表示、リスクリワード比に基づく利益目標の計算についても解説します。さらに、トレンド構造に基づいてエントリーポイント、ストップロス(SL)、テイクプロフィット(TP)の設定方法も扱います。