English Deutsch
preview
ダイナミックマルチペアEAの形成(第2回):ポートフォリオの分散化と最適化

ダイナミックマルチペアEAの形成(第2回):ポートフォリオの分散化と最適化

MetaTrader 5テスター |
52 8
Hlomohang John Borotho
Hlomohang John Borotho

はじめに

プロの取引において最も根深い課題のひとつは、一貫したポートフォリオ運用と堅牢なリスク管理体制の維持です。トレーダーはしばしば単一の資産や戦略に過度に依存してしまい、市場環境が急変した際に大きなドローダウンを被るリスクを高めてしまいます。さらに、相関性の高い金融商品に過剰レバレッジをかける傾向があるため、同時多発的な損失の可能性が増し、リターンの安定性を損ねることにもつながります。分散化・最適化が徹底されていないポートフォリオでは、パフォーマンスが不安定になり、感情的な判断や収益のばらつきを招きがちです。したがって、非相関資産群全体にわたってリスク調整後のリターンを戦略的にバランスさせる体系的なフレームワークが、持続的かつ長期的なパフォーマンスには不可欠です。

これらの課題を緩和するために、本稿ではポートフォリオ最適化、マルチアセット分散投資、およびオシレーターによる確認を組み合わせたブレイクアウト取引戦略を統合した定量的アプローチを提案します。複数の通貨ペアにわたってブレイクアウト戦略を同時に展開することで、高確率のモメンタム駆動型の価格変動を捉えつつ、非相関市場へのエクスポージャーによってリスクを分散できます。さらに、オシレーター系指標をエントリーシグナルの確認に用いることで、偽のブレイクアウトへの参加を最小限に抑え、無駄な取引を削減します。このアプローチは、利益機会を高めるだけでなく、異なる市場局面を体系的に捉えることでポートフォリオの安定性を強化します。結果として、ボラティリティに対する耐性が向上し、変化するマクロ環境とテクニカル条件に沿った一貫したパフォーマンスを実現します。



エキスパートアドバイザー(EA)ロジック

買いのモデル

EAはまず、UTC+2の午前10時から正午12時までの間の価格レンジを計算し、その期間内の最高値と最安値を特定します。正午12時以降に価格が先ほどの最高値を上抜けた場合、買いエントリーの機会が生じます。ただし、確認条件としてストキャスティクスが20以下であることが必要です。これは市場が売られすぎの状態にあることを示し、ブレイクアウトの余地が残されていることを示唆します。両条件が満たされた時点で、EAは買い注文を実行し、オシレーターによるフィルタリングによって誤シグナルを最小限に抑えつつブレイクアウトから利益を狙います。

売りのモデル

売りのモデルにおいても、EAはUTC+2の午前10時から正午12時までの価格レンジ計算をおこない、その期間内の高値と安値を特定します。正午12時以降に価格が確立された安値を下抜けた場合、売りのチャンスが検討されます。ただし、取引を実行するにはストキャスティクスが80以上である必要があります。これは市場が買われすぎの状態にあることを示し、下降ブレイクアウトが単なる騙しではなく、実際の売り圧力によって裏付けられていることが保証されます。両条件がそろった時点で、EAは売り注文を実行し、下落トレンドの勢いを捉えつつ早期のエントリーを回避します。

始めましょう。

//+------------------------------------------------------------------+
//|                                                      Dyna MP.mq5 |
//|                                  Copyright 2024, MetaQuotes Ltd. |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2024, MetaQuotes Ltd."
#property link      "https://www.mql5.com"
#property version   "1.00"

#include <Trade/Trade.mqh>
CTrade trade;

enum Signal_Breakout{
   Normal_Signal,
   Reversed_Signal,
};

「# include <Trade/Trade.mqh>」では、MQL5 Tradeライブラリがインクルードされます。Trade.mqhヘッダーファイルは、注文の開始、調整、終了などの取引タスクを効率化するクラスと関数を定義します。ここでは、tradeという名前のオブジェクトがCTradeクラスからインスタンス化されます。このクラスには、買い/売りのリクエスト送信、既存ポジションの変更、ポジションのクローズなど、取引操作を体系的に実行するためのメソッドが含まれています。

input group "--------------General Inputs--------------"
input string Symbols = "XAUUSD, GBPUSD, USDCAD, USDJPY";
input Signal_Breakout BreakOutMode = Reversed_Signal;
input double  In_Lot = 0.01;
input int TakeProfit = 500;
 double StopLoss = 500;
input bool TrailYourStop = false;
input int TrailingStop = 50;

// Stochastic
input int KPeriod = 21;
input int upprer_level = 80;

Symbolsは取引する資産を指定します(例:"XAUUSD,GBPUSD")。In_Lotは固定の取引サイズ(0.01ロット)を設定します。TakeProfit(500ポイント)およびStopLoss(1000ポイント)は、それぞれ利益確定とリスク制限の水準を定義します。TrailYourStopとTrailing Stop(70ポイント)は、動的なストップロスのトレーリング機能を有効化・調整します。ストキャスティクスにおいては、KPeriod(20)が%Kラインの計算期間を決定し、upper_level(80)が買われすぎの閾値を示します。これらの入力パラメータは、取引実行ルール、リスク管理、テクニカルシグナルの生成をバランスよく統合します。

//+------------------------------------------------------------------+
//|                           Global vars                            |
//+------------------------------------------------------------------+
int handles[];
double bufferM[];


int RangeStart = 600; 
int RangeDuration = 120;
int RangeClose = 1200;

int Num_symbs = 0;
string symb_List[];
string Formatted_Symbs[];

このセクションではグローバル変数を定義します。handles配列はストキャスティクスのハンドルを格納しており、EAが複数の資産を効率的に管理できるようにします。buffer配列は、インジケーターの値や過去の価格レベルなど、計算された値を格納します。RangeStart変数は600分(午前10時、UTC+2)に設定されており、レンジ測定の開始時刻を示します。RangeDurationは120分に設定されており、高値と安値を取得するための2時間の期間を定義します。RangeCloseが1200分(正午、UTC+2)に達すると、EAはレンジの計算を停止し、ブレイクアウトの条件を監視し始めます。

銘柄管理のために、symb_List配列は処理対象となる銘柄の元のリストを保持します。さらに、Formatted_Symbs配列はSymbols入力を解析した後に使用される銘柄の総数を保持します。さらに、Formatted_Symbs配列はSymbols入力を解析した後に使用される銘柄の総数を保持します。これらの変数によって、EAは複数資産にわたって動的に取引を実行し、ブレイクアウト検出のために正確なレンジ計算をおこなうことができます。

//+------------------------------------------------------------------+
//|                    Ranger Global Vars                            |
//+------------------------------------------------------------------+
struct RANGER{
   datetime start_time;
   datetime end_time;
   datetime close_time;
   double high;
   double low;
   bool b_entry;
   bool b_high_breakout;
   bool b_low_breakout;
   
   RANGER() : start_time(0), end_time(0), close_time(0), high(0), low(999999), b_entry(false), b_high_breakout(false), b_low_breakout(false) {};
};

Ranger構造体は、レンジブレイクアウト戦略のための主要なデータポイントを格納・管理します。これは、レンジの時間的な境界、価格水準、およびブレイクアウト条件を追跡するための構造化された方法を定義しています。start_time、end_time、close_timeの各変数は、レンジ計算の開始時刻、終了時刻、およびクローズ時刻を表しており、EAがブレイクアウトの監視期間を正確に把握できるようにします。highおよびlowの変数には、そのレンジ内で記録された最高値と最安値が格納されます。特にlowは、初期値として非常に高い値(999999)に設定されており、価格の更新が正確に行われるようにしています。low(999999)という設定は、価格系列から最小値を見つけ出すための一般的なパターンです。

さらに、3つのブール型フラグが定義されています。b_entryは、取引エントリーがすでに実行されたかどうかを追跡し、同じブレイクアウトイベント内で複数の取引が行われるのを防ぎます。b_high_breakoutは価格がレンジの高値を上抜けたかを示し、買いエントリーの条件を確認します。b_low_breakoutは価格がレンジの安値を下抜けたかを示し、売りエントリーの条件を確認します。コンストラクタRangerは、すべての値を初期状態に設定し、ライブ取引中に動的に更新される前にクリーンなデータ状態で構造体が開始されるようにします。

RANGER rangeArray[];
MqlTick prevTick[], currTick[];

これらの変数は、価格の動きを追跡し、EAにおけるレンジブレイクアウトのロジックを管理するうえで重要な役割を果たします。rangeArrayはRANGER構造体の配列であり、定義されたレンジブレイクアウトのパラメータを複数保持します。これにより、EAは複数の銘柄を同時に追跡することができ、それぞれの銘柄に対して開始・終了時刻、最高値・最安値、ブレイクアウト条件などのレンジデータを個別に管理することができます。

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit(){

   string separator = ",";
   ushort usprtr;
   usprtr = StringGetCharacter(separator, 0);
   StringSplit(Symbols, usprtr, symb_List);
   Num_symbs = ArraySize(symb_List);
   ArrayResize(Formatted_Symbs, Num_symbs);
   
   for(int i = 0; i < Num_symbs; i++){
      Formatted_Symbs[i] = symb_List[i];
   }
   
   ArrayResize(rangeArray, Num_symbs);
   ArrayResize(prevTick, Num_symbs);
   ArrayResize(currTick, Num_symbs);
   ArrayResize(handles, Num_symbs);   
   ArraySetAsSeries(bufferM, true);
   
   // Calculate initial ranges for each symbol
   for (int i = 0; i < ArraySize(Formatted_Symbs); i++) {
      CalculateRange(i, Formatted_Symbs[i]);  // Pass the symbol index
      handles[i] = iStochastic(Formatted_Symbs[i], PERIOD_CURRENT, KPeriod, 1, 3, MODE_SMA, STO_LOWHIGH);
      
      if(handles[i] == INVALID_HANDLE){
         Alert("Failed to create indicator handle");
         return INIT_FAILED;
      }
      
      StopLoss = SymbolInfoDouble(Formatted_Symbs[i], SYMBOL_POINT)*TrailingStop;
   }
   return(INIT_SUCCEEDED);
}

OnInit関数は、主要な変数の初期化、銘柄の処理、および必要なデータ構造の準備を担当します。銘柄リストは、区切り文字,を使って個別の取引可能な資産に分割され、それらはsymb_Listに格納されます。銘柄の総数Num_symbsを取得した後、Formatted_Symbs配列のサイズを調整し、それぞれの銘柄が適切に整形され、取引で使用可能であることを保証します。

次に、EAは複数の配列を動的にリサイズします。これには、レンジブレイクアウトのデータを格納するrangeArray、価格更新を追跡するためのprevTickとcurrTick、ストキャスティクス用のhandles、およびインジケーター値用のbufferMが含まれます。これにより、EAの実行前にすべての必要なデータ構造が正しく確保されます。

配列がセットアップされた後、EAはFormatted_Symbs内の各銘柄をループ処理し、CalculateRange関数を使って初期レンジを計算します。また、各銘柄に対してストキャスティクスのハンドル(iStochastic)を作成します。これは後にブレイクアウト確認のため、買われすぎ・売られすぎの状態をチェックするのに使用されます。インジケーターのハンドルが初期化に失敗した場合(INVALID_HANDLE)、EAはアラートを発し、INIT_FAILEDステータスで終了します。さらに、各銘柄のストップロスは、そのポイント値とトレーリングストップのパラメータに基づいて計算されます。すべてが正常に初期化されると、関数はINIT_SUCCEEDEDを返し、EAは価格変動の監視および取引の実行に進むことができます。

//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason){
   for(int i = 0; i < ArraySize(Formatted_Symbs); i++){
      if(handles[i] != INVALID_HANDLE){
         IndicatorRelease(handles[i]);
      }   
   }
}

OnDeinit関数はEAの初期化解除関数であり、EAがチャートから削除されたときや実行が停止したときにリソースを適切に解放する役割を担います。

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick(){
 
   for(int i = 0; i < ArraySize(Formatted_Symbs); i++){
      string symbol = Formatted_Symbs[i];
      prevTick[i] = currTick[i];
      SymbolInfoTick(symbol, currTick[i]);
      
      // Range Cal
      if(currTick[i].time > rangeArray[i].start_time && currTick[i].time < rangeArray[i].end_time){
         // flag
         rangeArray[i].b_entry = true;
         
         // high
         if(currTick[i].ask > rangeArray[i].high){
            rangeArray[i].high = currTick[i].ask;
         }
         
         // low
         if(currTick[i].bid < rangeArray[i].low){
            rangeArray[i].low = currTick[i].bid;
         }
      }
            
      // now calculate range
      if(((RangeClose >= 0 && currTick[i].time >= rangeArray[i].close_time)
         || (rangeArray[i].b_high_breakout && rangeArray[i].b_low_breakout)
         || (rangeArray[i].end_time == 0)
         || (rangeArray[i].end_time != 0 && currTick[i].time > rangeArray[i].end_time && !rangeArray[i].b_entry))){
         CalculateRange(i, Formatted_Symbs[i]);
      }
      checkBreak(i, Formatted_Symbs[i]);
   }
   
}

OnTick関数はEAの中核となる実行ループであり、新しいティックが発生するたびに実行されます。この関数は、リアルタイムの価格分析、レンジパラメータの調整、および設定されたすべての取引対象に対するブレイクアウトの機会を評価します。処理は、Formatted_Symbsリストにある各銘柄を順に評価することから始まります。各銘柄について、現在のティックデータを履歴バッファ(prevTick)に保存し、SymbolInfoTick関数を通じて最新の市場状態を取得します。

定義された取引時間帯(start_timeend_time)の間は、システムが価格の極値を動的に追跡します。セッション中の最高値は最も高いask価格で更新され、最安値は最も低いbid価格で更新されます。現在時刻がこの範囲内にある場合、b_entryフラグが有効になり、そのレンジが取引可能であることを示します。

取引時間帯の終了後には、以下の3つの条件のいずれかにより、レンジの再構築が評価されます。

  1. セッションの予定終了(close_time)
  2. 高値および安値の両方を同時にブレイクアウトした場合
  3. end_timeのタイムスタンプが無効または期限切れの場合
これらの条件のいずれかが満たされると、CalculateRangeが呼び出され、新しいレンジパラメータが生成されます。ループの最後にはcheckBreakが実行され、価格がレンジの境界を超えたかどうかを検出し、該当する取引を実行します。この仕組みにより、EAはリアルタイムの価格変動に対して継続的に市場を監視し、ブレイクアウト戦略に基づいた戦略的な注文を迅速に実行することができます。
//+------------------------------------------------------------------+
//|                  Range Calculation function                      |
//+------------------------------------------------------------------+
void CalculateRange(int index, string symbol) {
   for(index = 0; index < ArraySize(Formatted_Symbs); index++){
      symbol = Formatted_Symbs[index];
      
      // Reset all the range variables
      rangeArray[index].start_time = 0;
      rangeArray[index].end_time = 0;
      rangeArray[index].close_time = 0;
      rangeArray[index].high = 0.0;
      rangeArray[index].low = 999999;
      rangeArray[index].b_entry = false;
      rangeArray[index].b_high_breakout = false;
      rangeArray[index].b_low_breakout = false;
      
      // Calculate range start time
      int time_cycle = 86400;
      rangeArray[index].start_time = (currTick[index].time - (currTick[index].time % time_cycle)) + RangeStart * 60;
      for(int i = 0; i < 8; i++){
         MqlDateTime tmp;
         TimeToStruct(rangeArray[index].start_time, tmp);
         int dotw = tmp.day_of_week;
         if(currTick[index].time >= rangeArray[index].start_time || dotw == 6 || dotw == 0){
            rangeArray[index].start_time += time_cycle;
         }
      }
   
      // Calculate range end time
      rangeArray[index].end_time = rangeArray[index].start_time + RangeDuration * 60;
      for(int i = 0 ; i < 2; i++){
         MqlDateTime tmp;
         TimeToStruct(rangeArray[index].end_time, tmp);
         int dotw = tmp.day_of_week;
         if(dotw == 6 || dotw == 0){
            rangeArray[index].end_time += time_cycle;
         }
      }
      
      // Calculate range close
      rangeArray[index].close_time = (rangeArray[index].end_time - (rangeArray[index].end_time % time_cycle)) + RangeClose * 60;
      for(int i = 0; i < 3; i++){
         MqlDateTime tmp;
         TimeToStruct(rangeArray[index].close_time, tmp);
         int dotw = tmp.day_of_week;
         if(rangeArray[index].close_time <= rangeArray[index].end_time || dotw == 6 || dotw == 0){
            rangeArray[index].close_time += time_cycle;
         }
      } 
      
   }
}

CalculateRange関数は、複数の銘柄に対して時間ベースの取引レンジを初期化・設定します。Formatted_Symbs配列内の各銘柄について、この関数はまず、開始時刻・終了時刻・クローズ時刻、高値・安値の価格閾値、ブレイクアウトフラグなど、レンジに関連する重要なパラメータを初期値にリセットします。start_timeは、現在のティック時刻を24時間単位の時間サイクルに合わせ、そこからユーザー定義のRangeStart(分単位)を加算して計算されます。週末(土日)を回避し、時間が前後関係として正しいことを確認するために、必要に応じて日単位で時間を繰り上げるループ処理が行われます。これにより、市場の休場時間を考慮しつつ、取引ウィンドウの基準時刻が設定されます。

次に、RangeDurationをstart_timeに加算してend_time(レンジ終了時刻)を計算します。こちらも同様に、週末にかからないよう検証ループを使って調整がおこなわれます。最後に、EAがブレイクアウト監視を停止する基準時刻であるclose_timeを、RangeCloseに基づいて算出し、同様に週末を避けるよう調整します。これらの動的な時刻設定により、この関数はEAが複数の銘柄にわたって正確にレンジ取引条件を設定し、非取引時間や週末を回避しつつ、ブレイクアウト検出の精度を確保できるようにします。

bool CLots(double sl, double &lots){
   lots = In_Lot;

   if(!CHLots(lots)){return false;}
   return true;
}

CLots関数は、取引が実行される前にロットサイズを設定・検証するための関数です。この関数は2つのパラメータを受け取ります。1つ目はストップロス「sl」、2つ目は最終的なロットサイズを格納する参照変数「lots」です。

bool CHLots(double &lots){
   for(int i = 0; i < ArraySize(Formatted_Symbs); i++){
      string symbol = Formatted_Symbs[i];
      double min = SymbolInfoDouble(symbol, SYMBOL_VOLUME_MIN);
      double max = SymbolInfoDouble(symbol, SYMBOL_VOLUME_MAX);
      double step = SymbolInfoDouble(symbol, SYMBOL_VOLUME_STEP);
      if(lots < min){
         lots = min;
         return true;
      }
      if(lots > max){
         return false;
      }
      
      lots = (int)MathFloor(lots / step)  * step;
      
   }
   return true;

}

CHLots関数は、各銘柄におけるブローカー固有の取引ルールに準拠するようロットサイズを検証します。この関数は、Formatted_Symbsに登録された銘柄リストを順に処理し、ブローカーが定めた最小ロットサイズ、最大ロットサイズ、および許容されるロット増分ステップを取得します。これらのパラメータは、注文数量に関する運用上の制約を定義するものです。指定されたロット数が最小値を下回っている場合、関数はそれを最小許容ロット数に自動補正し、適正であることを示すためにtrueを返します。逆に、ロット数が最大制限を超える場合は、関数はfalseを返して取引リクエストを拒否し、不正な注文の実行を防ぎます。

精度を確保するため、関数はロット数をステップに沿って切り下げる処理を行います。具体的には、「MathFloor(lots/step)*step」の式を使用して、ロット数がステップ単位に合致するよう調整します。これにより、ブローカーから拒否される可能性のある端数や不正確なロット値を排除します。すべての条件を満たしており、補正が不要な場合には、関数はそのままtrueを返し、ロットサイズが有効であることを示します。このように、CHLots関数は取引量に関する検証を厳密に行い、ボリューム違反による注文拒否を防止し、ライブ取引環境におけるEAの信頼性を高める重要な保護機能となります。

//+------------------------------------------------------------------+
//|                      Check for Breakout                          |
//+------------------------------------------------------------------+
void checkBreak(int i, string symbol) {
   for (i = 0; i < ArraySize(Formatted_Symbs); i++) {
      symbol = Formatted_Symbs[i];
      
      //get indicator vals
      if(CopyBuffer(handles[i], 0, 1, 2, bufferM) != 2){
         Print("Failed to get indicator values");
         return;
      }

      int stopLevel = (int)SymbolInfoInteger(symbol, SYMBOL_TRADE_STOPS_LEVEL);
      int spread = (int)SymbolInfoInteger(symbol, SYMBOL_SPREAD);
      double Bid = SymbolInfoDouble(symbol, SYMBOL_BID);
      double Ask = SymbolInfoDouble(symbol, SYMBOL_ASK);

      if (currTick[i].time >= rangeArray[i].end_time && rangeArray[i].end_time > 0 && rangeArray[i].b_entry) {
         double rangeSize = rangeArray[i].high - rangeArray[i].low;

         // High Breakout (BUY/SELL)
         bool upperBreak = bufferM[0] >= upprer_level && bufferM[1] < upprer_level;
         bool lowerBreak = bufferM[0] <= (100 - upprer_level) && bufferM[1] > (100 - upprer_level);
         bool HighSigType,LowSigType;
         
         if(BreakOutMode == Normal_Signal){
            HighSigType = upperBreak;
         }else{HighSigType = lowerBreak;}
         if (!rangeArray[i].b_high_breakout && currTick[i].ask >= rangeArray[i].high && HighSigType) {
            rangeArray[i].b_high_breakout = true;

            double entry = NormalizeDouble(Ask + 100 * _Point, _Digits);
            double sl = rangeArray[i].low;
            //sl = NormalizeDouble(sl, true);
            double tp = entry + TakeProfit * _Point;

            double lots;
            if (!CLots(entry - sl, lots)) continue;

            if (!trade.PositionOpen(symbol, ORDER_TYPE_BUY, lots, currTick[i].ask, sl, tp, "High Breakout"))
               Print("Buy Order Failed: ", GetLastError());
         }                                                                      
                                                                                
         if(BreakOutMode == Normal_Signal){
            LowSigType = upperBreak;
         }else{LowSigType = lowerBreak; }                                                                       
         // Low Breakout (SELL)
         if (!rangeArray[i].b_low_breakout && currTick[i].bid <= rangeArray[i].low  && LowSigType) {
            rangeArray[i].b_low_breakout = true;

            double entry = NormalizeDouble(Bid - 100 * _Point, _Digits);
            double sl = rangeArray[i].high;
            //sl = NormalizeDouble(sl,true);
            double tp = entry - TakeProfit * _Point;

            double lots;
            if (!CLots(sl - entry, lots)) continue;

            if (!trade.PositionOpen(symbol, ORDER_TYPE_SELL, lots, currTick[i].bid, sl, tp, "Low Breakout"))
               Print("Sell Order Failed: ", GetLastError());
         }
      }
   }
}

この関数は、価格のブレイクアウトを監視し、ストキャスティクスのシグナルと価格変動を組み合わせて取引の実行を管理します。関数はFormatted_Symbs内の各銘柄を順に処理し、まずCopyBuffer関数を使ってオシレーターの値を取得しようとします。データ取得に失敗した場合は、誤った判断を避けるためにエラーを記録し、関数の処理を終了します。各銘柄について、ストップレベル、スプレッド、現在のbid/ask価格など、取引に必要な重要パラメータを取得します。ブレイクアウトの評価は、レンジ期間が終了した後にのみ行われます。これは、現在時刻がその銘柄のend_timeを超えており、かつb_entryフラグが有効になっていることを確認することで実現されます。これにより、分析が有効な取引ウィンドウ内に限定されるよう保証されます。

高値ブレイクアウト(買いシグナル)の場合、関数は2つの条件を確認します。1つ目はアスク価格がそのセッションの高値を上回ること、2つ目はストキャスティクスが売られすぎの状態(100 - upper_level未満)を示していることです。条件が満たされると、関数は買い注文を発行します。この際、エントリープライス、ストップロス(レンジの変動幅に基づく)、テイクプロフィットを計算し、CLots関数を通じてロットサイズの妥当性を検証します。

反対に、安値ブレイクアウト(売りシグナル)の場合は、ビッド価格がそのセッションの安値を下回り、かつオシレーターが買われすぎ(upper_levelより上)を示していることが条件となります。これらが成立すれば、同様のリスク管理パラメータに基づいて売り注文が発行されます。どちらのケースでも、注文に失敗した場合はエラーログが出力され、処理の透明性が保たれます。価格閾値とオシレーターによるシグナルを同期させることで、この関数は規律ある、条件主導型の取引執行を実現しています。

//+------------------------------------------------------------------+
//|                      Trailing Stoploss                           |
//+------------------------------------------------------------------+
void Trailler(){
   if(!TrailYourStop) return;
   
   for(int i = PositionsTotal()-1; i >= 0; i--){
      ulong ticket = PositionGetTicket(i);
      if(ticket <= 0) continue;
      
      if(!PositionSelectByTicket(ticket)) continue;
      
      // Get position details
      string symbol = PositionGetString(POSITION_SYMBOL);
      long magic;
      if(!PositionGetInteger(POSITION_MAGIC, magic)) continue;
      if(magic != MagicNumber) continue;

      // Get current prices
      MqlTick latestTick;
      if(!SymbolInfoTick(symbol, latestTick)) continue;
      
      long type;
      double openPrice, currentSl, currentTp;
      PositionGetInteger(POSITION_TYPE, type);
      PositionGetDouble(POSITION_PRICE_OPEN, openPrice);
      PositionGetDouble(POSITION_SL, currentSl);
      PositionGetDouble(POSITION_TP, currentTp);
      
      // Calculate pip values
      double pipSize = 10 * SymbolInfoDouble(symbol, SYMBOL_POINT);
      double currentPrice = type == POSITION_TYPE_BUY ? latestTick.bid : latestTick.ask;
      double priceMove = MathAbs(currentPrice - openPrice);
      
      // Calculate required moves
      double requiredMove = 70 * pipSize; // 20 pips
      double trailAmount = 10 * pipSize;  // 10 pips
      
      // Calculate new stop loss
      double newSl = currentSl;
      bool inProfit = type == POSITION_TYPE_BUY ? 
                     (currentPrice > openPrice) : 
                     (currentPrice < openPrice);
      
      if(inProfit && priceMove >= requiredMove){
         int steps = int(priceMove / requiredMove);
         if(type == POSITION_TYPE_BUY){
             newSl = openPrice + (steps * trailAmount);
             newSl = MathMax(newSl, currentSl + trailAmount);
         }
         else{
             newSl = openPrice - (steps * trailAmount);
             newSl = MathMin(newSl, currentSl - trailAmount);
         }
      }
      
      // Validate and modify SL
      if(newSl != currentSl){
         // Check stop levels
         double minDist = SymbolInfoInteger(symbol, SYMBOL_TRADE_STOPS_LEVEL) * _Point;
         newSl = NormalizeDouble(newSl, _Digits);
         
         if(type == POSITION_TYPE_BUY && (currentPrice - newSl) >= minDist){
             if(!trade.PositionModify(ticket, newSl, currentTp))
                 Print("Buy Trailing Failed: ", GetLastError());
         }
         else if(type == POSITION_TYPE_SELL && (newSl - currentPrice) >= minDist){
             if(!trade.PositionModify(ticket, newSl, currentTp))
                 Print("Sell Trailing Failed: ", GetLastError());
         }
      }
   }
}

トレーリングストップロス(TSL)機能は、取引が有利に動いた際に、利益を確保しつつリスクを最小限に抑えるために、ストップロスを自動的に調整する仕組みです。Trailer関数は、価格の動きと高値-安値レンジの割合に基づいてストップロスレベルを再計算・更新し、不必要な早期決済を防ぎつつ利益を確実に守ります。



結論

まとめると、ポートフォリオの最適化と分散投資は、複数の銘柄に資金を分散することでリスクを抑えつつリターンを最大化するために不可欠な戦略です。従来の取引手法は単一の通貨ペア戦略に偏りがちであり、高いボラティリティや市場固有のリスクにさらされやすくなります。分散投資はポートフォリオの耐久力を強化し、最適化手法は過去のパフォーマンスパターン、銘柄のボラティリティ指標、マーケット間の相関関係を分析することで、リスクとリターンの効率的なバランスを追求します。

結論として、取引戦略にポートフォリオ最適化と分散投資を取り入れることは、市場変動に対してより強靭で柔軟なアプローチを提供します。ブレイクアウト戦略とオシレーター指標を組み合わせることで、高確率のエントリーポイントを見極めつつ、リスクを動的に管理することが可能になります。この方法は、安定した利益獲得の可能性を高めるだけでなく、ドローダウンを軽減することで長期的な持続性も向上させます。

以下の結果を得るために、EAは基軸銘柄としてEURUSDを用い、遅延ゼロ理想的な約定環境、すべてのティックをモデル化する最高精度の条件でテストされました。テスト期間は2022年2月1日から2022年3月22日までです。入力設定では、BreakoutModeReversed_signalに設定され、TrailYourStopが有効(true)となっており、動的なストップロス調整を可能にしています。その他の入力パラメータはデフォルト値のまま使用しました。

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

添付されたファイル |
Dyna_MP.mq5 (37.35 KB)
最後のコメント | ディスカッションに移動 (8)
Alberto Tortella
Alberto Tortella | 19 4月 2025 において 13:48

OK、Input/SymbolsにEURUSDと 書いたら動くようになりました。

ありがとう。

CapeCoddah
CapeCoddah | 19 4月 2025 において 20:10

ストラテジー・テスターで、なぜこのような奇妙な期間を使ったのか興味があります。 2024年の全月を予想していたのですが。 トレーリング・ストップ・ロスのコンセプトはいいですね。私も同じ手法を使っています。私が工夫したのは、損益分岐点に達しそうになった後、取引がマイナスに転じた場合の損失を最小限に抑えることです。


これからも記事を楽しみにしています。

ケープコッダ

Brian Pereira
Brian Pereira | 23 4月 2025 において 08:07
Alberto Tortella ストキャスティクスオシレーターは グラフ上では正常に動作しているように見えます。


助けていただけますか?ありがとうございました。

各入力通貨はコンマで区切ってください。通貨と通貨の間にスペースを入れないでください。

CapeCoddah
CapeCoddah | 30 4月 2025 において 11:33

またまたこんにちは、


あなたのシステムをアクティブなチャートで使ってみたところ、いくつかの改善点が見つかりました。


Albertoの問題は、マーケットウォッチウィンドウのシンボルリストにすべてのペアが含まれていなかったことでしょう。

ArraySize(...)の代わりにNum_symblsを使うと、少し速くなります。 また、フルネームを綴ることで、他の人があなたのコードをより理解しやすくなり、多くのyntaxエラーを防ぐことができます。

DisplayObjectsはコードになかったので、私が追加した。

DisplayObjectsの中に、チャート・シンボルだけを選択する条件を追加しました。 他のシンボルを列挙する必要はありませんし、画面を乱雑にしてしまいます。 しかし、もしかしたら何か見落としているかもしれません。

最後に、レンジ計算に問題があります。 アクティブなチャート上で、ストラテジーテスター 上ではありませんが、EAを開始すると、現在の日付より先の未来のレイが生成されます。 例えば、4/30に開始すると、4/30の午前10時に開始し、5/1に終了するレイが生成されます。 この結果、表示されないレイがチャートに表示されませんが、オブジェクトリストには表示されます。 これは、あなたに直してもらいましょう。

私のコードを添付します。


ありがとう、CapeCoddah

CapeCoddah
CapeCoddah | 30 4月 2025 において 13:02
パート1とパート2のEASが同じなので、何かが混乱したのだと思う。 パート1とパート2が同じように見える。
オープニングレンジブレイクアウト日中取引戦略の解読 オープニングレンジブレイクアウト日中取引戦略の解読
オープニングレンジブレイクアウト(ORB)戦略は、市場が開いた直後に形成される初期の取引レンジが、買い手と売り手が価値に合意する重要な価格レベルを反映しているという考えに基づいて構築されています。特定のレンジを上抜けまたは下抜けするブレイクアウトを特定することで、市場の方向性が明確になるにつれて発生することが多いモメンタムを利用し、トレーダーは利益を狙うことができます。本記事では、Concretum Groupの論文から応用した3つのORB戦略を紹介します。
PythonとMQL5を使用した特徴量エンジニアリング(第4回):UMAP回帰によるローソク足パターン認識 PythonとMQL5を使用した特徴量エンジニアリング(第4回):UMAP回帰によるローソク足パターン認識
次元削減手法は、機械学習モデルのパフォーマンスを向上させるために広く用いられています。ここでは、UMAP (Uniform Manifold Approximation and Projection)という比較的新しい手法について説明します。UMAPは、古い手法に見られるデータの歪みや人工的な構造といった欠点を明確に克服することを目的として開発されました。UMAPは非常に強力な次元削減技術であり、似たローソク足を新たに効果的にグループ化できるため、アウトオブサンプル(未知データ)に対する誤差率を低減し、取引パフォーマンスを向上させることができます。
プライスアクション分析ツールキットの開発(第20回):External Flow (IV) — Correlation Pathfinder プライスアクション分析ツールキットの開発(第20回):External Flow (IV) — Correlation Pathfinder
Correlation Pathfinderは、「プライスアクション分析ツールキット開発」連載の一環として、通貨ペアの動的な関係を理解するための新しいアプローチを提供します。このツールはデータの収集と分析を自動化し、EUR/USDやGBP/USDなどのペアがどのように連動して動いているかを可視化します。リスク管理を強化し、より効果的にチャンスを捉えるための実用的かつリアルタイムな情報で、取引戦略のレベルを引き上げましょう。
手動バックテストを簡単に:MQL5でストラテジーテスター用のカスタムツールキットを構築する 手動バックテストを簡単に:MQL5でストラテジーテスター用のカスタムツールキットを構築する
この記事では、ストラテジーテスターでの手動バックテストを簡単におこなうための、カスタムMQL5ツールキットの設計について紹介します。設計と実装に焦点を当て、特にインタラクティブな取引操作の仕組みについて詳しく解説します。その後、このツールキットを使って、戦略を効果的にテストする方法を実演します。