English Русский Deutsch Português
preview
パラボリックSARを使ってトレーリングストップを追加する方法

パラボリックSARを使ってトレーリングストップを追加する方法

MetaTrader 5 | 17 9月 2024, 09:20
21 0
Artyom Trishkin
Artyom Trishkin

内容



はじめに

トレーリングストップは、ほとんどのトレーダーによく知られています。この機能はMetaTrader 5に組み込まれており、ストップロスレベルを自動的に調整し、現在の価格から一定の距離を保ちます。

MetaTrader 5でトレーリングストップを有効にする


トレーリングストップは、価格の動きに合わせてストップロスを自動的に追従させ、常に価格から一定の距離を保つ仕組みです。これにより、トレーダーはポジションを早期にクローズすることなく、得た利益の一部を確保できます。市場価格がポジション開始価格から離れるたびに、トレーリングストップはストップロスを現行価格に近づけ、指定された距離を維持します。一方で、価格が逆行して始値に近づいた場合、ストップロスはその位置に留まり、起こりうる市場変動による損失を最小限に抑える役割を果たします。

高度なトレーリングストップが必要な場合、MQL5を使って独自の関数を開発し、標準の機能を拡張することが可能です。

たとえば、ストップロスレベルを設定するためには、価格を受け取るプログラム関数があります。この関数は、StopLevelレベル(ストップを置ける最低限の距離)やFreezeLevelレベル(ポジションや保留中の注文を変更できない凍結距離)など、いくつかの禁止要因をチェックします。言い換えると、価格がFreezeLevelに近づいた場合、ストップ注文はトリガーされる見込みがあるため、変更が禁止されます。また、トレーリングストップにはいくつかの個別パラメータ設定があり、ストップロスレベルを指定価格に移動する前に、ポジションの銘柄やマジックナンバーなどの条件も確認されます。 これらの基準は、ストップロスポジションを指定されたレベルに移動させる直前にすべてチェックされます。

トレーリングの種類によって、ポジションのストップロス価格を計算するアルゴリズムは異なります。これらのアルゴリズムはトレーリング関数に渡され、トレーリングストップ機能で使用されます。

パラボリックSAR指標は、ストップロスが必要とするレベルの「ポインタ」として完璧に機能します。



パラボリックSAR (Stop and Reverse)指標は、テクニカル分析で広く利用されており、トレンドの終了や反転の可能性があるタイミングを見極めるために使用されます。ウェルズ・ワイルダーによって開発されたこの指標は、特に自動トレーリングストップロスに役立つツールとして人気があります。以下に、パラボリックSARが保護ストップをトレーリングするのに適している主な理由を説明します。

  1. 解釈のしやすさ:パラボリックSARは、チャート上に価格の上下に配置される点で視覚的に表されるため、非常に直感的です。ドットが価格の下にあるときは買いシグナル、上にあるときは売りシグナルとして解釈されます。

  2. 自動価格追従:パラボリックSARの大きな特徴は、価格変動に自動で対応し、時間の経過に応じて移動する点です。この特性により、トレーリングストップロスを設定するのに最適なツールとなり、トレンドに従ってストップロスを現行価格に近づけ、利益を保護します。

  3. 利益保護:未決済ポジションが利益に向かって動く際、パラボリックSARはストップロスを引き上げ、トレンドの反転が起こり得る場合にも蓄積された利益の一部を保護するのに役立ちます。

  4. エグジットシグナル:トレーリングストップとしての機能に加え、パラボリックSARは、ドットが価格を横切る瞬間にエグジットシグナルとしても機能します。これにより、トレンドの急変時にさらなる損失を防ぐことができます。

  5. 簡単なセットアップ:パラボリックSARのパラメータ(ステップと最大値)は、特定の市場のボラティリティや取引戦略に応じて簡単に調整可能です。そのため、さまざまな市場条件に対応できる汎用性の高いツールです。

  6. あらゆる時間枠に対応:この指標は、異なる時間枠においても効果的に利用できるため、長期投資家から短期トレーダーまで、さまざまなタイプの投資家に適しています。

トレーリングストップにパラボリックSARを活用することは、特にトレンドが発生している相場で非常に効果的です。このツールは、トレンドが続く限りポジションを維持し、利益を最大化するのに役立ちます。ただし、パラボリックSARを平坦な相場やボラティリティの低い状況で使用する際は注意が必要です。このような相場では、指標のドットの位置が頻繁に変わるため、トレンドが明確でないとポジションが早期に決済され、想定より早く手仕舞う可能性があります。


パラボリックSAR

トレーリングストップの構造を詳しく見てみましょう。

通常、トレーリングストップのコードは、自己完結型の複数のブロックに分けて設計され、一般的な構造から独立した関数として組み込むことが可能です。

  1. 必要なストップロスレベルを計算するブロック:その結果はトレーリングストップロスの処理をおこなうブロックに渡されます。
  2. トレーリングストップロスブロック:このブロックは、以下の要素で構成されます。
    1. フィルターブロック
    2. ストップロスをストップロスレベル計算ブロックから得られた値に設定するブロックには、以下が含まれます。
      1. 銘柄レベルに関するサーバー条件に準拠するためのフィルターブロック、およびストップロスをシフトするための条件
      2. ストップロス修正ブロック

必要なストップロスレベルを計算するためのブロック。ここではパラボリックSARです。その値(通常はバー1から)は、各ティックでトレーリングストップロスブロックに送られ、そこで選択された各ポジションのプロパティは、通常銘柄とマジックナンバーで未決済ポジションのリストをループしてフィルターブロックを通過します。次に、銘柄/マジックによるフィルタリングがパスされた場合、必要なストップロスレベルは、サーバーのストップレベル、トレーリングステップ、その前のポジションに対する必要なストップロスの値、およびポイント単位のポジション利益によるトレーリング開始の基準に準拠するための追加フィルタリングの対象となります。これらのすべてのフィルタリングが成功した場合、ポジションのストップロスは新しいレベルに修正されて設定されます。

すでに、ストップロスレベルを計算するためのブロックが存在しています。それがパラボリックSAR指標です。つまり、次に必要なのは、現在の銘柄とEAID(マジックナンバー)に基づいて選択されたポジションのストップロスレベルをシフトするブロックを作成することです。もしマジックナンバーを-1に設定すると、その銘柄で建てられたすべてのポジションがトレーリングされます。一方、特定のマジックナンバーが指定された場合は、そのマジックナンバーを持つポジションだけがトレーリングされます。トレーリング機能は、新しいバーが発生したときや新たにポジションが建てられたときにのみ起動されます。では、EAの例を挙げて説明してみましょう。

EAでは、EAの設定で指定されたパラメータでパラボリックSAR指標を作成します。指定されたバー(デフォルトでは最初のバー)から取得された指標値は、トレーリングストップ関数に渡され、トレーリングストップ関数は、ポジションのストップロスレベルをシフトするために必要なすべての計算を実行します。銘柄のStopLevelレベルを考慮する必要があり、これより近いレベルにはストップを置くことができません。現在のストップロスレベルもチェックされ、もしそれが関数に渡されたレベルと同じか、高い(買いの場合)か、低い(売りの場合)なら、ストップは移動する必要はありません。

これらのチェックはすべて、ストップロスポジションの修正基準をチェックするための特別な機能によって処理されます。必要なチェックがすべて完了した後、停止位置変更機能を使って停止位置を新しいレベルに移動します。

\MQL5\Experts\端末フォルダで、TrailingBySAR_01.mq5という新しいEAファイルを作成します。

新しいEAファイルを作成するウィザードの2番目のステップで、新しいウィンドウのOnTradeTransaction()ハンドラをチェックします。


OnTradeTransaction()ハンドラは、新規ポジションを建てる瞬間にトレーリングを開始するために必要です。

このハンドラはTradeTransactionイベントが発生したときに呼び出されます。このイベントも新しいポジションの開始を特徴とします。

取引とイベントの詳細については、「モスクワ証券取引所(MOEX)の為のトレードロボット作成は何から始めたら良いか」稿をご覧ください。

作成したEAファイルに以下の入力グローバル変数を追加します。

//+------------------------------------------------------------------+
//|                                             TrailingBySAR_01.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"

#define   SAR_DATA_INDEX   1  // bar we get Parabolic SAR data from

//--- input parameters
input ENUM_TIMEFRAMES   InpTimeframeSAR   =  PERIOD_CURRENT;   // Parabolic SAR Timeframe
input double            InpStepSAR        =  0.02;             // Parabolic SAR Step
input double            InpMaximumSAR     =  0.2;              // Parabolic SAR Maximum

//--- global variables
int      ExtHandleSAR =INVALID_HANDLE// Parabolic SAR handle
double   ExtStepSAR   =0;                 // Parabolic SAR step
double   ExtMaximumSAR=0;                 // Parabolic SAR maximum

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

   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//--- 

  }
//+------------------------------------------------------------------+
//| TradeTransaction function                                        |
//+------------------------------------------------------------------+
void OnTradeTransaction(const MqlTradeTransaction& trans,
                        const MqlTradeRequest& request,
                        const MqlTradeResult& result)
  {

  }


EAのOnInit()ハンドラで、指標設定で入力したパラメータに正しい値を設定し、指標を作成してハンドルを取得します。

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- set the Parabolic SAR parameters within acceptable limits
   ExtStepSAR   =(InpStepSAR<0.0001 ? 0.0001 : InpStepSAR);
   ExtMaximumSAR=(InpMaximumSAR<0.0001 ? 0.0001 : InpMaximumSAR);

//--- if there is an error creating the indicator, display a message in the journal and exit from OnInit with an error
   ExtHandleSAR =iSAR(Symbol(), InpTimeframeSAR, ExtStepSAR, ExtMaximumSAR);
   if(ExtHandleSAR==INVALID_HANDLE)
     {
      PrintFormat("Failed to create iSAR(%s, %s, %.3f, %.2f) handle. Error %d",
                  Symbol(), TimeframeDescription(InpTimeframeSAR), ExtStepSAR, ExtMaximumSAR, GetLastError());
      return(INIT_FAILED);
     }
//--- successful
   return(INIT_SUCCEEDED);
  }

指標の作成に成功すると、ハンドルが作成され、パラボリックSARから値を受信し続けるために使用されます。指標の作成中にエラーが発生した場合、操作ログに作成中の指標のデータとともにエラーメッセージが表示されます。指標が基づいている時間枠を説明するために、時間枠のテキスト説明を返す関数を使用します。

//+------------------------------------------------------------------+
//| Return timeframe description                                     |
//+------------------------------------------------------------------+
string TimeframeDescription(const ENUM_TIMEFRAMES timeframe)
  {
   return(StringSubstr(EnumToString(timeframe==PERIOD_CURRENT ? Period() : timeframe), 7));
  }

時間枠定数から、列挙テキストの一部を取得し、時間枠名のみを含む文字列を取得します。
例えば、PERIOD_H1定数から文字列「PERIOD_H1」を取得し、文字列から「H1」だけを返します。

新しいバーが開始されたかどうかを確認するには、現在のバーの開始時刻と、以前に記憶した開始時刻を比較する必要があります。チェック対象の値が等しくなければ、新しいバーが開かれています。
これは指標(現在の銘柄/期間の時系列の定義済み配列がすでにある)ではなく、EAなので、バーの開始時間を取得する関数を作成する必要があります。

//+------------------------------------------------------------------+
//| Return the bar opening time by timeseries index                  |
//+------------------------------------------------------------------+
datetime TimeOpenBar(const int index)
  {
   datetime array[1];
   ResetLastError();
   if(CopyTime(NULL, PERIOD_CURRENT, index, 1, array)!=1)
     {
      PrintFormat("%s: CopyTime() failed. Error %d", __FUNCTION__, GetLastError());
      return 0;
     }
   return array[0];
  }

取得したいバーのインデックスを関数に渡し、必要なデータを配列にコピーし、受け取ったデータを配列から返します。必要なのは1本のバーの時間だけなので、1次元の配列を定義します。

では、この関数を使って、新しいバーを開くフラグを返す関数を実装してみましょう。

//+------------------------------------------------------------------+
//| Return new bar opening flag                                      |
//+------------------------------------------------------------------+
bool IsNewBar(void)
  {
   static datetime time_prev=0;
   datetime        bar_open_time=TimeOpenBar(0);
   if(bar_open_time==0)
      return false;
   if(bar_open_time!=time_prev)
     {
      time_prev=bar_open_time;
      return true;
     }
   return false;
  }

ゼロバーの前回の開始時刻と、TimeOpenBar()関数から取得した現在の開始時刻を比較します。比較した値が等しくなければ、次のチェックのために新しい時間を記憶し、新しいバー開始フラグをtrueとして返します。エラーが発生した場合、または比較した値が等しい場合は、falseを返します。

パラボリックSARからデータを受信し、後続関数に値を送信するために、指標ハンドルでデータを受信する関数を記述します。

//+------------------------------------------------------------------+
//| Return Parabolic SAR data from the specified timeseries index    |
//+------------------------------------------------------------------+
double GetSARData(const int index)
  {
   double array[1];
   ResetLastError();
   if(CopyBuffer(ExtHandleSAR, 0, index, 1, array)!=1)
     {
      PrintFormat("%s: CopyBuffer() failed. Error %d", __FUNCTION__, GetLastError());
      return EMPTY_VALUE;
     }
   return array[0];
  }

ここでは、バーの開始時刻を取得する関数とまったく同じです。関数に渡されたインデックスに従って、1次元の配列で値を取得し、正常に受信された場合は、配列から値を返します。エラーの場合はEMPTY_VALUEを返します。

ストップロスポジションを設定するには、価格からのストップ距離がStopLevel銘柄レベルによって設定された制限内にないことを確認する必要があります。ストップロス価格がStopLevel距離で許容される価格より近い場合、「無効なストップ」エラーのため、ストップポジションは設定されません。このようなエラーを避けるために、停止位置を設定する前にこの距離を確認する必要があります。もう1つのレベル、フリーズレベル(FreezeLevel)があります。これは、価格からポジションのストップ(ストップロスまたはテイクプロフィット)までの距離を示すもので、その範囲内ではストップレベルがトリガーされる可能性があるため、変更できません。しかし、ほとんどの場合、これらのレベルはもう使われていないので、ここでは確認しません。

StopLevelレベルに関しては、微妙なニュアンスがあります。レベルが0に設定されていても、それがないことを意味するわけではありません。これは、このレベルの浮動値を扱っていることを意味します。多くの場合、2つのスプレッド値に等しくなります。あるいは3つのこともあります。ここでは、サーバーの設定に依存するため、値を選択する必要があります。そのために、StopLevel値を取得するためのカスタムパラメータを関数内に作成します。この関数は、StopLevelがゼロに設定されている場合、StopLevelを得るためにスプレッドに銘柄を乗じる乗数を渡します。StopLevelレベルが0でない場合、この値が単に返されます

//+------------------------------------------------------------------+
//| Return StopLevel value of the current symbol in points           |
//+------------------------------------------------------------------+
int StopLevel(const int spread_multiplier)
  {
   int spread    =(int)SymbolInfoInteger(Symbol(), SYMBOL_SPREAD);
   int stop_level=(int)SymbolInfoInteger(Symbol(), SYMBOL_TRADE_STOPS_LEVEL);
   return(stop_level==0 ? spread * spread_multiplier : stop_level);
  }


メインの後続関数を実装します。

//+------------------------------------------------------------------+
//| Trailing stop function by StopLoss price value                   |
//+------------------------------------------------------------------+
void TrailingStopByValue(const double value_sl, const long magic=-1, const int trailing_step_pt=0, const int trailing_start_pt=0)
  {
//--- price structure
   MqlTick tick={};
//--- in a loop by the total number of open positions
   int total=PositionsTotal();
   for(int i=total-1; i>=0; i--)
     {
      //--- get the ticket of the next position
      ulong  pos_ticket=PositionGetTicket(i);
      if(pos_ticket==0)
         continue;
         
      //--- get the symbol and position magic
      string pos_symbol = PositionGetString(POSITION_SYMBOL);
      long   pos_magic  = PositionGetInteger(POSITION_MAGIC);
      
      //--- skip positions that do not match the filter by symbol and magic number
      if((magic!=-1 && pos_magic!=magic) || pos_symbol!=Symbol())
         continue;
         
      //--- if failed to get the prices, move on
      if(!SymbolInfoTick(Symbol(), tick))
         continue;
      
      //--- get the position type, its opening price and StopLoss level
      ENUM_POSITION_TYPE pos_type=(ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE);
      double             pos_open=PositionGetDouble(POSITION_PRICE_OPEN);
      double             pos_sl  =PositionGetDouble(POSITION_SL);
      
      //--- if StopLoss modification conditions are suitable, modify the position stop level
      if(CheckCriterion(pos_type, pos_open, pos_sl, value_sl, trailing_step_pt, trailing_start_pt, tick))
         ModifySL(pos_ticket, value_sl);
     }
  }

ロジックは簡単で、端末内の未決済ポジションのリストをループし、チケットによって連続する各ポジションを選択し、ポジションの銘柄とマジックナンバーがポジションを選択するために設定されたフィルタに対応していることをチェックし、ストップロスレベルをシフトするための条件をチェックします。条件が整えば、ストップレベルを変更します。

以下は、ストップレベルの修正基準をチェックする関数です。

//+------------------------------------------------------------------+
//|Check the StopLoss modification criteria and return a flag        |
//+------------------------------------------------------------------+
bool CheckCriterion(ENUM_POSITION_TYPE pos_type, double pos_open, double pos_sl, double value_sl, 
                    int trailing_step_pt, int trailing_start_pt, MqlTick &tick)
  {
//--- if the stop position and the stop level for modification are equal, return 'false'
   if(NormalizeDouble(pos_sl-value_sl, Digits())==0)
      return false;

   double trailing_step = trailing_step_pt * Point(); // convert the trailing step into price
   double stop_level    = StopLevel(2) * Point();     // convert the StopLevel of the symbol into price
   int    pos_profit_pt = 0;                          // position profit in points
   
//--- depending on the type of position, check the conditions for modifying StopLoss
   switch(pos_type)
     {
      //--- long position
      case POSITION_TYPE_BUY :
        pos_profit_pt=int((tick.bid - pos_open) / Point());             // calculate the position profit in points
        if(tick.bid - stop_level > value_sl                             // if the price and the StopLevel level pending from it are higher than the StopLoss level (the distance to StopLevel is observed) 
           && pos_sl + trailing_step < value_sl                         // if the StopLoss level exceeds the trailing step based on the current StopLoss
           && (trailing_start_pt==0 || pos_profit_pt>trailing_start_pt) // if we trail at any profit or position profit in points exceeds the trailing start, return 'true'
          )
           return true;
        break;
        
      //--- short position
      case POSITION_TYPE_SELL :
        pos_profit_pt=int((pos_open - tick.ask) / Point());             // position profit in points
        if(tick.ask + stop_level < value_sl                             // if the price and the StopLevel level pending from it are lower than the StopLoss level (the distance to StopLevel is observed)
           && (pos_sl - trailing_step > value_sl || pos_sl==0)          // if the StopLoss level is below the trailing step based on the current StopLoss or a position has no StopLoss
           && (trailing_start_pt==0 || pos_profit_pt>trailing_start_pt) // if we trail at any profit or position profit in points exceeds the trailing start, return 'true'
          )
           return true;
        break;
        
      //--- return 'false' by default
      default: break;
     }
//--- no matching criteria
   return false;
  }

条件は簡単です。

  1. ストップレベルと、ストップを移動させる必要があるレベルが等しい場合、修正は必要ないので、falseを返します
  2. ストップレベルがStopLevelレベルで許容されるよりも価格に近い場合、エラーにより変更できない場合は、falseを返します
  3. 価格が最後の変更後まだ十分な距離を移動していない場合、変更するには時期尚早であり、トレーリングステップが維持されていないため、falseを返します
  4. 価格が指定した利益ポイントに達していない場合、修正するには早すぎるので、falseを返します

これらのシンプルなルールは、あらゆるトレーリングの基本です。基準を満たせば、ストップを変更する必要があります。そうでない場合は、次にこの関数が呼ばれたときに条件がチェックされます。

チケットによってポジションのストップロス価格を変更する関数を書いてみましょう。

//+------------------------------------------------------------------+
//| Modify StopLoss of a position by ticket                          |
//+------------------------------------------------------------------+
bool ModifySL(const ulong ticket, const double stop_loss)
  {
//--- if failed to select a position by ticket, report this in the journal and return 'false'
   ResetLastError();
   if(!PositionSelectByTicket(ticket))
     {
      PrintFormat("%s: Failed to select position by ticket number %I64u. Error %d", __FUNCTION__, ticket, GetLastError());
      return false;
     }
     
//--- declare the structures of the trade request and the request result
   MqlTradeRequest   request={};
   MqlTradeResult    result ={};

//--- fill in the request structure
   request.action    = TRADE_ACTION_SLTP;
   request.symbol    = PositionGetString(POSITION_SYMBOL);
   request.magic     = PositionGetInteger(POSITION_MAGIC);
   request.tp        = PositionGetDouble(POSITION_TP);
   request.position  = ticket;
   request.sl        = NormalizeDouble(stop_loss,(int)SymbolInfoInteger(Symbol(),SYMBOL_DIGITS));
   
//--- if the trade operation could not be sent, report this to the journal and return 'false'
   if(!OrderSend(request, result))
     {
      PrintFormat("%s: OrderSend() failed to modify position #%I64u. Error %d",__FUNCTION__, ticket, GetLastError());
      return false;
     }
     
//--- request to change StopLoss position successfully sent
   return true;
  }

関数に渡されたチケットに基づいてポジションを選択し、リクエスト構造の必須フィールドを埋めて、取引注文をサーバーに送信します。エラーの場合は、操作ログにエラーコードを含むメッセージを表示し、falseを返します。取引操作の詳細についてはMQL5言語ドキュメントを参照してください。

パラボリックSARの値に基づいてストップポジションをトレーリングする関数を実装してみましょう。

//+------------------------------------------------------------------+
//| StopLoss trailing function using the Parabolic SAR indicator     |
//+------------------------------------------------------------------+
void TrailingStopBySAR(const long magic=-1, const int trailing_step_pt=0, const int trailing_start_pt=0)
  {
//--- get the Parabolic SAR value from the first bar of the timeseries
   double sar=GetSARData(SAR_DATA_INDEX);
   
//--- if failed to obtain data, leave
   if(sar==EMPTY_VALUE)
      return;
      
//--- call the trailing function with the StopLoss price obtained from Parabolic SAR 
   TrailingStopByValue(sar, magic, trailing_step_pt, trailing_start_pt);
  }

まず、バー1から指標の値を取得します。値が得られない場合は終了します。パラボリックSARからの値が正常に受信された場合、その値をストップトレーリング関数に送信します。

それでは、EAハンドラでパラボリックSARのトレーリングを設定してみましょう。

OnTick()内:

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//--- if not a new bar, leave the handler
   if(!IsNewBar())
      return;
      
//--- trail position stops by Parabolic SAR
   TrailingStopBySAR();
  }

新しいバーが開始するたびに、パラボリックSARのトレーリング関数はデフォルト値で呼び出されます-銘柄に対して建てられたすべてのポジションは、そのマジックナンバーに関係なく、トレーリングされます。ポジションのストップレベルは、ポジションが建てられた時点から、トレーリングステップなしに、またポイント単位のポジションの利益を考慮することなく、指標の値に従って正確にトレーリングされます。

OnTradeTransaction()内:

//+------------------------------------------------------------------+
//| TradeTransaction function                                        |
//+------------------------------------------------------------------+
void OnTradeTransaction(const MqlTradeTransaction& trans,
                        const MqlTradeRequest& request,
                        const MqlTradeResult& result)
  {
   if(trans.type==TRADE_TRANSACTION_DEAL_ADD)
      TrailingStopBySAR();
  }

ポジションが建てられたときにトレーリングが機能するためには、端末内のディールリストに新しいディールが追加されていることを条件に、ハンドラ内でトレーリング関数が呼び出されます。このハンドラを使用しないと、新しいバーが開いたときにのみトレーリングがトリガーされます。

基本的なトレーリングストップ機能を作成し、パラボリックSAR指標をベースにしたトレーリングEAを作成しました。EAをコンパイルし、チャート上で実行することで、ポジションを建て、パラボリックSAR値でトレーリングを管理することができます。EAファイルを以下に添付します。


標準ライブラリCTradeを使用する

MQL5標準ライブラリは、エンドユーザーがより簡単にプログラムを開発できるように設計されています。このライブラリは、MQL5の内部関数への便利なアクセスを提供します。

この機会を利用して、ストップロスポジション変更関数をCTradeクラスのPositionModify()メソッドに置き換えてみましょう。

ポジション変更メソッドの内容を見てみましょう。

//+------------------------------------------------------------------+
//| Modify specified opened position                                 |
//+------------------------------------------------------------------+
bool CTrade::PositionModify(const ulong ticket,const double sl,const double tp)
  {
//--- check stopped
   if(IsStopped(__FUNCTION__))
      return(false);
//--- check position existence
   if(!PositionSelectByTicket(ticket))
      return(false);
//--- clean
   ClearStructures();
//--- setting request
   m_request.action  =TRADE_ACTION_SLTP;
   m_request.position=ticket;
   m_request.symbol  =PositionGetString(POSITION_SYMBOL);
   m_request.magic   =m_magic;
   m_request.sl      =sl;
   m_request.tp      =tp;
//--- action and return the result
   return(OrderSend(m_request,m_result));
  }

上のEAに書かれているストップポジション修正関数と比較します。

//+------------------------------------------------------------------+
//| Modify StopLoss of a position by ticket                          |
//+------------------------------------------------------------------+
bool ModifySL(const ulong ticket, const double stop_loss)
  {
//--- if failed to select a position by ticket, report this in the journal and return 'false'
   ResetLastError();
   if(!PositionSelectByTicket(ticket))
     {
      PrintFormat("%s: Failed to select position by ticket number %I64u. Error %d", __FUNCTION__, ticket, GetLastError());
      return false;
     }
     
//--- declare the structures of the trade request and the request result
   MqlTradeRequest   request={};
   MqlTradeResult    result ={};
   
//--- fill in the request structure
   request.action    = TRADE_ACTION_SLTP;
   request.symbol    = PositionGetString(POSITION_SYMBOL);
   request.magic     = PositionGetInteger(POSITION_MAGIC);
   request.tp        = PositionGetDouble(POSITION_TP);
   request.position  = ticket;
   request.sl        = NormalizeDouble(stop_loss,(int)SymbolInfoInteger(Symbol(),SYMBOL_DIGITS));
   
//--- if the trade operation could not be sent, report this to the journal and return 'false'
   if(!OrderSend(request, result))
     {
      PrintFormat("%s: OrderSend() failed to modify position #%I64u. Error %d",__FUNCTION__, ticket, GetLastError());
      return false;
     }
     
//--- request to change StopLoss position successfully sent
   return true;
  }

特に違いはありません。取引クラスのメソッドでは、一番最初に、チャートからEAを削除するフラグのチェックがあります。この関数にはそのようなチェック機能はありません。
IsStopped()標準関数の代わりに、同じ名前の取引クラスを仮パラメータで使用します。

//+------------------------------------------------------------------+
//| Checks forced shutdown of MQL5-program                           |
//+------------------------------------------------------------------+
bool CTrade::IsStopped(const string function)
  {
   if(!::IsStopped())
      return(false);
//--- MQL5 program is stopped
   PrintFormat("%s: MQL5 program is stopped. Trading is disabled",function);
   m_result.retcode=TRADE_RETCODE_CLIENT_DISABLES_AT;
   return(true);
  }

ここで、IsStopped()がtrueを返した場合、まず操作ログにEAがチャートから削除された旨のメッセージが表示され、次にEAの終了フラグが返されます。

取引関数とクラスメソッドの残りの違いは重要ではありません。これらは同じもので、実装が異なります。クラスメソッドでは、クラスヘッダーで宣言された構造体はクリアされますが、関数では、これらの構造体はローカルであり、関数が呼び出されたときに宣言され、すべてのフィールドはゼロで初期化されます。この関数では、取引要求の送信にエラーが発生した場合、直ちにエラーコードのメッセージが表示されます。その後、結果が返され、OrderSend()関数呼び出しは取引クラスのメソッドで単に返されます。

すでに実装されているEAに変更を加え、TrailingBySAR_02.mq5という新しい名前で保存してみましょう。ストラテジーテスターで、パラボリックSAR指標の値に基づいてポジションを建て、同じ指標の値に基づいて建てたポジションのストップロスレベルを移動させながら、トレーリングをテストできるようにしてみましょう。

取引クラスファイルをEAにインクルードし、入力でEAマジックナンバーを宣言し、グローバルエリアで取引クラスインスタンスを宣言します。さらに、OnInit()ハンドラで、入力からマジックナンバーを取引クラスオブジェクトに代入します

//+------------------------------------------------------------------+
//|                                             TrailingBySAR_02.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"

#define   SAR_DATA_INDEX   1  // bar we get Parabolic SAR data from

#include <Trade\Trade.mqh>    // replace trading functions with Standard Library methods

//--- input parameters
input ENUM_TIMEFRAMES   InpTimeframeSAR   =  PERIOD_CURRENT;   // Parabolic SAR Timeframe
input double            InpStepSAR        =  0.02;             // Parabolic SAR Step
input double            InpMaximumSAR     =  0.2;              // Parabolic SAR Maximum
input ulong             InpMagic          =  123;              // Magic Number

//--- global variables
int      ExtHandleSAR=INVALID_HANDLE;  // Parabolic SAR handle
double   ExtStepSAR=0;                 // Parabolic SAR step
double   ExtMaximumSAR=0;              // Parabolic SAR maximum
CTrade   ExtTrade;                     // trading operations class instance

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- set the magic number to the trading class object
   ExtTrade.SetExpertMagicNumber(InpMagic);

//--- set the Parabolic SAR parameters within acceptable limits
   ExtStepSAR   =(InpStepSAR<0.0001 ? 0.0001 : InpStepSAR);
   ExtMaximumSAR=(InpMaximumSAR<0.0001 ? 0.0001 : InpMaximumSAR);
   
//--- if there is an error creating the indicator, display a message in the journal and exit from OnInit with an error
   ExtHandleSAR =iSAR(Symbol(), InpTimeframeSAR, ExtStepSAR, ExtMaximumSAR);
   if(ExtHandleSAR==INVALID_HANDLE)
     {
      PrintFormat("Failed to create iSAR(%s, %s, %.3f, %.2f) handle. Error %d",
                  Symbol(), TimeframeDescription(InpTimeframeSAR), ExtStepSAR, ExtMaximumSAR, GetLastError());
      return(INIT_FAILED);
     }   
//--- successful
   return(INIT_SUCCEEDED);
  }


EAのOnTick()ハンドラに、ストラテジーテスターのパラボリックSARに従ってポジションを建てるコードを追加します。また、トレーリング関数にEAのマジックナンバーを渡すようにします。

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//--- if not a new bar, leave the handler
   if(!IsNewBar())
      return;
      
   if(MQLInfoInteger(MQL_TESTER))
     {
      //--- get Parabolic SAR data from bars 1 and 2
      double sar1=GetSARData(SAR_DATA_INDEX);
      double sar2=GetSARData(SAR_DATA_INDEX+1);
      
      //--- if the price structure is filled out and Parabolic SAR data is obtained
      MqlTick tick={};
      if(SymbolInfoTick(Symbol(), tick) && sar1!=EMPTY_VALUE && sar2!=EMPTY_VALUE)
        {
         //--- if Parabolic SAR on bar 1 is below Bid price, while on bar 2 it is above Bid price, open a long position
         if(sar1<tick.bid && sar2>tick.bid)
            ExtTrade.Buy(0.1);
         //--- if Parabolic SAR on bar 1 is above Ask, while on bar 2 it is below Ask price, open a long position
         if(sar1>tick.ask && sar2<tick.ask)
            ExtTrade.Sell(0.1);
        }
     }
      
//--- trail position stops by Parabolic SAR
   TrailingStopBySAR(InpMagic);
  }


OnTradeTransaction()ハンドラで、マジックナンバーをトレーリング関数に渡します

//+------------------------------------------------------------------+
//| TradeTransaction function                                        |
//+------------------------------------------------------------------+
void OnTradeTransaction(const MqlTradeTransaction& trans,
                        const MqlTradeRequest& request,
                        const MqlTradeResult& result)
  {
   if(trans.type==TRADE_TRANSACTION_DEAL_ADD)
      TrailingStopBySAR(InpMagic);
  }

EAのマジックナンバーを使ってテスターでポジションを建て、そのストップをトレーリングすることで、マジックナンバーによるフィルター動作を確認することができます。

汎用トレーリング関数で、修正関数の呼び出しを取引クラスメソッドの呼び出しに置き換えます

//+------------------------------------------------------------------+
//| Universal trailing stop function by StopLoss price value         |
//+------------------------------------------------------------------+
void TrailingStopByValue(const double value_sl, const long magic=-1, const int trailing_step_pt=0, const int trailing_start_pt=0)
  {
//--- price structure
   MqlTick tick={};
//--- in a loop by the total number of open positions
   int total=PositionsTotal();
   for(int i=total-1; i>=0; i--)
     {
      //--- get the ticket of the next position
      ulong  pos_ticket=PositionGetTicket(i);
      if(pos_ticket==0)
         continue;
         
      //--- get the symbol and position magic
      string pos_symbol = PositionGetString(POSITION_SYMBOL);
      long   pos_magic  = PositionGetInteger(POSITION_MAGIC);
      
      //--- skip positions that do not match the filter by symbol and magic number
      if((magic!=-1 && pos_magic!=magic) || pos_symbol!=Symbol())
         continue;
         
      //--- if failed to get the prices, move on
      if(!SymbolInfoTick(Symbol(), tick))
         continue;
         
      //--- get the position type, its opening price and StopLoss level
      ENUM_POSITION_TYPE pos_type=(ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE);
      double             pos_open=PositionGetDouble(POSITION_PRICE_OPEN);
      double             pos_sl  =PositionGetDouble(POSITION_SL);
      
      //--- if StopLoss modification conditions are suitable, modify the position stop level
      if(CheckCriterion(pos_type, pos_open, pos_sl, value_sl, trailing_step_pt, trailing_start_pt, tick))
         ExtTrade.PositionModify(pos_ticket, value_sl, PositionGetDouble(POSITION_TP));
     }
  }

さて、ストップレベルを変更するためには、以前に実装された関数ではなく、CTrade取引クラスのPositionModify()メソッドを呼び出します。これらの呼び出しの違いは、1つのパラメータにあります。もし関数がポジションのストップロス価格のみを変更するように書かれていた場合、パラメータを指定する際に、変更されるポジションのチケット値とそのストップレベルの新しいレベルを関数に渡す必要がありました。ポジションチケットとストップロスレベルの値に加えて、テイクプロフィットレベルも指定する必要があります。ポジションはすでに選択されており、そのテイクプロフィット値を変更する必要はないので、選択されたポジションのプロパティから直接変更せずにテイクプロフィット値をメソッドに渡します。

以前実装されていたModifySL()関数は、EAコードから削除されました。

EAをコンパイルし、ストラテジーテスターで任意の銘柄、任意のチャート時間枠で「毎ティック」モードで実行してみましょう。


見てわかるように、ポジションのストップはパラボリックSAR指標の最初のバーの値で正しくトレーリングされています。

EAファイルを以下に添付します。


EAで「数文字列で」レディメイドのトレーリングを実現する

しかし、トレーリングを簡単に作成し、EAで使用することができるにもかかわらず、その操作に必要なすべての関数を毎回新しいEAに書き込む必要はありません。
これはMQL5でも可能です。そのためには、プラグインファイルを一度書くだけでよく、あとはそれを目的のEAに接続し、トレーリング関数を呼び出す必要がある場所に書くだけで済みます。

すべての軌跡の作成された関数を1つの新しいファイルに移動しましょう。EAを含むフォルダに新しいプラグインファイルTrailingsFunc.mqhを作成します。このようなファイルは、すべてのインクルードファイルの共通フォルダ(\MQL5\Include\)か、その中のサブフォルダーに保存することが望ましいです。しかし、このテストでは、EAのあるフォルダに直接ファイルを作成し、そこからインクルードすれば十分です。

エディターでCtrl+Nを押し、新しいインクルードファイルを選択します。


次のウィザードウィンドウで、TrailingFuncファイルの名前を入力します。
デフォルトでは、ファイル名を入力する文字列には、インクルードファイルのルートディレクトリであるインクルードがすでに含まれています。つまり、ファイルはこのフォルダに作成されます。
その後、単純にEAのある希望のフォルダに移動するか、ファイル名の文字列に希望のフォルダへのパスを手動で入力します(Includeの代わりにExpertsと入力し、テストEAを使用する場合はテストEAのあるフォルダへのパスを入力し、その後に作成されたファイル名TrailingFuncを入力します)。


[Finish]をクリックすると、空のファイルが作成されます。

//+------------------------------------------------------------------+
//|                                                TrailingsFunc.mqh |
//|                                  Copyright 2024, MetaQuotes Ltd. |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2024, MetaQuotes Ltd."
#property link      "https://www.mql5.com"
//+------------------------------------------------------------------+
//| defines                                                          |
//+------------------------------------------------------------------+
// #define MacrosHello   "Hello, world!"
// #define MacrosYear    2010
//+------------------------------------------------------------------+
//| DLL imports                                                      |
//+------------------------------------------------------------------+
// #import "user32.dll"
//   int      SendMessageA(int hWnd,int Msg,int wParam,int lParam);
// #import "my_expert.dll"
//   int      ExpertRecalculate(int wParam,int lParam);
// #import
//+------------------------------------------------------------------+
//| EX5 imports                                                      |
//+------------------------------------------------------------------+
// #import "stdlib.ex5"
//   string ErrorDescription(int error_code);
// #import
//+------------------------------------------------------------------+


ここで、テストEAから以前に作成したすべての関数をここに転送する必要があります。

//+------------------------------------------------------------------+
//|                                                TrailingsFunc.mqh |
//|                                  Copyright 2024, MetaQuotes Ltd. |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2024, MetaQuotes Ltd."
#property link      "https://www.mql5.com"
//+------------------------------------------------------------------+
//| Simple trailing by value                                         |
//+------------------------------------------------------------------+
void SimpleTrailingByValue(const double value_sl, const long magic=-1, 
                           const int trailing_step_pt=0, const int trailing_start_pt=0, const int trailing_offset_pt=0)
  {
//--- price structure
   MqlTick tick={};
   
//--- in a loop by the total number of open positions
   int total=PositionsTotal();
   for(int i=total-1; i>=0; i--)
     {
      //--- get the ticket of the next position
      ulong  pos_ticket=PositionGetTicket(i);
      if(pos_ticket==0)
         continue;
         
      //--- get the symbol and position magic
      string pos_symbol = PositionGetString(POSITION_SYMBOL);
      long   pos_magic  = PositionGetInteger(POSITION_MAGIC);
      
      //--- skip positions that do not match the filter by symbol and magic number
      if((magic!=-1 && pos_magic!=magic) || pos_symbol!=Symbol())
         continue;
         
      //--- if failed to get the prices, move on
      if(!SymbolInfoTick(Symbol(), tick))
         continue;
         
      //--- get the position type, its opening price and StopLoss level
      ENUM_POSITION_TYPE pos_type=(ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE);
      double             pos_open=PositionGetDouble(POSITION_PRICE_OPEN);
      double             pos_sl  =PositionGetDouble(POSITION_SL);
      
      //--- if StopLoss modification conditions are suitable, modify the position stop level
      if(CheckCriterion(pos_type, pos_open, pos_sl, value_sl, trailing_step_pt, trailing_start_pt, tick))
         ModifySL(pos_ticket, value_sl);
     }
  }
//+------------------------------------------------------------------+
//|Check the StopLoss modification criteria and return a flag        |
//+------------------------------------------------------------------+
bool CheckCriterion(ENUM_POSITION_TYPE pos_type, double pos_open, double pos_sl, double value_sl, 
                    int trailing_step_pt, int trailing_start_pt, MqlTick &tick)
  {
//--- if the stop position and the stop level for modification are equal, return 'false'
   if(NormalizeDouble(pos_sl-value_sl, Digits())==0)
      return false;

   double trailing_step = trailing_step_pt * Point(); // convert the trailing step into price
   double stop_level    = StopLevel(2) * Point();     // convert the StopLevel of the symbol into price
   int    pos_profit_pt = 0;                          // position profit in points
   
//--- depending on the type of position, check the conditions for modifying StopLoss
   switch(pos_type)
     {
      //--- long position
      case POSITION_TYPE_BUY :
        pos_profit_pt=int((tick.bid - pos_open) / Point());             // calculate the position profit in points
        if(tick.bid - stop_level > value_sl                             // if the price and the StopLevel level pending from it are higher than the StopLoss level (the distance to StopLevel is observed) 
           && pos_sl + trailing_step < value_sl                         // if the StopLoss level exceeds the trailing step based on the current StopLoss
           && (trailing_start_pt==0 || pos_profit_pt>trailing_start_pt) // if we trail at any profit or position profit in points exceeds the trailing start, return 'true'
          )
           return true;
        break;
        
      //--- short position
      case POSITION_TYPE_SELL :
        pos_profit_pt=int((pos_open - tick.ask) / Point());             // position profit in points
        if(tick.ask + stop_level < value_sl                             // if the price and the StopLevel level pending from it are lower than the StopLoss level (the distance to StopLevel is observed)
           && (pos_sl - trailing_step > value_sl || pos_sl==0)          // if the StopLoss level is below the trailing step based on the current StopLoss or a position has no StopLoss
           && (trailing_start_pt==0 || pos_profit_pt>trailing_start_pt) // if we trail at any profit or position profit in points exceeds the trailing start, return 'true'
          )
           return true;
        break;
        
      //--- return 'false' by default
      default: break;
     }
//--- no matching criteria
   return false;
  }
//+------------------------------------------------------------------+
//| Modify StopLoss of a position by ticket                          |
//+------------------------------------------------------------------+
bool ModifySL(const ulong ticket, const double stop_loss)
  {
//--- if failed to select a position by ticket, report this in the journal and return 'false'
   ResetLastError();
   if(!PositionSelectByTicket(ticket))
     {
      PrintFormat("%s: Failed to select position by ticket number %I64u. Error %d", __FUNCTION__, ticket, GetLastError());
      return false;
     }
     
//--- declare the structures of the trade request and the request result
   MqlTradeRequest    request={};
   MqlTradeResult     result ={};
   
//--- fill in the request structure
   request.action    = TRADE_ACTION_SLTP;
   request.symbol    = PositionGetString(POSITION_SYMBOL);
   request.magic     = PositionGetInteger(POSITION_MAGIC);
   request.tp        = PositionGetDouble(POSITION_TP);
   request.position  = ticket;
   request.sl        = NormalizeDouble(stop_loss,(int)SymbolInfoInteger(request.symbol,SYMBOL_DIGITS));
   
//--- if the trade operation could not be sent, report this to the journal and return 'false'
   if(!OrderSend(request, result))
     {
      PrintFormat("%s: OrderSend() failed to modify position #%I64u. Error %d",__FUNCTION__, ticket, GetLastError());
      return false;
     }
     
//--- request to change StopLoss position successfully sent
   return true;
  }
//+------------------------------------------------------------------+
//| Return StopLevel in points                                       |
//+------------------------------------------------------------------+
int StopLevel(const int spread_multiplier)
  {
   int spread    =(int)SymbolInfoInteger(Symbol(), SYMBOL_SPREAD);
   int stop_level=(int)SymbolInfoInteger(Symbol(), SYMBOL_TRADE_STOPS_LEVEL);
   return(stop_level==0 ? spread * spread_multiplier : stop_level);
  }
//+------------------------------------------------------------------+
//| Return timeframe description                                     |
//+------------------------------------------------------------------+
string TimeframeDescription(const ENUM_TIMEFRAMES timeframe)
  {
   return(StringSubstr(EnumToString(timeframe==PERIOD_CURRENT ? Period() : timeframe), 7));
  }
//+------------------------------------------------------------------+
//| Return new bar opening flag                                      |
//+------------------------------------------------------------------+
bool IsNewBar(void)
  {
   static datetime time_prev=0;
   datetime        bar_open_time=TimeOpenBar(0);
   if(bar_open_time==0)
      return false;
   if(bar_open_time!=time_prev)
     {
      time_prev=bar_open_time;
      return true;
     }
   return false;
  }
//+------------------------------------------------------------------+
//| Return the bar opening time by timeseries index                  |
//+------------------------------------------------------------------+
datetime TimeOpenBar(const int index)
  {
   datetime array[1];
   ResetLastError();
   if(CopyTime(NULL, PERIOD_CURRENT, index, 1, array)!=1)
     {
      PrintFormat("%s: CopyTime() failed. Error %d", __FUNCTION__, GetLastError());
      return 0;
     }
   return array[0];
  }

パラボリックSAR指標からデータを受信する機能がEAに実装されました。この関数のデータは、指定された指示ハンドルによって取得されました。これは、パラボリックSARだけでなく、ストップロスポジションを設定する価格として使用するのに適した他の指標でも、このような指標として機能することを意味します。

そのため、ここではこの関数をハンドルで指標からデータを受け取る一般的な関数に改名しています。

//+------------------------------------------------------------------+
//| Return indicator data by handle                                  |
//| from the specified timeseries index                              |
//+------------------------------------------------------------------+
double GetIndData(const int handle_ind, const int index)
  {
   double array[1];
   ResetLastError();
   if(CopyBuffer(handle_ind, 0, index, 1, array)!=1)
     {
      PrintFormat("%s: CopyBuffer() failed. Error %d", __FUNCTION__, GetLastError());
      return EMPTY_VALUE;
     }
   return array[0];
  }

従って、上記関数から受け取った指標データに基づく後続関数が存在することになります。

//+------------------------------------------------------------------+
//| Trailing by indicator data specified by handle                   |
//+------------------------------------------------------------------+
void TrailingByDataInd(const int handle_ind, const int index=1, const long magic=-1, 
                       const int trailing_step_pt=0, const int trailing_start_pt=0, const int trailing_offset_pt=0)
  {
//--- get the Parabolic SAR value from the specified timeseries index
   double data=GetIndData(handle_ind, index);
   
//--- if failed to obtain data, leave
   if(data==EMPTY_VALUE)
      return;
      
//--- call the simple trailing function with the StopLoss price obtained from Parabolic SAR 
   SimpleTrailingByValue(data, magic, trailing_step_pt, trailing_start_pt, trailing_offset_pt);
  }

この関数では、まず、指定したバーインデックスから指定したハンドルで指標からデータを取得し、次に、指標から受信したストップロス値を渡して、値で後続関数を呼び出します。

したがって、この目的に適したあらゆる指標のデータを使って、ポジションのストップレベルをトレーリングすることができます。

パラボリックSAR指標の作成をEAコードに含めないために、ファイルに以下の関数を設定します。

//+------------------------------------------------------------------+
//| Create and return the Parabolic SAR handle                       |
//+------------------------------------------------------------------+
int CreateSAR(const string symbol_name, const ENUM_TIMEFRAMES timeframe, const double step_sar=0.02, const double max_sar=0.2)
  {
//--- set the indicator parameters within acceptable limits
   double step=(step_sar<0.0001 ? 0.0001 : step_sar);
   double max =(max_sar <0.0001 ? 0.0001 : max_sar);

//--- adjust the symbol and timeframe values
   ENUM_TIMEFRAMES period=(timeframe==PERIOD_CURRENT ? Period() : timeframe);
   string          symbol=(symbol_name==NULL || symbol_name=="" ? Symbol() : symbol_name);
 
//--- create indicator handle
   ResetLastError();
   int handle=iSAR(symbol, period, step, max);
   
//--- if there is an error creating the indicator, display an error message in the journal
   if(handle==INVALID_HANDLE)
     {
      PrintFormat("Failed to create iSAR(%s, %s, %.3f, %.2f) handle. Error %d",
                  symbol, TimeframeDescription(period), step, max, GetLastError());
     } 
//--- return the result of creating the indicator handle
   return handle;
  }

基本的に、CreateSAR()関数はTrailingBySAR_01.mq5テストEAのOnInit()ハンドラから移動したコードです。この方法を使えば、EAに指標の入力変数補正文字列を記述したり、そのハンドルを作成したりすることなく、単純にこの関数を呼び出すことができます。

コードのさらに下には、様々な移動平均を作成するための同様の関数があり、例えば、適応移動平均を作成する関数があります。

//+------------------------------------------------------------------+
//| Create and return Adaptive Moving Average handle                 |
//+------------------------------------------------------------------+
int CreateAMA(const string symbol_name, const ENUM_TIMEFRAMES timeframe,
              const int ama_period=9, const int fast_ema_period=2, const int slow_ema_period=30, const int shift=0, const ENUM_APPLIED_PRICE price=PRICE_CLOSE)
  {
//--- set the indicator parameters within acceptable limits
   int ma_period=(ama_period<1 ? 9 : ama_period);
   int fast_ema=(fast_ema_period<1 ? 2 : fast_ema_period);
   int slow_ema=(slow_ema_period<1 ? 30 : slow_ema_period);

//--- adjust the symbol and timeframe values
   ENUM_TIMEFRAMES period=(timeframe==PERIOD_CURRENT ? Period() : timeframe);
   string          symbol=(symbol_name==NULL || symbol_name=="" ? Symbol() : symbol_name);
 
//--- create indicator handle
   ::ResetLastError();
   int handle=::iAMA(symbol, period, ma_period, fast_ema, slow_ema, shift, price);
   
//--- if there is an error creating the indicator, display an error message in the journal
   if(handle==INVALID_HANDLE)
     {
      ::PrintFormat("Failed to create iAMA(%s, %s, %d, %d, %d, %s) handle. Error %d",
                    symbol, TimeframeDescription(period), ma_period, fast_ema, slow_ema,
                    ::StringSubstr(::EnumToString(price),6), ::GetLastError());
     }
//--- return the result of creating the indicator handle
   return handle;
  }

その他の関数は、上で紹介したものと同様です。ここで考慮する必要はありません。以下に添付したTrailingsFunc.mqhファイルにあります。

これらの関数は、移動平均を素早く作成するように設計されており、様々なタイプのトレーリングを作成することを目的とした独自の研究をおこなう際に、パラボリックSARデータの代わりにそのデータを使用することができます。


実装された関数をテストするには、テストEA TrailingBySAR_03.mq5を作成し、新しく作成したTrailingsFunc.mqhファイルをインクルードします。
グローバル領域で、作成した指標のハンドルを格納する変数を宣言し、OnInit()ハンドラで、パラボリックSAR指標の作成結果をこの変数に代入します

//+------------------------------------------------------------------+
//|                                             TrailingBySAR_03.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"

#define   SAR_DATA_INDEX   1  // bar we get Parabolic SAR data from

#include "TrailingsFunc.mqh"

//--- input parameters
input ENUM_TIMEFRAMES   InpTimeframeSAR   =  PERIOD_CURRENT;   // Parabolic SAR Timeframe
input double            InpStepSAR        =  0.02;             // Parabolic SAR Step
input double            InpMaximumSAR     =  0.2;              // Parabolic SAR Maximum
input long              InpMagic          =  123;              // Expert Magic Number

//--- global variables
int   ExtHandleSAR=INVALID_HANDLE;  // Parabolic SAR handle

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- create Parabolic SAR handle
   ExtHandleSAR=CreateSAR(Symbol(), InpTimeframeSAR, InpStepSAR, InpMaximumSAR);
   
//--- if there is an error creating the indicator, exit OnInit with an error
   if(ExtHandleSAR==INVALID_HANDLE)
      return(INIT_FAILED);

//--- successful
   return(INIT_SUCCEEDED);
  }


あとは、OnTick()およびOnTradeTransaction()ハンドラで作成した指標のデータに従って、EAの設定で指定したポジションマジックナンバーをトレーリング関数に渡してトレーリングを再開するだけです。

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//--- if not a new bar, leave the handler
   if(!IsNewBar())
      return;
      
//--- trail position stops by Parabolic SAR
   TrailingByDataInd(ExtHandleSAR, SAR_DATA_INDEX, InpMagic);
  }
//+------------------------------------------------------------------+
//| TradeTransaction function                                        |
//+------------------------------------------------------------------+
void OnTradeTransaction(const MqlTradeTransaction& trans,
                        const MqlTradeRequest& request,
                        const MqlTradeResult& result)
  {
   if(trans.type==TRADE_TRANSACTION_DEAL_ADD)
      TrailingByDataInd(ExtHandleSAR, SAR_DATA_INDEX, InpMagic);
  }

作成されたEAは、パラボリックSAR指標のデータに基づいたトレーリングストップです。このEAが起動され、EAの設定で設定されたマジックナンバーと一致する銘柄でオープンされたポジションのストップレベルをトレーリングします。


トレーリングをEAに接続する

最後に、ParabolicSARのトレーリングを、\MQL5\Experts\Advisors\ExpertMACD.mq5にある標準的なExpertMACDEAに接続してみましょう。

ExpertMACDPSAR.mq5として保存し、トレーリングを含むように変更します。

グローバル領域で、トレーリング関数ファイルをインクルードし、トレーリングの入力を追加し、パラボリックSARによって作成されたハンドルを格納する変数を宣言します。

//+------------------------------------------------------------------+
//|                                                   ExpertMACD.mq5 |
//|                             Copyright 2000-2024, MetaQuotes Ltd. |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2000-2024, MetaQuotes Ltd."
#property link      "https://www.mql5.com"
#property version   "1.00"
//+------------------------------------------------------------------+
//| Include                                                          |
//+------------------------------------------------------------------+
#include <Expert\Expert.mqh>
#include <Expert\Signal\SignalMACD.mqh>
#include <Expert\Trailing\TrailingNone.mqh>
#include <Expert\Money\MoneyNone.mqh>

#include "TrailingsFunc.mqh"

//+------------------------------------------------------------------+
//| Inputs                                                           |
//+------------------------------------------------------------------+
//--- inputs for expert
input group  " - ExpertMACD Parameters -"
input string Inp_Expert_Title            ="ExpertMACD";
int          Expert_MagicNumber          =10981;
bool         Expert_EveryTick            =false;
//--- inputs for signal
input int    Inp_Signal_MACD_PeriodFast  =12;
input int    Inp_Signal_MACD_PeriodSlow  =24;
input int    Inp_Signal_MACD_PeriodSignal=9;
input int    Inp_Signal_MACD_TakeProfit  =50;
input int    Inp_Signal_MACD_StopLoss    =20;

//--- inputs for trail
input group  " - PSAR Trailing Parameters -"
input bool   InpUseTrail       =  true;      // Trailing is Enabled
input double InpSARStep        =  0.02;      // Trailing SAR Step
input double InpSARMaximum     =  0.2;       // Trailing SAR Maximum
input int    InpTrailingStart  =  0;         // Trailing start
input int    InpTrailingStep   =  0;         // Trailing step in points
input int    InpTrailingOffset =  0;         // Trailing offset in points
//+------------------------------------------------------------------+
//| Global expert object                                             |
//+------------------------------------------------------------------+
CExpert ExtExpert;
int     ExtHandleSAR;
//+------------------------------------------------------------------+
//| Initialization function of the expert                            |
//+------------------------------------------------------------------+


OnInit()ハンドラで、指標を作成し、そのハンドルを変数に書き込みます

//+------------------------------------------------------------------+
//| Initialization function of the expert                            |
//+------------------------------------------------------------------+
int OnInit(void)
  {
//--- Initializing trail
   if(InpUseTrail)
     {
      ExtHandleSAR=CreateSAR(NULL,PERIOD_CURRENT,InpSARStep,InpSARMaximum);
      if(ExtHandleSAR==INVALID_HANDLE)
         return(INIT_FAILED);
     }
   
//--- Initializing expert
//...
//...


OnDeinit()ハンドラで、ハンドルを削除し、指標の計算部分を解放します

//+------------------------------------------------------------------+
//| Deinitialization function of the expert advisor                  |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
   ExtExpert.Deinit();
   IndicatorRelease(ExtHandleSAR);
  }


OnTick()およびOnTrade()ハンドラで、パラボリックSAR指標のトレーリングを開始します

//+------------------------------------------------------------------+
//| Function-event handler "tick"                                    |
//+------------------------------------------------------------------+
void OnTick(void)
  {
   ExtExpert.OnTick();
   TrailingByDataInd(ExtHandleSAR, 1, Expert_MagicNumber, InpTrailingStep, InpTrailingStart, InpTrailingOffset);
  }
//+------------------------------------------------------------------+
//| Function-event handler "trade"                                   |
//+------------------------------------------------------------------+
void OnTrade(void)
  {
   ExtExpert.OnTrade();
   TrailingByDataInd(ExtHandleSAR, 1, Expert_MagicNumber, InpTrailingStep, InpTrailingStart, InpTrailingOffset);
  }

パラボリックSARのための本格的なトレーリングを追加するために、EAファイルに追加する必要があるのはこれだけです。

ここで、同じEAで、別の指標、例えば移動平均線のハンドルを作成し、そのハンドルをトレーリング関数に渡すことができます。この場合、移動平均線に基づくトレーリングが得られます。異なる指標から得られた値を組み合わせ、計算された合計値をトレーリング関数に送信することもできます。また、異なる市場状況においては、異なるアルゴリズムを使用してトレーリングストップをおこない、必要なポジションストップの計算値をトレーリング関数に渡すこともできます。実験の余地はたくさんあります。

重要なことは、TrailingFunc.mqhファイルで紹介されている関数は、次のようなものを作成できることです。

  1. 様々なアルゴリズムや指標の値に基づいた、取引以外のカスタムEA
  2. 既存の取引EAに様々なトレーリングストップを追加し、指標の値に従って、または独自のアルゴリズムに従って動作させる

しかし、制限もあります。ロングポジションとショートポジションのトレーリング関数に同時に異なる値を渡すことはできません。また、別の銘柄で開かれたポジションをトレーリングすることもできません。トレーリングを接続する際には、いくつか不便な点もあります。EAで指標を作成し、そのハンドルを変数に格納する必要があります。トレーリングクラスを作ることで、上記のようなこと、また他のいくつかのことを取り除くことができます。それについては、次回以降に考えていきます。

作成したEAを、設定したパラメータを使ってテスターでシングルパスで実行してみましょう。

以下の設定を選択します。

  • 銘柄:EURUSD
  • 時間枠:M15
  • 実行の遅延なしに、すべてのティックで最後の年をテスト

入力設定:


後続をオフにして一定の間隔でテストした結果、以下の統計が得られました。



では、設定でトレーリングを有効にしてEAを起動してみましょう。


トレーリングの後、チャートが少し滑らかになったのは明らかです。読者の皆さんには、自分でトレーリングをテストするためのさまざまな解決策を試してみることをお勧めします。

すべてのファイルは、自主学習やテストのために記事に添付されています。


結論

トレーリングストップを素早く作成し、EAに接続する方法を学びました。トレーリングストップをEAに組み込むために必要なことは、以下の通りです。

  1. TrailingsFunc.mqhインクルードファイルをEAフォルダに配置する
  2. このファイルをEAファイルにインクルードする(#include"TrailingsFunc.mqh")
  3. EAのOnInit()にParabolicSAR指標の作成を追加する(ExtHandleSAR=CreateSAR(NULL,PERIOD_CURRENT);)
  4. OnTick()および(必要に応じて)OnTrade()またはOnTradeTransaction()EAハンドラにトレーリングの呼び出しを追加する(TrailingByDataInd(ExtHandleSAR);)
  5. EAのOnDeinit()でIndicatorRelease(ExtHandleSAR)を使用して、ParabolicSAR指標の計算部分を解放する


これで、このEAにはポジションを管理するための本格的なトレーリングストップが組み込まれたことになります。パラボリックSAR指標だけでなく、他の指標でもトレーリングすることができます。また、ストップロスレベルを計算するためのカスタムアルゴリズムを作成することもできます。

1つのEAで異なるパラメータを持つ複数のトレーリングを使用し、市況に応じて切り替えることができます。次回は、単純な関数の欠点から解放されたトレーリングクラスについて考えてみたいと思います。



MetaQuotes Ltdによってロシア語から翻訳されました。
元の記事: https://www.mql5.com/ru/articles/14782

添付されたファイル |
ExpertMACDPSAR.mq5 (14.04 KB)
時系列の非定常性の指標としての2標本コルモゴロフ–スミルノフ検定 時系列の非定常性の指標としての2標本コルモゴロフ–スミルノフ検定
この記事では、最も有名なノンパラメトリック同質性検定の1つである2標本のコルモゴロフ–スミルノフ検定について考察します。モデルデータと実際の相場の両方が分析されています。また、この記事では非定常性指標(iスミルノフ距離)の構築例も紹介しています。
多通貨エキスパートアドバイザーの開発(第9回):単一取引戦略インスタンスの最適化結果の収集 多通貨エキスパートアドバイザーの開発(第9回):単一取引戦略インスタンスの最適化結果の収集
EA開発の主な段階を概説しましょう。最初におこなうべき重要な作業の1つは、開発した取引戦略のインスタンスを最適化することです。最適化プロセスにおいて、テスターが通過したパスに関する必要な情報を一箇所に集約してみましょう。
エラー 146 (「トレードコンテキスト ビジー」) と、その対処方法 エラー 146 (「トレードコンテキスト ビジー」) と、その対処方法
この記事では、MT4において複数のEAの衝突をさける方法を扱います。ターミナルの操作、MQL4の基本的な使い方がわかる人にとって、役に立つでしょう。
ニューラルネットワークが簡単に(第87回):時系列パッチ ニューラルネットワークが簡単に(第87回):時系列パッチ
予測は時系列分析において重要な役割を果たします。この新しい記事では、時系列パッチの利点についてお話しします。