MT5で取引戦略を迅速に開発しデバッグする方法
「自分以外、信頼に足るものはない」(с) Debugger
スキャルピング自動システムはアルゴリズム取引の頂点にみなされていますが、コードを書くのは最も困難です。この記事では、受信ティックの分析に基づいて、戦略を構築するメソッドを示し、ビルトインツールとビジュアルテストをデバッグします。エントリーと決済の開発は、多くの場合、裁量取引の経験を必要とします。しかし、MT5ではヒストリー上で戦略をテストすることができます。
ティック上の取引アイデア
まず、価格変化を見ることができるティックチャート、すなわちグラフをプロットするインジケーターを作成する必要があります。このインジケーターは、コードベースで見つけることができます - https://www.mql5.com/ja/code/89。従来のものとは異なり、新しいチケットが到着したときにティックチャートをシフトする必要があります。
テストされたアイデアは、2つの連続ティック間の一連の変更に基づいて行われます。ポイントでのおおよその順序は次のようになります。
+1, 0, +2, -1, 0, +1, -2, -1, +1, -5, -1, +1, 0, -1, +1, 0, +2, -1, +1, +6, -1, +1,...
正規分布は、2ティック間の価格の変動の99%が3シグマ内にあると述べています。リアルタイムでティックごとに標準偏差を計算し、赤と青のアイコンの価格スパイクをマークします。視覚的にシャープ活用をするための戦略を選択することが可能になります。アイデアは非常にシンプルであり、ほとんどの数学家はこのプロセスを試しました。
ティック・インジケータを作成します
MetaEditorでMQLウィザードを実行して、名前と2つの入力パラメータを設定します。
- ticks - ティックの数は標準偏差の計算に使用されます
- gap - 係数はシグマの間隔を取得します。
次に、別のウィンドウでインジケータを表示し、2グラフィック・プロット、価格スパイクのシグナルのティックや色の矢印を指定します。
得られたドラフトに黄色でマークの変更を行います
//+------------------------------------------------------------------+ //| TickSpikeHunter.mq5 | //| Copyright 2016, MetaQuotes Software Corp. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2016, MetaQuotes Software Corp." #property link "https://www.mql5.com" #property version "1.00" #property indicator_separate_window #property indicator_buffers 3 #property indicator_plots 2 //---TickPrice プロット #property indicator_label1 "TickPrice" #property indicator_type1 DRAW_LINE #property indicator_color1 clrGreen #property indicator_style1 STYLE_SOLID #property indicator_width1 1 //---プロットシグナル #property indicator_label2 "Signal" #property indicator_type2 DRAW_COLOR_ARROW #property indicator_color2 clrRed,clrBlue,C'0,0,0',C'0,0,0',C'0,0,0',C'0,0,0',C'0,0,0',C'0,0,0' #property indicator_style2 STYLE_SOLID #property indicator_width2 1 //---入力パラメータ input int ticks=50; //計算でティック数 input double gap=3.0; //シグマにおけるチャネルの幅 //---インジケータバッファ double TickPriceBuffer[]; double SignalBuffer[]; double SignalColors[]; //---価格変更のカウンタ int ticks_counter; //---第一のインジケーターコール bool first; //+------------------------------------------------------------------+ //|カスタムインジケータ初期化関数| //+------------------------------------------------------------------+ int OnInit() { //---インジケータバッファマッピング SetIndexBuffer(0,TickPriceBuffer,INDICATOR_DATA); SetIndexBuffer(1,SignalBuffer,INDICATOR_DATA); SetIndexBuffer(2,SignalColors,INDICATOR_COLOR_INDEX); //--- プロットするときに無視する必要があります。 PlotIndexSetDouble(0,PLOT_EMPTY_VALUE,0); PlotIndexSetDouble(1,PLOT_EMPTY_VALUE,0); //--- このアイコンとしてシグナルがが出力されます PlotIndexSetInteger(1,PLOT_ARROW,159); //--- グローバル変数の初期化 ticks_counter=0; first=true; //---プログラムの初期化が成功しました return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ //|カスタムインジケータ反復関数| //+------------------------------------------------------------------+ 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[]) { //--- //--- 次の呼び出しのためにprev_calculated return(rates_total); } //+------------------------------------------------------------------+
OnCalculate()で事前に定義されたハンドラにコードを追加します。利便性のため、関数の最初の呼び出し時にインジケータ・バッファ内の値をゼロにし、そして、一連のフラグとして索引付けされます。これはTickPriceBuffer[0]に保存されます、すなわち、最新のティックの値を使用して、バッファーの最新の値を呼び出すことができるようになります。
また、ティックの主な取り扱いはApplyTick()関数に移動されます。
//+------------------------------------------------------------------+ //|カスタムインジケータ反復関数| //+------------------------------------------------------------------+ 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[]) { //---インジケータバッファをゼロにし、最初の呼び出しの間に直列フラグを設定 if(first) { ZeroMemory(TickPriceBuffer); ZeroMemory(SignalBuffer); ZeroMemory(SignalColors); //---シリーズ配列は、この場合には、より便利である後方に向けられています ArraySetAsSeries(SignalBuffer,true); ArraySetAsSeries(TickPriceBuffer,true); ArraySetAsSeries(SignalColors,true); first=false; } //---価格として現在の終値を使用 double lastprice=close[rates_total-1]; //---カウントティック ticks_counter++; ApplyTick(lastprice); //計算を実行し、バッファにシフト //--- 次の呼び出しのためにprev_calculated return(rates_total); } //+------------------------------------------------------------------+ //|計算のためにティック適用| //+------------------------------------------------------------------+ void ApplyTick(double price) { int size=ArraySize(TickPriceBuffer); ArrayCopy(TickPriceBuffer,TickPriceBuffer,1,0,size-1); ArrayCopy(SignalBuffer,SignalBuffer,1,0,size-1); ArrayCopy(SignalColors,SignalColors,1,0,size-1); //--- 最新の価格の値 TickPriceBuffer[0]=price; //--- }
現在、ApplyTick()は、最もシンプルな操作を実行します。 - ヒストリーの中で1つ前のポジションによって、すべてのバッファ値をシフトし、TickPriceBuffer[0]に最新のトリックを書き込みます。デバッグモードで指示を実行し、観察します。
現在のロウソク足の終値として使用されるBid価格は、多くの場合、変更されないので、グラフは、「プラトー」で描かれています。より直感的です - 「ギザギザ」を得るために、コードを調整します。
//---計算の価格が変更された場合 if(lastprice!=TickPriceBuffer[0]) { ticks_counter++; //カウントティック ApplyTick(lastprice); //計算を実行し、バッファにシフト }
よって、インジケータの最初のバージョンが作成されている価格の増分はありません。
標準偏差の補助バッファや計算を追加します
付加的な配列は、偏差を計算する必要があります。この配列は、ティックごとに価格の増分を格納します。このような配列のように、必要な場所で別の標識バッファーと対応するコードを追加します。
#property indicator_separate_window #property indicator_buffers 4 #property indicator_plots 2 ... //---インジケータバッファ double TickPriceBuffer[]; double SignalBuffer[]; double DeltaTickBuffer[]; double ColorsBuffers[]; ... //+------------------------------------------------------------------+ //|カスタムインジケータ初期化関数| //+------------------------------------------------------------------+ int OnInit() { //---インジケータバッファマッピング SetIndexBuffer(0,TickPriceBuffer,INDICATOR_DATA); SetIndexBuffer(1,SignalBuffer,INDICATOR_DATA); SetIndexBuffer(2,SignalColors,INDICATOR_COLOR_INDEX); SetIndexBuffer(3,DeltaTickBuffer,INDICATOR_CALCULATIONS); ... } //+------------------------------------------------------------------+ //|カスタムインジケータ反復関数| //+------------------------------------------------------------------+ int OnCalculate(const ...) //---インジケータバッファをゼロにし、最初の呼び出しの間に直列フラグを設定 if(first) { ZeroMemory(TickPriceBuffer); ZeroMemory(SignalBuffer); ZeroMemory(SignalColors); ZeroMemory(DeltaTickBuffer); //---シリーズ配列は、この場合には、より便利である後方に向けられています ArraySetAsSeries(TickPriceBuffer,true); ArraySetAsSeries(SignalBuffer,true); ArraySetAsSeries(SignalColors,true); ArraySetAsSeries(DeltaTickBuffer,true); first=false; } ... return(rates_total); } //+------------------------------------------------------------------+ //|計算のためにティック適用| //+------------------------------------------------------------------+ void ApplyTick(double price) { int size=ArraySize(TickPriceBuffer); ArrayCopy(TickPriceBuffer,TickPriceBuffer,1,0,size-1); ArrayCopy(SignalBuffer,SignalBuffer,1,0,size-1); ArrayCopy(SignalColors,SignalColors,1,0,size-1); ArrayCopy(DeltaTickBuffer,DeltaTickBuffer,1,0,size-1); //--- 最新の価格の値 TickPriceBuffer[0]=price; //---前の値との差を算出 DeltaTickBuffer[0]=TickPriceBuffer[0]-TickPriceBuffer[1]; //---標準偏差を取得 double stddev=getStdDev(ticks);
標準偏差の計算の準備ができています。まず、必要なだけのサイクルを使用して、配列のすべての要素を反復処理し、「ブルートフォース」を持つすべての計算を実行のために設定されたgetStdDev() 関数を記述します。
//+------------------------------------------------------------------+ //||「ブルートフォース」と標準偏差を算出 //+------------------------------------------------------------------+ double getStdDev(int number) { double summ=0,sum2=0,average,stddev; //---変更の合計をカウントし、期待利得を計算します for(int i=0;i<ticks;i++) summ+=DeltaTickBuffer[i]; average=summ/ticks; //---標準偏差を計算 sum2=0; for(int i=0;i<ticks;i++) sum2+=(DeltaTickBuffer[i]-average)*(DeltaTickBuffer[i]-average); stddev=MathSqrt(sum2/(number-1)); return (stddev); }
赤と青の円 - その後、ティックチャート上のサインを配置するためのブロックを追加
//+------------------------------------------------------------------+ //|計算のためにティック適用| //+------------------------------------------------------------------+ void ApplyTick(double price) { int size=ArraySize(TickPriceBuffer); ArrayCopy(TickPriceBuffer,TickPriceBuffer,1,0,size-1); ArrayCopy(SignalBuffer,SignalBuffer,1,0,size-1); ArrayCopy(SignalColors,SignalColors,1,0,size-1); ArrayCopy(DeltaTickBuffer,DeltaTickBuffer,1,0,size-1); //--- 最新の価格の値 TickPriceBuffer[0]=price; //---前の値との差を算出 DeltaTickBuffer[0]=TickPriceBuffer[0]-TickPriceBuffer[1]; //---標準偏差を取得 double stddev=getStdDev(ticks); //---価格変更が指定されたしきい値を超えた場合 if(MathAbs(DeltaTickBuffer[0])>gap*stddev) // シグナルは最初のティック上に表示されます。 { SignalBuffer[0]=price; //ドットを配置 string col="Red"; //ドットは、デフォルトでは赤です if(DeltaTickBuffer[0]>0) // 価格が急激に上昇 { SignalColors[0]=1; // その後、ドットが青色 col="Blue"; //ログインするためのストア } else // 価格が急落 SignalColors[0]=0; //ドットは赤 //--- EAのジャーナルに出力メッセージ PrintFormat("tick=%G change=%.1f pts, trigger=%.3f pts, stddev=%.3f pts %s", TickPriceBuffer[0],DeltaTickBuffer[0]/_Point,gap*stddev/_Point,stddev/_Point,col); } else SignalBuffer[0]=0; // シグナルなし //--- }
F5ボタン(スタート/再開のデバッグ)を押して、MT5の端末におけるインジケーターを見ます。
デバッグで、コードのエラーを識別し、プログラムの動作速度を向上させることができます。
コードプロファイリング操作をスピードアップします
実行速度は、リアルタイムの作業プログラムに必要です。MetaEditor開発フレームワークは、簡単かつ迅速にコードの任意の部分の時間消費を評価することができます。これを行うには、コードプロファイラを実行し、しばらくの間、プログラムの作業をさせることが必要です。1分あれば、プロファイリングできます。
計算時間(59.29パーセント)のほとんどはOnCalculate()関数から41回と呼ばれたApplyTick()関数の処理に費やされました。OnCalculate()自体は143回呼ばれていましたが、例では、前とは異なっていました。ApplyTick()関数自体にほとんど時間はかからず、インジケータのために意図された計算を実行しないArrayCopy()関数の呼び出しによって消費されました。ライン138行の標準偏差の計算は、全プログラムの実行時間の2.58パーセントです。
非生産コストを削減してみましょう。このためには、すべての配列(TickPriceBuffer、)ではなく、最新の200だけをコピーします。200の最新値は十分で、トレードセッションにおけるティック数は、数十あるいは数十万に達する可能性があります。それらすべてを表示する必要はありません。パラメータ - シフト=200 として、シフトの値を定義します。コードに黄色でマークされた行を追加します。
//---入力パラメータ input int ticks=50; // 計算ティック数 input int shift=200; //シフトされた値の数 input double gap=3.0; //シグマにおけるチャネルの幅 ... void ApplyTick(double price) { //---要素数は、各ティックのインジケータ・バッファにシフトします int move=ArraySize(TickPriceBuffer)-1; if(shift!=0) move=shift; ArrayCopy(TickPriceBuffer,TickPriceBuffer,1,0,move); ArrayCopy(SignalBuffer,SignalBuffer,1,0,move); ArrayCopy(SignalColors,SignalColors,1,0,move); ArrayCopy(DeltaTickBuffer,DeltaTickBuffer,1,0,move);
プロファイリングを実行し、参照してください。 ほとんどの時間は、標準偏差を計算するStdDev()の呼び出しで使用されています。
したがって、ApplyTick()の動作速度は、最適化の時間を短くすることができます。結局のところ、あまりにも多くの計算リソースが必要になることはありません。
分析コードの最適化
時には、最適に書かれたコードでも、さらに速く動作させることができます。式を多少変更し、標準偏差の計算を加速することができます。
したがって、単に価格増分の二乗和との和の二乗を計算することが可能となります。これで数学演算を実行することができます。単に、配列を減算し、合計を含む変数に入る配列要素を追加します。
自身の中に配列の値をシフトさせ、getStdDevOptimized()関数を作成します。
//+------------------------------------------------------------------+ //||式に基づいて標準偏差を計算します //+------------------------------------------------------------------+ double getStdDevOptimized(int number) { //--- static double X2[],X[],X2sum=0,Xsum=0; static bool firstcall=true; //---最初の呼び出し if(firstcall) { //---ティックの数よりも大きいような動的配列のサイズを設定 ArrayResize(X2,ticks+1); ArrayResize(X,ticks+1); //---計算の開始時に非ゼロ値を保証 ZeroMemory(X2); ZeroMemory(X); firstcall=false; } //---シフト配列 ArrayCopy(X,X,1,0,ticks); ArrayCopy(X2,X2,1,0,ticks); //---和の新しい入力値を算出 X[0]=DeltaTickBuffer[0]; X2[0]=DeltaTickBuffer[0]*DeltaTickBuffer[0]; //---新しい合計を計算 Xsum=Xsum+X[0]-X[ticks]; X2sum=X2sum+X2[0]-X2[ticks]; //---乗標準偏差 double S2=(1.0/(ticks-1))*(X2sum-Xsum*Xsum/ticks); //---ティックの合計をカウントし、期待利得を計算 double stddev=MathSqrt(S2); //--- return (stddev); }
getStdDevOptimized()関数を介して、ApplyTick()へ第二のメソッドを使用して標準偏差の計算を追加し、再びコードプロファイリングを実行してみましょう。
//---前の値との差を算出 DeltaTickBuffer[0]=TickPriceBuffer[0]-TickPriceBuffer[1]; //---標準偏差を取得 double stddev=getStdDev(ticks); double std_opt=getStdDevOptimized(ticks);
実行結果:
getStdDevOptimized() 内のブルートフォースとは異なり 、getStdDev() 関数は、半分の時間を必要とすることは明らかです。このように、最適な計算メソッドを使用すると、プログラムの動作速度を改善します。詳細については線形回帰の例によるインジケーター加速メソッドの記事をご覧ください。
ちなみに、標準関数を呼び出すことについて - このインジケーターで価格は時系列から得られるBid価格に基づいて閉じます。この価格を取得するには、 SymbolInfoDouble()と SymbolInfoTick()関数による2つ以上のメソッドがあります。コードにこれらの呼び出しを追加して、再度プロファイリングを実行してみましょう。
ご覧のように、ここでは動作速度に差があります。これはclose[]がユニバーサル関数とは異なり、任意の追加コストを必要としないため、理にかなっています。
リアルティックでのデバッグ
インジケーターと取引ロボットを書くとき、オンラインで作業中に発生する可能性があるすべての可能なシナリオを予測することは不可能です。幸いなことに、MetaEditorは、ヒストリーデータを使用してデバッグを行うことができます。ビジュアルモードでデバッグを実行すると、指定したヒストリー間隔でプログラムをテストすることができます。一時ストップし、希望日にテストをスキップすることが可能です。
注意:デバッグウィンドウに、ティックのモデリングモードを設定すると、リアルティックに基づきます。"。デバッグのトレードサーバによって保存されている実際のクオートを使用することができます。自動的に最初のテストでコンピュータにダウンロードされます。
これらのパラメータがMetaEditorに設定されていない場合は、ビジュアルテストは、現在のテスターの設定を使用します。「リアルティックに基づいた全ティック」を指定します。
奇妙なギャップを、ティックグラフで見ることができます。これは、アルゴリズムにエラーがあることを意味します。リアルタイムでテストしている間、それを追跡するためにどのくらいかかるかわかりません。この場合、ビジュアルモードのジャーナルログで奇妙なギャップが新しいバーが表示された瞬間に起こることを示しています。これです!インジケータのサイズは新しいバーへの移行時に1つずつバッファリングを増加することを忘れていました。コード修正を行います。
void ApplyTick(double price) { //---ティック価格のバッファ配列のサイズを保存する - チャート上のバーの数に等しいです。 static int prev_size=0; int size=ArraySize(TickPriceBuffer); //--- インジケータバッファのサイズが変更されなかった場合は、すべての要素をシフト if(size==prev_size) { //---要素数は、各ティックのインジケータ・バッファにシフトします int move=ArraySize(TickPriceBuffer)-1; if(shift!=0) move=shift; ArrayCopy(TickPriceBuffer,TickPriceBuffer,1,0,move); ArrayCopy(SignalBuffer,SignalBuffer,1,0,move); ArrayCopy(SignalColors,SignalColors,1,0,move); ArrayCopy(DeltaTickBuffer,DeltaTickBuffer,1,0,move); } prev_size=size; //--- 最新の価格の値 TickPriceBuffer[0]=price; //---前の値との差を算出
ビジュアルテストを実行し、新しいバーの瞬間をとらえるためにブレークポイントを配置します。新しいバーの最初のティックで、チャート上のバーの数は、1つ増加していて、必ずすべてが正しくなるように値を追加します。
そのように、コードの最適化は、エラーが修正された、異なる関数の実行時間が測定されている状態で行われています。さて、インジケータは、準備ができています。ビジュアルテストを実行し、シグナルがティックチャート上に表示された後に何が起こるかを観察することができます。さらに向上させる要因はありますか?コーディング完璧主義はイエスと言うだろう!まだ動作速度を向上させるために循環バッファを使用する試みがなされていません。興味のある方は、パフォーマンスの向上に与える影響を確認できます。
MetaEditorは取引戦略を開発するための準備実験室
自動取引システムを記述するためには、プログラムのデバッグやキャリブレーションの開発環境と強力なプログラミング言語だけでなく、追加のツールを持つことが重要です。この記事で説明します。
- 一次近似でティックチャートを作成します。
- F5ボタンを押して、リアルタイムモードでチャート上のデバッグを使用します。
- 非効率的なコード部分を識別するために、プロファイリングコードを実行します。
- ビジュアルモードで高速にデバッグを実行します。
- デバッグ時に必要な変数の値を表示します。
トレードシグナルを表示するインジケータを開発することは、多くの場合、取引ロボットを作成するために必要な最初のステップです。可視化は、取引する前に、やっぱりやめる決定をする材料になります。
効率的な売買ロボットを作成するにはMetaEditor開発環境の関数を利用します!
関連記事:
MetaQuotes Ltdによってロシア語から翻訳されました。
元の記事: https://www.mql5.com/ru/articles/2661
- 無料取引アプリ
- 8千を超えるシグナルをコピー
- 金融ニュースで金融マーケットを探索