English Русский 中文 Español Deutsch Português 한국어 Français Italiano Türkçe
予備インディケータによるメモリ消費削減

予備インディケータによるメモリ消費削減

MetaTrader 5インディケータ | 28 10月 2015, 11:04
1 323 0
ds2
ds2

1. 問題

おそらくみなさんはすでに Expert Advisors や処理に他の予備インディケータを使用するインディケータを使ったり作成されたことがおありかと思います。

たとえば、有名なインディケー MACD はEMA(指数移動平均)のコピーを2件使用し、その値の差を計算します。


そのような複合インディケータは複数のシンプルなインディケータと実は変わりはありません。たとえば、前に述べた MACD は単一の EMAに比べるとメモリおよび処理時間を3倍費やします。それはメインのインディケータ バッファとすべての予備インディケータバッファのバッファにメモリを割り当てる必要があるためです。

また MACD 以外にも、3つ以上の予備インディケータを使用するより複雑なインディケータもあります。

それ以上にこういったメモリ消費は以下の場合大幅に増えます。

  • インディケータが複数時間枠を取る(たとえば、複数時間枠についてウェーブの同時並行性を追跡する)ため、各時間枠に対して予備インディケータを個別にコピーするのです。
  • インディケータが複数通貨対応である。
  • トレーダーは複数の通貨ペアでトレーディングを行うためにインディケータを利用します(同時に20以上の通貨ペアでトレードする人達を知っています)。

こういった条件が組み合わさるとコンピュータ上で単純なメモリ不足に陥ります(そのようなインディケータを使用することでクライアント端末はギガバイトのメモリを必要とする、という実ケースを知っています)。ここに MetaTrader 5 クライアント端末におけるメモリ不足例があります。


そのような状況では、端末はチャート上にインディケータをセットすることができず、正確に計算することもできません(インディケータのコードがメモリアローケーションのエラーを処理しなければ)。またシャットダウンすらできません。

さいわいなことに、端末は仮想メモリをより活用してメモリ不足を補うことができます。すなわち、ハードディスクに情報の一部を格納するのです。プログラムはすべて処理されますが、ひじょうにゆっくりと、です。。。


2. 検証複合インディケータ

本稿の範囲内で調査を進めるために複合インディケータを作成します。それは MACDよりさらに複雑なものです。

作成するのはトレンドの起動を追跡するインディケータとします。それは 5 つの時間枠から発せられるシグナルを合計します。たとえばH4、H1、M15、M5、 M1といったシグナルです。それにより大小現れるトレンドのレゾナンスを修正することができます。それは推定値の信頼性を向上します。各時間枠におけるシグナルソースとして、MetaTrader 5の配布にインクルードされているインディケータIchimoku および Price_Channel を利用していきます。

  • 一目均衡表の転換線(赤)が基準線(ブルー)の上にくると、トレンドは上昇、下に来るとトレンドは下降です。


  • 価格が Price_Channel の真ん中より上ならトレンドは上昇、下にならトレンドは下降です。


トータルでわれわれのインディケータは10個の予備インディケータを使用しています。5とおりの時間枠それぞれが2つずつインディケータを使用しているのです。インディケータ Trender を呼びます。

以下にソースコード全体を記載します(本稿に添付もあります)。

#property indicator_separate_window
#property indicator_buffers 1
#property indicator_plots   1
#property indicator_minimum -1
#property indicator_maximum  1

#property indicator_type1   DRAW_HISTOGRAM
#property indicator_color1  DarkTurquoise

// The only buffer of the indicator
double ExtBuffer[];

// Timeframes of auxiliary indicators
ENUM_TIMEFRAMES TF[5] = {PERIOD_H4, PERIOD_H1, PERIOD_M15, PERIOD_M5, PERIOD_M1};

// Handles of auxiliary indicators for all timeframes
int h_Ichimoku[5], h_Channel[5];

//+------------------------------------------------------------------+
void OnInit()
  {
   SetIndexBuffer(0, ExtBuffer);
   ArraySetAsSeries(ExtBuffer, true);
   
   // Create auxiliary indicators
   for (int itf=0; itf<5; itf++)
     {
      h_Ichimoku[itf] = iCustom(Symbol(), TF[itf], 
                                "TestSlaveIndicators\\Ichimoku",
                                9, 26, 52
                               );
      h_Channel [itf] = iCustom(Symbol(), TF[itf],
                                "TestSlaveIndicators\\Price_Channel",
                                22
                               );
     }
  }
//+------------------------------------------------------------------+
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[])
  {
   ArraySetAsSeries(time, true);
  
   int limit = prev_calculated ? rates_total - prev_calculated : rates_total -1;

   for (int bar = limit; bar >= 0; bar--)
     {
      // Time of the current bar
      datetime Time  = time [bar];
      
      //--- Gather signals from all timeframes
      double Signal = 0; // total signal
      double bufPrice[1], bufTenkan[1], bufKijun [1], bufMid[1], bufSignal[1];
      for (int itf=0; itf<5; itf++)
        {
         //=== Bar price
         CopyClose(Symbol(), TF[itf], Time, 1, bufPrice);
         double Price = bufPrice[0];

         //=== The Ichimoku indicator         
         CopyBuffer(h_Ichimoku[itf], 0, Time, 1, bufTenkan);
         double Tenkan = bufTenkan[0];
         CopyBuffer(h_Ichimoku[itf], 1, Time, 1, bufKijun );    
         double Kijun  = bufKijun [0];
           
         if (Tenkan > Kijun) Signal++;
         if (Tenkan < Kijun) Signal--;
          
         //=== The channel indicator
         CopyBuffer(h_Channel [itf], 2, Time, 1, bufMid);
         double Mid = bufMid[0];

         if (Price > Mid) Signal++;
         if (Price < Mid) Signal--;
        }
        
      ExtBuffer[bar] = Signal/10;
     }

   return(rates_total);
  }
//+------------------------------------------------------------------+

このインディケータはチャート上、以下からのシグナルを収集するうち一番小さな時間枠で使用する必要があります。すべての小さなトレンドを見るにはそれが唯一の方法です。ここの場合は時間枠 M1 です。インディケータは以下のような表記になります。


ここからもっとも重要な部分に入っていきます。これまで述べたインディケータいにょって消費されるメモリ量を計算するのです。

Ichimoku インディケータのソースコードを見ます(コードの全容は添付にあります)。

#property indicator_buffers 5

そしてPrice_Channel インディケータです(コードの全容は添付にあります)。

#property indicator_buffers 3

それら行に8個のバッファが作成されているのが判ります。それを5とおりの時間枠で乗じます。Trender インディケータ自体のバッファを1つ追加します。合計41 バッファとなりました!!そのような大きな値がシンプルに見える(チャート上)インディケータの背後にあるものなのです。

クライアント端末のデフォルトプロパティとして、1バッファにはダブル タイプの値が約10万も含まれており、それはそれぞれ8バイトを消費します。よって41バッファはおよそ 31 MBのメモリを費やすこととなります。 またこれらはそれら自体の値です。それ以外にどのようなサービス情報がバッファに格納されているのかはわかりません。

「31 MB なんてたいしたことないさ」とおっしゃるかもしれません。しかし、トレーダーが多くの通貨ペアを使用すればそのボリュームは問題になります。インディケータに加え、チャート自体も多くのメモリを消費します。インディケータとは異なり、各バーは一度に複数の値を持ちます。OHLC、時刻、ボリュームです。コンピュータにそれをフィットする方法は?


3. 問題の解決法

もちろんメモリをもっとコンピュータにインストールすることはできます。ただ、技術的、経済的その他の理由によりこのバリアントが適切でない場合、またはインストールできるメモリ量をすでに使い果たしそれでも十分でない場合、それら消費量の多いインディケータを調べ、必要メモリ量を減らす必要があります。

そのためには、、、学校で習った幾何を思い出してください。われわれの複合的インチケータバッファがすべて長方形だとします。


この長方形の面積が消費されるメモリです。そのヨコかタテを減らすことで面積を減らすことができます。

この場合、ヨコはインディケータが利用するバーの数です。タテはインディケータバッファの数です。


4. バー数を減らす

4.1. シンプルな解決法

MetaTraderの設定を調整するのにプログラマーである必要はありません。



「チャートの最大バー数」値を減らすことでこれらウィンドウ内のインディケータバッファサイズが減ります。簡単で効果的、そしてだれにも利用できる方法です(トレーディング時トレーダーが深い価格履歴を必要としなければ)。

4.2. 他に解決法はあるのでしょうか?

MQL5 プログラマーはインディケータバッファはサイズをまえもって設定しない動的配列としてインディケータ内で宣言されることを知っています。例として以下で Ichimoku のバッファを5個以下に示します。

double    ExtTenkanBuffer[];
double    ExtKijunBuffer[];
double    ExtSpanABuffer[];
double    ExtSpanBBuffer[];
double    ExtChinkouBuffer[];

配列サイズは指定されていません。というのも MetaTrader 5 クライアント端末自体が利用可能な履歴として全体に設定するからです。

OnCalculate関数でも同様です。

int OnCalculate (const int rates_total,      // size of the array price[]
               const int prev_calculated,  // number of bars processed at the previous call
               const int begin,            // the start of reliable data
               const double& price[]       // array for the calculation
   );

この関数では価格バッファがインディケータに渡されます。それへのメモリはすでに端末により割り当てられており、プログラマーはそのサイズを変更することはできません。

また、MQL5によりあるインディケータバッファを別のバッファに対する価格バッファとして使用することが可能となります(「別インディケータに基づくインディケータ」を導きます)。しかしここでもプログラマーはサイズについては制限を設定することはできません。インディケータハンドルを渡すだけです。

よって MQL5 においてインディケータバッファのサイズに制約を加えることはできないのです。


5. バッファ数を減らす

ここではプログラマーに対する選択肢はひじょうに多岐にわたっています。複合インディケータのバッファ数を減らすシンプルな理論的方法を複数すでに見つけました。ただし、それらすべてが予備インディケータのバッファ数を減らすことを意味しています。それと言うのもメインのインディケータではすべてのバッファが必要だからです。

その方法を詳しくみていき、それらが実際役立つか、またどんなメリット/デメリットがあるか確認します。

5.1. "Need" 手法

予備インディケータにバッファが多く含まれるなら、そのすべてがメインのインディケータにとって必要ということではなさそうです。よって使用しないインディケータは費やすメモリを解放するため無効にすることができます。そのために、そのような予備インディケータのソースコードにいくらか変更を加える必要があります。

われわれの予備インディケータの一つでやってみます。 Price_Channel を例にとりましょう。このインディケータはバッファを3個持ち、Trender はそのうちの一つだけを読みます。そこで不必要なものを消去します。

インディケータ Price_Channel (初期インディケータ)と Price_Channel-Need(完全に作り直された)のコード全貌は本稿に添付があります。以降、それに加えられた変更のみお話ししていきます。

まずバッファカウンターを3から1に減らします

//#property indicator_buffers 3
  #property indicator_buffers 1
//#property indicator_plots   2
  #property indicator_plots   1

それから不必要な2つのバッファ配列を消去します。

//--- indicator buffers
//double    ExtHighBuffer[];
//double    ExtLowBuffer[];
 double    ExtMiddBuffer[];

ここでこのインディケータをコンパイルしようとしたら、コンパイラはこれら配列が呼ばれるすべての行を表示することになります。


この手法により変更が必要なものを素早く配置できるようになります。インディケータコードがひじょうに長いときはかなり便利です。

われわれの場合、トータルで「未宣言の指示子」が4個あります。 それらを修正します。

予想したとおり、そのうち2つは OnInit にセットされています。しかし他のもの同様その行を必要な ExtMiddBufferで消去する必要があります。そのかわり、別のバッファインデックスを持つ似た指示子を追加します。インディケータはもはやインデックス2を伴うバッファを持たないため、0 のみ可能です。

//   SetIndexBuffer(0,ExtHighBuffer,INDICATOR_DATA);
//   SetIndexBuffer(1,ExtLowBuffer,INDICATOR_DATA);
//   SetIndexBuffer(2,ExtMiddBuffer,INDICATOR_DATA);
     SetIndexBuffer(0,ExtMiddBuffer,INDICATOR_DATA);

ビジュアルモードで "cut" インディケータを使用しようと思っているなら、表示設定をバッファインデックスと共に変更する必要があることに配慮します。われわれの場合は以下です。

//#property indicator_type1   DRAW_FILLING
  #property indicator_type1   DRAW_LINE

可視化が必要でないなら、表示設定変更はスキップしてもかまいません。それによってエラーが出ることはありません。

「未宣言指示子」リストの作業を続けます。最後の2つの変更(また予想できたことです)は OnCalculate 内で行います。これはインディケータバッファ配列が書かれる部分です。ExtMiddBuffer は消去された ExtHighBuffer および ExtLowBuffer を呼ぶため、代わりに中間媒介変数が代用されます。

   //--- the main loop of calculations
   for(i=limit;i<rates_total;i++)
     {
//      ExtHighBuffer[i]=Highest(High,InpChannelPeriod,i);
        double      high=Highest(High,InpChannelPeriod,i);
//      ExtLowBuffer[i]=Lowest(Low,InpChannelPeriod,i);
        double      low=Lowest(Low,InpChannelPeriod,i);
//      ExtMiddBuffer[i]=(ExtHighBuffer[i]+ExtLowBuffer[i])/2.0;;
        ExtMiddBuffer[i]=(   high         +   low         )/2.0;;
     }

ご覧のように、この「外科手術」にはどこにも難しいことはありません。要求されるものはすばやくセットされます。複数の「外科的切除」とバッファ2つの除外です。10バッファ(2バッファ×5時間枠)分が複合インディケータ Trender 内部で節約されます。

Price_Channel および Price_Channel-Need をそれぞれ開き、バッファが消えているのを確認することができます。


Trender インディケータで Price_Channel-Need を使用するには、予備インディケ"Price_Channel" の名前を Trender のコードで"Price_Channel-Need" と修正する必要があります。その上、必要なバッファのインディケータを 2 から 0に変更する必要があります。本稿に既製の Trender-Need を添付しています。


5.2. "Aggregate"手法

メインのインディ桁が予備インディケータの2つ以上のデータを読み、それについて統合的処理を行う(たとえば追加や比較)なら、この処理はメインインディケータで行う必要はありません。予備インディケータで行い、結果をメインインディケータに渡せばよいのです。これに複数のバッファは必要ありません。よってそれらをすべて一つのバッファと置き換えます。

ここでは、その手法は Ichimokuに対して適用します。Trender が(0 - 転換および 1 - 基準)からバッファを2つ使用するので、以下となります。

         CopyBuffer(h_Ichimoku[itf], 0, Time, 1, bufTenkan);
         double Tenkan = bufTenkan[0];
         CopyBuffer(h_Ichimoku[itf], 1, Time, 1, bufKijun );    
         double Kijun  = bufKijun [0];
           
         if (Tenkan > Kijun) Signal++;
         if (Tenkan < Kijun) Signal--;

Ichimoku のバッファ 0 と 1 を一つのシグナルバッファに統合するなら、上に記載の Trender のフラグメントは以下と置換されます。

         CopyBuffer(h_Ichimoku[itf], 0, Time, 1, bufSignal);
         
         Signal += bufSignal[0];

本稿にTrender-Aggregate のコード全体を添付しています。

ここでは Ichimoku に加えられるべき主要な変更を見ていきます。

またこのインディケータには使用しないバッファが含まれています。よって "Aggregate" 手法に加え、"Need" 手法も適用できます。そこで Ichimoku に残ったのはバッファ5のみです。これは必要なバッファを統合するものです。

//#property indicator_buffers 5
  #property indicator_buffers 1
//#property indicator_plots   4
  #property indicator_plots   1

この唯一のバッファに新しい名前をつけます。

//--- indicator buffers
//double    ExtTenkanBuffer[];
//double    ExtKijunBuffer[];
//double    ExtSpanABuffer[];
//double    ExtSpanBBuffer[];
//double    ExtChinkouBuffer[];
  double    ExtSignalBuffer[];

新しい名前には実用的意味が含まれています。それにより以前に使用されていたバッファの名前をすべてインディケータから削除することができるようになります。そして("Need"手法で述べられたコンパイルを利用することで)変更が必要な行をすばやく見つけることができるようになります。

チャート上でインディケータを表示しようと考えているなら、表示設定を変更することも忘れないでください。またわれわれの場合、統合バッファはそれによって消費される2つのバッファを比較する異なる値範囲を持ちます。価格デリバティブは表示しませんが、2つのバッファのうちどちらが大きいか表示します。チャートの下に別にその結果を表示するとより利便性が高まります。

//#property indicator_chart_window
  #property indicator_separate_window

そのため OnInit に以下の変更を加えます。

//--- indicator buffers mapping
//   SetIndexBuffer(0,ExtTenkanBuffer,INDICATOR_DATA);
//   SetIndexBuffer(1,ExtKijunBuffer,INDICATOR_DATA);
//   SetIndexBuffer(2,ExtSpanABuffer,INDICATOR_DATA);
//   SetIndexBuffer(3,ExtSpanBBuffer,INDICATOR_DATA);
//   SetIndexBuffer(4,ExtChinkouBuffer,INDICATOR_DATA);
     SetIndexBuffer(0,ExtSignalBuffer,INDICATOR_DATA);

そしてもっとも面白い部分は OnCalculateです。注意:不要なバッファは単純に削除され("Need" 手法を用いて)、必要な ExtTenkanBuffer および ExtKijunBuffer は仮の変数 Tenkan と Kijunに置き換わります。こういった変数は統合バッファは ExtSignalBuffer の計算サイクルの末尾で使用されます。

   for(int i=limit;i<rates_total;i++)
     {
//     ExtChinkouBuffer[i]=Close[i];
      //--- tenkan sen
      double high=Highest(High,InpTenkan,i);
      double low=Lowest(Low,InpTenkan,i);
//     ExtTenkanBuffer[i]=(high+low)/2.0;
       double  Tenkan    =(high+low)/2.0;
      //--- kijun sen
      high=Highest(High,InpKijun,i);
      low=Lowest(Low,InpKijun,i);
//     ExtKijunBuffer[i]=(high+low)/2.0;
       double  Kijun    =(high+low)/2.0;
      //--- senkou span a
//     ExtSpanABuffer[i]=(ExtTenkanBuffer[i]+ExtKijunBuffer[i])/2.0;
      //--- senkou span b
      high=Highest(High,InpSenkou,i);
      low=Lowest(Low,InpSenkou,i);
//     ExtSpanBBuffer[i]=(high+low)/2.0;

       //--- SIGNAL
       double Signal = 0;
       if (Tenkan > Kijun) Signal++;
       if (Tenkan < Kijun) Signal--;
       ExtSignalBuffer[i] = Signal;
     }

合計マイナス4バッファです。Ichimokuに対して "Need"手法だけ適用するなら、マイナス3バッファに留まったでしょう。

Trender 全体では合計20バッファ(4バッファ×5時間枠)の節約です。

本稿にTrender-Aggregate のコード全体を添付しています。このインディケータを元のインディケータと比較するには両方を一つのチャートで開きます。ご記憶のとおり、変更されたインディケータはチャート下の別ウィンドウに表示されています。


5.3. "Include" 手法

バッファ数を減らす抜本的な方法は予備インディケータをすべて除去することです。そうすればインディケータにはただひとつだけバッファが残ります。それはメインのインディケータに属するものです。それ以上にバッファは減らせません。

予備インディケータのコードをメインのインディケータに移動することで同様の結果が得られます。この作業は時間がかかるように見えることもありますが、最終的な効果はその価値があるものです。もっとも難しいことはインディケータから移動されたコードの適応です。そのコードは別のインディケータコードで動作するようにできていないからです。

以下はそこで起こる主な問題です。

  • 名前の衝突変数、関数(特に OnCalculate のようなシステム関数)が同じ名前になる
  • バッファの欠如インディケータの中には、インディケータロジックがバッファ内データの格納/処理に緊密に連携していると対処不可能な障害となりえるものがあります。われわれの場合は、シンプルは配列を持つバッファの配置があらゆる問題の解決法とはいきません。われわれの目指すものはメモリ消費の削減だからです。目盛に巨大容量の履歴データを格納することを拒否することが重要なのです。

そこでこういった問題を効果的に解決してくれる手法を示していきます。

各予備インディケータをクラスとして書きます。そうするとインディケータの変数および関数はすべてつねに(そのクラス内において)ユニークな名前を持ち、他のインディケータと衝突することはありません。

数多くのインディケータが移動するなら、それらを使用するとき混乱がおきないようにそのクラスの標準化を考えます。このために基本のインディケータクラスを作成し、そこから全予備インディケータのクラスを継承します。

私は以下のようなクラスを書きました。

class CIndicator
  {
protected:
   string symbol;             // currency pair
   ENUM_TIMEFRAMES timeframe;  // timeframe

   double Open[], High[], Low[], Close[]; // simulation of price buffers
   int BufLen; // necessary depth of filling of price buffers

public:
   //--- Analogs of standard functions of indicators
   void Create(string sym, ENUM_TIMEFRAMES tf) {symbol = sym; timeframe = tf;};
   void Init();
   void Calculate(datetime start_time); // start_time - address of bar that should be calculated
  };

それを基に Ichimoku に対するクラスを作成します。まず、プロパティ形式で元の名前で入力パラメータを書きます。のちにインディケータのコードに変更する必要がないようにします。

class CIchimoku: public CIndicator
  {
private:
   // Simulation of input parameters of the indicator
   int InpTenkan;
   int InpKijun;
   int InpSenkou;

全バッファの名前をそのままにします。耳をお疑いですか?このインディケータのバッファ5個はすべて宣言します。ただしそれはフェイクです。どれもバー1本だけで構成されているのです。

public:   
   // Simulation of indicator buffers
   double ExtTenkanBuffer [1];
   double ExtKijunBuffer  [1];
   double ExtSpanABuffer  [1];
   double ExtSpanBBuffer  [1];
   double ExtChinkouBuffer[1];   

そうする理由は何でしょうか?コード内でこれ以上の変更を減らすためです。あとでお解りになります。Ichimoku から取得される OnCalculate のコードでそれを書くことで、継承メソッドCIchimoku.Calculateを再定義します。

履歴バーのループはこの関数が移動する際消去されることに注意が必要です。これでそこでは指定の時刻のバーが1本だけ計算されることになります。そして計算のメインコードはそのままです。インディケータのバッファ名とパラメータ名をすべて注意して保ったのはこのためです。

また価格バッファは Calculate メソッドのかなり冒頭部分の値で書き込まれていることにも注意が必要です。そこにはバーを1本計算するために要求される値と同数があります。

   void Calculate(datetime start_time)
     {
      CopyHigh (symbol,timeframe,start_time,BufLen,High);
      CopyLow  (symbol,timeframe,start_time,BufLen,Low );
      CopyClose(symbol,timeframe,start_time,1     ,Close);

//    int limit;
      //---
//    if(prev_calculated==0) limit=0;
//    else                   limit=prev_calculated-1;
      //---
//    for(int i=limit;i<rates_total;i++)
      int i=0;
        {
         ExtChinkouBuffer[i]=Close[i];
         //--- tenkan sen
         double high=Highest(High,InpTenkan,i);
         double low=Lowest(Low,InpTenkan,i);
         ExtTenkanBuffer[i]=(high+low)/2.0;
         //--- kijun sen
         high=Highest(High,InpKijun,i);
         low=Lowest(Low,InpKijun,i);
         ExtKijunBuffer[i]=(high+low)/2.0;
         //--- senkou span a
         ExtSpanABuffer[i]=(ExtTenkanBuffer[i]+ExtKijunBuffer[i])/2.0;
         //--- senkou span b
         high=Highest(High,InpSenkou,i);
         low=Lowest(Low,InpSenkou,i);
         ExtSpanBBuffer[i]=(high+low)/2.0;
        }
      //--- done
//    return(rates_total);     
     };

もちろん元のコードを保持する部分をスキップすることも可能です。ただ、ここではその大部分を書き換える必要があり、それで処理のロジックを理解することが要求されるのです。ここではインディケータはシンプルで理解しやすいものです。が、インディケータが複雑だったならどうなるのでしょうか?そんなときに役立つ方法をお伝えしてきました。

では CIchimoku.Init メソッドを書きます。ここではすべてがシンプルです。

   void Init(int Tenkan = 9, int Kijun = 26, int Senkou = 52)
     {
      InpTenkan = Tenkan; InpKijun = Kijun; InpSenkou = Senkou;
      BufLen = MathMax(MathMax(InpTenkan, InpKijun), InpSenkou);
     };

Ichimoku にはクラス CIchimokuに取り込まれる関数がもう2つあります。Highest および Lowestです。この2つの関数は価格バッファの特定部分において高値と安値を検索します。

われわれの価格バッファは現実のものではありません。それらはひじょうにサイズの小さいものです(上記 Calculate メソッドにそれらが書かれるのをすでに見ました)。それが関数 Highest および Lowest の処理ロジックを少しばかり変更する必要がある理由です。

この状況でも、最小限の変更原則に従っています。グローバル(バッファ長が全利用可能履歴の場合)からローカル(今や価格バッファが1本のインディケータバーの計算に要求される値だけ含むため)へバッファ内バーのインデックスを変更する1行を追加することが変更のすべてです。

   double Highest(const double&array[],int range,int fromIndex)
     {
       fromIndex=MathMax(ArraySize(array)-1, 0);
      double res=0;
   //---
      res=array[fromIndex];
      for(int i=fromIndex;i>fromIndex-range && i>=0;i--)
        {
         if(res<array[i]) res=array[i];
        }
   //---
      return(res);
     }

Lowest メソッドも同様に変更されます。

Price_Channel indicator にも同様の変更が加えられますが、それは CChannel という名前のクラスで表現されます。両クラスの全体コードは本稿添付のTrender-Include ファイルにあります。

コード移動の主な側面について述べてきました。述べてきた手法はほとんどのインディケータに対して十分だと考えます。

ただし非標準設定のインディケータには別の問題があるかもしれません。たとえば、Price_Channel にはこれといった特徴のない行があります。

   PlotIndexSetInteger(0,PLOT_SHIFT,1);
   PlotIndexSetInteger(1,PLOT_SHIFT,1);

それはインディケータチャートが 1 本のバーにおいて移動することを意味しています。ここでは、同じバー座標(時間)がそれらパラメータに設定されていたとしても、関数 CopyBuffer および CopyHigh が異なるバーを2本使用することになります。

この問題は Trender-Include(問題が存在しないCIchimoku クラスとは区別される CChannel クラスの必要な箇所に"ones"が追加されます)で解決されます。そのような「巧妙な」インディケータが必要な場合、どこで探せばよいかご存じですね。

移動に関しては終わりです。両インディケータは今 Trender-Include インディケータ内に2つのクラスとして書き込まれているのです。残りはそのインディケータを呼ぶ方法を変更することです。Trender では、ハンドル配列を持っていました。Trender-Include ではそれらがオブジェクト配列と置き換えられます。

// Handles of auxiliary indicator for all timeframes
//int h_Ichimoku[5], h_Channel[5];
// Instances of embedded auxiliary indicators
CIchimoku o_Ichimoku[5]; CChannel o_Channel[5];

ここで OnInit での予備インディケータ作成は以下のように記述されます。

   for (int itf=0; itf<5; itf++)
     {
      o_Ichimoku[itf].Create(Symbol(), TF[itf]);
      o_Ichimoku[itf].Init(9, 26, 52);
      o_Channel [itf].Create(Symbol(), TF[itf]);
      o_Channel [itf].Init(22);
     }

また OnCalculate内のCopyBuffer はオブジェクトプロパティの直接呼出しと置き換えられます。

         //=== The Ichimoku indicator
         o_Ichimoku[itf].Calculate(Time);

         //CopyBuffer(h_Ichimoku[itf], 0, Time, 1, bufTenkan);
         //double Tenkan = bufTenkan[0];
         double Tenkan = o_Ichimoku[itf].ExtTenkanBuffer[0];

         //CopyBuffer(h_Ichimoku[itf], 1, Time, 1, bufKijun );    
         //double Kijun  = bufKijun [0];
         double Kijun  = o_Ichimoku[itf].ExtKijunBuffer [0];
           
         if (Tenkan > Kijun) Signal++;
         if (Tenkan < Kijun) Signal--;
          
         //=== The Channel indicator
         o_Channel[itf].Calculate(Time);

         //CopyBuffer(h_Channel [itf], 2, Time, 1, bufMid);
         //double Mid = bufMid[0];
         double Mid = o_Channel[itf].ExtMiddBuffer[0];

         if (Price > Mid) Signal++;
         if (Price < Mid) Signal--;

マイナス40バッファです。労力の価値あり、です。

先に述べられているように "Need" および "Aggregate" 手法に従いTrender を変更したのち、ビジュアルモードで取得したインディケータを検証しました。

今すぐそういう検証をおこないます。初期インディケータ(Trender)を開き、チャート上で一つ(Trender-Include)を作り直します。すべてが正しく作成されていると言えます。両インディケータの線はお互い正確に一致しているからです。


5.4. それを一つづつ行うことはできるのでしょうか?

予備インディケータのバッファ数を減らす方法をすでに3とおり考察してきました。しかし、その方法をラディカルに変更しようとしたらどうなるのでしょうか?トータル数を減らすのでははく、メモリに同時に保持されるバッファ数を減らそうとした場合?言い方を変えると、すべてのインディケータを同時にロードする代わりに、一つずつメモリにインディケータをロードしていこうとするのです。その場合、「迂回」を作成する必要があります。一つ予備インディケータを作成し、そのデータを読み、削除し、次のインディケータを作成し、など全時間枠を確認するまでです。Ichimoku インディケータには大きな数のバッファがあります。5個です。理論的に最大5個のバッファを同時にメモリに保持することは可能で(メインのインディケータバッファをプラス1)、トータル節約バッファ数は35です!

それは可能なのでしょうか?MQL5 にはインディケータ削除の特殊関数 IndicatorRelease があります。

ただし、それは見かけほど簡単ではありません。MetaTrader 5 は MQL5の高速処理に配慮があります。それがすべて呼ばれた時間系列がキャッシュに保持される理由です。別の EAの場合はインディケータかスクリプトが それらを必要とします。そして長時間呼ばれなかった場合にのみアンロードされメモリは解放されるのです。このタイムアウトは30分の長さまでの可能性があります。

よってこの方法はつねにインディケータを作成し削除することはメモリをすぐに大幅節約するには不適切です。また、それによりコンピュータはかなり処理速度を落とす可能性があります。それはインディケータが作成される毎に価格履歴全体に対して計算がされるためです。メインのインディケータの各バーでそのような処理が行われるのはいかに合理的か考えます。

それでもなお、『インディケータ迂回』の考え方は「ブレーンストーム」という点ではひじょうに面白いものです。インディケータメモリの最適化について独自の考えがなにかおありなら、本稿に対するコメントを書いてください。本テーマについての次回の記事の一で理論的または実践的に利用されることでしょう。


6. メモリの実消費量計測

前章では予備インディケータバッファ数を削減するための作業手法を3とおり取り入れました。ここではメモリの実消費量をどのように削減するか分析します。

MS Windowsで「タスクマネージャ」を使用したターミナルによって消費されるメモリサイズを計測していきます。「処理」タブにクライアント端末で消費される RAM サイズと仮想メモリが確認できます。例えば次です。


端末での最小消費メモリを確認できる以下のアルゴリズムに従い計測が行われます(それはインディケータによって消費されるメモリ量に近いものです)。

  1. MetaQuotesデモサーバーから深い価格履歴をダウンロードします(履歴を自動でダウンロードするシンボルで検証するだけで十分です)。
  2. 次の計測に向けて端末を設定(必要なチャートとインディケータを開きます)し、不要な情報からのメモリを消去するため再起動します。
  3. 再起動した端末がすべてのインディケータ計算を完了するまで待ちます。プロセッサのロードがゼロとなることで確認します。
  4. タスクバーで端末を最小化します(端末の右上角にある標準的な「最小化」ボタンをクリックすることで行えます)。今計算に使用されないのはフリーメモリです(上に表示しているスクリーンショットでまだ最小化された状態でメモリ消費例を確認することができます。仮想メモリよりもRAM メモリ消費がずっと少ないのが判ります)。
  5. 「タスクマネージャ」では"Mem Usage" (RAM)および "VM size" (仮想メモリ)行が合計されています。これがWindows XPと呼ばれ、その名前はその他の OSバージョンとはやや異なっています。

計測パラメータ

  • 計測精度を上げるため、1つの価格チャートではなく、MetaQuotes のデモアカウントで使用可能なすべての通貨ペアを利用していきます。すなわち 22 M1 チャートです。そして平均値を計算します。
  • 「チャート内最大バー」オプション(4.1で述べられています)の標準値は100000です。
  • OS - Windows XP、 32 ビット

計測結果から予想されることは?メモが2つあります。

  1. Trender インディケータがバッファを 41 使用するとしても 41×100000 バーを消費するということではありません。その理由はバッファは5つの時間枠の中で配布され、大きな時間枠では小さな時間枠よりもバー数が少ないからです。たとえば、EURUSD のM1履歴には約400万バーがあり、H1 履歴はほんの70000 バー(4000000÷60)で構成されています。Trender内バッファ数を減らしたあとのメモリ消費削減量が同じだと考えてはいけないのはこのためです。
  2. メモリはインディケータ自体のみならずインディケータによって使用される価格系列でも消費されるのです。Trender は5個の時間枠を使用します。よって、数回にわたりバッファ数を減らしたら、メモリ消費総量も同じく減ります。なぜならメモリ内のこれら5個の価格系列がすべて使用されるからです。

消費量を計測する際はメモリ消費に影響する他の要因に直面する可能性があります。それがこういった実用的計測を行う理由です。インディケータ最適化結果としての実経済性を確認するためです。

下記に計測結果表を確認いただけますまず、空のターミナルで消費されるメモリサイズを計測しました。その値を次の計測値から引くことで、あるチャートで消費されるメモリサイズを計算することができます。次の計測値から端末消費メモリおよびあるチャートでの消費メモリ値を引くと、各インディケータで消費されるメモリサイズを取得します。

メモリ消費者
インディケータバッファ
時間枠
メモリ消費サイズ
クライアント端末
0
0
38Mb 端末消費
チャート
0
1
12Mb 空チャート消費
Trender インディケータ
41
5
46Mb 1インディケータ消費
Trender-Need インディケータ
31
5
42Mb 1インディケータ消費
Trender-Aggregate インディケータ 21
5
37Mb 1インディケータ消費
Trender-Include インディケータ 1
5
38Mb 1インディケータ消費


計測結果から導かれる結論

  • インディケータバッファ数の削減はインディケータにより消費される同等のメモリ削減にはつながりません。
この初めの効果については本章上記で述べています。もっと少ない時間枠を使用するインディケータの場合、バッファ数削減効果は大きくなるかもしれません。
  • 予備インディケータコードをメインのインディケータに移動することは必ずしもベストな結果に導くわけではありません。

Include 手法が Aggregate 手法ほど効果がないのはなぜでしょうか?理由を判断するためには、両インディケータのコードに関する主な違いを思い出す必要があります。Aggregateでは、計算に必要な価格系列は OnCalculateでのインプット配列として端末により渡されます。一方 Includeでは、データすべて(全時間枠に対して)が CopyHighCopyLowCopyClose を使用して各バーに対し積極的に要求されます。おそらくそれが、これら関数が使用される再価格系列をキャッシュする特殊性によって引き起こされる余分なメモリ消費につながるものと思われます。


おわりに

本稿では予備インディケータにおいてメモリ消費を削減する作業手法を3とおりと、クライアント端末を調整することでメモリを節約する方法を1つ述べてきました。

適用される方法はみなさんの状況での受け入れ可能性と妥当性によるものです。節約バッファ数とメモリサイズはみなさんが作業されているインディケータによります。大量に「削減」できるものもあれば、何も変わらない場合もあることでしょう。

メモリ節約により、端末で同時に使用する通貨ペア数を増やすことができるようになります。またみなさんのトレードポートフォリオの信頼性も高めます。単純にご自身のコンピュータの技術的リソースを気に掛けることで、それはデポジットの資金リソースに変わる可能性があるのです。


添付資料

本稿で記述されているインディケータは添付があります。どれもが動作するように、添付資料は"MQL5\Indicators\TestSlaveIndicators" フォルダに保存します。これは Trender インディケータの全バージョン(Trender-Include以外)がそこに予備インディケータを探すためです。


MetaQuotes Ltdによってロシア語から翻訳されました。
元の記事: https://www.mql5.com/ru/articles/259

添付されたファイル |
ichimoku.mq5 (4.97 KB)
price_channel.mq5 (4.34 KB)
trender.mq5 (2.94 KB)
trender-need.mq5 (2.94 KB)
2013 年第二四半期 MQL5マーケット 実績 2013 年第二四半期 MQL5マーケット 実績
1年半成功裏に実績を積み、MQL5 「マーケット」はトレーダーにとってトレーディング戦略およびテクニカルインディケータの最大のストアとなりました。そこでは世界中の開発者 350 名から提供される約 800 件のトレーディングアプリケーションが提供されています。100,000 件以上のトレーディングプログラムがすでにトレーダーにより購入され、MetaTrader 5 ターミナルにダウンロードされています。
MQL5のエリオット波動の自動分析の実装 MQL5のエリオット波動の自動分析の実装
市場分析の最も人気なメソッドの一つとして、エリオット波動法則があります。しかし、このプロセスは、かなり複雑であり、追加ツールを使用せざるをえません。その一つとして、自動マーカーがあります。この記事は、MQL5言語でのエリオット波動の自動分析ツールの作成を紹介します。
販売者や提供者でなくても MetaTrader AppStore 、トレードシグナルサービスから収入を得る方法 販売者や提供者でなくても MetaTrader AppStore 、トレードシグナルサービスから収入を得る方法
販売者や「マーケット」のアプリケーションまたは収益性あるシグナル提供者である必要なく、いますぐに MQL5.com で収入を手にし始めることが可能です。お好きなプロダクツを選択し、さまざまなウェブリソースにそのリンクを掲示します。潜在顧客の心を引きつければ、利益はあなたのものです!
MQL5でのWinInet利用パート2:POSTリクエストとファイル MQL5でのWinInet利用パート2:POSTリクエストとファイル
本稿では HTTP リクエストとサーバーとの情報交換を利用したインターネットとの連携原理の調査を続行します。そしてCMqlNet クラスの新しい関数、フォームからの情報送信方法、 POST リクエストを利用したファイル送信、Cookies を使用してログインしたウェブサイト上での承認について述べます。