English Русский 中文 Español Deutsch Português 한국어 Français Italiano Türkçe
Expert Advisor動作中のバランス曲線勾配調整

Expert Advisor動作中のバランス曲線勾配調整

MetaTrader 5トレーディング | 6 10月 2015, 16:25
495 0
Dmitriy Skub
Dmitriy Skub

はじめに

本項では、フィードバックを作成することで、Expert Advisorのパフォーマンスを向上させる方法の一つについて述べます。今回のフィードバックは、バランス曲線の勾配測定に関したものです。勾配調整は作業ボリュームのルールづけによって自動で行われます。Expert Advisorは以下のモードでトレードを行うことができます。 cut volumeにて、ロットの作業量(最初に調整されたものについて)にて、中間ボリュームにて。作業モードは自動で切り替わります。

フィードバック連鎖においては、異なるルール化特性が使用されます。それらは、ステップ、ヒステリシスのステップ、リニアです。あるシステムの特性に対してバランス曲線の勾配をントロールするシステムを調整することができます

主眼は自身のトレーディングシステムを監視する間、トレーダーの判断手順を自動化することです。作業の好ましくない期間にリスクを削減するのは合理的です。作業の通常モードに戻る時、リスクは初期レベルを復元することができます。

もちろん、このシステムは万能の解決策ではなく、収益性のあるExpert Advisorを損なうものではありません。ある意味、これはアカウントの多大な損失を防ぐExpert AdvisorのMM(資金管理)への追加です。

本稿はこの関数をExpert Advisorのコードに埋め込むライブラリも扱います。


処理原理

バランス曲線勾配をコントロールするシステムの処理原理を見ていきます。トレードをしているExpert Advisorがあるとしましょう。それの仮定されたバランス曲線は以下のような様子をしています。

バランス曲線勾配をコントロールするシステムの処理原理

図1 バランス曲線勾配をコントロールするシステムの処理原理

トレードの定数ボリュームを使うExpert Advisorの最初のバランス曲線は上記に示しています。クローズされたトレードは赤の点で示されています。点を曲線でつなげます。それはトレード(黒い太線)中のExpert Advisorのバランス変化を表します。

ここで、この線の勾配を時間軸(青い線で表示されています)まで連続してたどっていきます。もっと精密にするには、信号によってそれぞれのトレードをオープンする前に、前にクローズされた2件のトレード(記載をシンプルにするため、単に2件のトレードとします)によって線の勾配を計算します。勾配が指定の値を下回るとわれわれのコントロールシステムは動作を始めます。すなわち計算された勾配値と指定されたルール化関数に応じてボリュームを減らします。

そのような方法で、トレードが不成功期間にはいればボリュームはトレーディングのТ3...Т5 機会内でVmax.からVmin.へ減じます。Т5ポイント後、トレードボリュームの拒否モードにてトレーディングは最小指定ボリュームで行われます。 Expert Advisorの収益性が回復し、バランス曲線の勾配が指定の値を上回って上昇すると、ボリュームは増え始めます。これは Т8...Т10間隔中に発生します。Т10ポイント後、トレーディング処理ボリュームは初期状態のVmax.まで回復します。

前述のようなルールの結果としてのバランス曲線は図1の下側に表示されています。B1からB2への初期ドローダウンは減少し、B1からB2*となっているのが判ります。また、最大ボリュームに回復する間Т8...Т10に、収益はわずかに減少しているのも分かります。これは問題の裏側です。

グリーンは、トレーディングが最小指定ボリュームで行われたときのバランス曲線を強調表示しています。黄色は、最大値から最小値に移り変わる部分を表しています。ここでは複数の変遷バリアントが可能です。

  • stepped - 大値から最小値への個別のステップにおけるボリューム変化
  • linear - 決められた間隔を伴うバランス曲線傾斜各に依存して線的に変化するボリューム
  • ヒステリシスを伴うステップ - 最大値から最小値へのボリューム変化で、勾配角度の異なる値で逆行が行われます。

それを描画します。

ルール化特性タイプ

図2 ルール化特性タイプ

特性ルール化はシステムコントロール率に影響を与えます。 - 有効化または無効化の遅延、黄色は、最大値から最小値に移り変わるボリュームと逆行検証から最良結果を得るには実験的基本の特性を選択することが推奨されます。

そこで、バランス曲線傾斜に基づくフィードバックを伴うトレーディングシステムを強化します。そのようなボリュームのルール化はトレーディングシステムの一部としてはボリュームを持たないシステムに対してのみ適していいることに注意をしてください。たとえば、Martingale原理が使われていたら、初期のExpert Advisorを変更せず直接このシステムを使用することはできません。

また、以下の重点にも留意が必要です。

  • バランス曲線勾配管理の効果は直接処理の通常モードにおける作業ボリュームのボリューム拒否モードにおけるボリューム割合に依存します。この割合が大きいほど、管理は効果的です。そのため、初期作業ボリュームは可能な最小ボリュームよりもかなり大きくなっています。
  • Expert Advisorのバランスの上昇および下降の平均変化期間はコントロールシステムの反応時間よりかなり大きい必要があります。それ以外では、システムはバランス曲線の傾斜を決めregulateません。反応時間い対する平均期間の割合が大きいほど、システムは効果的であると言えます。この要件はほとんどすべての自動ルール化システム関する懸念です。


オブジェクトを基にしたプログラミング手法を使ったMQL5への実装

上記の手法を具現化するライブラリを書きます。そのために、MQL5の新しい機能を使用します。 - オブジェクトを基にしたプログラミング手法です。この手法によりコードの大部分を書きなおすことなく将来的にライブラリを簡単に作成し広げることができます。


TradeSymboクラスl

新しいMetaTrader 5プラットフォームには複数通貨検証が導入されているため、クラスを作成する必要があります。これはいずての作業シンボルに関しても全作業をそれ自体にカプセル化するものです。それによりこのライブラリを複数通貨対応のExpert Advisorsで使用することができます。このクラスは、その予備であるコントロールシステムに直接関与しません。よってこのクラスは作業シンボルの処理に使用されます。

//---------------------------------------------------------------------
//  Operations with work symbol:
//---------------------------------------------------------------------
class TradeSymbol
{
private:
  string  trade_symbol;                          // work symbol

private:
  double  min_trade_volume;                      // minimum allowed volume for trade operations
  double  max_trade_volume;                      // maximum allowed volume for trade operations
  double  min_trade_volume_step;                 // minimum change of volume
  double  max_total_volume;                      // maximum change of volume
  double  symbol_point;                          // size of one point
  double  symbol_tick_size;                      // minimum change of price
  int     symbol_digits;                        // number of digits after decimal point

protected:

public:
  void    RefreshSymbolInfo( );                  // refresh market information about the work symbol
  void    SetTradeSymbol( string _symbol );      // set/change work symbol
  string  GetTradeSymbol( );                     // get work symbol
  double  GetMaxTotalLots( );                    // get maximum cumulative volume
  double  GetPoints( double _delta );            // get change of price in points

public:
  double  NormalizeLots( double _requied_lot );  // get normalized trade volume
  double  NormalizePrice( double _org_price );   // get normalized price with consideration of step of change of quote

public:
  void    TradeSymbol( );                       // constructor
  void    ~TradeSymbol( );                      // destructor
};

クラスのストラクチャはひじょうにシンプルです。目的は指定されたシンボルに関する現在マーケット情報の処理を保管することです。主なメソッドはTradeSymbol::RefreshSymbolInfoTradeSymbol::NormalizeLots TradeSymbol::NormalizePriceです。ではひとつずつ考察します。

TradeSymbol::RefreshSymbolInfoメソッドは作業対象シンボルによりマーケット情報をリフレッシュします。

//---------------------------------------------------------------------
//  Refresh market information by work symbol:
//---------------------------------------------------------------------
void
TradeSymbol::RefreshSymbolInfo( )
{
//  If a work symbol is not set, don't do anything:
  if( GetTradeSymbol( ) == NULL )
  {
    return;
  }

//  Calculate parameters necessary for normalization of volume:
  min_trade_volume = SymbolInfoDouble( GetTradeSymbol( ), SYMBOL_VOLUME_MIN );
  max_trade_volume = SymbolInfoDouble( GetTradeSymbol( ), SYMBOL_VOLUME_MAX );
  min_trade_volume_step = SymbolInfoDouble( GetTradeSymbol( ), SYMBOL_VOLUME_STEP );

  max_total_volume = SymbolInfoDouble( GetTradeSymbol( ), SYMBOL_VOLUME_LIMIT );

  symbol_point = SymbolInfoDouble( GetTradeSymbol( ), SYMBOL_POINT );
  symbol_tick_size = SymbolInfoDouble( GetTradeSymbol( ), SYMBOL_TRADE_TICK_SIZE );
  symbol_digits = ( int )SymbolInfoInteger( GetTradeSymbol( ), SYMBOL_DIGITS );
}

これは複数メソッドで使用されているという点は重要です。現時点でのMQL5の認識ではパラメータを伴うコンストラクタの使用はできません。作業シンボルの初期設定には以下のメソッドを呼ぶ必要があります。

void    SetTradeSymbol( string _symbol );      // set/change work symbol

TradeSymbol::NormalizeLotsメソッドは正しい正常化されたボリュームを取得するのに使われます。ポジションサイズはブローカーによって許可されている可能な最小値を下回ることはできないことがわかっています。ポジション変更の最小ステップもブローカーによって判断され、それは異なる可能性があります。このメソッドは底に最も近いボリューム値を返します。

また、推測されたポジションボリュームがブローカーが許可する最高値を超えているか確認します。

//---------------------------------------------------------------------
//  Get normalized trade volume:
//---------------------------------------------------------------------
//  - input necessary volume;
//  - output is normalized volume;
//---------------------------------------------------------------------
double
TradeSymbol::NormalizeLots( double _requied_lots )
{
  double   lots, koeff;
  int      nmbr;

//  If a work symbol is not set, don't do anything:
  if( GetTradeSymbol( ) == NULL )
  {
    return( 0.0 );
  }

  if( this.min_trade_volume_step > 0.0 )
  {
    koeff = 1.0 / min_trade_volume_step;
    nmbr = ( int )MathLog10( koeff );
  }
  else
  {
    koeff = 1.0 / min_trade_volume;
    nmbr = 2;
  }
  lots = MathFloor( _requied_lots * koeff ) / koeff;

//  Lower limit of volume:
  if( lots < min_trade_volume )
  {
    lots = min_trade_volume;
  }

//  Upper limit of volume:
  if( lots > max_trade_volume )
  {
    lots = max_trade_volume;
  }

  lots = NormalizeDouble( lots, nmbr );
  return( lots );
}

TradeSymbol::NormalizePrice待ドットは正確で正常化された価格を取得するのに使われます。小数点(価格の正確性)以下の桁数は使用するシンボルについて決定されるので、価格を切り捨てる必要があります。また、シンボル(たとえば将来)の中には1よりも大きい価格変更の最小ステップを有するものもあります。そのため価格の値を最小discrecityの倍数にする必要があります。.

//---------------------------------------------------------------------
//  Normalization of price with consideration of step of price change:
//---------------------------------------------------------------------
double
TradeSymbol::NormalizePrice( double _org_price )
{
//  Minimal step of quote change in points:
  double  min_price_step = NormalizeDouble( symbol_tick_size / symbol_point, 0 );

  double  norm_price = NormalizeDouble( NormalizeDouble(( NormalizeDouble( _org_price / symbol_point, 0 )) / min_price_step, 0 ) * min_price_step * symbol_point, symbol_digits );
  return( norm_price );
}

必要であるが正常化されていない価格は関数に入力します。それは正常化された価格を返します。それは必要価格に最も近いものです。

その他メソッドの目的は明確にコメントに記述があります。それ以上の記述は求められません。


TBalanceHistorクラスy

名前から明らかなようにこのクラスはアカウントバランス履歴を処理します。また以下に述べるクラスの基本クラスでもあります。主要目的はExpert Advisorのトレード履歴にアクセスすることです。また、作業対象シンボル、『マジックナンバー』Expert Advisorの監視開始日、これら3つのエレメントで同時に履歴のフィルタが可能です。

//---------------------------------------------------------------------
//  Operations with balance history:
//---------------------------------------------------------------------
class TBalanceHistory
{
private:
  long      current_magic;            // value of "magic number" when accessing the history of deals ( 0 - any number )
  long      current_type;             // type of deals ( -1 - all )
  int       current_limit_history;   // limit of depth of history ( 0 - all history )
  datetime   monitoring_begin_date;   // date of start of monitoring history of deals
  int       real_trades;             // number of actual trades already performed

protected:
  TradeSymbol  trade_symbol;          // operations with work symbol

protected:
//  "Raw" arrays:
  double    org_datetime_array[ ];                                                                                                                                                      // date/time of trade
  double    org_result_array[ ];                                                                                                                                                                // result of trade

//  Arrays with data grouped by time:
  double    group_datetime_array[ ];                                                                                                                                            // date/time of trade
  double    group_result_array[ ];                                                                                                                                                      // result of trade

  double    last_result_array[ ];     // array for storing results of last trades ( points on the Y axis )
  double    last_datetime_array[ ];   // array for storing time of last trades ( points on the X axis )

private:
  void      SortMasterSlaveArray( double& _m[ ], double& _s[ ] );  // synchronous ascending sorting of two arrays

public:
  void      SetTradeSymbol( string _symbol );                      // set/change work symbol
  string    GetTradeSymbol( );                                    // get work symbol
  void      RefreshSymbolInfo( );                                 // refresh market information by work symbol
  void      SetMonitoringBeginDate( datetime _dt );                // set date of start of monitoring
  datetime  GetMonitoringBeginDate( );                            // get date of start of monitoring
  void      SetFiltrParams( long _magic, long _type = -1, int _limit = 0 );// set parameters of filtration of deals

public:
// Get results of last trades:
  int       GetTradeResultsArray( int _max_trades );

public:
  void      TBalanceHistory( );       // constructor
  void      ~TBalanceHistory( );      // destructor
};

前回のトレードや履歴結果を読むフィルタの設定はTBalanceHistory::SetFiltrParamsメソッドと用いて行います。それには以下の入力パラメータがあります。

  • _magic - 履歴から読み取られるトレードの『マジックナンバー』ゼロ値が指定されている場合は、どの『マジックナンバー』のトレードも読まれます。
  • _type - 読み取られる必要のある取引タイプ以下の値が可能です。 - DEAL_TYPE_BUY(ロングトレードだけを読むため)、 DEAL_TYPE_SELL( ショートトレードだけを読むため)、 -1(ショート、ロング両方を読むため)
  • _limit - 分析されたトレードの深さを制限します。ゼロに等しければすべての可能な履歴が分析されます。

初期設定では、以下の値はTBalanceHistoryクラスのオブジェクトが作成されるときに設定されます。すなわち _magic = 0、_type = -1、_limit = 0
です。

このクラスの主要メソッドはTBalanceHistory::GetTradeResultsArrayです。クラスメンバー配列last_result_arrayおよびlast_datetime_arrayに最終トレード結果を書き込みます。このメソッドは以下の入力パラメータがあります。

  • _max_trades - 履歴から読み取られ、アウトプット配列に書き込まれる最大トレード数傾斜各を計算するには最低2点が必要なので、この値は2以下ではありえません。この値がゼロであれば、全利用可能なトレード履歴が分析されます。実際、バランス曲線の傾斜計算に必要なポイント数はここで指定されます。
//---------------------------------------------------------------------
//  Reads the results of last (by time) trades to arrays:
//---------------------------------------------------------------------
//  - returns the number of actually read trades but not more than specified;
//---------------------------------------------------------------------
int
TBalanceHistory::GetTradeResultsArray( int _max_trades )
{
  int       index, limit, count;
  long      deal_type, deal_magic, deal_entry;
  datetime   deal_close_time, current_time;
  ulong     deal_ticket;                        // ticket of deal
  double    trade_result;
  string    symbol, deal_symbol;

  real_trades = 0;

//  Number of trades should be no less than two:
  if( _max_trades < 2 )
  {
    return( 0 );
  }

//  If a work symbol is not specified, don't do anything:
  symbol = trade_symbol.GetTradeSymbol( );
  if( symbol == NULL )
  {
    return( 0 );
  }

//  Request the history of deals and orders from the specified time to the current moment:
  if( HistorySelect( monitoring_begin_date, TimeCurrent( )) != true )
  {
    return( 0 );
  }

//  Calculate number of trades:
  count = HistoryDealsTotal( );

//  If there are less trades in the history than it is necessary, then exit:
  if( count < _max_trades )
  {
    return( 0 );
  }

//  If there are more trades in the history than it is necessary, then limit them:
  if( current_limit_history > 0 && count > current_limit_history )
  {
    limit = count - current_limit_history;
  }
  else
  {
    limit = 0;
  }

//  If needed, adjust dimension of "raw" arrays by the specified number of trades:
  if(( ArraySize( org_datetime_array )) != ( count - limit ))
  {
    ArrayResize( org_datetime_array, count - limit );
    ArrayResize( org_result_array, count - limit );
  }

//  Fill the "raw" array with trades from history base:
  real_trades = 0;
  for( index = count - 1; index >= limit; index-- )
  {
    deal_ticket = HistoryDealGetTicket( index );

//  If those are not closed deals, don't go further:
    deal_entry = HistoryDealGetInteger( deal_ticket, DEAL_ENTRY );
    if( deal_entry != DEAL_ENTRY_OUT )
    {
      continue;
    }

//  Check "magic number" of deal if necessary:
    deal_magic = HistoryDealGetInteger( deal_ticket, DEAL_MAGIC );
    if( current_magic != 0 && deal_magic != current_magic )
    {
      continue;
    }

//  Check symbol of deal:
    deal_symbol = HistoryDealGetString( deal_ticket, DEAL_SYMBOL );
    if( symbol != deal_symbol )
    {
      continue;
    }
                
//  Check type of deal if necessary:
    deal_type = HistoryDealGetInteger( deal_ticket, DEAL_TYPE );
    if( current_type != -1 && deal_type != current_type )
    {
      continue;
    }
    else if( current_type == -1 && ( deal_type != DEAL_TYPE_BUY && deal_type != DEAL_TYPE_SELL ))
    {
      continue;
    }
                
//  Check time of closing of deal:
    deal_close_time = ( datetime )HistoryDealGetInteger( deal_ticket, DEAL_TIME );
    if( deal_close_time < monitoring_begin_date )
    {
      continue;
    }

//  So, we can read another trade:
    org_datetime_array[ real_trades ] = deal_close_time / 60;
    org_result_array[ real_trades ] = HistoryDealGetDouble( deal_ticket, DEAL_PROFIT ) / HistoryDealGetDouble( deal_ticket, DEAL_VOLUME );
    real_trades++;
  }

//  if there are less trades than necessary, return:
  if( real_trades < _max_trades )
  {
    return( 0 );
  }

  count = real_trades;

//  Sort the "raw" array by date/time of closing the order:
  SortMasterSlaveArray( org_datetime_array, org_result_array );

// If necessary, adjust dimension of group arrays for the specified number of points:
  if(( ArraySize( group_datetime_array )) != count )
  {
    ArrayResize( group_datetime_array, count );
    ArrayResize( group_result_array, count );
  }
  ArrayInitialize( group_datetime_array, 0.0 );
  ArrayInitialize( group_result_array, 0.0 );

//  Fill the output array with grouped data ( group by the identity of date/time of position closing ):
  for( index = 0; index < count; index++ )
  {
//  Get another trade:
    deal_close_time = ( datetime )org_datetime_array[ index ];
    trade_result = org_result_array[ index ];

//  Now check if the same time already exists in the output array:
    current_time = ( datetime )group_datetime_array[ real_trades ];
    if( current_time > 0 && MathAbs( current_time - deal_close_time ) > 0.0 )
    {
      real_trades++;                      // move the pointer to the next element
      group_result_array[ real_trades ] = trade_result;
      group_datetime_array[ real_trades ] = deal_close_time;
    }
    else
    {
      group_result_array[ real_trades ] += trade_result;
      group_datetime_array[ real_trades ] = deal_close_time;
    }
  }
  real_trades++;                          // now this is the number of unique elements

//  If there are less trades than necessary, exit:
  if( real_trades < _max_trades )
  {
    return( 0 );
  }

  if( ArraySize( last_result_array ) != _max_trades )
  {
    ArrayResize( last_result_array, _max_trades );
    ArrayResize( last_datetime_array, _max_trades );
  }

//  Write the accumulated data to the output arrays with reversed indexation:
  for( index = 0; index < _max_trades; index++ )
  {
    last_result_array[ _max_trades - 1 - index ] = group_result_array[ index ];
    last_datetime_array[ _max_trades - 1 - index ] = group_datetime_array[ index ];
  }

//  In the output array replace the results of single trades with the accumulating total:
  for( index = 1; index < _max_trades; index++ )
  {
    last_result_array[ index ] += last_result_array[ index - 1 ];
  }

  return( _max_trades );
}

最初に必ず確認が行われます。- 作業対象シンボルが指定されているか、そして入力パラメータが正しいか。

それから現時点に対し指定された日付から取引履歴や注文履歴を読みます。それは以下のコード部分で行われます。

//  Request the history of deals and orders from the specified time to the current moment:
  if( HistorySelect( monitoring_begin_date, TimeCurrent( )) != true )
  {
    return( 0 );
  }

//  Calculate number of trades:
  count = HistoryDealsTotal( );

//  If there are less trades in the history than it is necessary, then exit:
  if( count < _max_trades )
  {
    return( 0 );
  }

また、履歴内の取引トータル数が確認されます。指定より小さければそれ以上なにもすることはありません。『生』配列が準備されるとすぐに、トレード履歴からの情報書き込みサイクルが実行されます。以下のように行われます。

//  Fill the "raw" array from the base of history of trades:
  real_trades = 0;
  for( index = count - 1; index >= limit; index-- )
  {
    deal_ticket = HistoryDealGetTicket( index );

//  If the trades are not closed, don't go further:
    deal_entry = HistoryDealGetInteger( deal_ticket, DEAL_ENTRY );
    if( deal_entry != DEAL_ENTRY_OUT )
    {
      continue;
    }

//  Check "magic number" of deal if necessary:
    deal_magic = HistoryDealGetInteger( deal_ticket, DEAL_MAGIC );
    if( _magic != 0 && deal_magic != _magic )
    {
      continue;
    }

//  Check symbols of deal:
    deal_symbol = HistoryDealGetString( deal_ticket, DEAL_SYMBOL );
    if( symbol != deal_symbol )
    {
      continue;
    }
                
//  Check type of deal if necessary:
    deal_type = HistoryDealGetInteger( deal_ticket, DEAL_TYPE );
    if( _type != -1 && deal_type != _type )
    {
      continue;
    }
    else if( _type == -1 && ( deal_type != DEAL_TYPE_BUY && deal_type != DEAL_TYPE_SELL ))
    {
      continue;
    }
                
//  Check time of closing of deal:
    deal_close_time = ( datetime )HistoryDealGetInteger( deal_ticket, DEAL_TIME );
    if( deal_close_time < monitoring_begin_date )
    {
      continue;
    }

//  So, we can rad another trade:
    org_datetime_array[ real_trades ] = deal_close_time / 60;
    org_result_array[ real_trades ] = HistoryDealGetDouble( deal_ticket, DEAL_PROFIT ) / HistoryDealGetDouble( deal_ticket, DEAL_VOLUME );
    real_trades++;
  }

//  If there are less trades than necessary, exit:
  if( real_trades < _max_trades )
  {
    return( 0 );
  }

最初に HistoryDealGetTicket関数を使って履歴からの取引チケットが読み取られ、それから取得チケットにより取引詳細情報の読み取りが行われます。取引きのクローズ(バランス分析のため)にのみ着目する都合上、まず取引タイプを確認します。DEAL_ENTRYパラメータによりHistoryDealGetInteger関数を呼ぶことで行います。関数がDEAL_ENTRY_OUTを返せばポジションクローズです。

取引の『マジックナンバー』の後、取引きタイプYはメソッドが指定する入力パラメータ)と取引シンボルがチェックされます。全取引きパラメータが要求を満たせば、取引終了時刻である最終パラメータがチェックされます。で以下のように行われます。

//  Check the time of closing of deal:
    deal_close_time = ( datetime )HistoryDealGetInteger( deal_ticket, DEAL_TIME );
    if( deal_close_time < monitoring_begin_date )
    {
      continue;
    }

取引きの日時は与えられた履歴監視開始日時と比較されます。与えられた日時より取引日時が大きければ、配列に行って取引情報を読み取ります。分単位で(今回の場合、終了時刻)取引ポイントと時刻における取引結果を読みます。その後、取引読み取りカウンターreal_tradesが増加し、サイクルを続けます。

『生』配列に必要な情報が書き込まれれば、取引終了時刻情報が保管されている配列をソートする必要があります。同時に、終了時刻の一致をorg_datetime_array配列に、org_result_array配列に取引結果を保持する必要があります。これは特別に書かれたメソッドを使って行います。

TBalanceHistory::SortMasterSlaveArray( double& _master[ ], double& _slave[ ] ). 最初のパラメータは _マスター- 昇順で保管する配列です。二番目のパラメータは_スレーブ - 最初の配列エレメントとこの配列エレメントは同期して移動する必要がある配列です。ソートは『バブル』メソッドによって行われます。

上述のすべての処理後、時間によって保管された取引時刻と結果を伴う2つの配列があります。バランス曲線上のポイント1つのみ(Y軸上の点)直億のそれぞれの瞬間(X軸上の点)に対応することができるので、同一の終了時刻(もしあれば)で配列エレメントをグループ化する必要があります。この処理はコードの以下の部分で行われます。

//  Fill the output array with grouped data ( group by identity of date/time of closing of position ):
  real_trades = 0;
  for( index = 0; index < count; index++ )
  {
//  Get another trade:
    deal_close_time = ( datetime )org_datetime_array[ index ];
    trade_result = org_result_array[ index ];

//  Now check, if the same time already exists in the output array:
    current_time = ( datetime )group_datetime_array[ real_trades ];
    if( current_time > 0 && MathAbs( current_time - deal_close_time ) > 0.0 )
    {
      real_trades++;                      // move the pointer to the next element
      group_result_array[ real_trades ] = trade_result;
      group_datetime_array[ real_trades ] = deal_close_time;
    }
    else
    {
      group_result_array[ real_trades ] += trade_result;
      group_datetime_array[ real_trades ] = deal_close_time;
    }
  }
  real_trades++;                          // now this is the number of unique elements

特に『同じ』終了時刻の取引はすべてここで合計されます。結果はTBalanceHistory::group_datetime_array(終了時刻)およびTBalanceHistory::group_result_array(取引結果)に書き込まれます。その後、ユニークエレメントを伴いソートされた2つの配列を取得します。この場合時刻の特定は1分以内に行われます。この変換は描画が可能です。

同時刻取引きのグループ化

図3 同時刻取引きのグループ化

1分以内の取引(図の左側)はすべて一つにグループ化され、結果を合計して四捨五入されます(図の右側)。取引き終了時刻の『ブレ』は平滑化することができ変動率の安定性を高めることができます。

その後、取得した配列について別の2つの変換を行う必要があります。エレメントの順序をゼロエレメントに対応するよう最初の取引を逆にします。単一取引き結果を集積トータル、すなわちバランスと置き換えます。それはコードの以下 にて行われます。

//  Write the accumulated data into output arrays with reversed indexation:
  for( index = 0; index < _max_trades; index++ )
  {
    last_result_array[ _max_trades - 1 - index ] = group_result_array[ index ];
    last_datetime_array[ _max_trades - 1 - index ] = group_datetime_array[ index ];
  }

//  Replace the results of single trades with the cumulative total in the output array:
  for( index = 1; index < _max_trades; index++ )
  {
    last_result_array[ index ] += last_result_array[ index - 1 ];
  }


TBalanceSlopeクラス

このクラスはアカウントのバランス曲線処理をします。それはTBalanceHistoryクラスから生成され、すべての保護されたパブリックデータとメソッドを引き継ぎます。そのストラクチャを詳しく見ます。

//---------------------------------------------------------------------
//  Operations with the balance curve:
//---------------------------------------------------------------------
class TBalanceSlope : public TBalanceHistory
{
private:
  double    current_slope;               // current angle of slope of the balance curve
  int       slope_count_points;          // number of points ( trades ) for calculation of slope angle
        
private:
  double    LR_koeff_A, LR_koeff_B;      // rates for the equation of the straight-line regression
  double    LR_points_array[ ];          // array of point of the straight-line regression

private:
  void      CalcLR( double& X[ ], double& Y[ ] );  // calculate the equation of the straight-line regression

public:
  void      SetSlopePoints( int _number );        // set the number of points for calculation of angle of slope
  double    CalcSlope( );                         // calculate the slope angle

public:
  void      TBalanceSlope( );                     // constructor
  void      ~TBalanceSlope( );                    // destructor
};

バランス曲線上の指定された点の(トレード)量について描かれたリニアなリグレッションの勾配角度によるバランス曲線の傾斜角を決定します。このため、まずTA*x + Bから以下の直線レグレッションの方程式を計算する必要があります。次のメソッドがこのジョブを行います。

//---------------------------------------------------------------------
//  Calculate the equation of the straight-line regression:
//---------------------------------------------------------------------
//  input parameters:
//    X[ ] - arras of values of number series on the X axis;
//    Y[ ] - arras of values of number series on the Y axis;
//---------------------------------------------------------------------
void
TBalanceSlope::CalcLR( double& X[ ], double& Y[ ] )
{
  double    mo_X = 0, mo_Y = 0, var_0 = 0, var_1 = 0;
  int       i;
  int       size = ArraySize( X );
  double    nmb = ( double )size;

//  If the number of points is less than two, the curve cannot be calculated:
  if( size < 2 )
  {
    return;
  }

  for( i = 0; i < size; i++ )
  {
    mo_X += X[ i ];
    mo_Y += Y[ i ];
  }
  mo_X /= nmb;
  mo_Y /= nmb;

  for( i = 0; i < size; i++ )
  {
    var_0 += ( X[ i ] - mo_X ) * ( Y[ i ] - mo_Y );
    var_1 += ( X[ i ] - mo_X ) * ( X[ i ] - mo_X );
  }

//  Value of the A coefficient:
  if( var_1 != 0.0 )
  {
    LR_koeff_A = var_0 / var_1;
  }
  else
  {
    LR_koeff_A = 0.0;
  }

//  Value of the B coefficient:
  LR_koeff_B = mo_Y - LR_koeff_A * mo_X;

//  Fill the array of points that lie on the regression line:
  ArrayResize( LR_points_array, size );
  for( i = 0; i < size; i++ )
  {
    LR_points_array[ i ] = LR_koeff_A * X[ i ] + LR_koeff_B;
  }
}

ここで、初期データに比例するレグレッション直線のポジションの最小エラーを計算するための最小二乗法を使用します。計算された線上のY座標を保存する配列もまた書きます。この配列は今のところ使用せず以降の開発にとっておきます。

与えられたクラスで使用される主要なメソッドはTBalanceSlope::CalcSlopeです。それはバランス曲線の勾配角度を返します。それは指定された最終トレードの量によって計算されます。実際の記述です。

//---------------------------------------------------------------------
//  Calculate slope angle:
//---------------------------------------------------------------------
double
TBalanceSlope::CalcSlope( )
{
//  Get result of trading from the history of trades:
  int      nmb = GetTradeResultsArray( slope_count_points );
  if( nmb < slope_count_points )
  {
    return( 0.0 );
  }

//  Calculate the regression line by the results of last trades:
  CalcLR( last_datetime_array, last_result_array );
  current_slope = LR_koeff_A;

  return( current_slope );
}

まず、バランス曲線の以前ポイントの指定量を分析します。基本クラスであるTBalanceSlope::GetTradeResultsArrayを呼ぶことで行われます。読まれた点の量が指定よりも低くなければ、レグレッションラインが計算されます。それはTBalanceSlope::CalcLRメソッドによって行います。前のステップ、last_result_array配列とlast_datetime_array 配列が書かれます。これらは基本クラスに属し引数として使われます。

残りのメソッドはシンプルで込み入った記述は必要ありません。


TBalanceSlopeControlクラス

これは作業ボリュームを変更することによってバランス曲線を管理する基本クラスです。TBalanceSlope から生成され、そこからパブリックな保護されたデータとメソッドをすべて継承します。このクラスの唯一の目的はバランス曲線の現在の勾配に応じて現在の作業ボリュームを計算することです。詳しく見ていきます。

//---------------------------------------------------------------------
//  Managing slope of the balance curve:
//---------------------------------------------------------------------
enum LotsState
{
  LOTS_NORMAL = 1,            // mode of trading with normal volume
  LOTS_REJECTED = -1,         // mode of trading with lowered volume
  LOTS_INTERMEDIATE = 0,      // mode of trading with intermediate volume
};
//---------------------------------------------------------------------
class TBalanceSlopeControl : public TBalanceSlope
{
private:
  double    min_slope;          // slope angle that corresponds to the mode of volume rejection
  double    max_slope;          // slope angle that corresponds to the mode of normal volume
  double    centr_slope;        // slope angle that corresponds to the mode of volume switching without hysteresis

private:
  ControlType  control_type;    // type of the regulation function

private:
  double    rejected_lots;      // volume in the rejection mode
  double    normal_lots;        // volume in the normal mode
  double    intermed_lots;      // volume in the intermediate mode

private:
  LotsState current_lots_state; // current mode of volume

public:
  void      SetControlType( ControlType _control );  // set type of the regulation characteristic
  void      SetControlParams( double _min_slope, double _max_slope, double _centr_slope );

public:
  double    CalcTradeLots( double _min_lots, double _max_lots );  // get trade volume

protected:
  double    CalcIntermediateLots( double _min_lots, double _max_lots, double _slope );

public:
  void      TBalanceSlopeControl( );   // constructor
  void      ~TBalanceSlopeControl( );  // destructor
};

現在ボリュームの計算前に初期パラメータを設定する必要があります。以下のメソッドを呼ぶことでそれを行います。

void      SetControlType( ControlType _control );  // set type of the regulation characteristic

入力パラメータ_コントロール - これレギュレーション特性タイプです。以下の値を持つことができます。

  • STEP_WITH_HYSTERESISH - ヒステリシスを伴うレギュレーション特性stepped
  • STEP_WITHOUT_HYSTERESIS - ヒステリシスを伴わないレギュレーション特性stepped
  • LINEAR - リニアなレギュレーション特性
  • hysteresisを伴うルール化特性stepped NON_LINEAR - ノンリニアなレギュレーション特性(本バージョンには実装なし)
void      SetControlParams( double _min_slope, double _max_slope, double _centr_slope );

入力パラメータは以下です。

  • _min_slope - slope angle of the balance curve that corresponds to trading with minimal volume;
  • _max_slope - slope angle of the balance curve that corresponds to trading with maximal volume;
  • _centr_slope - slope angle of the balance curve that corresponds to the stepped regulation characteristic without hysteresis;

ボリュームは下記メソッドを用いて計算されます。

//---------------------------------------------------------------------
//  Get trade volume:
//---------------------------------------------------------------------
double
TBalanceSlopeControl::CalcTradeLots( double _min_lots, double _max_lots )
{
//  Try to calculate slope of the balance curve:
  double    current_slope = CalcSlope( );

//  If the specified amount of trades is not accumulated yet, trade with minimal volume:
  if( GetRealTrades( ) < GetSlopePoints( ))
  {
    current_lots_state = LOTS_REJECTED;
    rejected_lots = trade_symbol.NormalizeLots( _min_lots );
    return( rejected_lots );
  }

//  If the regulation function is stepped without hysteresis:
  if( control_type == STEP_WITHOUT_HYSTERESIS )
  {
    if( current_slope < centr_slope )
    {
      current_lots_state = LOTS_REJECTED;
      rejected_lots = trade_symbol.NormalizeLots( _min_lots );
      return( rejected_lots );
    }
    else
    {
      current_lots_state = LOTS_NORMAL;
      normal_lots = trade_symbol.NormalizeLots( _max_lots );
      return( normal_lots );
    }
  }

//  If the slope of linear regression for the balance curve is less than the allowed one:
  if( current_slope < min_slope )
  {
    current_lots_state = LOTS_REJECTED;
    rejected_lots = trade_symbol.NormalizeLots( _min_lots );
    return( rejected_lots );
  }

//  If the slope of linear regression for the balance curve is greater than specified:
  if( current_slope > max_slope )
  {
    current_lots_state = LOTS_NORMAL;
    normal_lots = trade_symbol.NormalizeLots( _max_lots );
    return( normal_lots );
  }

//  The slope of linear regression for the balance curve is within specified borders (intermediate state):
  current_lots_state = LOTS_INTERMEDIATE;

//  Calculate the value of intermediate volume:
  intermed_lots = CalcIntermediateLots( _min_lots, _max_lots, current_slope );
  intermed_lots = trade_symbol.NormalizeLots( intermed_lots );

  return( intermed_lots );
}

TBalanceSlopeControl::CalcTradeLotメソッドの主な実装重要ポイントは以下です。

  • 指定された最小トレード量が集積されるまで、最小ボリュームの取引それは合理的です。なぜならトレーディングに設定した直後はExpert Advisor が現在どの期間(収益があるかないか)にあるのかわかっていないからです。
  • ルール化関数がヒステリシスを伴わないsteppedであれば、TBalanceSlopeControl::SetControlParamsメソッドによりモード切り替えの勾配を設定するのに_centr_slopeパラメータを使用すればよいだけです。_min_slopeパラメータおよび _max_slopeパラメータは無視されます。 MetaTrader 5ストラテジーテスタにおいて正しい最適化は本パラメータによって行われます。

計算された傾斜角に応じてトレーディングは最小、最大、中間ボリュームで行われます。中間ボリュームはシンプルなTBalanceSlopeControl::CalcIntermediateLotsメソッドによって行われます。 この目祖度は保護されており、クラス内部で使用されます。以下がそのコードです。

//---------------------------------------------------------------------
//  Calculation of intermediate volume:
//---------------------------------------------------------------------
double
TBalanceSlopeControl::CalcIntermediateLots( double _min_lots, double _max_lots, double _slope )
{
  double    lots;

//  If the regulation function is stepped with hysteresis:
  if( control_type == STEP_WITH_HYSTERESISH )
  {
    if( current_lots_state == LOTS_REJECTED && _slope > min_slope && _slope < max_slope )
    {
      lots = _min_lots;
    }
    else if( current_lots_state == LOTS_NORMAL && _slope > min_slope && _slope < max_slope )
    {
      lots = _max_lots;
    }
  }
//  If the regulation function is linear:
  else if( control_type == LINEAR )
  {
    double  a = ( _max_lots - _min_lots ) / ( max_slope - min_slope );
    double  b = normal_lots - a * .max_slope;
    lots = a * _slope + b;
  }
//  If the regulation function is non-linear ( not implemented yet ):
  else if( control_type == NON_LINEAR )
  {
    lots = _min_lots;
  }
//  If the regulation function is unknown:
  else
  {
    lots = _min_lots;
  }

  return( lots );
}

このクラスのその他メソッドには記述は必要ありません。


Expert Advisorへのシステム埋め込み例

バランス曲線の傾斜を支配するシステムをExert Advisorに実装する手順を段階的に考察します。


ステップ 1 - Expert Advisorに開発したライブラリを接続するインストラクションを追加

#include  <BalanceSlopeControl.mqh>


ステップ2 - バランス曲線の勾配をコントロールするシステムの設置パラメータ外部変数をExpert Advisorに追加

//---------------------------------------------------------------------
//  Parameters of the system of controlling the slope of the balance curve;
//---------------------------------------------------------------------
enum SetLogic 
{
  No = 0,
  Yes = 1,
};
//---------------------------------------------------------------------
input SetLogic     UseAutoBalanceControl = No;
//---------------------------------------------------------------------
input ControlType  BalanceControlType = STEP_WITHOUT_HYSTERESIS;
//---------------------------------------------------------------------
//  Amount of last trades for calculation of LR of the balance curve:
input int          TradesNumberToCalcLR = 3;
//---------------------------------------------------------------------
//  Slope of LR to decrease the volume to minimum:
input double       LRKoeffForRejectLots = -0.030;
//---------------------------------------------------------------------
//  Slope of LR to restore the normal mode of trading:
input double       LRKoeffForRestoreLots = 0.050;
//---------------------------------------------------------------------
//  Slope of LR to work in the intermediate mode:
input double       LRKoeffForIntermedLots = -0.020;
//---------------------------------------------------------------------
//  Decrease the initial volume to the specified value when the LR is inclined down
input double       RejectedLots = 0.10;
//---------------------------------------------------------------------
//  Normal work volume in the mode of MM with fixed volume:
input double       NormalLots = 1.0;

ステップ3 - TBalanceSlopeControlタイプのオブジェクトをExpert Advisorに追加

TBalanceSlopeControl  BalanceControl;

この宣言は関数定義前、Expert Advisorの最初に追加できます。

ステップ4 - バランス曲線の傾斜をコントロールするシステムの初期化コードをExpert AdvisorのOnInit関数に追加

//  Adjust our system of controlling the slope of the balance curve:
  BalanceControl.SetTradeSymbol( Symbol( ));
  BalanceControl.SetControlType( BalanceControlType );
  BalanceControl.SetControlParams( LRKoeffForRejectLots, LRKoeffForRestoreLots, LRKoeffForIntermedLots );
  BalanceControl.SetSlopePoints( TradesNumberToCalcLR );
  BalanceControl.SetFiltrParams( 0, -1, 0 );
  BalanceControl.SetMonitoringBeginDate( 0 );

ステップ5 - 現在マーケットの情報リフレッシュメソッド呼び出しをExpert AdvisorのOnTick関数に追加

//  Refresh market information:
  BalanceControl.RefreshSymbolInfo( );

このメソッドの呼び出しはOnTick関数のごく最初または新規バー到来確認後(そういう確認機能のあるExpert Advisorsについて)に追加することができます。

ステップ6 - ポジションがオープンコードの前に現在ボリュームの計算コードを追加

if( UseAutoBalanceControl == Yes )
  {
    current_lots = BalanceControl.CalcTradeLots( RejectedLots, NormalLots );
  }
  else
  {
    current_lots = NormalLots;
  }

Expert Advisorに「資金管理」システムが使われているなら、NormalLotsの代わりにTBalanceSlopeControl::CalcTradeLotsメソッドを書く必要があります。これExpert AdvisorのMMシステムで計算された現在ボリュームです。

Expert Advisor BSCSの検証 - 上記内蔵システムでTestExpert.mq5 本稿に添付されています。その処理原則は CCIインディケータレベルの交点を基にしています。本Expert Advisor検証目的に作成されており、実アカウントへの動作には適していません。 EURUSDのH4時間枠 (2008.07.01 - 2010.09.01)にて検証を行います。

このEAの動作結果を分析します。傾斜無効化のコントロールシステムによるバランス変更チャートを以下に示します。そこで、 UseAutoBalanceControl外部パラメータへのNo値を設定します。

バランス変更の初期チャート

図4 バランス変更の初期チャート

UseAutoBalanceControl 外部パラメータをYes に設定し、Expert Advisorの検証を行います。バランス勾配コントロールシステムの有効化チャートを取得します。

有効化コントロールシステムによるバランス変更チャート

図5 有効化コントロールシステムによるバランス変更チャート

上記チャート(図4)におけるほとんどの期間はカットされ下方のチャート(図5)から平坦になっているように見えます。これがわれわれのシステムの動作結果です。Expert Advisorの主要動作パラメータ比較が行えます。

パラメータ UseAutoBalanceControl = No UseAutoBalanceControl = Yes
トタルネット収益 18 378.00 17 261.73
プロフィット要因 1.47 1.81
リカバリ要因 2.66 3.74
予想ペイオフ 117.81 110.65
バランスの絶対ドローダウンn 1 310.50 131.05
資本の絶対ドローダウン 1 390.50 514.85
バランスの最大ドローダウン 5 569.50 (5.04%) 3 762.15 (3.35%)
資本の最大ドローダウン 6 899.50 (6.19%) 4 609.60 (4.08%)

比較パラメータ中の最高パラメータはグリーンでマークされています。収益と予想されたペイオフはわずかに減少しています。これはレギュレーションの別の側面で、作業ボリューム状態を切り替えることによるラグの結果として現れます。とにかく、Expert Advisor動作率に改善が見られます。特に、ドローダウンとプロフィットファクタに改善が見られます。


おわりに

本システムを改善する方法をいくらか見ました。

  • Expert Advisorが動作の好ましくない期間に入るときのバーチャルトレーディングを使いました。そうすると、通常の作業ボリュームはもはや重要でななくなります。それによりドローダウンを軽減することができます。
  • Expert Advisorの現在の動作状態を判断するより複雑なアルゴリズム(収益性のあるなし)を使うと、たとえば、そのような分析にはニューロンネットを適用することができます。このケースにはもちろんさらなる調査が必要です。

よって、ここではシステム動作の原理と結果を考察しています。それによりExpert Advisorの特性の質が改善されます。資金管理システムと協働することで、あるケースでは、リスクを増やすことなく収益性を改善することができます。

もう一度思い出していただきたいと思います。予備システムをひとつ失うことでExpert Advisorは収益性を失う、ということを。

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

添付されたファイル |
自動トレーディングシステム選手権2010に向けたExpert Advisor迅速作成法 自動トレーディングシステム選手権2010に向けたExpert Advisor迅速作成法
自動トレーディングシステム選手権2010に参加するためのエクスパート開発をめざし、すぐに使えるExpert Advisorテンプレートを使用します。Even novice MQL5プログラマの初心者でもこのタスクをこなすことは可能です。というのも戦略のために基本クラス、関数、テンプレートがすでに準備されているからです。よってみなさんのトレーディングの考えに合う最低限のコードを書いて実装すれば十分です。
Expert AdvisorプログラミングにおけるMQL5標準トレードクラスライブラリの使用 Expert AdvisorプログラミングにおけるMQL5標準トレードクラスライブラリの使用
本稿は、Expert Advisorプログラミングにおいてポジションのクローズ、変更、指値注文出し、取引セット前のマージン削除と検証を実装する MQL5標準トレードクラスライブラリの使用方法について述べていきます。注文と取引詳細情報の取得に使用できるトレードクラスの使い方もお見せします。
エラーとログの発見 エラーとログの発見
MetaEditor 5にはデバッグ機能が備わっています。しかし、MQL5プログラムを書くとき、個別の値ではなく検証中やオンライン作業中に現れるメッセージをすべて表示したいと思うことがよくあります。ログファイルのサイズが大きい場合、要求されるメッセージを速く簡単に回復するよう自動化するのは疑う余地がありません。本稿では、MQL5プログラム内エラーを発見する方法とログ手法を考察していきます。また、ファイルに単にログインし、ログを簡単に閲覧するためのプログラム、LogMonについての知識も深めていきます。
プロフィット引き出しモデル構築のためのTesterWithdrawal() 関数の使用 プロフィット引き出しモデル構築のためのTesterWithdrawal() 関数の使用
本稿は処理中に資産の特定部分の引き出しをするトレードシステムにおけるリスク見積をするためのTesterWithDrawal()関数使用について述べていきます。また、ストラテジーテスタにおける資産の引き出し計算のアルゴリズムへのこの関数の影響についても述べます。この関数はExpert Advisorsのパラメータ最適化に有用です。