ジグザグおよび ATR例によるクラスとしてのインディケータ実装
Aleksandr Chugunov | 29 10月, 2015
それは何のために必要なのでしょうか?
MetaQuotes ソフトウェア社は MetaTrader クライアント端末の新しい第5版においてカスタムインディケータとの連携コンセプトを改定しました。現在それらはずっと速く実行されます。ユニークな入力パラメータを持つそれぞれのインディケータの唯一の例があります。よってあるシンボルのチャート10件にそれのコピーを使ったとしても計算は一度ですむのです。
あるアルゴリズムの処理には変更はありません。サーバーや重要な履歴同期 との連携を失うと、値 prev_calculated(または MetaTrader 4でのIndicatorCounted())はゼロになります。こうなると履歴全体のインディケータの再計算が必要になります。(あらゆる状況においてインディケータ値が正確に計算されるのを保証するため開発者が意図的にこうしています。)インディケータの計算スピードに影響を与える事柄は複数あります。
- 大きな期間: rates_total
- 複雑さ。リソースを消費する計算
- 複数のシンボルおよび期間の使用
- 脆弱なPC
他にもあてはまる状況もあるでしょう。履歴全体についてインディケータを再計算する問題はより現実的な問題です。また、情報変換チャネルが劣悪だと問題はさらに悪くなります。
もちろん、追加の入力パラメータによってインディケータ計算の深さに制限をもたせることも可能ですが、インディケータiCustom 使用時には微妙なものがあります。あらゆるチャートやカスタムインディケータによって使用されるバーの最大数はターミナル全体に対してグローバルスコープ上に設定されます。メモリはカスタムインディケータの各バッファに割り当てられ、TERMINAL_MAXBARSのみがそれに制限を加えます。
ただし、重要な追加事項があります。インディケータのアルゴリズム内で計算したバーの最大数に制限を加えると(たとえば入力パラメータを使用したりコードに直接)、メモリは新規バーが作成される際動的に割り当てられます。(指定の制限 TERMINAL_MAXBARS に対して徐々に増加(またはもう少し増加します。このアルゴリズムは開発者に完全に依存し、次回構築時に開発者がそれを変更することは可能です。))
全履歴に対するインディケータ再計算を避ける方法
いまのところ、この問題解決には以下の方法を考えています。- MetaQuotes にプラットフォームレベルでこの問題を改定するよう依頼する。
- prev_calculatedの類似体を実装するために別クラスを作成します。
prev_calculatedを計算するアルゴリズムをインディケータそのものにビルトできるとして別のバリアントがありました。が MetaTrader 5は MetaTrader 4と異なり、 prev_calculated がゼロになる際、インディケータバッファをすべて「消去」してしまうようです。(すなわち全インディケータ配列を強制的にゼロにします。これはプラットフォームレベルで取り入れられているのでコントロールは不可能です。)
では、各バリアントを個別に分析していきます。
- 最初のバリアントは開発者のみに依存します。たぶん彼らは本稿が発行された後、それについて考えるでしょう。そしてフル装備のメカニズム実装はカスタムインディケータのブロック計算パフォーマンスに多大な影響を与え(ただしこのメカニズムは選択肢の一つとして実装することが可能です。)、それらはすべてを現状維持とします。
- 第二のバリアントprev_calculatedの類似体実装をする特殊クラスの作成それは、カスタムインディケータ内(値prev_calculatedを取得するためだけに)および必要なカスタムインディケータを計算するために個別に開発されるクラスと共に Expert Advisors(またはスクリプト)で使用するデータプロバイダ内の両方で使用が可能です。
問題解決の第二バリアントのメリット、デメリット
メリット- 配列エレメントへのリングアクセスの作成と共に動的配列 についてのメモリ割り当てを通して要求されるメモリのボリュームが固定
- 要求に応じた計算(セマフォ、フラグ、イベントなどを使わず)をするための個別クラス を使う際の同期とインディケータ計算
- インディケータ計算を個別に呼ぶ際、再計算結果は拡張フォームで返される(たとえば、変更はなく、最終レイのみ変更され、新規レイは追加される、など).
- インディケータ値計算に使用される価格履歴自体のコピー格納が必要
- データ比較の理論処理を用いたターミナル履歴を伴う履歴の同期を手動で行う必要性
prev_calculatedの類似体実装のためのCCustPrevCalculated クラス作成
クラスの実装自体はなんら面白みはありません。アルゴリズムは履歴を双方向へ広げることと、左側を「切り捨てる」可能性を考えます。また、このアルゴリズムは計算済みデータの内側に履歴を挿入します。(MetaTrader 4では実際にそうなっています。MetaTrader 5 ではまだ見たことはありません。)クラスのソースコードはファイル CustPrevCalculated.mqhにあります。
重要な点について述べます。
配列エレメントへのリングアクセス作成
このクラスを作成するには、コンベンショナル集約ではない方法を使います。すなわち、配列にメモリを一度に割り当てるのと配列コピーの過剰な手順を避けるためのリングアクセスです。5つのエレメントを例にとりそれを考察します。
まず、列挙が0から始まる配列について作業します。さて、配列サイズを保ったまま(新規バー追加)次の値を追加するのに必要なものは何でしょうか?方法は2つあります。
- セル1~4にメモリセル2~5をそれぞれコピーします。これで空のセル5ができます。
- 格納されている情報を変えずに配列インデックスを変更します。(ラップアラウンドのアドレッシング)
二番目のバリアント実装のために、変数が一つ必要です。それを DataStartIndと名づけます。この変数は配列のゼロインデックスの位置を格納します。今後の計算がしやすいように、その列挙は配列の通常インデックスに対応させます。(すなわちそれはゼロから始まります。)BarsLimit 変数には、配列のエレメント数を格納します。よって、仮想インデックス 'I' に対する配列エレメントの実アドレスは以下の簡単な式を使って計算されます。
- (DataStartInd+I) % BarsLimit :通常列挙用
- (DataStartInd+DataBarsCount-1-I) % BarsLimit :時系列内のような位置を決めるため
履歴同期のアルゴリズム
私自身は、クライアント端末の履歴を伴う履歴コピー(ローカル履歴)の同期アルゴリズムの動作モデルを3種類選択し実装しました。- CPCHSM_NotSynch :ローカル履歴の同期はすでに形成されたバー(ご自身の責任において)に対しては行われません。実際、このモードはインディケータに対して自由に使用することが可能です。ここで価格値のささいな変動は計算精度(MA、ADXなど)にたいした影響を与えることはありません。 ZigZagにとってこのモードは重要です。たとえば、ここでは一つのピークが別のピークを超えることは重大なのです。
- CPCHSM_Normal :ローカル履歴は以下に記載するアルゴリズムによる新規バーごとに同期されます。
- CPCHSM_Paranoid :ローカル履歴は以下に述べる同期データの関数を呼ぶたびに同期されます。
同期のメカニズム自体はプログラマーによる別のパラメータセットに基づいて行われます。それはHSMinute(HistorySynchSecondとして格納されます。)です。「ディーラーセンター」は履歴の最終 HSMinute 分のみ修正すると考えられます。その期間の同期中に相違が見られなければ、履歴は同一とみなされ、比較は停止します。違いが見つかれば、全履歴がチェックされ修正されます。
その上、このアルゴリズムは初期化の際に指定されたストラクチャ MqlRatesからの価格、スプレッド、ボリュームのみ確認します。たとえば、ZigZagを描画するのに必要なのは高値と安値のみです。
クラスCCustPrevCalculatedの実用
CCustPrevCalculated クラスを初期化するには、問題がない場合に 'true' を返す関数InitData()を呼ぶ必要があります。CCustPrevCalculated CustPrevCalculated; CustPrevCalculated.InitData(_Symbol, _Period, 150, CPCHSM_Normal, CPCH_high|CPCH_low, 15);履歴を同期するためには関数 PrepareData()を呼ぶ必要があります。
CPCPrepareDataResultCode resData; resData = CustPrevCalculated.PrepareData();
PrepareData() 関数によって返されるバリアント値
enum CPCPrepareDataResultCode { CPCPDRC_NoData, // Returned when there is no data for calculation (not prepared by the server) CPCPDRC_FullInitialization, // Full initialization of the array has been performed CPCPDRC_Synch, // Synchronization with adding new bars has been performed CPCPDRC_SynchOnlyLastBar, // Synchronization of only the last bar has been performed (possible cutting of the history) CPCPDRC_NoRecountNotRequired // Recalculation has not been performed, since the data was not changed };
データアクセスのためのClass CCustPrevCalculated クラス関数
注意:計算速度を上げるために、配列オーバーフローチェックは省きます。精度を上げるために、インデックスが正しくなければ誤った値は返されます。
名前 | 用途 |
---|---|
uint GetDataBarsCount() | 変数バー数を返します。 |
uint GetDataBarsCalculated() | 変更のないバー数を返します。 |
uint GetDataStartInd() | ラップアラウンドアクセス(カスタムインディケータへの)用インデックスを返します。 |
bool GetDataBarsCuttingLeft() | 左側からのバー切り捨て結果を返します。 |
double GetDataOpen(int shift, bool AsSeries) | 移動バーに対する「オープン」を返します。 |
double GetDataHigh(int shift, bool AsSeries) | 移動バーに対する「高値」を返します。 |
double GetDataLow(int shift, bool AsSeries) | 移動バーに対する「安値」を返します。 |
double GetDataClose(int shift, bool AsSeries) | 移動バーに対する「クローズ」を返します。 |
datetime GetDataTime(int shift, bool AsSeries) | 移動バーに対する「時刻」を返します。 |
long GetDataTick_volume(int shift, bool AsSeries) | 移動バーに対する「ティックボリューム」を返します。 |
long GetDataReal_volume(int shift, bool AsSeries) | 移動バーに対する「実ボリューム」を返します。 |
int GetDataSpread(int shift, bool AsSeries) | 移動バーに対する「スプレッド」を返します。 |
クラス CCustPrevCalculatedの将来の最適化例
- 複数配列(特定の目的のために決められた)に切り替えに関してMqlRates から拒否(メモリ要求を減らしますが、コピーする配列を呼ぶ回数の負荷を増やします。)
- 特定タイプの配列インデックス を確実に使用するのための各アクセス関数二分割(パラメータ«bool AsSeries»からのリフューズ)メリットは理論条件 «if (AsSeries)»のみです。
CCustPrevCalculated クラスのデータに基づくZigZagカスタムインディケータ計算のためのCCustZigZagPPC作成
このアルゴリズムはカスタムインディケータProfessional ZigZag を基にしています。このクラスのソースコードはファイル ZigZags.mqh にあります。またライブラリOutsideBar.mqh は外部バーと連携して使われます。
われわれのインディケータのバー記述に個別のストラクチャを作成します。
struct ZZBar { double UP, DN; // Buffers of the ZigZag indicator OrderFormationBarHighLow OB; // Buffer for caching of an external bar };
また、クラス計算の戻り結果を判断します。
enum CPCZZResultCode { CPCZZRC_NotInitialized, // Class is no initialized CPCZZRC_NoData, // Faield to receive data (including the external bar) CPCZZRC_NotChanged, // No changes of ZZ rays CPCZZRC_Changed // ZZ rays changed };
CCustZigZagPPC クラスを初期化するためには Init() 関数を一度呼ぶ必要があります。問題なければそれは 'true' を返します。
CCustZigZagPPC ZZ1; ZZ1.Init(CustPrevCalculated, _Symbol, _Period, 150, CPCHSM_Normal, CPCH_high|CPCH_low, 15, 0, true, 12, 10);
インディケータ計算のためには CCustPrevCalculatedクラスの前回の計算データを基にした更新データを開始する必要があります。
CPCPrepareDataResultCode resZZ1; resZZ1 = ZZ1.PrepareData(resData);
そしてプロシージャ Calculate()を呼びます。
if ( (resZZ1 != CPCPDRC_NoData) && (resZZ1 != CPCPDRC_NoRecountNotRequired) )
ZZ1.Calculate();
クラスCCustPrevCalculated を複数の CCustZigZagPPC クラスと共に使用する用例全体はファイル ScriptSample_CustZigZagPPC.mq5にあります。
クラスCCustZigZagPPCのデータアクセス関数
名前 | 用途 |
---|---|
uint GetBarsCount() | 変数バー数を返します。 |
uint GetBarsCalculated() | 計算されたバー数を返します。 |
double GetUP(uint shift, bool AsSeries) | バーに対するZigZagピーク値を返します。 |
double GetDN(uint shift, bool AsSeries) | バーに対するZigZag最小値を返します。 |
OrderFormationBarHighLow GetOB(uint shift, bool AsSeries) | バーに対する「外部」値を返します。 |
ビジュアルチェックとプログラムチェック
ビジュアルチェックのために、元のインディケータをチャートにアタッチし、その上に同じ入力パラメータ(両インディケータを確認するために別の色を選択する必要があります。)を伴う特別に書かれた検証用インディケータ Indicator_CustZigZag.mq5 をアタッチします。
赤:元のインディケータ、ブルー:最終100バーについて計算されるここでのインディケータ
同じようにExpert Advisorも比較します。なにか違いがありますか?クラスiCustom("AlexSTAL_ZigZagProf") および CCustZigZagPPC から得られる結果は検証用Expert Advisor のExpert_CustZigZagPPC_test.mq5内で毎ティックごとに比較されます。計算に関する情報はジャーナルに表示されます。(最初のバーでは計算は行われません。なぜならアルゴリズム用の履歴が存在しないからです。)
(EURUSD,M1) 1.35797; 1.35644; 1.35844; 1.35761; 1.35901; 1.35760; 1.35959; 1.35791; 1.36038; 1.35806; 1.36042; 1.35976; 1.36116; 1.35971; // it is normal
(EURUSD,M1) Tick processed: 1.35797; 1.35644; 1.35844; 1.35761; 1.35901; 1.35760; 1.35959; 1.35791; 1.36038; 1.35806; 1.36042; 1.35976; 1.36116;
(EURUSD,M1) Divergence on the bar: 7
このExpert Advisor についてもっと詳しく考察します。動作の グローバル変数 を決めます。
#include <ZigZags.mqh> CCustPrevCalculated CustPrevCalculated; CCustZigZagPPC ZZ1; int HandleZZ;
変数を初期化します。
int OnInit() { // Creating new class and initializing it CustPrevCalculated.InitData(_Symbol, _Period, 150, CPCHSM_Normal, CPCH_high|CPCH_low, 15); // Initializing the class ZZ ZZ1.Init(GetPointer(CustPrevCalculated), _Symbol, _Period, 150, CPCHSM_Normal, CPCH_high|CPCH_low, 15, 0, true, 12, 10); // Receiving handle for the custom indicator HandleZZ = iCustom(_Symbol, _Period, "AlexSTAL_ZigZagProf", 12, 10, 0 , true); Print("ZZ_handle = ", HandleZZ, " error = ", GetLastError()); return(0); }ティックをExpert Advisorで処理します。
void OnTick() { // Calculation of data CPCPrepareDataResultCode resData, resZZ1; resData = CustPrevCalculated.PrepareData(); // Start recalculation for each indicator! PrepareData obligatory! resZZ1 = ZZ1.PrepareData(resData); // Расчет данных ZZ1 if ( !((resZZ1 != CPCPDRC_NoData) && (resZZ1 != CPCPDRC_NoRecountNotRequired)) ) return; // Получим результаты расчета ZZ1.Calculate();
CCustZigZagPPCによって計算されたバーZZ1.GetBarsCalculated()を取得しました。iCustomデータ("AlexSTAL_ZigZagProf") とCCustZigZagPPCクラスをデータ比較するコードを追加します。
int tmpBars = (int)ZZ1.GetBarsCalculated(); double zzUP[], zzDN[]; CopyBuffer(HandleZZ, 0, 0, tmpBars, zzUP); CopyBuffer(HandleZZ, 1, 0, tmpBars, zzDN); // Perform comparison string tmpSt1 = "", tmpSt2 = ""; for (int i = (tmpBars-1); i >= 0; i--) { double tmpUP = ZZ1.GetUP(i, false); double tmpDN = ZZ1.GetDN(i, false); if (tmpUP != zzUP[i]) Print("Divergence on the bar: ", i); if (tmpDN != zzDN[i]) Print("Divergence on the bar: ", i); if (tmpUP != EMPTY_VALUE) tmpSt1 = tmpSt1 + DoubleToString(tmpUP, _Digits) + "; "; if (tmpDN != EMPTY_VALUE) tmpSt1 = tmpSt1 + DoubleToString(tmpDN, _Digits) + "; "; if (zzUP[i] != EMPTY_VALUE) tmpSt2 = tmpSt2 + DoubleToString(zzUP[i], _Digits) + "; "; if (zzDN[i] != EMPTY_VALUE) tmpSt2 = tmpSt2 + DoubleToString(zzDN[i], _Digits) + "; "; } Print("Tick processed: ", tmpSt1); Print(" ", tmpSt2); }
ここに Expert Advisor またはスクリプトでのCCustZigZagPPCクラスの簡単な活用法があります。CopyBuffer()の代わりの直接アクセス関数 GetUP()、 GetDN()、GetOB()
インディケータの個別クラスへの移動(iATRの例により)
通常プラン
1. 準備段階
- MyIndicator.mqh を別の名前のファイルとして(この例ではATRsample.mqhと名づけています。)コピーし、コピーしたファイルをMetaEditor 5で開きます。
- 独自のインディケータ名でテキスト "MyInd" を置き換えます。(ここでは"ATR" としています。)
2. クラスに対する初期(元の)インディケータから取られる外部パラメータを選択し、それらを初期化することを宣言します。
この例ではATR インディケータがひとつの外部パラメータを持ちます。input int InpAtrPeriod=14; // ATR period
- このパラメータをわれわれのクラスとこのクラスの初期化関数に追加します。
class CCustATR { protected: ... uchar iAtrPeriod; ... public: ... bool Init(CCustPrevCalculated *CPC, string Instr, ENUM_TIMEFRAMES TF, int Limit, CPCHistorySynchMode HSM, uchar HS, uint HSMinute, uchar AtrPeriod);
- Init 関数の本文ヘッダを変更し、インプット値で変数パラメータを初期化します。
bool CCustATR::Init(CCustPrevCalculated *CPC, string Instr, ENUM_TIMEFRAMES TF, int Limit, CPCHistorySynchMode HSM, uchar HS, uint HSMinute, uchar AtrPeriod) { ... BarsLimit = Limit; iAtrPeriod = AtrPeriod; ...
3. 初期インディケータで要求するバッファ数を決め、クラスで宣言します。またINDICATOR_DATA バッファを返す関数を宣言します。
- ストラクチャを変更します。
struct ATRBar { double Val; // Indicator buffers };
独自のストラクチャへ
struct ATRBar { double ATR; double TR; };
- ゼロ値を決めます。
CPCPrepareDataResultCode CCustATR::PrepareData(CPCPrepareDataResultCode resData) { ... for (uint i = (DataBarsCalculated == 0)?0:(DataBarsCalculated+1); i < DataBarsCount; i++) { Buf[PInd(i, false)].ATR = EMPTY_VALUE; Buf[PInd(i, false)].TR = EMPTY_VALUE; } ...
- INDICATOR_DATA バッファの戻り値関数を変更し、追加します。
以下から
class CCustATR { ... double GetVal(uint shift, bool AsSeries); // returns the Val value of the buffer for a bar ...
に変更します。(バッファが一つしかなければ変更手順はとばしてかまいません。)
class CCustATR { ... double GetATR(uint shift, bool AsSeries); // Возвращает значение буфера ATR для бара ...
そして対応する関数のコードを変更します。
double CCustATR::GetATR(uint shift, bool AsSeries) { if ( shift > (DataBarsCount-1) ) return(EMPTY_VALUE); return(Buf[PInd(shift, AsSeries)].ATR); }注意:バッファ値を返す複数の関数の代わりに追加のパラメータであるバッファ数またはバッファ名を持つ関数を一つだけ使用することも可能です。
4. クラスの対応する関数に対して初期インディケータ関数 OnCalculate() こロジックをコピーします。
- 第一チェック
CPCATRResultCode CCustATR::Calculate() { ... // Check if there are enough bars for the calculation if (DataBarsCount <= iAtrPeriod) return(CPCATRRC_NoData); ...
- 計算:最初のティックで、そして次のティックで計算するバー数
if ( DataBarsCalculated != 0 ) BarsForRecalculation = DataBarsCount - ATRDataBarsCalculated - 1; else { Buf[PInd(0, false)].TR = 0.0; Buf[PInd(0, false)].ATR = 0.0; //--- filling out the array of True Range values for each period for (uint i = 1; i < DataBarsCount; i++) Buf[PInd(i, false)].TR = MathMax(CustPrevCalculated.GetDataHigh(i, false), CustPrevCalculated.GetDataClose(i-1, false)) - MathMin(CustPrevCalculated.GetDataLow(i, false), CustPrevCalculated.GetDataClose(i-1, false)); //--- first AtrPeriod values of the indicator are not calculated double firstValue = 0.0; for (uint i = 1; i <= iAtrPeriod; i++) { Buf[PInd(i, false)].ATR = 0; firstValue += Buf[PInd(i, false)].TR; } //--- calculating the first value of the indicator firstValue /= iAtrPeriod; Buf[PInd(iAtrPeriod, false)].ATR = firstValue; BarsForRecalculation = DataBarsCount - iAtrPeriod - 2; }
- 各ティックごとの計算
for (uint i = (DataBarsCount - BarsForRecalculation - 1); i < DataBarsCount; i++) { Buf[PInd(i, false)].TR = MathMax(CustPrevCalculated.GetDataHigh(i, false), CustPrevCalculated.GetDataClose(i-1, false)) - MathMin(CustPrevCalculated.GetDataLow(i, false), CustPrevCalculated.GetDataClose(i-1, false)); Buf[PInd(i, false)].ATR = Buf[PInd(i-1, false)].ATR + (Buf[PInd(i, false)].TR-Buf[PInd(i-iAtrPeriod, false)].TR) / iAtrPeriod; ...
以上です。われわれのクラスが作成されました。目で確認するためにテストインディケータ(今回の例ではIndicator_ATRsample.mq5)を作成することも可能です。
本稿を修正している間に思いついたことがあります。それはただ一つのカスタムインディケータをCCustPrevCalculated クラスと共に使用すると、カスタムインディケータ(この例ではCCustZigZagPPC および CCustATR)内でのこのクラスの作成、初期化、同期を統合できる、というものです。この目的のためにカスタムインディケータの初期化関数を呼びだすとき、オブジェクトに対するゼロポインターの使用が必要です。
ATR.Init(NULL, _Symbol, _Period, iBars, CPCHSM_Normal, 0, 30, InpAtrPeriod);
一般的ストラクチャでは
#include <CustPrevCalculated.mqh> #include <ATRsample.mqh> CCustPrevCalculated CustPrevCalculated; CCustATR ATR; int OnInit() { CustPrevCalculated.InitData(_Symbol, _Period, iBars, CPCHSM_Normal, 0, 30); ATR.Init(GetPointer(CustPrevCalculated), _Symbol, _Period, iBars, CPCHSM_Normal, 0, 30, InpAtrPeriod); } int OnCalculate(...) { CPCPrepareDataResultCode resData = CustPrevCalculated.PrepareData(); CPCPrepareDataResultCode resATR = ATR.PrepareData(resData); if ( (resATR != CPCPDRC_NoData) && (resATR != CPCPDRC_NoRecountNotRequired) ) ATR.Calculate(); }
が以下を簡素化するでしょう。
#include <ATRsample.mqh> CCustATR ATR; int OnInit() { ATR.Init(NULL, _Symbol, _Period, iBars, CPCHSM_Normal, 0, 30, InpAtrPeriod); } int OnCalculate(...) { ATR.Calculate(); }実用例はファイルIndicator_ATRsample2.mq5にあります。
ストラテジーテスタパフォーマンスに記述されたテクノロジーの影響
確認のため、3つのバリアントのうちの一つに従いティックごとにゼロバーインディケータ値を受け取る検証用Expert Advisor (TestSpeed_IndPrevCalculated.mq5) を作成しました。
enum eTestVariant { BuiltIn, // Built-in indicator iATR Custom, // Custom indicator iCustom("ATR") IndClass // Calculation in the class };
このExpert Advisor は以下の最適化パラメータで1エージェントにつき10回実行しました。
- シンボル: EURUSD
- 期間:履歴全体 [1993..2001]
- トエードモード:ティックごと
- 外部パラメータ: FalseParameter [0..9]
インディケータの3つのバリアントのうちの一つを使ったときの最適化時間を計測しました。チェック結果は線形ヒストグラムをして表示します。
以下は最適化時間計測に使用されたExpert Advisor のソースコードです。
//+------------------------------------------------------------------+ //| TestSpeed_IndPrevCalculated.mq5 | //| Copyright 2011, AlexSTAL | //| http://www.alexstal.ru | //+------------------------------------------------------------------+ #property copyright "Copyright 2011, AlexSTAL" #property link "http://www.alexstal.ru" #property version "1.00" //--- connect the include file with the CustATR class #include <ATRsample.mqh> //--- set the selection of the parameter as an enumeration enum eTestVariant { BuiltIn, // Built-in indicator iATR Custom, // Custom indicator iCustom("ATR") IndClass // Calculation withing the class }; //--- input variables input eTestVariant TestVariant; input int FalseParameter = 0; //--- period of the ATR indicator const uchar InpAtrPeriod = 14; //--- handle of the built-in or custom indicator int Handle; //--- indicator based on the class CCustATR *ATR; //+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- switch(TestVariant) { case BuiltIn: Handle = iATR(_Symbol, _Period, InpAtrPeriod); break; case Custom: Handle = iCustom(_Symbol, _Period, "Examples\ATR", InpAtrPeriod); break; case IndClass: ATR = new CCustATR; ATR.Init(NULL, _Symbol, _Period, 100, CPCHSM_Normal, 0, 30, InpAtrPeriod); break; }; //--- return(0); } //+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { switch(TestVariant) { case IndClass: delete ATR; break; }; } //+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { double tmpValue[1]; switch(TestVariant) { case BuiltIn: CopyBuffer(Handle, 0, 0, 1, tmpValue); break; case Custom: CopyBuffer(Handle, 0, 0, 1, tmpValue); break; case IndClass: ATR.Calculate(); tmpValue[0] = ATR.GetATR(0, true); break; }; } //+------------------------------------------------------------------+
見てのとおり、このテクノロジーは通常のカスタムインディケータに比べストラテジーテスタのパフォーマンスをそれほど落とすものではありません。
テクノロジーの実用に関する注意
- ストラテジーテスタでExpert Advisor を検証する場合、prev_calculated 値はカスタムインディケータ内でゼロにすることはできません。それがこのモードで履歴の同期が無効な理由です。
- インディケータの計算は、クラスの最初の初期化で厳密に設定された最後の 'n'バーでのみ行われます。
- 計算は初期化されたクラスの特定シンボルと期間に対して正確にバインディングします。他のシンボルや期間を対象に計算を行うにはクラスの新規インスタンス作成が必要です。
おわりに
それぞれの状況でプログラマーはタスクに実装する異なるバリアントの利点と欠点に配慮する必要があります。ここで提案した実装にはそれ自体のメリット、デメリットがあります。
追記失敗をしない者は何もしていないのと同じだ!誤りをみつけたら、ご一報ください。