
Expert Advisor動作中のバランス曲線勾配調整
はじめに
本項では、フィードバックを作成することで、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::RefreshSymbolInfo、 TradeSymbol::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の特性の質が改善されます。資金管理システムと協働することで、あるケースでは、リスクを増やすことなく収益性を改善することができます。
MetaQuotes Ltdによってロシア語から翻訳されました。
元の記事: https://www.mql5.com/ru/articles/145





- 無料取引アプリ
- 8千を超えるシグナルをコピー
- 金融ニュースで金融マーケットを探索