MQL5クックブック:カスタムシンボルを使用したトレーディング戦略ストレステストe

Denis Kirichenko | 22 1月, 2020

イントロダクション

少し前に、MetaTrader5ターミナルに新しい関数が追加されました: カスタムシンボルの作成。 アルゴリズムトレーダーは、トレードサーバーを必要とせず、トレード環境を完全に制御できる一方で、場合によっては自分自身のブローカーとして機能できます。 ただし、ここでの「一部のケース」はテストモードのみを指します。 もちろん、カスタムシンボルのオーダーはブローカーによって実行することはできません。 にもかかわらず、この機能は、アルゴトレーダーがより慎重にトレード戦略をテストすることができます。

この記事では、このようなテストの条件を作成します。 カスタム シンボル クラスから始めましょう。


1. カスタム シンボル クラス CiCustomSymbol

標準ライブラリには、シンボル プロパティへの簡単なアクセスを行うためのクラスが用意されています。 CSymbolInfo クラスです。 実際には、このクラスは仲介機能を担います。ユーザーのリクエストがあった場合、クラスはサーバーと通信し、リクエストされたプロパティの値の形式で応答を受け取ります。

今回の目的は、カスタムシンボルの同様のクラスを作成することです。 さらに、作成、削除、その他のメソッドを追加する必要があるため、このクラスの関数はさらに広くなります。 一方、サーバーへの接続に関連付けるメソッドは使用しません。 Refresh()、IsSynchronized()などが含まれます。

トレード環境を作成するためのこのクラスは、カスタムシンボルを操作するための標準関数をカプセル化します。

クラス宣言の構造を以下に示します。

//+------------------------------------------------------------------+
//| Class CiCustomSymbol.                                            |
//| Purpose: Base class for a custom symbol.                         |
//+------------------------------------------------------------------+
class CiCustomSymbol : public CObject
  {
   //--- === Data members === ---
private:
   string            m_name;
   string            m_path;
   MqlTick           m_tick;
   ulong             m_from_msc;
   ulong             m_to_msc;
   uint              m_batch_size;
   bool              m_is_selected;
   //--- === Methods === ---
public:
   //--- constructor/destructor
   void              CiCustomSymbol(void);
   void             ~CiCustomSymbol(void) {};
   //--- create/delete
   int               Create(const string _name,const string _path="",const string _origin_name=NULL,
                            const uint _batch_size=1e6,const bool _is_selected=false);
   bool              Delete(void);
   //--- methods of access to protected data
   string            Name(void) const { return(m_name); }
   bool              RefreshRates(void);
   //--- fast access methods to the integer symbol properties
   bool              Select(void) const;
   bool              Select(const bool select);
   //--- service methods
   bool              Clone(const string _origin_symbol,const ulong _from_msc=0,const ulong _to_msc=0);
   bool              LoadTicks(const string _src_file_name);
   bool              ChangeSpread(const uint _spread_size,const uint _spread_markup=0,
                                  const ENUM_SPREAD_BASE _spread_base=SPREAD_BASE_BID);
   --- API
   bool              SetProperty(ENUM_SYMBOL_INFO_DOUBLE _property,double _val) const;
   bool              SetProperty(ENUM_SYMBOL_INFO_INTEGER _property,long _val) const;
   bool              SetProperty(ENUM_SYMBOL_INFO_STRING _property,string _val) const;
   double            GetProperty(ENUM_SYMBOL_INFO_DOUBLE _property) const;
   long              GetProperty(ENUM_SYMBOL_INFO_INTEGER _property) const;
   string            GetProperty(ENUM_SYMBOL_INFO_STRING _property) const;
   bool              SetSessionQuote(const ENUM_DAY_OF_WEEK _day_of_week,const uint _session_index,
                                     const datetime _from,const datetime _to);
   bool              SetSessionTrade(const ENUM_DAY_OF_WEEK _day_of_week,const uint _session_index,
                                     const datetime _from,const datetime _to);
   int               RatesDelete(const datetime _from,const datetime _to);
   int               RatesReplace(const datetime _from,const datetime _to,const MqlRates &_rates[]);
   int               RatesUpdate(const MqlRates &_rates[]) const;
   int               TicksAdd(const MqlTick &_ticks[]) const;
   int               TicksDelete(const long _from_msc,long _to_msc) const;
   int               TicksReplace(const MqlTick &_ticks[]) const;
   //---
private:
   template<typename PT>
   bool              CloneProperty(const string _origin_symbol,const PT _prop_type) const;
   int               CloneTicks(const MqlTick &_ticks[]) const;
   int               CloneTicks(const string _origin_symbol) const;
  };
//+------------------------------------------------------------------+

選択したシンボルからフル関数のカスタムシンボルを作成するメソッドから始めましょう。


1.1 The CiCustomSymbol::Create() method

すべてのカスタムシンボルの可能性を使用するには、作成するか、作成されていることを確認する必要があります。 

//+------------------------------------------------------------------+
//| Create a custom symbol                                           |
//| Codes:                                                           |
//|       -1 - failed to create;                                     |
//|        0 - a symbol exists, no need to create;                   |
//|        1 - successfully created.                                 |
//+------------------------------------------------------------------+
int CiCustomSymbol::Create(const string _name,const string _path="",const string _origin_name=NULL,
                           const uint _batch_size=1e6,const bool _is_selected=false)
  {
   int res_code=-1;
   m_name=m_path=NULL;
   if(_batch_size<1e2)
     {
      ::Print(__FUNCTION__+": a batch size must be greater than 100!");
     }
   else
     {
      ::ResetLastError();
      //--- attempt to create a custom symbol
      if(!::CustomSymbolCreate(_name,_path,_origin_name))
        {
         if(::SymbolInfoInteger(_name,SYMBOL_CUSTOM))
           {
            ::PrintFormat(__FUNCTION__+": a custom symbol \"%s\" already exists!",_name);
            res_code=0;
           }
         else
           {
            ::PrintFormat(__FUNCTION__+": failed to create a custom symbol. Error code: %d",::GetLastError());
           }
        }
      else
         res_code=1;
      if(res_code>=0)
        {
         m_name=_name;
         m_path=_path;
         m_batch_size=_batch_size;
         //--- if the custom symbol must be selected in the "Market Watch"
         if(_is_selected)
           {
            if(!this.Select())
               if(!this.Select(true))
                 {
                  ::PrintFormat(__FUNCTION__+": failed to set the \"Market Watch\" symbol flag. Error code: %d",::GetLastError());
                  return false;
                 }
           }
         else
           {
            if(this.Select())
               if(!this.Select(false))
                 {
                  ::PrintFormat(__FUNCTION__+": failed to unset the \"Market Watch\" symbol flag. Error code: %d",::GetLastError());
                  return false;
                 }
           }
         m_is_selected=_is_selected;
        }
     }
//---
   return res_code;
  }
//+------------------------------------------------------------------+

このメソッドは、値を数値コードとして返します。

_batch_sizeと_is_selectedのパラメータについて、単語を次に示します。

最初のパラメーター (_batch_size) は、ティックの読み込みに使用されるバッチのサイズを設定します。ティックはバッチで読み込まれます:データは最初に補助配列に読み込まれ、配列が満たされると、データはカスタムシンボルのティックデータベース(ティック履歴)にロードされます。 一方で、このアプローチを使用すると、巨大な配列を作成する必要がありません。他方では、ティックデータベースを頻繁に更新する必要もありません。 補助ティック配列のデフォルトサイズは100万です。

2番目のパラメータ(_is_selected)は、ティックをデータベースに直接書き込むか、最初にMarket Watchウィンドウに追加するかを決定します。

例として、 TestCreation.mql5 スクリプトを実行して、カスタムシンボルを作成します。

このメソッド呼び出しによって返されるコードは、ジャーナルに表示されます。

2019.08.11 12:34:08.055 TestCreation (EURUSD,M1) A custom symbol "EURUSD_1" creation has returned the code: 1

カスタム シンボルの作成に関する詳細については、ドキュメントを参照してください。


1.2 The CiCustomSymbol::Delete() メソッド

このメソッドは、カスタムシンボルを削除します。 シンボルを削除する前に、メソッドはマーケットウォッチウィンドウからシンボルを削除します。 失敗した場合、削除手続きは中断されます。

//+------------------------------------------------------------------+
//| Delete                                                           |
//+------------------------------------------------------------------+
bool CiCustomSymbol::Delete(void)
  {
   ::ResetLastError();
   if(this.Select())
      if(!this.Select(false))
        {
         ::PrintFormat(__FUNCTION__+": failed to set the \"Market Watch\" symbol flag. Error code: %d",::GetLastError());
         return false;
        }
   if(!::CustomSymbolDelete(m_name))
     {
      ::PrintFormat(__FUNCTION__+": failed to delete the custom symbol \"%s\". Error code: %d",m_name,::GetLastError());
      return false;
     }
//---
   return true;
  }
//+------------------------------------------------------------------+

例として、簡単なスクリプトTestDeleteをしてみましょう。MQL5を使用すると、カスタムシンボルが削除されます。 成功すると、対応するログがジャーナルに追加されます。

2019.08.11 19:13:59.276 TestDeletion (EURUSD,M1) A custom symbol "EURUSD_1" has been successfully deleted.



1.3 CiCustomSymbol::Clone() メソッド

このメソッドは、クローンを実行します。選択したシンボルに基づいて、現在のカスタムシンボルのプロパティを決定します。 簡単に言えば、元のシンボルのプロパティ値を受け取り、別のシンボルにコピーします。 ユーザーは、ティックヒストリーのクローンを設定することもできます。 これを行うには、時間間隔を定義する必要があります。

//+------------------------------------------------------------------+
//| Clone a symbol                                                   |
//+------------------------------------------------------------------+
bool CiCustomSymbol::Clone(const string _origin_symbol,const ulong _from_msc=0,const ulong _to_msc=0)
  {
   if(!::StringCompare(m_name,_origin_symbol))
     {
      ::Print(__FUNCTION__+": the origin symbol name must be different!");
      return false;
     }
   ::ResetLastError();
//--- if to load history
   if(_to_msc>0)
     {
      if(_to_msc<_from_msc)
        {
         ::Print(__FUNCTION__+": wrong settings for a time interval!");
         return false;
        }
      m_from_msc=_from_msc;
      m_to_msc=_to_msc;
     }
   else
      m_from_msc=m_to_msc=0;
//--- double
   ENUM_SYMBOL_INFO_DOUBLE dbl_props[]=
     {
      SYMBOL_MARGIN_HEDGED,
      SYMBOL_MARGIN_INITIAL,
      SYMBOL_MARGIN_MAINTENANCE,
      SYMBOL_OPTION_STRIKE,
      SYMBOL_POINT,
      SYMBOL_SESSION_PRICE_LIMIT_MAX,
      SYMBOL_SESSION_PRICE_LIMIT_MIN,
      SYMBOL_SESSION_PRICE_SETTLEMENT,
      SYMBOL_SWAP_LONG,
      SYMBOL_SWAP_SHORT,
      SYMBOL_TRADE_ACCRUED_INTEREST,
      SYMBOL_TRADE_CONTRACT_SIZE,
      SYMBOL_TRADE_FACE_VALUE,
      SYMBOL_TRADE_LIQUIDITY_RATE,
      SYMBOL_TRADE_TICK_SIZE,
      SYMBOL_TRADE_TICK_VALUE,
      SYMBOL_VOLUME_LIMIT,
      SYMBOL_VOLUME_MAX,
      SYMBOL_VOLUME_MIN,
      SYMBOL_VOLUME_STEP
     };
   for(int prop_idx=0; prop_idx<::ArraySize(dbl_props); prop_idx++)
     {
      ENUM_SYMBOL_INFO_DOUBLE curr_property=dbl_props[prop_idx];
      if(!this.CloneProperty(_origin_symbol,curr_property))
         return false;
     }
//--- integer
   ENUM_SYMBOL_INFO_INTEGER int_props[]=
     {
      SYMBOL_BACKGROUND_COLOR,
      SYMBOL_CHART_MODE,
      SYMBOL_DIGITS,
      SYMBOL_EXPIRATION_MODE,
      SYMBOL_EXPIRATION_TIME,
      SYMBOL_FILLING_MODE,
      SYMBOL_MARGIN_HEDGED_USE_LEG,
      SYMBOL_OPTION_MODE,
      SYMBOL_OPTION_RIGHT,
      SYMBOL_ORDER_GTC_MODE,
      SYMBOL_ORDER_MODE,
      SYMBOL_SPREAD,
      SYMBOL_SPREAD_FLOAT,
      SYMBOL_START_TIME,
      SYMBOL_SWAP_MODE,
      SYMBOL_SWAP_ROLLOVER3DAYS,
      SYMBOL_TICKS_BOOKDEPTH,
      SYMBOL_TRADE_CALC_MODE,
      SYMBOL_TRADE_EXEMODE,
      SYMBOL_TRADE_FREEZE_LEVEL,
      SYMBOL_TRADE_MODE,
      SYMBOL_TRADE_STOPS_LEVEL
     };
   for(int prop_idx=0; prop_idx<::ArraySize(int_props); prop_idx++)
     {
      ENUM_SYMBOL_INFO_INTEGER curr_property=int_props[prop_idx];
      if(!this.CloneProperty(_origin_symbol,curr_property))
         return false;
     }
//--- string
   ENUM_SYMBOL_INFO_STRING str_props[]=
     {
      SYMBOL_BASIS,
      SYMBOL_CURRENCY_BASE,
      SYMBOL_CURRENCY_MARGIN,
      SYMBOL_CURRENCY_PROFIT,
      SYMBOL_DESCRIPTION,
      SYMBOL_FORMULA,
      SYMBOL_ISIN,
      SYMBOL_PAGE,
      SYMBOL_PATH
     };
   for(int prop_idx=0; prop_idx<::ArraySize(str_props); prop_idx++)
     {
      ENUM_SYMBOL_INFO_STRING curr_property=str_props[prop_idx];
      if(!this.CloneProperty(_origin_symbol,curr_property))
         return false;
     }
//--- history
   if(_to_msc>0)
     {
      if(this.CloneTicks(_origin_symbol)==-1)
         return false;
     }
//---
   return true;
  }
//+------------------------------------------------------------------+


一部のプロパティはターミナルレベルで設定されるため、すべてのプロパティをコピーできるわけではないことに注意してください。 値は取得できますが、制御することはできません(get-properties)。

カスタムシンボルに get プロパティを設定しようとすると、エラー5307(ERR_CUSTOM_SYMBOL_PROPERTY_WRONG)が返されます。 さらに、「ランタイムエラー」セクションの下には、カスタムシンボル用のエラーの個別グループがあります。

例として、基本的なシンボルのクローンを作成するシンプルなスクリプト TestClone.mql5を実行してみましょう。 複製の試行が成功すると、次のログがジャーナルに表示されます。

2019.08.11 19:21:06.402 TestClone (EURUSD,M1) A base symbol "EURUSD" has been successfully cloned.



1.4 CiCustomSymbol::LoadTicks() メソッド

このメソッドは、ファイルからティックを読み取り、後で使用するために読み込みます。 メソッドは、このカスタムシンボルの既存のティックデータベースを以前に削除することに注意してください。 

//+------------------------------------------------------------------+
//| Load ticks                                                       |
//+------------------------------------------------------------------+
bool CiCustomSymbol::LoadTicks(const string _src_file_name)
  {
   int symbol_digs=(int)this.GetProperty(SYMBOL_DIGITS);;
//--- delete ticks
   if(this.TicksDelete(0,LONG_MAX)<0)
      return false;
//--- open a file
   CFile curr_file;
   ::ResetLastError();
   int file_ha=curr_file.Open(_src_file_name,FILE_READ|FILE_CSV,',');
   if(file_ha==INVALID_HANDLE)
     {
      ::PrintFormat(__FUNCTION__+": failed to open a %s file!",_src_file_name);
      return false;
     }
   curr_file.Seek(0,SEEK_SET);
//--- read data from a file
   MqlTick batch_arr[];
   if(::ArrayResize(batch_arr,m_batch_size)!=m_batch_size)
     {
      ::Print(__FUNCTION__+": failed to allocate memory for a batch array!");
      return false;
     }
   ::ZeroMemory(batch_arr);
   uint tick_idx=0;
   bool is_file_ending=false;
   uint tick_cnt=0;
   do
     {
      is_file_ending=curr_file.IsEnding();
      string dates_str[2];
      if(!is_file_ending)
        {
         //--- time
         string time_str=::FileReadString(file_ha);
         if(::StringLen(time_str)<1)
           {
            ::Print(__FUNCTION__+": no datetime string - the current tick skipped!");
            ::PrintFormat("The unprocessed string: %s",time_str);
            continue;
           }
         string sep=".";
         ushort u_sep;
         string result[];
         u_sep=::StringGetCharacter(sep,0);
         int str_num=::StringSplit(time_str,u_sep,result);
         if(str_num!=4)
           {
            ::Print(__FUNCTION__+": no substrings - the current tick skipped!");
            ::PrintFormat("The unprocessed string: %s",time_str);
            continue;
           }
         //--- datetime
         datetime date_time=::StringToTime(result[0]+"."+result[1]+"."+result[2]);
         long time_msc=(long)(1e3*date_time+::StringToInteger(result[3]));
         //--- bid
         double bid_val=::FileReadNumber(file_ha);
         if(bid_val<.0)
           {
            ::Print(__FUNCTION__+": no bid price - the current tick skipped!");
            continue;
           }
         //--- ask
         double ask_val=::FileReadNumber(file_ha);
         if(ask_val<.0)
           {
            ::Print(__FUNCTION__+": no ask price - the current tick skipped!");
            continue;
           }
         //--- volumes
         for(int jtx=0; jtx<2; jtx++)
            ::FileReadNumber(file_ha);
         //--- fill in the current tick
         MqlTick curr_tick= {0};
         curr_tick.time=date_time;
         curr_tick.time_msc=(long)(1e3*date_time+::StringToInteger(result[3]));
         curr_tick.bid=::NormalizeDouble(bid_val,symbol_digs);
         curr_tick.ask=::NormalizeDouble(ask_val,symbol_digs);
         //--- flags
         if(m_tick.bid!=curr_tick.bid)
            curr_tick.flags|=TICK_FLAG_BID;
         if(m_tick.ask!=curr_tick.ask)
            curr_tick.flags|=TICK_FLAG_ASK;
         if(curr_tick.flags==0)
            curr_tick.flags=TICK_FLAG_BID|TICK_FLAG_ASK;;
         if(tick_idx==m_batch_size)
           {
            //--- add ticks to the custom symbol
            if(m_is_selected)
              {
               if(this.TicksAdd(batch_arr)!=m_batch_size)
                  return false;
              }
            else
              {
               if(this.TicksReplace(batch_arr)!=m_batch_size)
                  return false;
              }
            tick_cnt+=m_batch_size;
            //--- log
            for(uint idx=0,batch_idx=0; idx<::ArraySize(dates_str); idx++,batch_idx+=(m_batch_size-1))
               dates_str[idx]=::TimeToString(batch_arr[batch_idx].time,TIME_DATE|TIME_SECONDS);
            ::PrintFormat("\nTicks loaded from %s to %s.",dates_str[0],dates_str[1]);
            //--- reset
            ::ZeroMemory(batch_arr);
            tick_idx=0;
           }
         batch_arr[tick_idx]=curr_tick;
         m_tick=curr_tick;
         tick_idx++;
        }
      //--- end of file
      else
        {
         uint new_size=tick_idx;
         if(new_size>0)
           {
            MqlTick last_batch_arr[];
            if(::ArrayCopy(last_batch_arr,batch_arr,0,0,new_size)!=new_size)
              {
               ::Print(__FUNCTION__+": failed to copy a batch array!");
               return false;
              }
            //--- add ticks to the custom symbol
            if(m_is_selected)
              {
               if(this.TicksAdd(last_batch_arr)!=new_size)
                  return false;
              }
            else
              {
               if(this.TicksReplace(last_batch_arr)!=new_size)
                  return false;
              }
            tick_cnt+=new_size;
            //--- log
            for(uint idx=0,batch_idx=0; idx<::ArraySize(dates_str); idx++,batch_idx+=(tick_idx-1))
               dates_str[idx]=::TimeToString(batch_arr[batch_idx].time,TIME_DATE|TIME_SECONDS);
            ::PrintFormat("\nTicks loaded from %s to %s.",dates_str[0],dates_str[1]);
           }
        }
     }
   while(!is_file_ending && !::IsStopped());
   ::PrintFormat("\nLoaded ticks number: %I32u",tick_cnt);
   curr_file.Close();
//---
   MqlTick ticks_arr[];
   if(::CopyTicks(m_name,ticks_arr,COPY_TICKS_INFO,1,1)!=1)
     {
      ::Print(__FUNCTION__+": failed to copy the first tick!");
      return false;
     }
   m_from_msc=ticks_arr[0].time_msc;
   if(::CopyTicks(m_name,ticks_arr,COPY_TICKS_INFO,0,1)!=1)
     {
      ::Print(__FUNCTION__+": failed to copy the last tick!");
      return false;
     }
   m_to_msc=ticks_arr[0].time_msc;
//---
   return true;
  }
//+------------------------------------------------------------------+

このバリアントでは、以下のティック構造項目がインプットされます

struct MqlTick 
  { 
   datetime     time;          // Last price update time 
   double       bid;           // Current Bid price 
   double       ask;           // Current Ask price 
   double       last;          // Current price of the last trade (Last)
   ulong        volume;        // Volume for the current Last price
   long         time_msc;      // Last price update time in milliseconds 
   uint         flags;         // Tick flags 
   double       volume_real;   // Volume for the current Last price
  };

The TICK_FLAG_BID|TICK_FLAG_ASK値は、最初のティックフラグに設定されます。 さらなる値は、どの価格(Bidまたは質問)が変更されたかによって異なります。 価格がいずれも変更されていない場合は、最初のティックとして処理されます。

build 2085 以降では、ティックヒストリーを読み込むだけでバーヒストリーを形成できます。 ヒストリーが読み込まれている場合は、プログラムで足のヒストリー をリクエストできます。

例として、ファイルからティックをロードするシンプルなスクリプト TestLoad. mql5を実行してみましょう。 データ ファイルは 、% MQL5/Files フォルダの下に配置する必要があります。 この例では、ファイルはEURUSD1_tick.csv です。 2019 年 8 月 1 日と 2 日の EURUSD ティックがあります。 さらに、ティック データ ソースについて検討します。

スクリプトを実行すると、読み込まれたティックのがジャーナルに表示されます。 また、ターミナルのティックデータベースからデータをリクエストすることで、利用可能なティック数を再確認します。 354,400 ティックがコピーされました。 したがって、数値は等しくなります。 また、2,697の1分間の足を受け取りました。 

NO      0       15:52:50.149    TestLoad (EURUSD,H1)    
LN      0       15:52:50.150    TestLoad (EURUSD,H1)    Ticks loaded from 2019.08.01 00:00:00 to 2019.08.02 20:59:56.
FM      0       15:52:50.152    TestLoad (EURUSD,H1)    
RM      0       15:52:50.152    TestLoad (EURUSD,H1)    Loaded ticks number: 354400
EJ      0       15:52:50.160    TestLoad (EURUSD,H1)    Ticks from the file "EURUSD1_tick.csv" have been successfully loaded.
DD      0       15:52:50.170    TestLoad (EURUSD,H1)    Copied 1-minute rates number: 2697
GL      0       15:52:50.170    TestLoad (EURUSD,H1)    The 1st rate time: 2019.08.01 00:00
EQ      0       15:52:50.170    TestLoad (EURUSD,H1)    The last rate time: 2019.08.02 20:56
DJ      0       15:52:50.351    TestLoad (EURUSD,H1)    Copied ticks number: 354400


その他のメソッドは、API メソッドのグループに属します。


2. データにチェッベアークを付けます。 ソース

ティックデータは、需要と供給が拮抗している価格シリーズを形成します。

価格シリーズの性質は、長いトレーダーやEAの考察の対象となっています。 このシリーズは、分析の対象であり、意思決定の基礎となります。

この記事のコンテキストでは、次のアイデアを適用できます。

ご存知のように、FXは店頭相場です。 したがって、ベンチマーククオートはありません。 その結果、参照に使用できるティックアーカイブ(ティックヒストリー)はありません。 しかし、通貨先物があり、そのほとんどはシカゴ・マーカンタイルトレード所でトレードされています。 おそらく、これらのクオートは、ベンチマークとして何らかの役割を果たすことができます。 ただし、ヒストリーデータは無料では取得できません。 場合によっては、無料のテスト期間を持つことができます。 しかし、この場合でも、ウェブサイトに登録し、営業マネージャーと通信する必要があります。 一方、ブローカー間の競争があります。 したがって、クオートはブローカー間で大きな差はありません。 しかし、通常、ブローカーはティックアーカイブを保存せず、ダウンロードはできません。

そこで、無料の情報源の一つとして、デュカスコピーのウェブサイトがあります。

簡単な登録の後、ティックを含むヒストリーデータをダウンロードできます。

ファイル内の行は、次の 5 つの列で構成されます。

ティックの手動ダウンロードの不便さは、1日しか選択できないということです。 ティックの数年のヒストリーが必要な場合は、ダウンロードに多くの時間と労力がかかります。


クォントデータマネージャ

図1 クォントデータマネージャアプリの[データ]タブでダウンロードするためのシンボル


デュカスコピー銀行のサイトからティックのアーカイブをダウンロードする補助アプリがあります。 そのうちの1つはQuant Data Manager(クォントデータマネージャ)です。 図1 は、アプリケーション ウィンドウのタブの 1 つを示します。 


CiCustomSymbol::LoadTicks() メソッドは、上記のアプリで使用するcsvファイル形式に調整されます。


3. トレード戦略のストレステスト

トレーディング戦略テストは、マルチアスペクトプロセスです。 ほとんどの場合、"testing" は、ヒストリークオート (バック テスト) を使用したトレードアルゴリズムの実行を指します。 しかし、トレード戦略をテストする他の方法があります。 

そのうちの一つはStress Testingです。

ストレス テストは、特定のシステムまたはエンティティの安定性を判断するために使用する、意図的に強烈なテストまたは徹底的なテストの一形態です。

このアイデアは簡単です:戦略の加速ストレスを提供するトレード戦略操作の特定の条件を作成します。 このような条件の究極の目標は、トレードシステムの信頼性が悪化する可能性のある条件に対する耐性をチェックすることです。


3.1 スプレッドチェンジ

スプレッドファクタは、追加コストの量を決定するので、トレード戦略にとって重要です。 短期的なトレードを目指す戦略は、特にスプレッドサイズに敏感です。 場合によっては、リターンスプレッドの比率が100%を超える場合があります。 

基本的なスプレッドサイズとは異なるカスタムシンボルを作成してみましょう。 このために、新しいメソッドを作成してみましょう CiCustomSymbol::ChangeSpread()

//+------------------------------------------------------------------+
//| Change the initial spread                                        |
//| Input parameters:                                                |
//|     1) _spread_size - the new fixed value of the spread, pips.   |
//|        If the value > 0 then the spread value is fixed.          |
//|     2) _spread_markup - a markup for the floating value of the   |
//|        spread, pips. The value is added to the current spread if |
//|        _spread_size=0.                                           |
//|     3) _spread_base - a type of the price to which a markup is   |
//|        added in case of the floating value.                      |
//+------------------------------------------------------------------+
bool CiCustomSymbol::ChangeSpread(const uint _spread_size,const uint _spread_markup=0,
                                  const ENUM_SPREAD_BASE _spread_base=SPREAD_BASE_BID)
  {
   if(_spread_size==0)
      if(_spread_markup==0)
        {
         ::PrintFormat(__FUNCTION__+":  neither the spread size nor the spread markup are set!",
                       m_name,::GetLastError());
         return false;
        }
   int symbol_digs=(int)this.GetProperty(SYMBOL_DIGITS);
   ::ZeroMemory(m_tick);
//--- copy ticks

   int tick_idx=0;
   uint tick_cnt=0;
   ulong from=1;
   double curr_point=this.GetProperty(SYMBOL_POINT);
   int ticks_copied=0;
   MqlDateTime t1_time;
   TimeToStruct((int)(m_from_msc/1e3),t1_time);
   t1_time.hour=t1_time.min=t1_time.sec=0;
   datetime start_datetime,stop_datetime;
   start_datetime=::StructToTime(t1_time);
   stop_datetime=(int)(m_to_msc/1e3);
   do
     {
      MqlTick custom_symbol_ticks[];
      ulong t1,t2;
      t1=(ulong)1e3*start_datetime;
      t2=(ulong)1e3*(start_datetime+PeriodSeconds(PERIOD_D1))-1;
      ::ResetLastError();
      ticks_copied=::CopyTicksRange(m_name,custom_symbol_ticks,COPY_TICKS_INFO,t1,t2);
      if(ticks_copied<0)
        {
         ::PrintFormat(__FUNCTION__+": failed to copy ticks for a %s symbol! Error code: %d",
                       m_name,::GetLastError());
         return false;
        }
      //--- there are some ticks for the current day
      else
         if(ticks_copied>0)
           {
            for(int t_idx=0; t_idx<ticks_copied; t_idx++)
              {
               MqlTick curr_tick=custom_symbol_ticks[t_idx];
               double curr_bid_pr=::NormalizeDouble(curr_tick.bid,symbol_digs);
               double curr_ask_pr=::NormalizeDouble(curr_tick.ask,symbol_digs);
               double curr_spread_pnt=0.;
               //--- if the spread is fixed
               if(_spread_size>0)
                 {
                  if(_spread_size>0)
                     curr_spread_pnt=curr_point*_spread_size;
                 }
               //--- if the spread is floating
               else
                 {
                  double spread_markup_pnt=0.;
                  if(_spread_markup>0)
                     spread_markup_pnt=curr_point*_spread_markup;
                  curr_spread_pnt=curr_ask_pr-curr_bid_pr+spread_markup_pnt;
                 }
               switch(_spread_base)
                 {
                  case SPREAD_BASE_BID:
                    {
                     curr_ask_pr=::NormalizeDouble(curr_bid_pr+curr_spread_pnt,symbol_digs);
                     break;
                    }
                  case SPREAD_BASE_ASK:
                    {
                     curr_bid_pr=::NormalizeDouble(curr_ask_pr-curr_spread_pnt,symbol_digs);
                     break;
                    }
                  case SPREAD_BASE_AVERAGE:
                    {
                     double curr_avg_pr=::NormalizeDouble((curr_bid_pr+curr_ask_pr)/2.,symbol_digs);
                     curr_bid_pr=::NormalizeDouble(curr_avg_pr-curr_spread_pnt/2.,symbol_digs);
                     curr_ask_pr=::NormalizeDouble(curr_bid_pr+curr_spread_pnt,symbol_digs);
                     break;
                    }
                 }
               //--- new ticks
               curr_tick.bid=curr_bid_pr;
               curr_tick.ask=curr_ask_pr;
               //--- flags
               curr_tick.flags=0;
               if(m_tick.bid!=curr_tick.bid)
                  curr_tick.flags|=TICK_FLAG_BID;
               if(m_tick.ask!=curr_tick.ask)
                  curr_tick.flags|=TICK_FLAG_ASK;
               if(curr_tick.flags==0)
                  curr_tick.flags=TICK_FLAG_BID|TICK_FLAG_ASK;
               custom_symbol_ticks[t_idx]=curr_tick;
               m_tick=curr_tick;
              }
            //--- replace ticks
            int ticks_replaced=0;
            for(int att=0; att<ATTEMTS; att++)
              {
               ticks_replaced=this.TicksReplace(custom_symbol_ticks);
               if(ticks_replaced==ticks_copied)
                  break;
               ::Sleep(PAUSE);
              }
            if(ticks_replaced!=ticks_copied)
              {
               ::Print(__FUNCTION__+": failed to replace the refreshed ticks!");
               return false;
              }
            tick_cnt+=ticks_replaced;
           }
      //--- next datetimes
      start_datetime=start_datetime+::PeriodSeconds(PERIOD_D1);
     }
   while(start_datetime<=stop_datetime && !::IsStopped());
   ::PrintFormat("\nReplaced ticks number: %I32u",tick_cnt);
//---
   return true;
  }
//+------------------------------------------------------------------+

カスタムシンボルのスプレッドサイズを変更する方法はなんでしょうか?

まず、固定スプレッドを設定します。 これを行うには、_spread_size パラメータに正の値を指定します。 この部分は、次の rule にかかわらず、テスターで動作します。

ストラテジーテスターでは、スプレッドは常にフローティングと見なされます。 つまり、シンボルインフォ整数(シンボル、SYMBOL_SPREAD_FLOAT) は常に true を返します。

次に、マークアップを使用可能なスプレッド値に追加できます。 これを行うには、_spread_markup パラメータを定義します。 

また、このメソッドでは、参照スプレッド値として機能する価格を指定できます。 ENUM_SPREAD_BASE列挙体によって行われます。

//+------------------------------------------------------------------+
//| Spread calculation base                                          |
//+------------------------------------------------------------------+
enum ENUM_SPREAD_BASE
  {
   SPREAD_BASE_BID=0,    // bid price
   SPREAD_BASE_ASK=1,    // ask price
   SPREAD_BASE_AVERAGE=2,// average price
  };
//+------------------------------------------------------------------+

Bid価格(SPREAD_BASE_BID)を使用する場合は、Ask=Bid+計算スプレッドです。 Ask価格(SPREAD_BASE_ASK)を使用する場合は、Bid= Ask - 計算されたスプレッドです。 平均価格(SPREAD_BASE_AVERAGE)を使用する場合、Bid = 平均価格 - (計算されたスプレッド/2)です。

CiCustomSymbol::ChangeSpread() メソッドは、特定のシンボル プロパティの値を変更しませんが、各ティックでのスプレッド値を変更します。 更新されたティックはティックベースに保存されます。


メソッドの動作を確認するには、TestChangeSpread.mq5を使います。 スクリプトが正常に実行されると、次のログが Journal に追加されます。

2019.08.30 12:49:59.678 TestChangeSpread (EURUSD,M1)    Replaced ticks number: 354400


これは、以前にロードされたすべてのティックのティックサイズが変更されたことを意味します。 


次の表は、EURUSD データを使用して、さまざまなスプレッド値を持つ戦略テストの結果を示しています (表 1)。

スプレッド1(12~17ポイント) スプレッド 2 (25 ポイント) スプレッド 2 (50 ポイント)
トレード数
172 156 145
純利益、 $
4 018.27 3 877.58 3 574.1
Max. エクイティドローダウン、 %
11.79 9.65 8.29
 トレードあたりの利益、 $
 23.36  24.86  24.65

表 1. 異なるスプレッド値で結果をテストする

列 "スプレッド 1" は、実際の浮動小数点スプレッド (5 桁のクオートを持つ 12 ~ 17 ポイント) を持つ結果を反映します。

スプレッドが高いほど、トレードの数は少なくなりました。 その結果、ドローダウンが減少しました。 また、この場合、トレード収益性は高まっています。


3.2 ストップレベルとフリーズレベルの変更

一部の戦略は、ストップレベルとフリーズレベルに依存する場合があります。 多くのブローカーは、スプレッドとゼロフリーズレベルに等しいストップレベルを提供します。 しかし、ブローカーがレベルを上げることがあります。 通常、ボラティリティの増加または安値流動性の期間中に発生します。 この情報は、ブローカーのトレードルールから取得できます。 

このようなトレードルールからの抽出の例を次に示します。

"予約オーダーまたは T/P および S/L オーダーを配置できる最小距離は、シンボルスプレッドと等しくなります。 重要なマクロ経済統計や経済ニュースのリリースの10分前に、S/Lオーダーの最小距離を10スプレッドに増やす可能性があります。 トレード終了時間の30分前に、S/Lオーダーのこのレベルは25スプレッドに増加します。

レベル (SYMBOL_TRADE_STOPS_LEVELおよびSYMBOL_TRADE_FREEZE_LEVEL) は、CiCustomSymbol::SetProperty() メソッドを使用して構成できます。 

シンボルのプロパティはテスターでは動的に変更できないことに注意してください。 現在のバージョンでは、テスターは事前に構成されたカスタム シンボル パラメータで動作します。 プラットフォーム開発者は、この重要性を強調しています。 したがって、この機能は未来のビルドで追加される可能性があります。


3.3 マージン要件の変更

個々のマージン要件は、カスタムシンボルに対して設定することもできます。 パラメータの値は、プログラムによって設定できます: SYMBOL_MARGIN_INITIAL、SYMBOL_MARGIN_MAINTENANCE、SYMBOL_MARGIN_HEDGED。 したがって、トレード商品の証拠金レベルを定義することができます。 

ポジションとボリューム (SYMBOL_MARGIN_LONG、SYMBOL_MARGIN_SHORTなど) には、個別のタイプのマージン識別子もあります。 manual mode で設定されます。 

レバレッジバリエーションは、ドローダウンに対するレジスタンスとストップアウトを回避する能力の面でトレード戦略のテストを可能にします。


結論

この記事では、トレード戦略ストレステストの側面を強調しました。

カスタムシンボル設定では、独自のシンボルのパラメータを設定できます。 すべてのアルゴトレーダーは、特定のトレード戦略に必要なパラメータの特定のセットを選択することができます。 発見された興味深いオプションの1つは、カスタムシンボルの相場深度に関するものです。

アーカイブには、例の一部として処理されたティック付きのファイルと、スクリプトのソース ファイルとカスタム シンボル クラスがあります。 

fxsaberに感謝し、モデレーターのArtyom Trishkin(アルチョム・トリシキン)Slava(スラヴァ)に感謝の意を表します。 エラー、バグ、質問&提案"スレッド(ロシア語)。