インディケータの経済的計算原理
はじめに
人々の実際的行動の一つか二つのセグメントでリソースを失わないようにするという考え方は、おそらく人類の発展と進歩をたどる上でもっとも重要で差し迫った主題の一つでしょう。MQL5でのプログラミングもこの点においては例外ではありません。もちろんタスク範囲がビジュアルトレーディングに限られているなら、プログラミングのフロー多くのは気づかれずにすみます。
しかし、自動トレーディングに関連するすべては、まず最大限経済的にコードを書くことを要求します。さもなければ、トレーディングロボットの検証と最適化のプロセスはたいへん時間のかかるものとなってしまい事実上完了を待つのは不可能になるかもしれません。この状況のタイプで価値あるなにかを作成するという考えははなはだ疑わしいものと思えるのです。
トレーディング戦略の実装に着手する前に、プログラミングの詳細に慣れることは意味あることかもしれません。それはExpert Advisorsの最適化および検証時間に影響するものです。多くのExpert Advisorsはユーザーインディケータの呼び出しを含むので、そこから始めていきます。
通例、関連点はそう多くはありません。インディケータ構築の際にはそのことを覚えておく必要があります。そこでそれらを順番にさっと見ていくのがもっとも理合理的でしょう。
それぞれのインディケータティックを再計算します。それはまだ計算されていない、標準的なインディケータに新規作成されたバーです。
」RSI、ADX、ATR、CCIなど標準タイプのインディケータの基本は、クローズされたバーにおいてこれらインディケータの計算は一度だけ行われ、そののちの計算は新規作成バーにのみ行われます。ただひとつの例外は現在オープンしているバーで、そこで計算はバーがクローズされるまでティックごとに繰り返し行われます。
未計算バーでインディケータの計算を行うのが合理的かどうか知るもっとも簡単な方法は、ストラテジーテスタで、そのような(最適化された)インディケータの実行結果を、全バーで全時間(最適化されていない)計算したバーと比較することです。
これはたいへん簡単に行えます。Expert Advisorは空のOnInit ()関数とOnTick ()関数で作成されます。要求されるバージョンの最適化されたまたは最適化されていないインディケータのExpert Advisorでの呼び出しを書き、両方の場合についてテスタで実行した結果を喜ぶだけです。例として、私の記事『初心者のためのMQL5のユーザーインディケータ 』からインディケータSMA.mq5を抜き出し、そこに行の置き換えを行います。
if (prev_calculated == 0) // if this is the first start, then make a recalculation of all existing bars first = MAPeriod - 1 + begin; else first = prev_calculated - 1; // on all subsequent starts, make calculations only on newly appeared bars
以下で
first = MAPeriod - 1 + Begin; / / On all ticks the bars are being recalculated
結果、プログラミングコードの最適化されていないバージョンを取得しました。(SMA !!!!!!. mq5です。)これは原版と違い、各ティックですべての値を再計算します。 厳密にいうと、両方の場合のエキスパートコードバージョンは 実質的に似通っています。それでそのうち一つだけを提供します。 (SMA_Test.mq5)
//+------------------------------------------------------------------+ //| SMA_Test.mq5 | //| Copyright 2010, MetaQuotes Software Corp. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2010, MetaQuotes Software Corp." #property link "https://www.mql5.com" #property version "1.00" int Handle; //+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //----+ //----+ Obtaining the indicator handle Handle = iCustom(Symbol(), 0, "SMA"); if (Handle == INVALID_HANDLE) Print(" Not able to obtain the indicator handle SMA"); //----+ return(0); } //+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { //----+ //--- Release the indicator handle IndicatorRelease(Handle); //----+ } //+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { //----+ double SMA[1]; //----+ Using the indicator handle, copy the values of the indicator // buffer in a specially prepared for this statistical array CopyBuffer(Handle, 0, 0, 1, SMA); //----+ } //+------------------------------------------------------------------+
これで検証が行えます。本稿で述べる検証はすべて、バー変更のシミュレーションモードを使用し、それは現実の「ティックごと」にできる限り近いものであることを知っておく必要があります。
最適化されたインディケータのテスターにおける実行結果があります。
赤色は検証が経過した時間を示します。これは多すぎではありません!しかし、インディケータSMA !!!!!!.mq5の検証を完了するのにかなり長い間待つ必要がありました。
基本的に、今回のケースのテストプロセスの回数は前回のものとは500回以上異なります。これは、かなり短い検証期間が選択されているにもかかわらず、です。しかし、Expert Advisorの検証中は、そのような莫大な計算コストには我慢できますが、そのパラメータの最適化についてはそういうわけにはいきません。
そのためこれは、経済的に書かれたコードはプログラミング分野のプロの時間つぶしではなく、むしろご自身のコードを書のにかなり話題のやり方だということの最も有力な証拠です。
インターネット上に、パソコンのパフォーマンスを最高に持っていくことに特化したまとまったウェブサイト Overclockers.ruがあります。この実践の基本手段はずっと高価なコンピュータコンポーネントを使い、CPUとRAMメモリのクロックスピードを上げることです。
その後、過剰クロックCPUに対してより高価な冷水器を使うか、またはそれを液体窒素処理機に浸すかします。そのような行動の結果は、PCパフォーマンスを二倍、それどころか三倍にすら高めます。
経済的コード記述であるコンポーネントは、しばしばほとんど労なく多くを達成するのに役立ちます。もちろんこの手法はCelleron300AをCore 2 Quad Q6600に変換することはできませんが、それでも平均的標準価格のPCを最先端機種のコンピュータにのみ期待できるような用途に機能させることはできます。
ある程度標準的インディケータにおける現在クローズ中バーの再計算の繰り返し
このプログラムコードの最適化が無差別にすべてのインディケータに適応できるのであれば、こんなにすばらしいことはないでしょう。けれど、そんなにうまくはいかないものです。インディケータのまとまったグループがあります。それはインディケータをすでに既存の履歴データにロードしている間、そのような手法のもと、一度のみ普通に計算を始めます。
インディケータのロード後作成されるすべてのバーで、その値はまったく誤っていることが判るのです。これが起こる主な理由は、インディケータコードからの変数の中には、そういったインディケータ依存のものがあり、それは以前のバーのインディケータの計算後同じ変数を持つためです。正式には、それは以下のようなものです。
SomeVariable(bar) = Function(SomeVariable(bar - 1))
そこでは:
- SomeVariable() — なんらかのバーのいくつかの変数値
- bar - 計算が行われるバーナンバー
明らかな理由により、実際のコードではそのような依存はわかりにくい関数形式をしています。しかし、この基礎は変わりません。たとえば、T3(非最適化インディケータT3 !!!!!!mq5)を移動するためにはコードの一部、われわれにとって適切なもの、以下のようなものです。
e1 = w1 * series + w2 * e1;
e2 = w1 * e1 + w2 * e2;
e3 = w1 * e2 + w2 * e3;
e4 = w1 * e3 + w2 * e4;
e5 = w1 * e4 + w2 * e5;
e6 = w1 * e5 + w2 * e6;
//----
T3 = c1 * e6 + c2 * e5 + c3 * e4 + c4 * e3;
変数e1, e2, e3, e4, e5, e6ははっきりそのような関数依存にあります。それは新規バーごとに一度だけ計算をするためにこのコードを使うようになっています。 しかし、現在バーは同様の計算によって、クローズまで繰り返しとばされます。
そして、現在バーのこれら変数値は、つねに変わります。たとえ現在バーについて変わる前でも以前のバーでの計算後はそのまま残ります。
そのため、以前のバー(現在バーに対して)のこれら変数値は静的変数に保存され、バーが次に変化するまで再利用のため変換する必要があります。そこでは変数の最後から二番目の値が再び e1, e2, e3, e4, e5 , e6といして保存される必要があります。
値を似たように操る追加コードはとりわけシンプルです。まず、OnCalculate ()関数内に値を保存するために、ローカルの静的変数を宣言する必要があります。
//---- declare the static variables for storing the valid values of the coefficients static double e1_, e2_, e3_, e4_, e5_, e6_;
こののち、現在バーのサイクル内で計算前、新規に作成されるバーの数がゼロより大きいとき、変数値を記憶することは重要です。
//---- memorize the values of variables before the run on the current bar if (rates_total != prev_calculated && bar == rates_total - 1) { e1_ = e1; e2_ = e2; e3_ = e3; e4_ = e4; e5_ = e5; e6_ = e6; }
そしてサイクル処理のブロック以前に逆変換で変数値を復元します。
//---- restore the values of the variables
e1 = e1_;
e2 = e2_;
e3 = e3_;
e4 = e4_;
e5 = e5_;
e6 = e6_;
当然、計算された係数の初期化は、OnCalculate ()関数が最初に起動するとき、一度だけ開始されます。そして、係数自体ではなく、対応している静的変数の初期化が行われました。
//---- calculating the starting number first for the cycle of recalculation of bars if (prev_calculated == 0) // verification of the first start of the indicator calculation { first = begin; // the starting number for calculating all of the bars //---- the starting initialization of calculated coefficients e1_ = price[first]; e2_ = price[first]; e3_ = price[first]; e4_ = price[first]; e5_ = price[first]; e6_ = price[first]; }
結果、最終インディケータT3.mq5が最も低コストな方法で 計算を行います。すべては何でもありません。が、つねに類似の関数依存がそんなに簡単に特定できるとは限りません。この場合、すべてのインディケータ変数値は静的変数内に記憶することができ、同じ方法で復元されます。
そしてあとになって初めて、どの変数がほんとうに復元が必要か、どれには必要ないかわかり始めるのです。これを行うには、チャートで最適化されていないインディケータと最適化されたバージョンのインディケータを見守り、その動作を確認し、徐々に一変数ずつ復元リストから削除する必要があります。最後に、ほんとうに復元が必要な変数のみ残ります。
通常のインディケータのプログラムコードと連携するためのロジック型を提供しました。そこで現在バーと新規作成バーの再集計が行われます。塗り替え、将来を見通すインディケータについては、そののユニークな特性により、似たようなごく標準的でシンプルなコード最適化手段は作成できません。そして経験豊かなExpert Advisorプログラマーの多くがこれを必要とはしていません。そのため、ここではこれらインディケータ完成の詳細な分析の考察ができます。
MQL5コードを過度にに遅くする可能性のあるインディケータ呼び出し機能
タスクは完了し、われわれは最適化されたインディケータを手にしています。それは最も経済的方法でバーを数えます。ここでは数行コードを書くだけで十分です。そしてこのインディケータをExpert Advisorの、またはインディケータのコートに呼び、インディケータバッファから計算値を取得します。
しかしこれは、どんなタイプの処理がこの数行のコードに潜んでいるか見つける手間を省いていては、実際見かけほど簡単ではありません。
ユーザーとテクニカルインディケータから値を取得することについては、MQL5の時系列指数からのように、ユーザーの変数配列にデータをコピーすることで行います。これは現在アカウントデータとっては全く必要のないデータを作る結果となりえます。
いくつかのテクニカルインディケータからこのすべてを特定のデータ受信管理において検証する最も簡単な方法例として、 iAMA の動向を取り、このテクニカルインディケータを基にカスタムインディケータAMkAをビルドします。
データをコピーするには、開始位置とコピーのための必要なエレメント数の要求がある関数呼び出しの最初のバージョン CopyBuffer ()を使用します。AMkAインディケータにおいては、現在バーで動きのあるインクレメンテ―ションはテクニカル指数 標準偏差を使って処理されます。そして、トレーディング信号を取得するために、このインクレメントはすべて処理された標準偏差値と比較されます。
そのため、最もシンプルにAMkA指数を実装するには、まず、中でインディケータバッファがムービングインクレメンテ―ション(インディケータdAMA)の値を含むインディケータを作成します。それから、その他のインディケータ内に、AMAインクレメントを伴なうインディケータハンドルを使用して、「標準偏差」インディケータによって処理された結果値を取得します。
類似インディケータ作成プロセスはすでに細部にわたりさまざまな記事のこういったトピックで検証済みです。よって、ここではそれに時間をかけることはしません。呼ばれたインディケータのインディケータバッファにアクセスする詳細を他のインディケータコードで分析します。
MQL5例は多くのインターネット上の情報源で見つかります。そこではそれぞれの著者が文字通りインディケータバッファの全コンテンツを中間動的配列にコピーしています。 この後、すべての値はひとつずつこれら中間的配列からサイクルオペレータを使用して最終的なインディケータバッファに変換されます。
この方法はひじょうにわれわれの問題を解決シンプルしてくれます。
if (CopyBuffer(AMA_Handle, 0, 0, rates_total, Array) <= 0) return(0); ArrayCopy(AMA_Buffer, Array, 0, 0, WHOLE_ARRAY);
(インディケータdAMA !!!!!!. mq5) または以下です。
if (CopyBuffer(AMA_Handle, 0, 0, rates_total, Array) <= 0) return(0); for(bar = 0; bar < rates_total; bar++) { AMA_Buffer[bar] = Array[bar]; /* here is the code of indicator calculations */ }
しかし、そのようなシンプルな解決法のために費やすものとは? まず、何が極限的に合理的な処理の仕方であるか、少し理解するのがよいでしょう。 まず、中間配列Array []を使用するにあたり正当な必要性はまったくありません。またデータは直接インディケータバッファAMA []にコピーする必要があります。.
第二に、各インディケータティックで3とおりに値をコピーする必要があります。
- 新規作成バーから
- クローズしたバーから
- 現在オープンしているバーから
インディケータバッファに残っている値はすでに存在しており、複数回書き直すことに意味はありません。
//--- calculation of the required number of copied data int to_copy; if(prev_calculated > rates_total || prev_calculated <= 0)// verification for the first start of indicator calculation to_copy = rates_total - begin; // calculated number of all bars else to_copy = rates_total - prev_calculated + 1; // calculated number of only new bars //--- copy the reappearing data into the indicator buffer AMA_Buffer[] if (CopyBuffer(AMA_Handle, 0, 0, to_copy, AMA_Buffer) <= 0) return(0);
この場合の最終コードがいくらか多少複雑(インディケータdAMA.mq5)なのは当然のことすが、ここではこの稿の最初に提案した両方の場合について検証を行い適切な結論を引き出す方法論を使用することができます今回は、検証期間を1年に増やします。
遂に、テスター戦略ジャーナル内で検証を通過し、Expert Advisorの dAMA_Testを検証するに必要な時間を得ました。
結果としての検証を通過する時間43,937ミリ秒は妥当な範囲です。Expert Advisor dAMA !!!!!!_ Testでの検証通過時間の類似結果について何が言えないのでしょうか。
検証通過時間は960 625ミリ秒で、前のケースより20倍長くなっています。結論はきわめて明らかです。コードはもっとも経済的方法で書かれる必要があり、それで不要な計算をしなくなります。
AMkAインディケータは上述の原理に基づいて構築されていますが、何も新しいことは提示していません。そこでこの場合のデータコピーについて少し時間を取りたいと思います。
//---- declaring local arrays double dAMA_Array[], StdDev_Array[]; //---- indexation of elements in arrays just like in time series ArraySetAsSeries(dAMA_Array, true); ArraySetAsSeries(StdDev_Array, true); //--- calculation of the number of copied data int to_copy; if(prev_calculated > rates_total || prev_calculated <= 0)// verification of the first start of indicator calculation to_copy = rates_total - begin; // calculated number of all bars else to_copy = rates_total - prev_calculated + 1; // calculated number of only new bars //--- copy the newly appeared data into the indicator buffer and local dynamic arrays if(CopyBuffer(dAMAHandle, 1, 0, to_copy, AMABuffer ) <= 0) return(0); if(CopyBuffer(dAMAHandle, 0, 0, to_copy, dAMA_Array ) <= 0) return(0); if(CopyBuffer(StdDevHandle, 0, 0, to_copy, StdDev_Array) <= 0) return(0);
今データは単一のインディケータバッファと2つのローカルで宣言された中間計算用の動的配列にコピーされていること以外、すべては完全に似た方法で行われています。
インディケータ内での全インディケータ計算の実装は最適化への方法の一つです。
これはすべてたいへん興味深くありますが、そのようなあとに続くユーザーの呼び出しとテクニカルインディケータの複雑な構造にはいささか疑問を感じますそれにはいくらか計測が必要です。しかし、それを行うにはAMkAインディケータのコードを持っても何の影響もありません。それはユーザーインディケータ内に配置され、他のインディケータ呼び出しには使われないはずです。
MQL5でインディケータを書くプロセスをすべて把握しているプログラマーにとってはこの問題は大した労力を要求しません。まず、ユーザーインディケータAMA.mq5のコードを書きます。それから、AMkA_.mq5 indicator実装のため、コードの必要なエレメントをそこに追加します。もうすこし時間があります。インディケータ計算の大きなサイクル、そこでは AMAインディケータのインクレメントを伴ってユーザー配列がロードされます。
//---- the main cycle of calculating the AMkA indicator for(bar = first; bar < rates_total; bar++) { //---- load the increments of the AMA indicator into the array for intermediate calculations for(iii = 0; iii < ama_period; iii++) dAMA[iii] = AMABuffer[bar - iii - 0] - AMABuffer[bar - iii - 1];
この配列は処理に使用され、これはdAMA.mq5インディケータのデータに基づくテクニカルインディケータStDevの計算に似ています。
//---- find the simple average of increments of AMA Sum = 0.0; for(iii = 0; iii < ama_period; iii++) Sum += dAMA[iii]; SMAdif = Sum / ama_period; //---- find the sum of the square differences of increments and the average Sum = 0.0; for(iii = 0; iii < ama_period; iii++) Sum += MathPow(dAMA[iii] - SMAdif, 2); //---- determine the final value of the meansquare deviation of StDev from the increment of AMA StDev = MathSqrt(Sum / ama_period);
コードの残り部分は、AMkA.mq5インディケータのコードに対してひじょうに似通っており、われわれにとってはなんら興味は引きません。 これでExpert AdvisorsのAMkA__Test.mq5とAMkA_Test.mq5の助けを借りて、インディケータAMkA_.mq5とAMkA.mq5の検証を開始できます。
AMkA_.mq5インディケータを検証するのに何も難しいことはありません。検証時間は受入可能なフレームワーク内です。
しかし、インディケータAMkA.mq5は信じられないくらいゆっくりです。
この結果はこの『兄弟分』からすると7倍悪くなっています。そこで他にコメントは?結論は、まったく明らかです。インディケータを数回連続で次々に呼び出すような複雑なコンストラクションを構築することは注意深いとはいえず、これは予備検証にのみ適しています。
明らかにこの結果はクライアント端末の検証バージョンで取得されたもので、今すぐ将来的にすべてがどうなるか予想するのは簡単ではありません。来るトレーディングロボット選手権では、これが妥当で有効な話題であるとはっきり議論される可能性があります。
Expert Advisorsからインディケータを呼び出すいくつかの機能
ユーザーデータとインディケータのプログラムコード上のテクニカルインディケータへのアクセスを最適化することに関連したすべては、Expert Advisorsのプログラムコードにあるユーザーデータとインディケータへのアクセスの最適化に等しく適用することができます。すでに述べた内容に追加して、IExpert Advisorコードは別の要素も持っています。それはトレーディングシステムの検証と最適化に多大な影響を与える可能性のあるものです。
ひじょうに多くのExpert Advisorsが通常バーが変化するときのみインディケータデータを処理します。この理由でこれらExpert Advisors内では、 CopyBuffer () 関数をそれぞれのティックで呼ぶ必要はありません。
この状況では、Expert Advisorsはバー変更の間のみインディケータバッファからデータを取得する必要があります。インディケータ呼び出しは括弧の後ろのブロックに配置する必要があります。インディケータバッファからの必要データが全てうまく中間計算用配列内にコピーできたらそれへのアクセスは各バーが変化するときだけ許可されます。
そのようなフィルターとして提供するにはユーザー関数がもっとも適しており、それが現在バーに変化があるとき時間を論理ユニットに返します。ファイルIsNewBar.mqhは、この関数の私バージョン、と言ってもかなり汎用な、を含んでいます。 c
bool IsNewBar ( int Number, // Number of call to the function IsNewBar in the program code of the expert Advisor string symbol, // The chart symbol on which the data calculation is done ENUM_TIMEFRAMES timeframe // Timeframe of the chart on which the data calculation is done )
Expert Advisorコードでこの関数を使うときは、以下のようにします。
//---- declaration of a static variable - array for storing the values of the AMA indicator static double AMA_Array[3]; //---- calling up the AMA indicator for copying its values into the AMA_Array array if (IsNewBar(0, Symbol(), 0)) { CopyBuffer(AMA_Handle, 0, 1, 3, AMA_Array); }
この場合異なった動作をするのはかなり合理的です。CopyBuffer ()を呼ぶとき、AMA_Array []配列のデータはコピーされず、そのような場合はデータコピーをうまくいかせるオプションに至るまで各ティックについてこの関数を呼び出す必要があります。 そのようなオプションは特定のフィルタでやや複雑に実装されます。
//---- declaration of a static variable - array for storing values of the AMA indicator static double AMA_Array[3]; //---- declaration of the static variable for storing the results of copying the data from the AMA indicator static bool Recount; //---- calling up the AMA indicator for copying its values into the AMA_Array array if (IsNewBar(0, Symbol(), 0) || Recount) { if (CopyBuffer(AMA_Handle, 0, 1, 3, AMA_Array) < 0) { Recount = true; // attempt of data copying was unsuccessful return; // exit the function OnTick() } //---- All operations of copying from the indicator buffers are successfully completed // there is no need for returning to this block until the next bar change Recount = false; }
Expert Advisorのコード内のインディケータ値のコピー関数の合理的な呼び出しについてはわかりました。Expert AdvisorsでIsNewBar ()関数を適用する利点を検証することは可能です。
ストラテジーテスタで検証可能はExpert Advisorsのオプションが2つあります。一つはAMA_Test.ex5です。これはティックごとにインディケータバッファからデータをコピーします。
二番目のIsNewBar_AMA_Test.mq5はバー変更の間のみデータのコピーをします。
そうです!検証結果は少しがっかりする内容です。ティックごとIsNewBar ()関数を呼ぶのはユーザー配列の3つ のセルにデータをコピーするよりもずっと高くつきます。
ここでもうひとつ重要な点に着目していただきたいと思います。インディケータの目立たないように見える部分です。OnInit ()関数にインディケータのハンドルを取得すると、このインディケータのからのデータをOnTick ()関数内にコピーしようがしまいが、それの未計算の計算と現在バーはまだティックごとにコピーが行われるのです。
よって、われわれのExpert Advisorが現在オープンしているバーから数えられたインディケータ値を要求しなければ、節約という意味でこれらの値を無効化するのがより良い方法です。これはきわめて簡単です。変化にさきがけ、インディケータ内でバーの再カウントの主要サイクルの右側のボーダーを1ずつ減らします。AMA.mq5インディケータのこのサイクルは以下です。
//---- main cycle of indicator calculation for(bar = first; bar < rates_total; bar++)
そして、変化後は以下のようになります。
//---- main cycle of indicator calculation for(bar = first; bar < rates_total - 1; bar++)
インディケータAMA_Ex.mq5これでこのインディケータ (Expert Advisor AMA_Ex_Test.mq5)の検証が可能です。
見てのとおり、この結果はAMAインディケータより21%良好です。これは悪くない結果ですが、ただ、この結果はもっとよくなる可能性があります。
おわりに
結局、プログラムコードの効率はかなり主観的なパラメータです。効率は測定可能で、論理的に分析でき、ある状況ではかなり増大します。これを行う手法はそんなに難しいものではありません。必要なのはいくばくかの忍耐、そして自動化されたトレーディングシステムの利益性に直接影響する練習をもっと行うことです。
MetaQuotes Ltdによってロシア語から翻訳されました。
元の記事: https://www.mql5.com/ru/articles/109
- 無料取引アプリ
- 8千を超えるシグナルをコピー
- 金融ニュースで金融マーケットを探索