最適化数点のシンプルな考え
はじめに
EA がトレードするための一貫した戦略を見つけ出したところでそれを EURUSDチャートで起動します。よろしいですか?このペアで戦略がより収益性のあるものとなることができるのでしょうか?当比例数でロットを増やす必要なく戦略がもっと良い結果を出す通貨ペアが他にありますか?
EURUSD と標準タイムフレーム H1 を選択し、結果に満足できなければどうしますか?タイムフレーム H4 の通貨ペア EURJPY に変更しますか?
また、ビットのオペレーションシステムで、それによりシステムの検証スピードを心配することがなくなれば、最適化中に完璧な列挙の一部であり最終レポートで無視すべき結果を出すトレーディングシステムの入力パラメータのばかげた組合せを忘れますか?
私はこういった『ちょっとした問題』を自分で解決してきました。それで本稿で効果的なソリューションを共有しますですがより最適なソリューションがあればそれを認めます。
タイムフレームによる最適化
MQL5 は完璧なタイムフレーム設定を提供しています。:月次チャートへのM1、M2、M3、M4... H1、H2 などです。合計タイムフレームは 21 あります。最適化のプロセスにおいては、その中でどのタイムフレームが自分の戦略に適するか知りたいと思います。М1 や М5 のような短いもの、H2 や H4 など中程度のもの、D1 や W1 といった長期のものです。
最初はこれほど多様な選択肢は必要ありません。いずれにせよ、タイムフレーム М5 で戦略が有効であることが証明されれば、最適化の次のステップでは М3 や М6 でも同様に作用するか確認します。
入力パラメータとして ENUM_TIMEFRAMES タイプの変数を使用する場合、以下のようになります。
input ENUM_TIMEFRAMES marcoTF= PERIOD_M5;
そしてオプティマイザは 21 とおりの最適化バリエーションを提供します。この量がほんとうに必要なのでしょうか?
最初は必要ありません。では最適化をシンプルにするには?まず列挙を定義します。
enum mis_MarcosTMP { _M1= PERIOD_M1, _M5= PERIOD_M5, _M15=PERIOD_M15, // _M20=PERIOD_M20, _M30=PERIOD_M30, _H1= PERIOD_H1, _H2= PERIOD_H2, _H4= PERIOD_H4, // _H8= PERIOD_H8, _D1= PERIOD_D1, _W1= PERIOD_W1, _MN1=PERIOD_MN1 };
ここで関心あるタイムフレームを追加または削除します。最適化のために、コードの冒頭で入力変数を定義します。
input mis_MarcosTMP timeframe= _H1;
そしてlibrary .mqhで新規機能を定義します。
//----------------------------------------- DEFINE THE TIMEFRAME ---------------------------------------------------------- ENUM_TIMEFRAMES defMarcoTiempo(mi_MARCOTMP_CORTO marco) { ENUM_TIMEFRAMES resp= _Period; switch(marco) { case _M1: resp= PERIOD_M1; break; case _M5: resp= PERIOD_M5; break; case _M15: resp= PERIOD_M15; break; //case _M20: resp= PERIOD_M20; break; case _M30: resp= PERIOD_M30; break; case _H1: resp= PERIOD_H1; break; case _H2: resp= PERIOD_H2; break; case _H4: resp= PERIOD_H4; break; //case _H8: resp= PERIOD_H8; break; case _D1: resp= PERIOD_D1; break; case _W1: resp= PERIOD_W1; break; case _MN1: resp= PERIOD_MN1; } return(resp); }
グローバル変数の範囲内で新しい変数を宣言します。
ENUM_TIMEFRAMES marcoTmp= defMarcoTiempo(marcoTiempo); //timeframe is defined as a global variable
"marcoTmp" はグローバル変数で、必要なチャートのタイムフレームを決めるのに EA tによって使用されます。オプティマイザのパラメータテーブルでは "marcoTiempo" 変数の起動間隔が定義されます。 М6 や М12 を分析するのにこれは時間やリソースを使わず関心のステップだけを対象とします。このように異なるタイムフレームでの EA の動作結果を分析することができるのです。
それは確かに以下を用いて行われます。
ENUM_TIMEFRAMES marcoTmp= (ENUM_TIMEFRAMES)marcoTiempo;
みなさんが完璧主義者でコードをシンプルにしようとして詳しく調べるなら、プログラムの数か月後あるいは数年後これは明らかとなります。または VPS を使用し、コンピュータパフォーマンスの最適化により料金を抑えようとするなら。
シンボルまたはシンボルセットの最適化
MetaTrader 5 ストラテジーテスタでは、最適化モードがあります。それは銘柄ウィンドウで選択されるシンボルすべてについて EA を実行するのを手助けします。しかしこの関数は選択されたシンボルが別のパラメータであるかのように最適化を手配することはありません。よって選択されたシンボルが15あるとすれば、テスターは実行を 15 回分準備します。どのようにわれわれの EA に最も適したシンボルがどれであるか見つけることができるのでしょうか?それがマルチ通貨 EAであるなら、シンボルのどのグループがどのパラメータセットで最良結果を出すのでしょうか?文字列変数は MQL5 では最適化されません。それはどのように行うことができるのでしょうか?
入力パラメータ値を使ってシンボルや数種のシンボルを以下のようにコード化します。
input int selecDePar= 0; string cadParesFX= selecPares(selecDePar);
最適化パラメータとして "selecDePar" パラメータが使用されます。それは文字列変数に変換されます。EAでは "cadParesFX" 変数を使用します。通貨ペア(戦略がマルチ通貨であってもなくてもこのコードに対しては無関係です)はその他の標準的な最適化パラメータと共にこの変数に格納されることになります。
//------------------------------------- SELECT THE SET OF PAIRS ------------------------------------- string selecPares(int combina= 0) { string resp="EURUSD"; switch(combina) { case 1: resp= "EURJPY"; break; case 2: resp= "USDJPY"; break; case 3: resp= "USDCHF"; break; case 4: resp= "GBPJPY"; break; case 5: resp= "GBPCHF"; break; case 6: resp= "GBPUSD"; break; case 7: resp= "USDCAD"; break; case 8: resp= "CADJPY"; break; case 9: resp= "XAUUSD"; break; case 10: resp= "EURJPY;USDJPY"; break; case 11: resp= "EURJPY;GBPJPY"; break; case 12: resp= "GBPCHF;GBPJPY"; break; case 13: resp= "EURJPY;GBPCHF"; break; case 14: resp= "USDJPY;GBPCHF"; break; case 15: resp= "EURUSD;EURJPY;GBPJPY"; break; case 16: resp= "EURUSD;EURJPY;GBPCHF"; break; case 17: resp= "EURUSD;EURJPY;USDJPY"; break; case 18: resp= "EURJPY;GBPCHF;USDJPY"; break; case 19: resp= "EURJPY;GBPUSD;GBPJPY"; break; case 20: resp= "EURJPY;GBPCHF;GBPJPY"; break; case 21: resp= "USDJPY;GBPCHF;GBPJPY"; break; case 22: resp= "EURUSD;USDJPY;GBPJPY"; break; case 23: resp= "EURUSD;EURJPY;USDJPY;GBPUSD;USDCHF;USDCAD"; break; case 24: resp= "EURUSD;EURJPY;USDJPY;GBPUSD;USDCHF;USDCAD;AUDUSD"; break; } return(resp); }
目標に応じてペアの組合せを決め、分析される間隔をテスターに伝えます。15~22 び間隔(下図参照)における "selecDePar" パラメータを最適化するコマンドをストラテジーテスタに与えます。単一通貨についての比較結果が欲しい場合はどうするのでしょうか?その場合、間隔 0~9 で最適化を実行します。
たとえば、EAが cadParesFX= "EURUSD;EURJPY;GBPCHF" パラメータの値を受け取ります。OnInit() で "cargaPares()" 関数を呼び出します。それは動的配列 arrayPares[] に cadParesFX パラメータ内で ";" によって区切られる文字列を書き込みます。グローバル変数はすべて動的配列にロードされる必要があります。それは可能であればシンボルに新規バーのオープン制御を含むシンボルすべての値を保存します。単一のシンボルを処理している場合、配列の次元も一次元です。
//-------------------------------- STRING CONVERSION FROM CURRENCY PAIRS INTO AN ARRAY ----------------------------------------------- int cargaPares(string cadPares, string &arrayPares[]) { //convierte "EURUSD;GBPUSD;USDJPY" a {"EURUSD", "GBPUSD", "USDJPY"}; devuelve el número de paresFX string caract= ""; int i= 0, k= 0, contPares= 1, longCad= StringLen(cadPares); if(cadPares=="") { ArrayResize(arrayPares, contPares); arrayPares[0]= _Symbol; } else { for (k= 0; k<longCad; k++) if (StringSubstr(cadPares, k, 1)==";") contPares++; ArrayResize(arrayPares, contPares); ZeroMemory(arrayPares); for(k=0; k<longCad; k++) { caract= StringSubstr(cadPares, k, 1); if (caract!=";") arrayPares[i]= arrayPares[i]+caract; else i++; } } return(contPares); }
OnInit() ではこの関数は以下のような方法で実装されます。
string ar_ParesFX[]; //array, containing names of the pairs for the EA to work with int numSimbs= 1; //variable, containing information about the number of symbols it works with int OnInit() { //... numSimbs= cargaPares(cadParesFX, ar_ParesFX); //returns the ar_ParesFX array with pairs for work in the EA //... }
numSimbs>1 であれば、OnChartEvent() 関数が呼ばれます。これはマルチ通貨システムで動作します。それ以外は OnTick() 関数が使用されます。
void OnTick() { string simb=""; bool entrar= (nSimbs==1); if(entrar) { .../... simb= ar_ParesFX[0]; gestionOrdenes(simb); .../... } return; } //+------------------------------------------------------------------+ //| EVENT HANDLER | //+-----------------------------------------------------------------+ void OnChartEvent(const int idEvento, const long& lPeriodo, const double& dPrecio, const string &simbTick) { bool entrar= nSimbs>1 && (idEvento>=CHARTEVENT_CUSTOM); if(entrar) { .../... gestionOrdenes(simbTick); .../... } }
これは入力パラメータの役目をする関数がすべて最低でも問合せを受けているシンボルを持つ必要があることを意味します。たとえば、Digits() 関数の代わりに以下を使用する必要があります。
//--------------------------------- SYMBOLS OF A SYMBOL --------------------------------------- int digitosSimb(string simb= NULL) { int numDig= (int)SymbolInfoInteger(simb, SYMBOL_DIGITS); return(numDig); }
すなわち、Ask や Bid など МetaТtarder 4 に前からあるその他の変数だけでなく、Symbol() や Point() 関数のことは忘れる必要があるのです。
//----------------------------------- POINT VALUE in price (Point())--------------------------------- double valorPunto(string simb= NULL) { double resp= SymbolInfoDouble(simb, SYMBOL_POINT); return(resp); }
//--------------------------- precio ASK-BID ----------------------------------------- double precioAskBid(string simb= NULL, bool ask= true) { ENUM_SYMBOL_INFO_DOUBLE precioSolic= ask? SYMBOL_ASK: SYMBOL_BID; double precio= SymbolInfoDouble(simb, precioSolic); return(precio); }
そのようなコードに存在するバーオープンを制御する関数も忘れるようにました。EURUSD で受け取られるティックが新規バーのオープンを伝えていたら、USDJPY のティックは次の2秒の間には受け取られない可能性があります。結果、次の USDJPY のティックで EURUSD についてこのイベントが2秒前に発生していたとしても EA はこのシンボルについて新規バーがオープンしていることを発見することになります。
//------------------------------------- NEW MULTI-CURRENCY CANDLESTICK ------------------------------------- bool nuevaVelaMD(string simb= NULL, int numSimbs= 1, ENUM_TIMEFRAMES marcoTmp= PERIOD_CURRENT) { static datetime arrayHoraNV[]; static bool primVez= true; datetime horaVela= iTime(simb, marcoTmp, 0); //received opening time of the current candlestick bool esNueva= false; int codS= buscaCadArray(simb, nombreParesFX); if(primVez) { ArrayResize(arrayHoraNV, numSimbs); ArrayInitialize(arrayHoraNV, 0); primVez= false; } esNueva= codS>=0? arrayHoraNV[codS]!= horaVela: false; if(esNueva) arrayHoraNV[codS]= horaVela; return(esNueva); }
この方法で私は最適化パス中に以下を発見しました。
- EA は EURUSD でうまく動作する。
- EURJPY はひじょうによくない。
- USDJPY では満足のいく程度に動作する。
- ペア EURUSD、GBPCHF、EURJPY ではひじょうに良く動作します(実ケース)。
期間 М5 と他の最適化パラメータの特定の組合せにも当てはまりますが、Н1 または Н2 には当てはまりません。
ここにやっかいな瞬間があります。私はそれについて技術的サポートを依頼しました。なぜそれが起こるのかわかりませんが、ストラテジーテスタで選択すrシンボルについて最適化結果が異なるのです。結果確認のために、戦略作成中このペアを固定し、これがオプティマイザで分析できるペアの一つであることを確認するのはその理由によります。
パラメータの組合せの最適化
最適化に関わるパラメータの組合せから非論理的な組合せがいつくか適しているとわかることがあります。その中には戦略を非合理的なものにするものがあります。たとえば、エントリーの変数がトレード処理に設定されるスプレッド値を決定する場合、平均的なブローカーのスプレッドが 30 より小さく XAUUSD が 400 であるようなさまざまなペアに対するこの変数を最適化します。それらが50を超え、XAUUSD が200より小さければ、こういったペアを分析するのは合理的ではありません。データをオプティマイザに渡し、『maxSpread を間隔 20 で 0~600 に』設定しましたが、他のパラメータを伴うその設定は意味をなさない数多くの組合せを産み出します。
以下は前セクションで説明したパターンで、"selecPares()" 関数内での最適化のためにペアを決定しました。EURUSD はオプション 0 に割り当てられ、 XAUUSD はオプション 9 に割り当てられています。それからブール "paramCorrect" タイプのグローバル変数を定義します。
bool paramCorrect= (selecDePar<9 && maxSpread<50) || (selecDePar==9 && maxSpread>200);
paramCorrect が正しいポジションの真にあれば、OnInit() だけで処理を実行します。
int OnInit() { ENUM_INIT_RETCODE resp= paramCorrect? INIT_SUCCEEDED: INIT_PARAMETERS_INCORRECT; if (paramCorrect) { //... nSimbs= cargaPares(cadParesFX, nombreParesFX); //return the array nombreParesFX containing pairs for work in the EA //... function of the EA initialization } return(resp); }
paramCorrect が正しくないポジション偽にあれば、EA は OnInit() で処理を実行せず、ストラテジーテスタに INIT_PARAMETERS_INCORRECT を返します。それは不正な入力データセットを意味します。ストラテジーテスタが OnInit() からINIT_PARAMETERS_INCORRECT 値を受け取ると、このパラメータセットは実装のため他の検証エージェントには渡されず、最適化結果を持つテーブル内の行にはゼロが入り赤で強調されます(下図参照)。
プログラムシャットダウンの理由は入力変数として OnDeinit() に渡され、それが EA の終了理由を知るのに役立ちます。ですが、これは別問題です。
void OnDeinit(const int motivo) { if(paramCorrect) { //functions of the program shutdown } infoDeInit(motivo); return; } //+-------------------------------------- INFORMATION ABOUT THE PROGRAM SHUTDOWN---------------------------- string infoDeInit(int codDeInit) { //informs of the reason of the program shutdown string texto= "program initialization...", text1= "CIERRE por: "; switch(codDeInit) { case REASON_PROGRAM: texto= text1+"The EA finished its work with the ExpertRemove() function"; break; //0 case REASON_ACCOUNT: texto= text1+"The account was changed"; break; //6 case REASON_CHARTCHANGE: texto= text1+"Symbol or timeframe change"; break; //3 case REASON_CHARTCLOSE: texto= text1+"The chart was closed"; break; //4 case REASON_PARAMETERS: texto= text1+"Input parameters changed by the user"; break; //5 case REASON_RECOMPILE: texto= text1+"The program was recompiled"; break; //2 case REASON_REMOVE: texto= text1+"The program was deleted from the chart"; break; //1 case REASON_TEMPLATE: texto= text1+"Another chart template was used"; break; //7 case REASON_CLOSE: texto= text1+"The terminal was closed"; break; //9 case REASON_INITFAILED: texto= text1+"The OnInit() handler returned non-zero value"; break; //8 default: texto= text1+"Other reason"; } Print(texto); return(texto); }
それは、指定の段階でオプティマイザによって受け取られるパラメータセットが "paramCorrect" を偽に設定する(たとえばスプレッドが100ポイントに設定された)と、EA を実行せず、コンピュータリソースとMQL5.сommunity アカウントのレンタルエージェントの費用を不必要に使うことなく、この最適化ステップがゼロになることです。
確かに上記はすべて関数OnTesterInit()、 ParameterGetRange()、 ParameterSetRange() を用いて実装可能ですが、上で記述したパターンの方がシンプルに思われます。OnTesterInit() のパターンに一貫性がない場合でもこれは動作することが保証されています。
おわりに
МetaТrader 5 における最適化タイムフレームの検索スピードアップ、МetaТrader 5 が文字列変数の最適化を許可しないときの『シンボル』パラメータの最適化、EA が使用する数多くのシンボルに対してそれを公平にすることについてお話してきました。非理論的な入力パラメータを排除しコンピュータのパフォーマンスを維持し費用を節約するべく最適化ステップ数を減らす方法の実例を見てきました。
上記のアイデアは新しいものではなく、初心者やある程度の経験あるプログラマーによって実装可能なものです。こういったアイデアは情報とプログラムをデバッグする用法をながらく調査した結果です。シンブルですが、効果的なアイデアです。MQL5 に利益を生んでほしければその考えをシェアするのはなぜかお尋ねになるかもしれません。答えはプログラマーの『孤独』を克服するためです。
読んでいただいてありがとうございました。最後まで読んでいただけていて、みなさんが経験あるプログラマーである場合は、私のことをあまり厳しく評価しないでください。
MetaQuotes Ltdによりスペイン語から翻訳されました。
元の記事: https://www.mql5.com/es/articles/1052
- 無料取引アプリ
- 8千を超えるシグナルをコピー
- 金融ニュースで金融マーケットを探索