![考えられる.EAをリアルタイムで最適化するためのインジケータの使用法](https://c.mql5.com/i/articles/overlay.png)
考えられる.EAをリアルタイムで最適化するためのインジケータの使用法
コンテンツ
- イントロダクション
- 1. アイディア
- 2. トレーディングストラテジ
- 3. テスターインジケータの準備
- 3.1. 仮想トレードのクラス
- 3.2. インジケータのプログラミング
- 4. EAの作成
- 5. アプローチのテスト
- 結論
イントロダクション
我々はEAで起動するたびに、最適なパラメータを選択する問題に直面します。 そのパラメータを見つけるために、ヒストリーデータでストラテジを最適化します。 しかし、周知のように、相場にはある一定の動きがあります。 時間が経過すると、選択したパラメータの優位性が失われます。
したがって、EA の再最適化が必要です。 このサイクルは一定です。 すべてのユーザーが自分自身で再最適化のタイミングを決定する必要があります。 しかし、そのプロセスを自動化することは不可能でしょうか。 解決策は何でしょうか。 おそらく、カスタム構成ファイルを使用してターミナルを実行することによって、標準ストラテジーテスターのプログラム制御を考えたことがあるかもしれません。 今回は、型破りなアプローチを提供し、インジケータにテスターの機能を割り当てたいと思います。
1. アイディア
もちろん、このインジケータは決してストラテジーテスターではありません。 では、EA の最適化にどのように役立つことができるでしょうか。 このアイデアは、インジケータに EA の操作ロジックを実装し、リアルタイムで仮想トレードの収益性を追跡することにあります。 ストラテジーテスタで最適化を行う場合、指定されたパラメータに対して一連のテストを繰り返し行います。 ストラテジーテスターのパスと同様の異なるパラメータを持つ単一のインジケータの複数のインスタンスを同時に起動することによって、同じことを行います。 決定を下す際には、EAはインジケータを起動し、最適なパラメータを選択します。
なぜこのホイールを再発明するか疑問に思うかもしれません。 長所と短所を分析してみましょう。 確かに、このアプローチの主な利点は、ほとんどリアルタイムの条件で EA の最適化です。 2番目の利点は、テストがブローカーのリアルティックで実行されるということです。 一方で、統計データが収集されるまで待たなければならないので、リアルタイムでのテストは巨大な欠点があります。 もう一つの利点は、インジケータは全体のヒストリーを再計算せず現在のティックだけ再計算しますが、ストラテジーテスターの場合は最初からヒストリーに沿って実行されます。 このアプローチは、適切なタイミングで高速な最適化を提供します。 したがって、ほとんどすべての足で最適化を行うことができます。
このアプローチの短所は、テストのティックヒストリーの欠如があります。 もちろん、 CopyTicks またはCopyTicksRangeを使用することができます。 しかし、ティックのヒストリーをダウンロードする時間が必要であり、膨大なデータ量の再計算、コンピューティングパワーと時間が必要です。 メタトレーダー5上で単一のシンボルのタスクで稼働させるインジケータを使用することを忘れないようにしましょう。 したがって、ここでは別の制限があります-あまりにも多くのインジケータは、ターミナルが遅くなる可能性があります。
説明された欠点のリスクを最小限に抑えるには、次の仮定を行います。
- テスターインジケーターを初期化する場合、ヒストリーはM1 OHLC 価格によって計算されます。 オーダーの利益/損失を計算するときに、SL は最初の高値/安値 (オーダーの種類に応じて) によって TP に続いてチェックされます。
- ポイント1に続いて、オーダーはロウソクの始値でのみ開かれています。
- 実行中のテストインジケータの合計数を減らすには、意味のあるメソッドを適用して、使用するパラメータを選択します。 ここでは、インジケータロジックに従って、最小ステップとフィルタパラメータを追加できます。 たとえば、MACD を使用している間に、高速および低速の MA のパラメータ範囲が重なっている場合、テスターインジケータは、EA の操作ロジックと矛盾するため、低速 MAの期間が高速MA1に満たないパラメータのセットに対しては起動されません。 また、最初に多くのダマシシグナルを含むオプションを破棄して、ピリオドの間に最小の不一致を追加することができます。
2. トレーディングストラテジ
このメソッドをテストするため、3つの標準インジケータ WPR、RSI と ADX に基づいて簡単なストラテジを使用してみましょう。 WPR が売られ過ぎのレベルを上方 (レベル-80) を越えると、買いシグナルが作動します。 RSI は、買われ過ぎ領域 (レベル70の上) にあるべきではありません。 両方のインジケータがオシレータであるため、その使用はレンジな動きで正当化されます。 レンジの存在は、レベル40を超えてはならない ADX インジケータによってチェックされます。
売りシグナルは対称になります。 WPR インジケータは、-20の買われ過ぎレベルをクロスし、RSIが30の売られ過ぎのラインを超える必要があります。 ADX は、買いと同じように、レンジを管理します。
前述したように、相場のエントリは、シグナルに続く新しいロウソク足で実行されます。 決済は、固定SL/TP によって実行されます。
損失管理のため、一度に1つのポジションを取ることはありません。
3. テスターインジケータの準備
3.1. 仮想トレードのクラス
トレードストラテジを定義したので、テストインジケータを開発します。 まず、インジケータで追跡する仮想オーダーを準備する必要があります。 この記事 [1] には既に仮想オーダークラスが記述されています。 わずかな追加でこれ活用することができます。 前に説明したクラスには、現在の Ask と Bid の価格を使用してオーダーがクローズされた瞬間をチェックするティックメソッドがあります。 このメソッドは、リアルタイムでのみ機能する場合に適用され、ヒストリーデータのチェックには適用できません。 そのパラメータに価格とスプレッドを追加して、前述の関数を少し変更してみましょう。 操作の実行後、このメソッドはオーダーの状態を返します。 この追加の結果として、このメソッドは次の形式になります。
bool CDeal::Tick(double price, int spread) { if(d_ClosePrice>0) return true; //--- switch(e_Direct) { case POSITION_TYPE_BUY: if(d_SL_Price>0 && d_SL_Price>=price) { d_ClosePrice=price; i_Profit=(int)((d_ClosePrice-d_OpenPrice)/d_Point); } else { if(d_TP_Price>0 && d_TP_Price<=price) { d_ClosePrice=price; i_Profit=(int)((d_ClosePrice-d_OpenPrice)/d_Point); } } break; case POSITION_TYPE_SELL: price+=spread*d_Point; if(d_SL_Price>0 && d_SL_Price<=price) { d_ClosePrice=price; i_Profit=(int)((d_OpenPrice-d_ClosePrice)/d_Point); } else { if(d_TP_Price>0 && d_TP_Price>=price) { d_ClosePrice=price; i_Profit=(int)((d_OpenPrice-d_ClosePrice)/d_Point); } } break; } return IsClosed(); }
添付ファイルにクラスコード全体があります。
3.2. インジケータのプログラミング
次に、インジケータ自体をコーディングしてみましょう。 テスターインジケータはEAの役割を何らかの形で果たしているため、そのインプットはEAのパラメータに似ています。 最初に、テスト期間、およびインジケータパラメータの SL および TP レベルを設定します。 次に、適用されるインジケータのパラメータを指定します。 最後に、トレードの方向性と統計データの平均期間を示します。 各パラメータの使用についての詳細は、インジケータコードで使用されている間に提供されます。
input int HistoryDepth = 500; //ヒストリーデプス (足) input int StopLoss = 200; //SL (ポイント) input int TakeProfit = 600; //TP (ポイント) //---RSI インジケータパラメータ input int RSIPeriod = 28; //RSI の期間 input double RSITradeZone = 30; //買われ過ぎ売られ過ぎゾーンサイズ //---WPR インジケータパラメータ input int WPRPeriod = 7; //期間 WPR input double WPRTradeZone = 30; //買われ過ぎ売られ過ぎ ゾーンサイズ //---ADX インジケータパラメータ input int ADXPeriod = 11; //ADX 期間 input int ADXLevel = 40; //レンジレベル ADX //--- input int Direction = -1; //トレードの方向 "-1 "-すべて、 "0 "-買い、 "1 "-売り //--- input int AveragePeriod = 10; //平均期間
EA との計算とデータ交換に、以下のデータを含む9つのインジケータバッファを作成します。
1. 収益性の高いトレードの確率。
double Buffer_Probability[];
2. テスト期間の利益率。
double Buffer_ProfitFactor[];
3. SL と TP レベル。 2つのバッファは、EA でインジケータハンドルと指定されたレベルを照合する配列を作成するか、トレードの実行時にハンドルによってインジケータパラメータをリクエストすることによって除外できます。 しかし、現在の解決策は、最も簡単なものと思われます。
double Buffer_TakeProfit[]; double Buffer_StopLoss[];
4. テストされた期間内に実行されたトレードの合計数とその収益性の数値を計算するためのバッファ。
double Buffer_ProfitCount[]; double Buffer_DealsCount[];
5. 次の2つのバッファは、前の値を計算するための補助で、現在の足に対してのみ同様のデータを格納します。
double Buffer_ProfitCountCurrent[]; double Buffer_DealsCountCurrent[];
6. そして、最後に、EAに取引を実行するためのシグナルを送信するバッファ。
double Buffer_TradeSignal[];
指定したバッファに加えて、開いている情報を格納するための配列、直近のトレードの時刻を記録するための変数、インジケータハンドルを格納するための変数、およびグローバル変数ブロック内のインジケータから情報を取得するための配列を宣言します。
CArrayObj Deals; datetime last_deal; int wpr_handle,rsi_handle,adx_handle; double rsi[],adx[],wpr[];
関数の先頭にあるインジケータを初期化します。
int OnInit() { //---RSI インジケータハンドルを取得 rsi_handle=iRSI(Symbol(),PERIOD_CURRENT,RSIPeriod,PRICE_CLOSE); if(rsi_handle==INVALID_HANDLE) { Print("Test Indicator",": Failed to get RSI handle"); Print("Handle = ",rsi_handle," error = ",GetLastError()); return(INIT_FAILED); } //---WPR インジケータハンドルを取得 wpr_handle=iWPR(Symbol(),PERIOD_CURRENT,WPRPeriod); if(wpr_handle==INVALID_HANDLE) { Print("Test Indicator",": Failed to get WPR handle"); Print("Handle = ",wpr_handle," error = ",GetLastError()); return(INIT_FAILED); } //---ADX インジケータハンドルを取得 adx_handle=iADX(Symbol(),PERIOD_CURRENT,ADXPeriod); if(adx_handle==INVALID_HANDLE) { Print("Test Indicator",": Failed to get ADX handle"); Print("Handle = ",adx_handle," error = ",GetLastError()); return(INIT_FAILED); }
次に、インジケータバッファを動的配列に関連付けます。
//---インジケータバッファのマッピング SetIndexBuffer(0,Buffer_Probability,INDICATOR_CALCULATIONS); SetIndexBuffer(1,Buffer_DealsCount,INDICATOR_CALCULATIONS); SetIndexBuffer(2,Buffer_TradeSignal,INDICATOR_CALCULATIONS); SetIndexBuffer(3,Buffer_ProfitFactor,INDICATOR_CALCULATIONS); SetIndexBuffer(4,Buffer_ProfitCount,INDICATOR_CALCULATIONS); SetIndexBuffer(5,Buffer_TakeProfit,INDICATOR_CALCULATIONS); SetIndexBuffer(6,Buffer_StopLoss,INDICATOR_CALCULATIONS); SetIndexBuffer(7,Buffer_DealsCountCurrent,INDICATOR_CALCULATIONS); SetIndexBuffer(8,Buffer_ProfitCountCurrent,INDICATOR_CALCULATIONS);
すべての配列に時系列プロパティを割り当てます。
ArraySetAsSeries(Buffer_Probability,true); ArraySetAsSeries(Buffer_ProfitFactor,true); ArraySetAsSeries(Buffer_TradeSignal,true); ArraySetAsSeries(Buffer_DealsCount,true); ArraySetAsSeries(Buffer_ProfitCount,true); ArraySetAsSeries(Buffer_TakeProfit,true); ArraySetAsSeries(Buffer_StopLoss,true); ArraySetAsSeries(Buffer_DealsCountCurrent,true); ArraySetAsSeries(Buffer_ProfitCountCurrent,true); //--- ArraySetAsSeries(rsi,true); ArraySetAsSeries(wpr,true); ArraySetAsSeries(adx,true);
関数の最後に、トレードの配列をリセットし、直近のトレードの日付だけでなく、インジケータに名前を割り当てる。
Deals.Clear(); last_deal=0; //--- IndicatorSetString(INDICATOR_SHORTNAME,"Test Indicator"); //--- return(INIT_SUCCEEDED); }
インジケータの現在のデータは、GetIndValue 関数でダウンロードされます。 インプットでは、指定された関数は、読み込まれたデータのヒストリーの必要な深さを受信し、出力時に読み込まれた要素の数を返します。 インジケータのデータは、グローバルに宣言された配列に格納されます。
int GetIndValue(int depth) { if(CopyBuffer(wpr_handle,MAIN_LINE,0,depth,wpr)<=0 || CopyBuffer(adx_handle,MAIN_LINE,0,depth,adx)<=0 || CopyBuffer(rsi_handle,MAIN_LINE,0,depth,rsi)<=0) return -1; depth=MathMin(ArraySize(rsi),MathMin(ArraySize(wpr),ArraySize(adx))); //--- return depth; }
相場参入シグナルをチェックするには、BuySignal および SellSignal 関数を作成します。 添付ファイル内に関数のコードがあります。
よくあるインジケータのように、主な関数は、OnCalculate 関数に集中します。 関数の操作は、論理的に2つのフローに分けることができます。
- 複数の足を再計算する場合 (初期化後の最初の起動または新しい足のオープン)。 このフローでは、M1 のタイムフレームのヒストリーデータに基づいて、各計算された足と未処理のトレードの処理ストップオーダーのトレードを開始するための相場参入シグナルをチェックします。
- 新しい足はまだ形成されていませんが、各新しいティックのオープンポジションのストップオーダーのアクティベーションをチェックしてください。
関数の最初に、関数の直近の起動以降の新しい足の数を確認します。 インジケータを初期化した後の最初の起動である場合、インジケータの再計算の幅がテストの深さを超えないように設定し、インジケータバッファを初期状態に戻します。
int OnCalculate(const int rates_total, const int prev_calculated, const datetime &time[], const double &open[], const double &high[], const double &low[], const double &close[], const long &tick_volume[], const long &volume[], const int &spread[]) { //--- int total=rates_total-prev_calculated; if(prev_calculated<=0) { total=fmin(total,HistoryDepth); //--- ArrayInitialize(Buffer_Probability,0); ArrayInitialize(Buffer_ProfitFactor,0); ArrayInitialize(Buffer_TradeSignal,0); ArrayInitialize(Buffer_DealsCount,0); ArrayInitialize(Buffer_ProfitCount,0); ArrayInitialize(Buffer_TakeProfit,TakeProfit*_Point); ArrayInitialize(Buffer_StopLoss,StopLoss*_Point); ArrayInitialize(Buffer_DealsCountCurrent,0); ArrayInitialize(Buffer_ProfitCountCurrent,0); }
次に、新しいロウソク足が開いたときに最初のロジックフローの操作が来ます。 まず、適用されたインジケータの現在のデータをダウンロードします。 データのダウンロードエラーが発生した場合は、インジケータが再計算されるのを待機している次のティックまで関数を終了します。
if(total>0) { total=MathMin(GetIndValue(total+2),rates_total); if(total<=0) return prev_calculated;
次に、[時系列] プロパティを受信価格配列に割り当てます。
if(!ArraySetAsSeries(open,true) || !ArraySetAsSeries(high,true) || !ArraySetAsSeries(low,true) || !ArraySetAsSeries(close,true) || !ArraySetAsSeries(time,true) || !ArraySetAsSeries(spread,true)) return prev_calculated;
各足を再計算するためのメインループは次のようになります。 ループの開始時に、再計算された足のインジケータバッファを初期化します。
for(int i=total-3;i>=0;i--) { Buffer_TakeProfit[i]=TakeProfit*_Point; Buffer_StopLoss[i]=StopLoss*_Point; Buffer_DealsCount[i]=Buffer_DealsCountCurrent[i]=0; Buffer_ProfitCount[i]=Buffer_ProfitCountCurrent[i]=0;
その後、まだ計算された足に別の取引を開いたかどうかを確認します。 そうでない場合は、以前に作成した関数を呼び出してインジケータのエントリシグナルをチェックします。 相場参入シグナルがある場合は、仮想トレードを作成し、シグナルバッファに対応するシグナルを書き込みます。
if(last_deal<time[i]) { if(BuySignal(i)) { double open_price=open[i]+spread[i]*_Point; double sl=open_price-StopLoss*_Point; double tp=open_price+TakeProfit*_Point; CDeal *temp=new CDeal(_Symbol,rates_total-i,POSITION_TYPE_BUY,time[i],open_price,sl,tp); if(temp!=NULL) Deals.Add(temp); Buffer_TradeSignal[i]=1; } else /*BuySignal*/ if(SellSignal(i)) { double open_price=open[i]; double sl=open_price+StopLoss*_Point; double tp=open_price-TakeProfit*_Point; CDeal *temp=new CDeal(_Symbol,rates_total-i,POSITION_TYPE_SELL,time[i],open_price,sl,tp); if(temp!=NULL) Deals.Add(temp); Buffer_TradeSignal[i]=-1; } else /*SellSignal*/ Buffer_TradeSignal[i]=0; }
オープンポジションで動作するようにしましょう。 まず、現在の時間枠を確認します。 インジケーターがM1で動作する場合は、OnCalculate 関数パラメーターで取得した時系列データによって、ストップオーダーのアクティベーションがチェックされます。 まず、現在の時間枠を確認します。 ...
if(Deals.Total()>0) { if(PeriodSeconds()!=60) { MqlRates rates[]; int rat=CopyRates(_Symbol,PERIOD_M1,time[i],(i>0 ? time[i-1] : TimeCurrent()),rates);
クオートをダウンロードした後、各分のバーでオープンディールのストップオーダーのアクティベーションをチェックするためのループを調整します。 再計算されたバーの適切なインジケーターバッファーに、クローズおよび収益性のある取引を合計します。 この配列は CheckDeals 関数で処理されます。 チェックされた分足のデータは関数パラメータで渡されます。 関数演算アルゴリズムは以下のように考えられます。
int closed=0, profit=0; for(int r=0;(r<rat && Deals.Total()>0);r++) { CheckDeals(rates[r].open,rates[r].high,rates[r].low,rates[r].close,rates[r].spread,rates[r].time,closed,profit); if(closed>0) { Buffer_DealsCountCurrent[i]+=closed; Buffer_ProfitCountCurrent[i]+=profit; } }
同様の代替ブロックが次に来ます。 M1 のタイムフレーム上の分のクオートやインジケータ操作のダウンロードが失敗した場合には、現在のタイムフレームのデータでトレードをチェックします。
if(rat<0) { CheckDeals(open[i],high[i],low[i],close[i],spread[i],time[i],closed,profit); Buffer_DealsCountCurrent[i]+=closed; Buffer_ProfitCountCurrent[i]+=profit; } } else /* PeriodSeconds()!=60 */ { int closed=0, profit=0; CheckDeals(open[i],high[i],low[i],close[i],spread[i],time[i],closed,profit); Buffer_DealsCountCurrent[i]+=closed; Buffer_ProfitCountCurrent[i]+=profit; } } /* Deals.Total()>0 */
最後に、ストラテジ運用統計を分析してみましょう。 テストされた期間内に開いたトレードの数と、そのうちのどれくらいが利益で決済したかを計算します。
Buffer_DealsCount[i+1]=NormalizeDouble(Buffer_DealsCount[i+2]+Buffer_DealsCountCurrent[i+1]-((i+HistoryDepth+1)<rates_total ? Buffer_DealsCountCurrent[i+HistoryDepth+1] : 0),0); Buffer_ProfitCount[i+1]=NormalizeDouble(Buffer_ProfitCount[i+2]+Buffer_ProfitCountCurrent[i+1]-((i+HistoryDepth+1)<rates_total ? Buffer_ProfitCountCurrent[i+HistoryDepth+1] : 0),0); Buffer_DealsCount[i]=NormalizeDouble(Buffer_DealsCount[i+1]+Buffer_DealsCountCurrent[i]-((i+HistoryDepth)<rates_total ? Buffer_DealsCountCurrent[i+HistoryDepth] : 0),0); Buffer_ProfitCount[i]=NormalizeDouble(Buffer_ProfitCount[i+1]+Buffer_ProfitCountCurrent[i]-((i+HistoryDepth)<rates_total ? Buffer_ProfitCountCurrent[i+HistoryDepth] : 0),0);
有効なトレードがある場合は、トレードで利益を得た確率と、テスト期間内のストラテジ利益率を計算します。 利益獲得確率の急激な変化を回避するために、このパラメータは、インジケータパラメータで設定された平均期間を使用して指数関数的な平均的な式に従ってスムージングされます。
if(Buffer_DealsCount[i]>0) { double pr=2.0/(AveragePeriod-1.0); Buffer_Probability[i]=((i+1)<rates_total && Buffer_Probability[i+1]>0 && Buffer_DealsCount[i+1]>=AveragePeriod ? Buffer_ProfitCount[i]/Buffer_DealsCount[i]*100*pr+Buffer_Probability[i+1]*(1-pr) : Buffer_ProfitCount[i]/Buffer_DealsCount[i]*100); if(Buffer_DealsCount[i]>Buffer_ProfitCount[i]) { double temp=(Buffer_ProfitCount[i]*TakeProfit)/(StopLoss*(Buffer_DealsCount[i]-Buffer_ProfitCount[i])); Buffer_ProfitFactor[i]=((i+1)<rates_total && Buffer_ProfitFactor[i+1]>0 ? temp*pr+Buffer_ProfitFactor[i+1]*(1-pr) : temp); } else Buffer_ProfitFactor[i]=TakeProfit*Buffer_ProfitCount[i]; } } }
各ティックの処理フローには同様のロジックが含まれているため、ここでは完全な説明を提供しても意味がありません。 添付ファイル内ですべてのインジケータ関数のコード全体を詳しく見ることができます。
以前、既存の取引のストップオーダーの活発化の確認が CheckDeals 関数で遂行されることを述べました。 その操作アルゴリズムを考えてみましょう。 パラメータでは、関数は、分析された足のクオート、クローズドと収益性の高いトレードの数を返すための変数への2つのリンクを取得しました。
関数の開始時に、返された変数をリセットし、結果の論理変数を宣言します。
bool CheckDeals(double open,double high,double low,double close,int spread,datetime time,int &closed, int &profit) { closed=0; profit=0; bool result=true;
さらに、配列内のすべてのトレードを繰り返し処理するループが関数に配置されます。 取引オブジェクトへのポインタは、ループ内で1つずつ取得されます。 オブジェクトへの誤ったポインタがある場合は、配列からこのトレードを削除し、次に進みます。 操作の実行中にエラーが発生した場合は、結果の変数を ' false ' に設定します。
for(int i=0;i<Deals.Total();i++) { CDeal *deal=Deals.At(i); if(CheckPointer(deal)==POINTER_INVALID) { if(Deals.Delete(i)) i--; else result=false; continue; }
次に、ロウソク足が開いた時点でトレードが開かれたかどうかを確認します。 そうでない場合は、次の取引に移動します。
if(deal.GetTime()>time) continue;
最後に、各価格のチェックされたトレードのティックメソッドを呼び出すことによって、始値、高値、安値、終値のトレードストップオーダーのアクティベーションを確認します。 このメソッドのアルゴリズムは、現在のセクションの先頭に記述されています。 チェックシーケンスは、売買取引によって異なることに注意してください。 まず、SL のアクティベーションがチェックされ、TP それにが続きます。 このアプローチは、ある程度トレード結果を過小評価するかもしれませんが、未来のトレードで損失を低減します。 ストップオーダーのいずれかがトリガされると、クローズドトレードの数が増加し、利益の場合には、同様に収益性の高いトレードの数が増加します。 トレードを決済すると、再計算を行わないように配列から削除されます。
if(deal.Tick(open,spread)) { closed++; if(deal.GetProfit()>0) profit++; if(Deals.Delete(i)) i--; if(CheckPointer(deal)!=POINTER_INVALID) delete deal; continue; } switch(deal.Type()) { case POSITION_TYPE_BUY: if(deal.Tick(low,spread)) { closed++; if(deal.GetProfit()>0) profit++; if(Deals.Delete(i)) i--; if(CheckPointer(deal)!=POINTER_INVALID) delete deal; continue; } if(deal.Tick(high,spread)) { closed++; if(deal.GetProfit()>0) profit++; if(Deals.Delete(i)) i--; if(CheckPointer(deal)!=POINTER_INVALID) delete deal; continue; } break; case POSITION_TYPE_SELL: if(deal.Tick(high,spread)) { closed++; if(deal.GetProfit()>0) profit++; if(Deals.Delete(i)) i--; if(CheckPointer(deal)!=POINTER_INVALID) delete deal; continue; } if(deal.Tick(low,spread)) { closed++; if(deal.GetProfit()>0) profit++; if(Deals.Delete(i)) i--; if(CheckPointer(deal)!=POINTER_INVALID) delete deal; continue; } break; } } //--- return result; }
インジケータとそのすべての関数の完全なコードが添付ファイルに用意されています。
4. EAの作成
テスターインジケータを作成したので、EA を開発しましょう。 EA のパラメータでは、静的な変数の数 (すべてのパスに共通) を設定し、ストラテジーテスタと同様に、変更されたパラメータの初期値と終了値、および値の変更ステップを定義します。 また、EA パラメータでは、相場参入シグナルを選択するための基準も指定していますが、テスト期間の利益と最低利益率を実現する最小確率です。 また、得られた統計データの客観性を維持するために、テスト期間に必要な最低トレード数を明記しておきましょう。
input double Lot = 0.01; input int HistoryDepth = 500; //ヒストリーデプス (足) //---RSI インジケータパラメータ input int RSIPeriod_Start = 5; //RSI の期間 input int RSIPeriod_Stop = 30; //RSI の期間 input int RSIPeriod_Step = 5; //RSI の期間 //--- input double RSITradeZone_Start = 30; //買われ過ぎ売られ過ぎ ゾーンサイズ開始 input double RSITradeZone_Stop = 30; //買われ過ぎ売られ過ぎ ゾーンサイズのストップ input double RSITradeZone_Step = 5; //買われ過ぎ売られ過ぎ ゾーンサイズステップ //---WPR インジケータパラメータ input int WPRPeriod_Start = 5; //期間 WPR 開始 input int WPRPeriod_Stop = 30; //期間 WPR ストップ input int WPRPeriod_Step = 5; //期間 WPR ステップ //--- input double WPRTradeZone_Start = 20; //買われ過ぎ売られ過ぎ ゾーンサイズ開始 input double WPRTradeZone_Stop = 20; //買われ過ぎ売られ過ぎ ゾーンサイズのストップ input double WPRTradeZone_Step = 5; //買われ過ぎ売られ過ぎ ゾーンサイズステップ //---ADX インジケータパラメータ input int ADXPeriod_Start = 5; //ADX 期間開始 input int ADXPeriod_Stop = 30; //ADX 期間ストップ input int ADXPeriod_Step = 5; //ADX 期間ステップ //--- input int ADXTradeZone_Start = 40; //レンジレベル ADX 開始 input int ADXTradeZone_Stop = 40; //レンジレベル ADX ストップ input int ADXTradeZone_Step = 10; //レンジレベル ADX ステップ //---設定 input int TakeProfit_Start = 600; //テイクプロフィットスタート input int TakeProfit_Stop = 600; //テイクプロフィットストップ input int TakeProfit_Step = 100; //テイクプロフィットステップ //--- input int StopLoss_Start = 200; //ストップロス スタート input int StopLoss_Stop = 200; //ストップロスストップ input int StopLoss_Step = 100; //ストップロス ステップ //--- input double MinProbability = 60.0; //最小確率 input double MinProfitFactor = 1.6; //最小プロフィットファクタ input int MinOrders = 10; //ヒストリーの中でのトレードの最小数
グローバル変数で、トレーディングオペレーションクラスのインスタンスとテスターインジケータのハンドルを格納するための配列を宣言します。
CArrayInt ar_Handles; CTrade Trade;
EA の OnInit 関数では、テストされたパラメータのすべてのオプションを繰り返し処理するための一連のネストされたループを配置し、売買取引の別のテストを追加します。 このアプローチは、テストされたストラテジによって追跡されないグローバルトレンドの影響を考慮することができます。 テスターインジケータはループ内で初期化されます。 インジケータのダウンロードが失敗した場合は、INIT_FAILED の結果で関数を終了します。 インジケータが正常に読み込まれた場合は、そのハンドルを配列に追加します。
int OnInit() { //--- for(int rsi=RSIPeriod_Start;rsi<=RSIPeriod_Stop;rsi+=RSIPeriod_Step) for(double rsi_tz=RSITradeZone_Start;rsi_tz<=RSITradeZone_Stop;rsi_tz+=RSITradeZone_Step) for(int wpr=WPRPeriod_Start;wpr<=WPRPeriod_Stop;wpr+=WPRPeriod_Step) for(double wpr_tz=WPRTradeZone_Start;wpr_tz<=WPRTradeZone_Stop;wpr_tz+=WPRTradeZone_Step) for(int adx=ADXPeriod_Start;adx<=ADXPeriod_Stop;adx+=ADXPeriod_Step) for(double adx_tz=ADXTradeZone_Start;adx_tz<=ADXTradeZone_Stop;adx_tz+=ADXTradeZone_Step) for(int tp=TakeProfit_Start;tp<=TakeProfit_Stop;tp+=TakeProfit_Step) for(int sl=StopLoss_Start;sl<=StopLoss_Stop;sl+=StopLoss_Step) for(int dir=0;dir<2;dir++) { int handle=iCustom(_Symbol,PERIOD_CURRENT,"::Indicators\\TestIndicator\\TestIndicator.ex5",HistoryDepth, sl, tp, rsi, rsi_tz, wpr, wpr_tz, adx, adx_tz, dir); if(handle==INVALID_HANDLE) return INIT_FAILED; ar_Handles.Add(handle); }
すべてのテスターインジケータが正常に起動したら、トレード操作のクラスを初期化し、関数の実行を完了します。
Trade.SetAsyncMode(false); if(!Trade.SetTypeFillingBySymbol(_Symbol)) return INIT_FAILED; Trade.SetMarginMode(); //--- return(INIT_SUCCEEDED); }
トレーディングシグナルはソートされ、トレーディングオペレーションは OnTick 関数で実行されます。 以前に足のオープニングでのみポジションを開くことを決めたので、関数の冒頭にこのイベントの発生を確認します。
void OnTick() { //--- static datetime last_bar=0; datetime cur_bar=(datetime)SeriesInfoInteger(_Symbol,PERIOD_CURRENT,SERIES_LASTBAR_DATE); if(cur_bar==last_bar) return;
2番目の制限は一度に1つ以上のオープン取引です。 したがって、開いているポジションがある場合は、関数の実行をストップします。
if(PositionSelect(_Symbol)) { last_bar=cur_bar; return; }
コントロールポイントをチェックした後、シグナルの検索ですべてのインジケータを繰り返し処理のメインループに進みます。 ループの開始時に、インジケータのシグナルバッファを読み込みます。 インジケータがまだ再計算されていない場合、またはトレードシグナルがない場合は、次のインジケータに進みます。
int signal=0; double probability=0; double profit_factor=0; double tp=0,sl=0; bool ind_caclulated=false; double temp[]; for(int i=0;i<ar_Handles.Total();i++) { if(CopyBuffer(ar_Handles.At(i),2,1,1,temp)<=0) continue; ind_caclulated=true; if(temp[0]==0) continue;
次のステップは、受信したシグナルが他のインジケータから以前に受信したシグナルと矛盾しないかどうかを確認することです。 相反するシグナルの存在は、エラーの確率を増加させるので、次のロウソクの形成が始まる前に、関数を終了します。
if(signal!=0 && temp[0]!=signal) { last_bar=cur_bar; return; } signal=(int)temp[0];
次に、テスト期間内の最小必要なトレード数の有無を確認します。 サンプルが不十分な場合は、次のインジケータに移動します。
if(CopyBuffer(ar_Handles.At(i),1,1,1,temp)<=0 || temp[0]<MinOrders) continue;
さらに、収益性の高いトレード確率は、同様の方法で検証されます。
if(CopyBuffer(ar_Handles.At(i),0,1,1,temp)<=0 || temp[0]<MathMax(probability,MinProbability)) continue;
分析されたインジケータによる収益性の高いトレードの確率の不一致と以前にチェックしたものが 1% 未満の場合は、利益率と利益/リスク比に基づいて、2つのパスから最適なものが選択されます。 最適なパスデータは、次のタスクに保存されます。
if(MathAbs(temp[0]-probability)<=1) { double ind_probability=temp[0]; //--- if(CopyBuffer(ar_Handles.At(i),3,1,1,temp)<=0 || temp[0]<MathMax(profit_factor,MinProfitFactor)) continue; double ind_profit_factor=temp[0]; if(CopyBuffer(ar_Handles.At(i),5,1,1,temp)<=0) continue; double ind_tp=temp[0]; if(CopyBuffer(ar_Handles.At(i),6,1,1,temp)<=0) continue; double ind_sl=temp[0]; if(MathAbs(ind_profit_factor-profit_factor)<=0.01) { if(sl<=0 || tp/sl>=ind_tp/ind_sl) continue; } //--- probability=ind_probability; profit_factor=ind_profit_factor; tp=ind_tp; sl=ind_sl; }
収益性の高いトレードを取得する確率が明らかに大きい場合は、パスの利益率の要件がチェックされます。 すべての要件が満たされている場合、パスデータはさらに保存されます。
else /* MathAbs(temp[0]-probability)<=1 */ { double ind_probability=temp[0]; //--- if(CopyBuffer(ar_Handles.At(i),3,1,1,temp)<=0 || temp[0]<MinProfitFactor) continue; double ind_profit_factor=temp[0]; if(CopyBuffer(ar_Handles.At(i),5,1,1,temp)<=0) continue; double ind_tp=temp[0]; if(CopyBuffer(ar_Handles.At(i),6,1,1,temp)<=0) continue; double ind_sl=temp[0]; probability=ind_probability; profit_factor=ind_profit_factor; tp=ind_tp; sl=ind_sl; } }
1つのテスターインジケータがすべてをチェックした後に再計算されない場合は、インジケータが再計算されるのを待機している次のティックまで関数を終了します。
if(!ind_caclulated) return;
インジケータが正常にチェックされ、アクティブなトレードシグナルがない場合は、新しい足が形成される前に関数を終了します。
last_bar=cur_bar; //--- if(signal==0 || probability==0 || profit_factor==0 || tp<=0 || sl<=0) return;
関数の最後に、エントリシグナルがある場合は、ベストパスに従ってオーダーを送信します。
if(signal==1) { double price=SymbolInfoDouble(_Symbol,SYMBOL_ASK); tp+=price; sl=price-sl; Trade.Buy(Lot,_Symbol,price,sl,tp,"Real Time Optimizator"); } else if(signal==-1) { double price=SymbolInfoDouble(_Symbol,SYMBOL_BID); tp=price-tp; sl+=price; Trade.Sell(Lot,_Symbol,price,sl,tp,"Real Time Optimizator"); } }
添付ファイルで完全な EA のコードを詳しく見ることができます。
5. アプローチのテスト
このメソッドを示すために、得られたEAは標準EAの並列最適化され、同様の期間の可変パラメータを使用したフォワードテストされます。 条件を等しく保つために、統計データによってフィルタリングすることなく、1つだけテスターインジケータを起動し、すべてのシグナルの情報のEA を作成しました。 その構造は、シグナル・コンシステンシ・フィルタリング・ブロックを除き、上記で作成された EA に似ています。 完全な EA コードは添付ファイル (ClassicExpert.mq5) にあります。
テストは、2018年の7ヶ月間、H1で実施されています。 テストされた期間の1/3 は標準的な EA のフォワードテスト用に取っておきました。
インジケータ計算期間が最適化パラメータとして選択されました。 5から30までの1つの範囲の値をすべてのインジケータに使用しました。
最適化の結果は提案手法の矛盾を示しました。 最適化中に小さな利益を示したパラメータ値は、フォワードテスト中に損失が発生することが判明しました。 全体では、パスのいずれも分析期間内に利益を示しました。
最適化とフォワードテストのグラフィカルな分析の結果は、WPR インジケータ期間による収益性ゾーンのシフトにつながる価格の動きの構造の変化を示しました。
提案手法に従って開発された EA をテストするために、解析期間を同じに保ちながら同様のテストパラメータを指定しました。 相場参入シグナルを選別するために、最低利益率を 60%、テスト期間中の最低プロフィットファクタを2として指定しました。 テストの深さは500ロウソク足です。
テスト中に、このEAは、分析期間内に1.66 のプロフィットファクタで利益を示しました。 ビジュアルモードでのテスト中に、テストエージェントは 1250 MB の RAM を占有していました。
結論
著書稿では、EAをリアルタイムに最適化して開発するメソッドを提案しました。 テストは、実際のトレードのアプローチの実行可能性を示しています。 提案手法に基づくEAは、ストラテジの収益性期間中に利益を得て、損失の発生時に活動をストップさせます。 同時に、計算資源の面でもそのメソッドが求められています。 RAMは、すべての適用されるインジケータを含める必要がある一方、CPU 速度はすべての読み込まれたインジケータを再計算することができます。
レファレンス
記事で使用するプログラム
# |
名称 |
タイプ |
詳細 |
---|---|---|---|
1 | Deal.mqh | クラスライブラリ | 仮想トレードのクラス |
2 | TestIndicator.mq5 | インジケータ | テスターインジケータ |
3 | RealTimeOptimization.mq5 | EA | 提案手法に基づく EA |
4 | ClassicExpert.mq5 | EA | 比較最適化の標準法に基づくEA |
MetaQuotes Ltdによってロシア語から翻訳されました。
元の記事: https://www.mql5.com/ru/articles/5061
![ソーシャルトレーディング収益性の高いシグナルをさらに良くすることはできるでしょうか?](https://c.mql5.com/2/33/Social_Trading_avatar.png)
![特定のディストリビューション法によるカスタムシンボルを用いた時系列モデリング](https://c.mql5.com/2/33/Custom_series_modelling.png)
![MQL5レシピ - オープンヘッジポジションのプロパティを取得しましょう](https://c.mql5.com/2/34/position.png)
![リバーシング: 聖杯や危険な妄想?](https://c.mql5.com/2/33/avatar5008.png)
![MQL5 - MetaTrader 5クライアントターミナルにビルトインされたトレードストラテジーの言語](https://c.mql5.com/i/registerlandings/logo-2.png)
- 無料取引アプリ
- 8千を超えるシグナルをコピー
- 金融ニュースで金融マーケットを探索