
最適化アルゴリズムを使用してEAパラメータをオンザフライで設定する
内容
1.はじめに
2.自己最適化を備えたEAのアーキテクチャ
3.指標の仮想化
4.戦略の仮想化
5.機能性のテスト
1.はじめに
EAや戦略全般を扱う際に、最適化アルゴリズムの適用方法についてよく質問を受けます。この記事では、最適化アルゴリズムの実用的な側面について見ていきたいと思います。
ミリ秒単位で大きな違いが生じる今日の金融界では、アルゴリズム取引がますます必要になってきています。最適化アルゴリズムは、効率的な取引戦略を構築する上で重要な役割を果たします。おそらく、最適化アルゴリズムと取引には共通点がないと懐疑的に考える方もいらっしゃるでしょう。ただし、この記事では、この2つの分野がどのように相互作用し、そこからどのようなメリットが得られるかを紹介します。
初心者トレーダーにとって、最適化アルゴリズムの基本原理を理解することは、収益性の高い取引を見つけ、リスクを最小限に抑えるための強力なツールとなります。熟練したプロフェッショナルにとって、この分野の深い知識は新たな地平を切り開き、期待を上回る洗練された取引戦略の構築に役立ちます。
EAの自己最適化とは、過去のデータと現在の市場状況に基づいて、EAがより良いパフォーマンスを達成するために取引戦略のパラメータを適応させるプロセスです。このプロセスには以下のような側面があります。
- データ収集と分析:EAは過去の市場データを収集し、分析する必要があります。これには、統計分析、機械学習、人工知能など、さまざまなデータ分析技術が用いられます。
- 目標設定:EAは、達成すべき目標を明確に定める必要があります。それは利益の最大化であったり、リスクの最小化であったり、一定の収益性の達成であったりします。
- 最適化アルゴリズムの適用:最良の結果を得るために、EAは様々な最適化アルゴリズムを使用することができます。これらのアルゴリズムは、EAが戦略パラメータの最適値を見つけるのを助けます。
- テストと検証:最適化後、EAをバックテストし、現在の市場環境に対して検証し、その効率性を確認する必要があります。テストは、EAのパフォーマンスと変化する市場環境に適応する能力を評価するのに役立ちます。
- 監視と更新:EAは常にパフォーマンスを監視し、必要に応じて戦略パラメータを更新する必要があります。市場は常に変化しており、EAは新しい状況やトレンド、ボラティリティの変化に適応していかなければなりません。
取引で最適化アルゴリズムを使用する主なシナリオはいくつかあります。
- 取引戦略パラメータの最適化:最適化アルゴリズムは、取引戦略のパラメータを調整するために使用できます。これらの方法を使用することで、移動平均の期間、損切りや利食いのレベル、あるいは売買シグナルや売買ルールに関連するその他のパラメータについて、最適な値を決定することができます。
- 市場参入/撤退時期の最適化:最適化アルゴリズムは、過去のデータと現在の市場状況に基づいて、ポジションをエントリおよびエグジットする最適なタイミングを決定するのに役立ちます。例えば、最適化アルゴリズムを使用して、売買シグナルの最適な時間間隔を決定することができます。
- ポートフォリオ管理:最適化アルゴリズムは、与えられた目標を達成するためのポートフォリオにおける最適な資産配分を決定するのに役立ちます。例えば、平均分散最適化(Mean-Variance Optimization)のような最適化手法を用いて、期待リターンとリスクが与えられた場合の最も効率的な資産の組み合わせを見つけることができます。これには、株式、債券、その他の資産の最適な組み合わせを決定することや、ポジションのサイズやポートフォリオの分散を最適化することが含まれます。
- 取引戦略の開発:最適化アルゴリズムは、新しい取引戦略を開発するために使用することができます。例えば、遺伝的プログラミングを使用して、過去のデータに基づいてポジションのエントリとエグジットの最適ルールを進化的に探索することができます。
- リスク管理:最適化アルゴリズムは取引のリスク管理に役立ちます。例えば、最適化アルゴリズムを使用して最適なポジションサイズを計算したり、潜在的な損失を最小限に抑える動的な損切りレベルを決定したりすることができます。
- 最適な取引商品の選択:最適化アルゴリズムは、取引に最適な取引商品や資産を選択する際に役立ちます。例えば、収益性、ボラティリティ、流動性などの様々な基準に基づいて資産をランク付けするために、最適化アルゴリズムを使用することができます。
- 金融市場の予測:最適化アルゴリズムは、金融市場の予測に利用できます。最適化アルゴリズムは、予測モデルのパラメータを調整したり、予測モデルの最適な組み合わせを選択するために使用することができます。
これらは、最適化アルゴリズムを取引に利用するシナリオの一例に過ぎません。全体として、最適化アルゴリズムは、最適な戦略の発見からリスクやポートフォリオの管理まで、取引の様々な側面を自動化し、改善するのに役立ちます。
2.自己最適化を備えたEAのアーキテクチャ
EAの自己最適化を確実にするためにはいくつかの方式が考えられますが、最も単純で、必要な能力と機能を実装するのに必要最小限の方式として、図1に示すものがあります。
「歴史」の時間軸上では、EAは最適化が決定される「今」の時点にいます。EAは、最適化プロセスを管理する「マネージャー関数」を呼び出します。EAはこの関数に「最適化パラメータ」を渡します。
マネージャーは、「最適化ALGO」または「AO」にパラメータの「セット」を要求します。その後、マネージャーはそのセットを仮想取引戦略「EA Virt」に転送します。この仮想取引戦略は、実際の戦略EAの完全なアナログであり、機能し、取引操作を実行します。
「EA Virt」は、歴史上の「過去」の時点から「今」の時点までの仮想取引をおこないます。管理者は、「最適化パラメータ」の母集団サイズで指定された回数だけ「EA Virt」を実行します。「EA Virt」は、履歴での実行結果をff resultという形で返します。
ff resultとは、適応度関数、または適応度、または最適化基準の結果であり、ユーザーの裁量で何でもよいです。例えば、残高、利益係数、数学的期待値、あるいは複雑な基準、また「歴史」時間の多くの時点で測定された積分値や累積微分値などです。このように、適応度関数の結果、すなわち「ff result」は、ユーザーが取引戦略の品質を示す重要な指標と考えるものです。
次に、特定のセットに対する評価である「ff result」がマネージャーから最適化アルゴリズムに渡されます。
停止条件に達すると、マネージャーは最適なセットをEAに転送します。その後、EAは「time now」ポイントから「reoptimiz」再最適化ポイントまで、新しい更新されたパラメータで動作(取引)を継続し、所定の履歴の深さまで再度最適化されます。
再最適化ポイントは、さまざまな理由で選ぶことができます。それは、以下の例のように厳密に定義された過去のバーの数であったり、特定の条件、例えば、取引指標がある臨界レベルまで減少することであったりします。
図1:EAの自己最適化構造
「最適化ALGO」最適化アルゴリズム方式によれば、特定の取引戦略、マネージャー、仮想戦略に関係なく、自律的に仕事を遂行する「ブラックボックス」とみなすことができます(ただし、外部にあるものはすべてブラックボックスでもある)。マネージャーは最適化アルゴリズムにセットを要求し、このセットの評価を送り返します。この評価は、最適化アルゴリズムが次のセットを決定するために使用されます。このサイクルは、ユーザーの要求を満たす最適なパラメータセットが見つかるまで続けられます。このように、最適化アルゴリズムは最適なパラメータを探索します。まさに、「EA Virt」の適応度関数で指定されたユーザーのニーズを満たすパラメータを探索するのです。
3.指標の仮想化
履歴データでEAを実行するには、取引口座で作業するときと同じ取引操作を実行する取引戦略の仮想コピーを作成する必要があります。指標を使用しない場合、EA内の論理条件の仮想化は比較的簡単になります。一連の価格の時点に応じた論理的なアクションを記述するだけでよいのです。同時に、指標の使用はより複雑な作業であり、ほとんどの場合、取引戦略は様々な指標に依存しています。
問題は、最適な指標パラメータを探索する際に、ある反復において現在のセットで指標ハンドルを作成する必要があることです。履歴データの実行が完了したら、これらのハンドルは削除する必要があります。そうしないと、RAMがすぐにいっぱいになってしまうからです。特に、1セットのパラメータ(セット)に対して可能なオプションの数が多い場合はそうです。この手順を銘柄チャート上でおこなう場合は問題ないが、テスターではハンドルの削除はできません。
この問題を解決するには、実行可能なEA内で指標の計算を「仮想化」し、ハンドルの使用を避ける必要があります。ストキャスティクスを例にとってみましょう。
各指標の計算部分には、標準的なOnCalculate関数が含まれています。この関数は、例えば Calculateと名前を変え、実質的には変更せずに残すべきです。
指標をクラスとして設計する必要があります(構造体も適している)。これを C_Stochasticと呼びましょう。クラス宣言では、メインの指標バッファをpublicフィールドとして登録し(追加の計算バッファはprivateにできる)、指標パラメータを渡す必要のあるInit初期化関数を宣言する必要があります。
//—————————————————————————————————————————————————————————————————————————————— class C_iStochastic { public: void Init (const int InpKPeriod, // K period const int InpDPeriod, // D period const int InpSlowing) // Slowing { inpKPeriod = InpKPeriod; inpDPeriod = InpDPeriod; inpSlowing = InpSlowing; } public: int Calculate (const int rates_total, const int prev_calculated, const double &high [], const double &low [], const double &close []); //--- indicator buffers public: double ExtMainBuffer []; public: double ExtSignalBuffer []; private: double ExtHighesBuffer []; private: double ExtLowesBuffer []; private: int inpKPeriod; // K period private: int inpDPeriod; // D period private: int inpSlowing; // Slowing }; //——————————————————————————————————————————————————————————————————————————————
Calculateメソッドでの指標自体の計算と同様にです。指標の計算は、標準端末に含まれる指標とまったく変わりません。唯一の違いは、指標バッファのサイズ分布とその初期化です。
これは、指標仮想化の原理を理解するための非常に簡単な例です。計算は、指標パラメータで指定された期間の深さ全体に対して実行されます。直近のバーだけを計算する機能を追加したり、リングバッファを実装したりすることも可能ですが、この記事の目的は、指標を仮想フォームに変換する際に最小限の介入で済み、最小限のプログラミングスキルを持つユーザーがアクセスできる簡単な例を示すことです。
//—————————————————————————————————————————————————————————————————————————————— int C_iStochastic::Calculate (const int rates_total, const int prev_calculated, const double &high [], const double &low [], const double &close []) { if (rates_total <= inpKPeriod + inpDPeriod + inpSlowing) return (0); ArrayResize (ExtHighesBuffer, rates_total); ArrayResize (ExtLowesBuffer, rates_total); ArrayResize (ExtMainBuffer, rates_total); ArrayResize (ExtSignalBuffer, rates_total); ArrayInitialize (ExtHighesBuffer, 0.0); ArrayInitialize (ExtLowesBuffer, 0.0); ArrayInitialize (ExtMainBuffer, 0.0); ArrayInitialize (ExtSignalBuffer, 0.0); int i, k, start; start = inpKPeriod - 1; if (start + 1 < prev_calculated) { start = prev_calculated - 2; Print ("start ", start); } else { for (i = 0; i < start; i++) { ExtLowesBuffer [i] = 0.0; ExtHighesBuffer [i] = 0.0; } } //--- calculate HighesBuffer[] and ExtHighesBuffer[] for (i = start; i < rates_total && !IsStopped (); i++) { double dmin = 1000000.0; double dmax = -1000000.0; for (k = i - inpKPeriod + 1; k <= i; k++) { if (dmin > low [k]) dmin = low [k]; if (dmax < high [k]) dmax = high [k]; } ExtLowesBuffer [i] = dmin; ExtHighesBuffer [i] = dmax; } //--- %K start = inpKPeriod - 1 + inpSlowing - 1; if (start + 1 < prev_calculated) start = prev_calculated - 2; else { for (i = 0; i < start; i++) ExtMainBuffer [i] = 0.0; } //--- main cycle for (i = start; i < rates_total && !IsStopped (); i++) { double sum_low = 0.0; double sum_high = 0.0; for (k = (i - inpSlowing + 1); k <= i; k++) { sum_low += (close [k] - ExtLowesBuffer [k]); sum_high += (ExtHighesBuffer [k] - ExtLowesBuffer [k]); } if (sum_high == 0.0) ExtMainBuffer [i] = 100.0; else ExtMainBuffer [i] = sum_low / sum_high * 100; } //--- signal start = inpDPeriod - 1; if (start + 1 < prev_calculated) start = prev_calculated - 2; else { for (i = 0; i < start; i++) ExtSignalBuffer [i] = 0.0; } for (i = start; i < rates_total && !IsStopped (); i++) { double sum = 0.0; for (k = 0; k < inpDPeriod; k++) sum += ExtMainBuffer [i - k]; ExtSignalBuffer [i] = sum / inpDPeriod; } //--- OnCalculate done. Return new prev_calculated. return (rates_total); } //——————————————————————————————————————————————————————————————————————————————
さらに、MACD指標の仮想化の例を挙げます。
//—————————————————————————————————————————————————————————————————————————————— class C_iMACD { public: void Init (const int InpFastEMA, // Fast EMA period const int InpSlowEMA, // Slow EMA period const int InpSignalSMA) // Signal SMA period { inpFastEMA = InpFastEMA; inpSlowEMA = InpSlowEMA; inpSignalSMA = InpSignalSMA; maxPeriod = InpFastEMA; if (maxPeriod < InpSlowEMA) maxPeriod = InpSlowEMA; if (maxPeriod < InpSignalSMA) maxPeriod = InpSignalSMA; } public: int Calculate (const int rates_total, const int prev_calculated, const double &close []); //--- indicator buffers public: double ExtMacdBuffer []; public: double ExtSignalBuffer []; private: double ExtFastMaBuffer []; private: double ExtSlowMaBuffer []; private: int ExponentialMAOnBuffer (const int rates_total, const int prev_calculated, const int begin, const int period, const double& price [],double& buffer []); private: int SimpleMAOnBuffer (const int rates_total, const int prev_calculated, const int begin, const int period, const double& price [],double& buffer []); private: int inpFastEMA; // Fast EMA period private: int inpSlowEMA; // Slow EMA period private: int inpSignalSMA; // Signal SMA period private: int maxPeriod; }; //——————————————————————————————————————————————————————————————————————————————
指標指定部分:
//—————————————————————————————————————————————————————————————————————————————— int C_iMACD::Calculate (const int rates_total, const int prev_calculated, const double &close []) { if (rates_total < maxPeriod) return (0); ArrayResize (ExtMacdBuffer, rates_total); ArrayResize (ExtSignalBuffer, rates_total); ArrayResize (ExtFastMaBuffer, rates_total); ArrayResize (ExtSlowMaBuffer, rates_total); ArrayInitialize (ExtMacdBuffer, 0.0); ArrayInitialize (ExtSignalBuffer, 0.0); ArrayInitialize (ExtFastMaBuffer, 0.0); ArrayInitialize (ExtSlowMaBuffer, 0.0); ExponentialMAOnBuffer (rates_total, prev_calculated, 0, inpFastEMA, close, ExtFastMaBuffer); ExponentialMAOnBuffer (rates_total, prev_calculated, 0, inpSlowEMA, close, ExtSlowMaBuffer); int start; if (prev_calculated == 0) start = 0; else start = prev_calculated - 1; //--- calculate MACD for (int i = start; i < rates_total && !IsStopped (); i++) ExtMacdBuffer [i] = ExtFastMaBuffer [i] - ExtSlowMaBuffer [i]; //--- calculate Signal SimpleMAOnBuffer (rates_total, prev_calculated, 0, inpSignalSMA, ExtMacdBuffer, ExtSignalBuffer); return (rates_total); } //——————————————————————————————————————————————————————————————————————————————
指数平滑化計算を変更する必要はまったくありません。
//—————————————————————————————————————————————————————————————————————————————— int C_iMACD::ExponentialMAOnBuffer (const int rates_total, const int prev_calculated, const int begin, const int period, const double& price [],double& buffer []) { //--- check period if (period <= 1 || period > (rates_total - begin)) return (0); //--- save and clear 'as_series' flags bool as_series_price = ArrayGetAsSeries (price); bool as_series_buffer = ArrayGetAsSeries (buffer); ArraySetAsSeries (price, false); ArraySetAsSeries (buffer, false); //--- calculate start position int start_position; double smooth_factor = 2.0 / (1.0 + period); if (prev_calculated == 0) // first calculation or number of bars was changed { //--- set empty value for first bars for (int i = 0; i < begin; i++) buffer [i] = 0.0; //--- calculate first visible value start_position = period + begin; buffer [begin] = price [begin]; for (int i = begin + 1; i < start_position; i++) buffer [i] = price [i] * smooth_factor + buffer [i - 1] * (1.0 - smooth_factor); } else start_position = prev_calculated - 1; //--- main loop for (int i = start_position; i < rates_total; i++) buffer [i] = price [i] * smooth_factor + buffer [i - 1] * (1.0 - smooth_factor); //--- restore as_series flags ArraySetAsSeries (price, as_series_price); ArraySetAsSeries (buffer, as_series_buffer); //--- return (rates_total); } //——————————————————————————————————————————————————————————————————————————————
単純平滑化の計算にも変更は必要ありません。
//—————————————————————————————————————————————————————————————————————————————— int C_iMACD::SimpleMAOnBuffer (const int rates_total, const int prev_calculated, const int begin, const int period, const double& price [],double& buffer []) { //--- check period if (period <= 1 || period > (rates_total - begin)) return (0); //--- save as_series flags bool as_series_price = ArrayGetAsSeries (price); bool as_series_buffer = ArrayGetAsSeries (buffer); ArraySetAsSeries (price, false); ArraySetAsSeries (buffer, false); //--- calculate start position int start_position; if (prev_calculated == 0) // first calculation or number of bars was changed { //--- set empty value for first bars start_position = period + begin; for (int i = 0; i < start_position - 1; i++) buffer [i] = 0.0; //--- calculate first visible value double first_value = 0; for (int i = begin; i < start_position; i++) first_value += price [i]; buffer [start_position - 1] = first_value / period; } else start_position = prev_calculated - 1; //--- main loop for (int i = start_position; i < rates_total; i++) buffer [i] = buffer [i - 1] + (price [i] - price [i - period]) / period; //--- restore as_series flags ArraySetAsSeries (price, as_series_price); ArraySetAsSeries (buffer, as_series_buffer); //--- return (rates_total); } //——————————————————————————————————————————————————————————————————————————————
4.戦略の仮想化
最適化アルゴリズムに関する私の記事の読者の1人であるLUIS ALBERTO BIANUCCI氏が、ストキャスティクス指標に基づくEAのコードを提供してくれました。彼は、AOコアライブラリを使用してEAで自己学習をアレンジする方法を示すために、このコードに基づいて例を作成し、記事でこの例を検討するよう私に依頼しました。従って、他のユーザーは、自身の開発で最適化アルゴリズムを接続する際に、この方法を使用することができます。この方法は、アルゴリズムが普遍的な形式で設計されており、あらゆるユーザー プロジェクトにうまく適用できるため、「母集団最適化アルゴリズム」の連載で説明したあらゆる最適化アルゴリズムを接続するのに適していることを強調したいと思います。
EAの一部としての指標の仮想化についてはすでに見てきました。次に、戦略の仮想化について考えてみましょう。EAコードの冒頭で、ライブラリのインポート、標準取引ライブラリのインクルードファイル、仮想ストキャスティクスのインクルードファイルを宣言します。
次に「入力」つまりEAのパラメータが登場します。中でも注目すべきはInpKPeriod_PとInpUpperLevel_Pです。これらは、ストキャスティクス指標の期間とレベルを表し、最適化する必要があります。
input string InpKPeriod_P = "18|9|3|24"; //STO K period: it is necessary to optimize
input string InpUpperLevel_P = "96|88|2|98"; //STO upper level: it is necessary to optimize
パラメータは文字列型で宣言され、パラメータは複合型で、デフォルト値、最適化初期値、ステップ値、最終値を含みます。
//—————————————————————————————————————————————————————————————————————————————— #import "\\Market\\AO Core.ex5" bool Init (int colonySize, double &range_min [], double &range_max [], double &range_step []); //------------------------------------------------------------------------------ void Preparation (); void GetVariantCalc (double &variant [], int pos); void SetFitness (double value, int pos); void Revision (); //------------------------------------------------------------------------------ void GetVariant (double &variant [], int pos); double GetFitness (int pos); #import //—————————————————————————————————————————————————————————————————————————————— #include <Trade\Trade.mqh>; #include "cStochastic.mqh" input group "==== GENERAL ===="; sinput long InpMagicNumber = 132516; //Magic Number sinput double InpLotSize = 0.01; //Lots input group "==== Trading ===="; input int InpStopLoss = 1450; //Stoploss input int InpTakeProfit = 1200; //Takeprofit input group "==== Stochastic ==|value|start|step|end|=="; input string InpKPeriod_P = "18|9|3|24"; //STO K period : it is necessary to optimize input string InpUpperLevel_P = "96|88|2|98"; //STO upper level: it is necessary to optimize input group "====Self-optimization===="; sinput bool SelfOptimization = true; sinput int InpBarsOptimize = 18000; //Number of bars in the history for optimization sinput int InpBarsReOptimize = 1440; //After how many bars, EA will reoptimize sinput int InpPopSize = 50; //Population size sinput int NumberFFlaunches = 10000; //Number of runs in the history during optimization sinput int Spread = 10; //Spread MqlTick Tick; CTrade Trade; C_iStochastic IStoch; double Set []; double Range_Min []; double Range_Step []; double Range_Max []; double TickSize = 0.0;
OnInit関数でEAを初期化する際、最適化されるパラメータの数に応じてパラメータ配列のサイズを設定します。Set:パラメータのセット、Range_Min:パラメータの最小値(開始値)、Range_Step:パラメータのステップ、Range_Max:パラメータの最大値。文字列パラメータから対応する値を取り出し、配列に代入します。
//—————————————————————————————————————————————————————————————————————————————— int OnInit () { TickSize = SymbolInfoDouble (_Symbol, SYMBOL_TRADE_TICK_SIZE); ArrayResize (Set, 2); ArrayResize (Range_Min, 2); ArrayResize (Range_Step, 2); ArrayResize (Range_Max, 2); string result []; if (StringSplit (InpKPeriod_P, StringGetCharacter ("|", 0), result) != 4) return INIT_FAILED; Set [0] = (double)StringToInteger (result [0]); Range_Min [0] = (double)StringToInteger (result [1]); Range_Step [0] = (double)StringToInteger (result [2]); Range_Max [0] = (double)StringToInteger (result [3]); if (StringSplit (InpUpperLevel_P, StringGetCharacter ("|", 0), result) != 4) return INIT_FAILED; Set [1] = (double)StringToInteger (result [0]); Range_Min [1] = (double)StringToInteger (result [1]); Range_Step [1] = (double)StringToInteger (result [2]); Range_Max [1] = (double)StringToInteger (result [3]); IStoch.Init ((int)Set [0], 1, 3); // set magicnumber to trade object Trade.SetExpertMagicNumber (InpMagicNumber); //--- return (INIT_SUCCEEDED); } //——————————————————————————————————————————————————————————————————————————————
EAコードのOnTick関数の中に、自己最適化コールブロックであるOptimize関数を挿入します。これは図1の「マネージャー」であり、最適化を開始します。最適化が必要な外部変数には、Set配列の値を使用します。
//—————————————————————————————————————————————————————————————————————————————— void OnTick () { //---------------------------------------------------------------------------- if (!IsNewBar ()) { return; } //---------------------------------------------------------------------------- if (SelfOptimization) { //-------------------------------------------------------------------------- static datetime LastOptimizeTime = 0; datetime timeNow = iTime (_Symbol, PERIOD_CURRENT, 0); datetime timeReop = iTime (_Symbol, PERIOD_CURRENT, InpBarsReOptimize); if (LastOptimizeTime <= timeReop) { LastOptimizeTime = timeNow; Print ("-------------------Start of optimization----------------------"); Print ("Old set:"); ArrayPrint (Set); Optimize (Set, Range_Min, Range_Step, Range_Max, InpBarsOptimize, InpPopSize, NumberFFlaunches, Spread * SymbolInfoDouble (_Symbol, SYMBOL_TRADE_TICK_SIZE)); Print ("New set:"); ArrayPrint (Set); IStoch.Init ((int)Set [0], 1, 3); } } //---------------------------------------------------------------------------- if (!SymbolInfoTick (_Symbol, Tick)) { Print ("Failed to get current symbol tick"); return; } //data preparation------------------------------------------------------------ MqlRates rates []; int dataCount = CopyRates (_Symbol, PERIOD_CURRENT, 0, (int)Set [0] + 1 + 3 + 1, rates); if (dataCount == -1) { Print ("Data get error"); return; } double hi []; double lo []; double cl []; ArrayResize (hi, dataCount); ArrayResize (lo, dataCount); ArrayResize (cl, dataCount); for (int i = 0; i < dataCount; i++) { hi [i] = rates [i].high; lo [i] = rates [i].low; cl [i] = rates [i].close; } int calc = IStoch.Calculate (dataCount, 0, hi, lo, cl); if (calc <= 0) return; double buff0 = IStoch.ExtMainBuffer [ArraySize (IStoch.ExtMainBuffer) - 2]; double buff1 = IStoch.ExtMainBuffer [ArraySize (IStoch.ExtMainBuffer) - 3]; //---------------------------------------------------------------------------- // count open positions int cntBuy, cntSell; if (!CountOpenPositions (cntBuy, cntSell)) { Print ("Failed to count open positions"); return; } //---------------------------------------------------------------------------- // check for buy if (cntBuy == 0 && buff1 <= (100 - (int)Set [1]) && buff0 > (100 - (int)Set [1])) { ClosePositions (2); double sl = NP (Tick.bid - InpStopLoss * TickSize); double tp = NP (Tick.bid + InpTakeProfit * TickSize); Trade.PositionOpen (_Symbol, ORDER_TYPE_BUY, InpLotSize, Tick.ask, sl, tp, "Stochastic EA"); } //---------------------------------------------------------------------------- // check for sell if (cntSell == 0 && buff1 >= (int)Set [1] && buff0 < (int)Set [1]) { ClosePositions (1); double sl = NP (Tick.ask + InpStopLoss * TickSize); double tp = NP (Tick.ask - InpTakeProfit * TickSize); Trade.PositionOpen (_Symbol, ORDER_TYPE_SELL, InpLotSize, Tick.bid, sl, tp, "Stochastic EA"); } } //——————————————————————————————————————————————————————————————————————————————
Optimize関数は、連載「母集団最適化アルゴリズム」の最適化アルゴリズムテストスクリプトで通常実行されるのと同じアクションを実行します。
2.1.母集団の準備
2.2.最適化アルゴリズムからパラメータのセットを取得する
2.3.渡されたパラメータで適応度関数を計算しする
2.4.最適なソリューションを更新する
2.5.アルゴリズムから最適解を得る
//—————————————————————————————————————————————————————————————————————————————— void Optimize (double &set [], double &range_min [], double &range_step [], double &range_max [], const int inpBarsOptimize, const int inpPopSize, const int numberFFlaunches, const double spread) { //---------------------------------------------------------------------------- double parametersSet []; ArrayResize(parametersSet, ArraySize(set)); //---------------------------------------------------------------------------- int epochCount = numberFFlaunches / inpPopSize; Init(inpPopSize, range_min, range_max, range_step); // Optimization------------------------------------------------------------- for (int epochCNT = 1; epochCNT <= epochCount && !IsStopped (); epochCNT++) { Preparation (); for (int set = 0; set < inpPopSize; set++) { GetVariantCalc (parametersSet, set); SetFitness (VirtualStrategy (parametersSet, inpBarsOptimize, spread), set); } Revision (); } Print ("Fitness: ", GetFitness (0)); GetVariant (parametersSet, 0); ArrayCopy (set, parametersSet, 0, 0, WHOLE_ARRAY); } //——————————————————————————————————————————————————————————————————————————————
VirtualStrategy関数は、過去のデータを使用して戦略のテストをおこないます(図1の図では「EA Virt」)。これは、setパラメータの配列、barsOptimize:最適化するバーの数、および spread 値を取ります。
まず、データを準備します。過去のデータはrates配列にロードされます。次に、ストキャスティクスの計算に必要な配列 hi、lo、clが作成されます。
次に、ストキャスティクス指標が初期化され、過去のデータに基づいて計算されます。計算に失敗した場合、この関数は-DBL_MAX値(適応度関数の最悪値)を返します。
続いて、EAのメインコードに完全に対応したロジックで、履歴データを使用して戦略をテストします。取引を保存するためにdealsオブジェクトが作成されます。その後、履歴データを実行し、指標値とupLevelとdnLevelのレベルに基づいて、各バーでポジションの開始と終了の条件を確認します。条件が満たされれば、ポジションはオープンまたはクローズされます。
履歴データの実行が完了すると、この関数は完了した取引数を確認します。取引がなかった場合、この関数は「-DBL_MAX」値を返します。あった場合は、最終残高を返します。
VirtualStrategyの戻り値は適応度関数の値です。この場合、これは最終的な残高のポイント値である(先に述べたように、適性関数は残高でも、利益係数でも、過去のデータに基づく取引操作の結果品質を示す他の指標でもよい)。
仮想戦略は、EAの戦略とできるだけ一致させることが重要です。この例では、取引は始値でおこなわれ、これはメインEAのバーの始値のコントロールに対応します。取引戦略ロジックがティックごとに実行される場合、ユーザーは仮想テス ト中にティック履歴をダウンロードし、それに応じてVirtualStrategy関数を調整する必要があります。
//—————————————————————————————————————————————————————————————————————————————— double VirtualStrategy (double &set [], int barsOptimize, double spread) { //data preparation------------------------------------------------------------ MqlRates rates []; int dataCount = CopyRates(_Symbol, PERIOD_CURRENT, 0, barsOptimize + 1, rates); if (dataCount == -1) { Print ("Data get error"); return -DBL_MAX; } double hi []; double lo []; double cl []; ArrayResize (hi, dataCount); ArrayResize (lo, dataCount); ArrayResize (cl, dataCount); for (int i = 0; i < dataCount; i++) { hi [i] = rates [i].high; lo [i] = rates [i].low; cl [i] = rates [i].close; } C_iStochastic iStoch; iStoch.Init ((int)set [0], 1, 3); int calc = iStoch.Calculate (dataCount, 0, hi, lo, cl); if (calc <= 0) return -DBL_MAX; //============================================================================ //test of strategy on history------------------------------------------------- S_Deals deals; double iStMain0 = 0.0; double iStMain1 = 0.0; double upLevel = set [1]; double dnLevel = 100.0 - set [1]; double balance = 0.0; //running through history----------------------------------------------------- for (int i = 2; i < dataCount; i++) { if (i >= dataCount) { deals.ClosPos (-1, rates [i].open, spread); deals.ClosPos (1, rates [i].open, spread); break; } iStMain0 = iStoch.ExtMainBuffer [i - 1]; iStMain1 = iStoch.ExtMainBuffer [i - 2]; if (iStMain0 == 0.0 || iStMain1 == 0.0) continue; //buy------------------------------- if (iStMain1 <= dnLevel && dnLevel < iStMain0) { deals.ClosPos (-1, rates [i].open, spread); if (deals.GetBuys () == 0) deals.OpenPos (1, rates [i].open, spread); } //sell------------------------------ if (iStMain1 >= upLevel && upLevel > iStMain0) { deals.ClosPos (1, rates [i].open, spread); if (deals.GetSels () == 0) deals.OpenPos (-1, rates [i].open, spread); } } //---------------------------------------------------------------------------- if (deals.histSelsCNT + deals.histBuysCNT <= 0) return -DBL_MAX; return deals.balance; } //——————————————————————————————————————————————————————————————————————————————
もし 「母集団最適化アルゴリズム」連載の最適化アルゴリズム(Evolution of Social Groups(ESG)アルゴリズムは、記事アーカイブで例として提供されています)を使用したい場合は、EAでアルゴリズムへのパスを指定する必要があります。
#include "AO_ESG.mqh"
Optimize関数で、ESGアルゴリズムオブジェクトを宣言し、最適化パラメータの境界値を設定します。そして、Optimize関数はESGを使用する場合、次のようになります。
//—————————————————————————————————————————————————————————————————————————————— void Optimize (double &set [], double &range_min [], double &range_step [], double &range_max [], const int inpBarsOptimize, const int inpPopSize, const int numberFFlaunches, const double spread) { //---------------------------------------------------------------------------- int epochCount = numberFFlaunches / inpPopSize; C_AO_ESG AO; int Population_P = 200; //Population size int Groups_P = 100; //Number of groups double GroupRadius_P = 0.1; //Group radius double ExpansionRatio_P = 2.0; //Expansion ratio double Power_P = 10.0; //Power AO.Init (ArraySize (set), Population_P, Groups_P, GroupRadius_P, ExpansionRatio_P, Power_P); for (int i = 0; i < ArraySize (set); i++) { AO.rangeMin [i] = range_min [i]; AO.rangeStep [i] = range_step [i]; AO.rangeMax [i] = range_max [i]; } // Optimization------------------------------------------------------------- for (int epochCNT = 1; epochCNT <= epochCount && !IsStopped (); epochCNT++) { AO.Moving (); for (int set = 0; set < ArraySize (AO.a); set++) { AO.a [set].f = VirtualStrategy (AO.a [set].c, inpBarsOptimize, spread); } AO.Revision (); } Print ("Fitness: ", AO.fB); ArrayCopy (set, AO.cB, 0, 0, WHOLE_ARRAY); } //——————————————————————————————————————————————————————————————————————————————
私の最適化連載で紹介している検索アルゴリズム戦略は、最も純粋な形、つまり「そのままの形」で、それらを分析し、互いに比較するためのシンプルで明確なアプローチを提供しています。重複の排除やその他のテクニックなど、検索を高速化する方法は含まれておらず、より多くの反復と時間を必要とします。
5.機能性のテスト
まず、SelfOptimizationパラメータのfalse モードで、つまり自己最適化なしで、以下のスクリーンショットに示すパラメータで、ストキャスティクス指標に基づく自己最適化EAを1年間テストしてみましょう。
図2:EAの設定
図3:自己最適化を無効にした場合の結果
図4:自己最適化を有効にした場合の結果
まとめ
この記事では、EAでの自己最適化の配置について説明しました。この方法は非常にシンプルで、EAのソースコードに最小限の介入しか必要としません。特定の戦略ごとに、最適化と取引がおこなわれる履歴セグメントの最適な長さを決定するために、一連の実験をおこなうことをお勧めします。これらの価値観は、個人や戦略に依存します。
戦略そのものが利益を生むセットを持っていなければ、最適化は良い結果をもたらさないことを理解することが重要です。そもそもそこに金がなければ、砂から金を抽出することは不可能です。最適化は戦略のパフォーマンスを向上させる便利なツールですが、何もないところに収益性の高いセットを作り出すことはできません。したがって、まず利益が見込める戦略を立て、最適化によってそれを改善する必要があります。
このアプローチの利点は、ウォークフォワードテストを使用して過去のデータで戦略をテストできることと、特定の戦略に対応する適切な最適化基準を見つけられることです。ウォークフォワードテストでは、時間の経過に伴う市場環境の変化を考慮に入れながら、過去のデータに基づいて戦略の効率性を評価することができます。これは、ある戦略がある一定期間のみうまく機能するが、リアルタイムではうまく適用できないという過剰最適化を避けるのに役立ちます。このように、ウォークフォワードテストは、戦略のパフォーマンスをより確実に評価することができます。
ウォークフォワードテスト(WFT)は、金融市場における取引戦略を評価テストするための手法です。これは、過去のデータに基づく取引戦略の効率性と持続可能性、および将来的な収益性を提供する能力を判断するために使用されます。
WFTの基本的な考え方は、利用可能なデータをいくつかの期間に分割することです。すなわち、戦略を開発調整するために使用する過去の期間(訓練期間)と、戦略を評価テストするために使用するその後の期間(テスト期間)です。このプロセスを数回繰り返します。毎回、訓練期間は1ステップずつ前倒しされ、テスト期間も前倒しされます。このように、戦略はさまざまな時間枠でテストされ、変化する市場環境に適応できることを確認します。
ウォークフォワードテストは、時間の経過による市場環境の変化を考慮に入れるため、戦略を評価するのにより現実的な方法です。また、過去のデータで戦略をオーバー訓練することを避け、現実世界でのパフォーマンスをより正確に理解するのにも役立ちます。
この記事に添付されているアーカイブには、最適化アルゴリズムをEAに接続する方法を示す例があります。読者はコードを研究し、それをご自分の特定の戦略に適用することで、最適な結果を得ることができます。
例示したものは、実際の口座での取引を意図したものではなく、そのために必要な確認を欠いています。これらは、自己最適化の可能性を示すことのみを意図したものです。
MetaQuotes Ltdによってロシア語から翻訳されました。
元の記事: https://www.mql5.com/ru/articles/14183





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