English Русский 中文 Español Deutsch Português
マーケット用の任意の非標準チャートのインディケータを作成するには

マーケット用の任意の非標準チャートのインディケータを作成するには

MetaTrader 4 | 27 6月 2016, 15:59
1 792 0
Vladimir Karputov
Vladimir Karputov

目次

 

ローソク足から練行足チャートまで

今日、トレーダーの中で最も人気のチャートタイプと言えば、市場の現在の状況を簡単に評価することができるローソク足でしょう。ローソク足チャートは、一つのローソクが捕捉する対象期間の価格変動についての明瞭な情報を与えてくれます。しかし、一部のトレーダー達は、チャートが時間の要素を含んでいて、価格変動にのみ対処をするようになっていることを欠点として捉えています。そして、『三目並べ』や『練行』、『カギ』、『レンジバー』、等量チャートなどが登場しました。 

オフラインチャートとMQL4言語でのプログラミング、そして少しの経験で、これらの全てのチャートをMetaTrader 4で入手することができます。独自の総括的ツール(ブローカーにも無く、そもそも存在しない)や、プラットフォームにない非標準時間軸を伴ったチャートを作成することも可能です。多くの開発者が、これらを作成する為にDLL呼び出しや難しいスキームを使用しています。この記事では、DLLの知識を必要としないだけでなく、マーケットに製品として簡単に公開できる(完全に独立し完成したアプリケーションである為)、任意の難易度の『2in1』タイプのインディケータの作成方法をご紹介します。

この記事の例は、マーケットで無料アプリケーションとしてダウンロードすることができます。

  • USDx Chart MT4は、インディケータ『USDx Chart MT4』が、通常のバーやローソク足の代わりにドル指標を描くオフラインチャートを構築します。 
  • Renko Chart MT4は、インディケータ『Renko Chart MT4』が、全てのバーが練行タイプの『レンガ』を持つオフラインの練行チャートを作成します。『レンガ』自体は影を持っておらず、『レンガ』のサイズは設定で指定されます。

非標準シンボルまたは期間のオフラインチャートを作成するインディケータにはDLLを呼び出す必要はなく、全てMQL4のツールで解決できます。その為に同じインディケータが、オンラインチャートでもオフラインチャートでも動作するというスキームを適用します。この時、インディケータは動作する場所(オンラインチャートかオフラインチャートか)に応じて自分の機能を変えます。

オンラインチャートでは、インディケータは相場を収集し組み立て、オフラインチャート(標準/非標準期間)を作成し、それを更新する『操作』モードで動作します。オフラインチャートでは、このインディケータは他のものと同じように、相場を分析し様々なオブジェクトと図形を構築します。記事の中の全ての例は、新しいスクリプト PeriodConverter.mq4をベースにしていることに留意してください


1. インディケータ"IndCreateOffline"は、オフラインチャートを作成します

このインディケータをIndCreateOfflineと名付けます。インディケータには、オフラインチャートの期間に対応する入力パラメータが1つだけあります。このインディケータについて、少し説明します。インディケータIndCreateOfflineは、*.hstファイルを作成し、オフラインチャートを開くという、たった一つのタスクを実行します。

インディケータでは、二つの主要機能が使用されます。一つ目は一度だけ使用され、*.hstファイルを作成し、ファイルヘッダーを形成します。二つ目の機能は、*.hstファイルへ相場履歴を記録する為にあります。

1.1. インディケータ"キャップ"の編集

これをすぐに行うのが面倒であっても、最初にファイルの説明を追加する必要があります。これは時間が経つにつれて、私たちのインディケータの役割を思い出すのに役立ちます。

#property version   "1.00"
#property description "The indicator creates an offline chart"
#property strict
#property indicator_chart_window

勿論、理論的にはオフラインチャートは、あらゆる期間で作成することができます。しかしながら、誰かが急に1000万という期間に興味を持つかもしれないという、とんでもないトレーダーのファンタジーは除くとしましょう。2分、3分、4分、6分の、全部で4つのバージョンを選択することができます。

#property indicator_chart_window
//+------------------------------------------------------------------+
//| Enumerations of periods offline chart                            |
//+------------------------------------------------------------------+
enum ENUM_OFF_TIMEFRAMES
  {
   M2=2,                      // period M2
   M3=3,                      // period M3
   M4=4,                      // period M4
   M6=6,                      // period M6
  };
//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |

次のステップは、グローバル変数のキャップに追加をします(これらをターミナルのグローバル変数と混同しないでください)。

   M6=6,                      // period M6
  };
//--- input parameter
input ENUM_OFF_TIMEFRAMES  ExtOffPeriod=M6;
//---
bool     crash=false;         // false -> error in the code
int      HandleHistory=-1;    // handle for the opened "*.hst" file
datetime time0;               //
ulong    last_fpos=0;         //
long     last_volume=0;       //
int      periodseconds;       //
int      i_period;            //
MqlRates rate;                //
long     ChartOffID=-1;       // ID of the offline chart
//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |

1.2. チャートタイプの制御

私たちのインディケータIndCreateOfflineは、データの正確さを保つことができるので、オンラインチャートでのみ起動する必要があります。CHART_IS_OFFLINEを使うことで、インディケータが設置されているチャートタイプの判別をすることができます。OnCalculate()の後に、チャートタイプを返すIsOffline関数を追加します。

//--- return value of prev_calculated for next call
   return(rates_total);
  }
//+------------------------------------------------------------------+ 
//| The function checks offline mode of the chart                    | 
//+------------------------------------------------------------------+ 
bool IsOffline(const long chart_ID=0)
  {
   bool offline=ChartGetInteger(chart_ID,CHART_IS_OFFLINE);
   return(offline);
  }
//+------------------------------------------------------------------+

IsOffline関数の呼び出しは、OnInit()で行います。

int OnInit()
  {
   if(!IsOffline(ChartID()) && Period()!=PERIOD_M1)
     {
      Print("The period on the online chart must be \"M1\"!");
      crash=true;
     }
//---
   return(INIT_SUCCEEDED);
  }

インディケータが、PERIOD_M1に等しくない期間のオンラインチャートにある場合(IsOffline(ChartID())==false)、crash 変数によってtrueが与えられることにご注意ください。crash==trueの時にインディケータはオンラインチャートに残り、何もすることはありません。この場合、『エキスパートアドバイザ』タブにこのようなメッセージが表示されます。

IndCreateOffline EURUSD,H1: The period on the online chart must be "M1"!

しかし、インディケータ自体はオンラインチャートに残り、ユーザーが期間をPERIOD_M1に変更するまで待機します。

なぜPERIOD_M1という期間が重要なのか?ここでは、二つの重要なポイントがあります。

ポイント1: オフラインチャートの総期間の形成。オフラインチャートの総期間を計算するPeriodConverter.mq4スクリプトを例として見てみましょう

#property show_inputs
input int InpPeriodMultiplier=3; // Period multiplier factor
int       ExtHandle=-1;
//+------------------------------------------------------------------+
//| script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
  {
   datetime time0;
   ulong    last_fpos=0;
   long     last_volume=0;
   int      i,start_pos,periodseconds;
   int      cnt=0;
//---- History header
   int      file_version=401;
   string   c_copyright;
   string   c_symbol=Symbol();
   int      i_period=Period()*InpPeriodMultiplier;
   int      i_digits=Digits;
   int      i_unused[13];
   MqlRates rate;
//---  

このような入力パラメータでは、スクリプトが設置されるオンラインチャートの期間は、"PERIOD_M3"と等しくなります。InpPeriodMultiplier=3の場合、3の期間のオフラインチャートの取得が見込まれます。しかし、実際には私達は9と等しい期間のオフラインチャートを取得します。

   i_period=Period()*InpPeriodMultiplier=3*3=9

したがって、3の期間のチャートを取得するには、PERIOD_M1の期間のオンラインチャートを使用する必要があります。 

ポイント2:履歴のファイルへの記録。履歴ファイルの形成時には、時系列配列(Open[]、Low[]、High[]、Volume[]、Time[])からのデータを使用します。これらは全て現在の期間の現在のチャートのデータを使用します。"PERIOD_M1"チャートのデータをベースにした、任意の人工形成期間より何がより正確なのでしょうか?正解は、PERIOD_M1チャートのみです。 

上記のインディケータの変更については、IndCreateOfflineStep1.mq4でご覧いただけます。

1.3. 履歴ファイルのヘッダー作成機能

履歴ファイルのヘッダーを作成する関数を、CreateHeader()と名付けます。

bool CreateHeader(
   const ENUM_OFF_TIMEFRAMES offline_period // period of offline chart
   );

パラメータ

offline_period

[in]  オフラインチャートの期間。 

戻り値

履歴ファイルが正常に作成された場合はtrue、エラーが発生した場合はfalseとなります。

機能の一覧:

//+------------------------------------------------------------------+ 
//| The function checks offline mode of the chart                    | 
//+------------------------------------------------------------------+ 
bool IsOffline(const long chart_ID=0)
  {
   bool offline=ChartGetInteger(chart_ID,CHART_IS_OFFLINE);
   return(offline);
  }
//+------------------------------------------------------------------+
//| Create history header                                            |
//+------------------------------------------------------------------+
bool CreateHeader(const ENUM_OFF_TIMEFRAMES offline_period)
  {
//---- History header
   int      file_version=401;
   string   c_copyright;
   string   c_symbol=Symbol();
   i_period=Period()*offline_period;
   int      i_digits=Digits;
   int      i_unused[13];
//---  
   ResetLastError();
   HandleHistory=FileOpenHistory(c_symbol+(string)i_period+".hst",FILE_BIN|FILE_WRITE|FILE_SHARE_WRITE|FILE_SHARE_READ|FILE_ANSI);
   if(HandleHistory<0)
     {
      Print("Error open ",c_symbol+(string)i_period,".hst file ",GetLastError());
      return(false);
     }
   c_copyright="(C)opyright 2003, MetaQuotes Software Corp.";
   ArrayInitialize(i_unused,0);
//--- write history file header
   FileWriteInteger(HandleHistory,file_version,LONG_VALUE);
   FileWriteString(HandleHistory,c_copyright,64);
   FileWriteString(HandleHistory,c_symbol,12);
   FileWriteInteger(HandleHistory,i_period,LONG_VALUE);
   FileWriteInteger(HandleHistory,i_digits,LONG_VALUE);
   FileWriteInteger(HandleHistory,0,LONG_VALUE);
   FileWriteInteger(HandleHistory,0,LONG_VALUE);
   FileWriteArray(HandleHistory,i_unused,0,13);
   return(true);
  }
//+------------------------------------------------------------------+

履歴ファイル"*.hst"自体の作成と、ファイルヘッダーの作成(いくつかの最初の実行列)の他に、CreateHeader()関数ではHandleHistory変数に作製したハンドルが格納されます。

1.4. *.hstファイルの最初の履歴記録

*.hstファイルの作成とそのヘッダーの記入後に、ファイルへ相場履歴の最初の記録を行う必要があります。つまり、現時点での全ての履歴でファイルを埋める必要があります。ファイルへの最初の履歴の記録は、FirstWriteHistory()関数が行います(これはインディケータで見ることができます)。

私達のインディケータでは、いつ『最初の履歴記録』が発生するのでしょうか?これはインディケータの初回ダウンロード時に起こると予想するのが論理的です。

最初のダウンロードは、インディケータ内のprev_calculated変数の値で制御することができます(制御する必要があります)。prev_calculated==0という値は、最初のダウンロードが行われることを意味します。しかし同時に、prev_calculated==0は、ダウンロードは初回ではないが、履歴はダウンロードされているということを意味することもあります。履歴のダウンロードの際に何をすべきかは、OnCalculate()のコード編集の際にお話ししましょう。

1.5. オンラインレートの記録

*.hstファイルの作成とヘッダーの記入、そして最初の履歴の記録が終わったら、オンラインレートの記録へ移ることができます。これはCollectTicks()関数が行います(これはインディケータで見ることができます)。

上記のインディケータの変更は、IndCreateOfflineStep2.mq4ファイルの中で見ることができます。 

1.6. OnCalculate()関数の編集

first_start変数を入力しましょう。これは初回起動後にtrue値を保存します。言い換えれば、first_start==trueの時は、私達のインディケータが、まだ*.hstファイルを作成していないということがわかります。

//--- input parameter
input ENUM_OFF_TIMEFRAMES  ExtOffPeriod=M6;
//---
bool     first_start=true;    // true -> it's first start
bool     crash=false;         // false -> error in the code

私達のインディケータのOnCalculate()関数の動作アルゴリズム:

algorithm

図1. OnCalculate()関数のアルゴリズム 

インディケータの最終バージョンは、IndCreateOffline.mq4ファイルで参照できます。 

 

2. オフラインチャートでのインディケータの起動

2.1. ChartSetSymbolPeriod()を使用したオフラインチャートの更新 

重要:オフラインチャートの更新には、ChartRedraw()の代わりに現在のパラメータのChartSetSymbolPeriod()を呼び出す必要があります。ChartSetSymbolPeriod()の呼び出しは、3秒に1度以上の頻度の間隔を持つCollectTicks()関数で行われます。

また、オフラインチャートの動作ニュアンスを考慮する必要があります。オフラインチャートに設定されたインディケータは、更新するごとに自分のOnCalculate()関数でprev_calculated==0を受け取ります。この特性を覚えておいてください。この特性を考慮した方法は、以下に記載します。 

2.2. 操作モード 

私達が欲する事は、同じインディケータが通常のオンラインチャートでも、オフラインチャートでも動作することです。この時、インディケータが設置されているのがオンラインかオフラインかによって、インディケータの動作が変わります。インディケータがオンラインチャートにある場合、インディケータの機能は上記のインディケータIndCreateOffline.mq4とよく似ています。しかし、オフラインチャートにある場合、インディケータは通常のインディケータのように動作を開始します。

さて、私達のインディケータをIndMACDDoubleDutyと名付けましょう。これは上記のIndCreateOffline.mq4と標準インディケータのMACD.mq4をベースに構築されています。未来のインディケータの下書きを用意してください。MetaEditorでIndCreateOffline.mq4ファイルを開き、次にメニューの『ファイル』->『名前をつけて保存』でIndMACDDoubleDuty.mq4のインディケータ名を記入してください。 

すぐにインディケータの説明文を追加しましょう。私達のインディケータは、これからオフラインチャートを作成し2つの目的を持ちます。

//+------------------------------------------------------------------+
//|                                            IndMACDDoubleDuty.mq4 |
//|                        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 description "The indicator creates an offline chart."
#property description "May work on an online chart and off-line graph."
#property strict
#property indicator_chart_window 

『操作』モードでは、インディケータはPERIOD_M1のオンラインチャートに起動されている場合に動作します。このモードでは、インディケータは次の機能を実行します。

  • *.hstファイルを作成し、それを埋めます。
  • *.hstファイルをベースにオフラインチャートを開きます。
  • 相場を受信すると*.hstファイルに履歴を記録し、オフラインチャートを更新します。
どのモードでインディケータが動作しているかを記憶する為に、mode_offline変数を導入します。

//--- input parameter
input ENUM_OFF_TIMEFRAMES  ExtOffPeriod=M6;
//---
bool     first_start=true;    // true -> it's first start
bool     crash=false;         // false -> error in the code
bool     mode_offline=true;   // true -> on the offline chart
int      HandleHistory=-1;    // handle for the opened "*.hst" file
datetime time0;               //

これに応じて、OnInit()も少し変化します。

int OnInit()
  {
   mode_offline=IsOffline(ChartID());
   if(!mode_offline && Period()!=PERIOD_M1)
     {
      Print("The period on the online chart must be \"M1\"!");
      crash=true;
     }
//---
   return(INIT_SUCCEEDED);
  }

OnCalculate()に変更を加えます。

//+------------------------------------------------------------------+
//| Custom indicator iteration function                              |
//+------------------------------------------------------------------+
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(crash)
      return(rates_total);

   if(!mode_offline) // work in the online chart 
     {
      if(prev_calculated==0 && first_start) // first start
        {
         .
         .
         .
         first_start=false;
        }
      //---
      CollectTicks();
     }
//--- return value of prev_calculated for next call
   return(rates_total);
  }

この段階では、PERIOD_M1のオンラインチャートに設置されたインディケータは、『操作』モードへ移行し、オフラインチャートを作成します。

上記のインディケータの変更は、IndMACDDoubleDutyStep1.mq4ファイルで参照することができます。 

2.3. オフラインチャートへのインディケータのコピー

IndMACDDoubleDutyインディケータに、新しい機能を追加します。『操作』モードにありながら、インディケータが作成したオフラインチャートに自分のコピーを送信するようにします。ここでは、ChartSaveTemplate関数とChartApplyTemplate関数が役に立ちます。OnCalcalculate()のアルゴリズムは、このようになります。

algorithm_2

図2. OnCalculate()関数のアルゴリズム 

OnCalculate()のコードに追加の機能を加えます。

         else
            Print(__FUNCTION__,"Opening offline chart id=",ChartOffID);
         ResetLastError();
         if(!ChartSaveTemplate(0,"IndMACDDoubleDuty"))
           {
            Print("Error save template: ",GetLastError());
            first_start=false;
            crash=true;
            return(rates_total);
           }
         ResetLastError();
         if(!ChartApplyTemplate(ChartOffID,"IndMACDDoubleDuty"))
           {
            Print("Error apply template: ",GetLastError());
            first_start=false;
            crash=true;
            return(rates_total);
           }
        }
      //---
      if(prev_calculated==0 && !first_start) // a deeper history downloaded or history blanks filled

ここで、私達のインディケータはPERIOD_M1のオンラインチャートへの設置時に、『操作』モードへ移行し、オフラインチャートを作成しそこへ自分をコピーします。

上記のインディケータの変更については、IndMACDDoubleDutyStep2.mq4ファイルで参照することができます。  

2.4. MACD.mq4への統合

私達のインディケータは自分自身をオフラインチャートに設置しますが、まだ何も表示しないし、何も計算しません。これを修正しましょう。私達のインディケータを標準のMACD.mq4に統合します。 

まず、MACD.mq4インディケータの入力パラメータを私達のコードに挿入しましょう。

#property strict

#include <MovingAverages.mqh>

//--- MACD indicator settings
#property  indicator_separate_window
#property  indicator_buffers 2
#property  indicator_color1  Silver
#property  indicator_color2  Red
#property  indicator_width1  2
//--- indicator parameters
input int InpFastEMA=12;   // Fast EMA Period
input int InpSlowEMA=26;   // Slow EMA Period
input int InpSignalSMA=9;  // Signal SMA Period
//--- indicator buffers
double    ExtMacdBuffer[];
double    ExtSignalBuffer[];
//--- right input parameters flag
bool      ExtParameters=false;
//+------------------------------------------------------------------+
//| Enumerations of periods offline chart                            |
//+------------------------------------------------------------------+ 

それから、OnInit()にコードを追加します。ここでは、インディケータMACDのパラメータの初期化は、オフラインやオンラインチャート、またはオンラインチャートの期間がPERIOD_M1と異なる場合など、あらゆる条件下で行われるということに留意してください。

int OnInit()
  {
   mode_offline=IsOffline(ChartID());
   if(!mode_offline && Period()!=PERIOD_M1)
     {
      Print("The period on the online chart must be \"M1\"!");
      crash=true;
     }
//--- init MACD indicator
   IndicatorDigits(Digits+1);
//--- drawing settings
   SetIndexStyle(0,DRAW_HISTOGRAM);
   SetIndexStyle(1,DRAW_LINE);
   SetIndexDrawBegin(1,InpSignalSMA);
//--- indicator buffers mapping
   SetIndexBuffer(0,ExtMacdBuffer);
   SetIndexBuffer(1,ExtSignalBuffer);
//--- name for DataWindow and indicator subwindow label
   IndicatorShortName("MACD("+IntegerToString(InpFastEMA)+","+IntegerToString(InpSlowEMA)+","+IntegerToString(InpSignalSMA)+")");
   SetIndexLabel(0,"MACD");
   SetIndexLabel(1,"Signal");
//--- check for input parameters
   if(InpFastEMA<=1 || InpSlowEMA<=1 || InpSignalSMA<=1 || InpFastEMA>=InpSlowEMA)
     {
      Print("Wrong input parameters");
      ExtParameters=false;
      return(INIT_FAILED);
     }
   else
      ExtParameters=true;
//---
   return(INIT_SUCCEEDED);
  }

次のステップで、OnCalculate()の最初のコードを変更します。もしオンラインチャートにあり、その期間がPERIOD_M1と等しくない場合、MACDのパラメータの計算の機能を与える必要があります。以前はこうでした。

const long &volume[],
                const int &spread[])
  {
//---
   if(crash)
      return(rates_total);

   if(!mode_offline) // work in the online chart 
     {
      if(prev_calculated==0 && first_start) // first start
        {

 これからはこうなります。

const long &volume[],
                const int &spread[])
  {
//---
   if(!mode_offline && !crash) // work in the online chart 
     {
      if(prev_calculated==0 && first_start) // first start
        {

次にインディケータのMACDのパラメータの計算コードをOnCalculate()の終わりに追加します。

         FirstWriteHistory(ExtOffPeriod);
         first_start=false;
        }
      //---
      CollectTicks();
     }
//---
   int i,limit;
//---
   if(rates_total<=InpSignalSMA || !ExtParameters)
      return(0);
//--- last counted bar will be recounted
   limit=rates_total-prev_calculated;
   if(prev_calculated>0)
      limit++;
//--- macd counted in the 1-st buffer
   for(i=0; i<limit; i++)
      ExtMacdBuffer[i]=iMA(NULL,0,InpFastEMA,0,MODE_EMA,PRICE_CLOSE,i)-
                       iMA(NULL,0,InpSlowEMA,0,MODE_EMA,PRICE_CLOSE,i);
//--- signal line counted in the 2-nd buffer
   SimpleMAOnBuffer(rates_total,prev_calculated,0,InpSignalSMA,ExtMacdBuffer,ExtSignalBuffer);
//--- return value of prev_calculated for next call
   return(rates_total);
  }

2.5. オフラインチャートでのインディケータの簡単な計算

項目2の始めに記載したニュアンスを思い出してください。

また、オフラインチャートの動作ニュアンスを考慮する必要があります。オフラインチャートに設定されたインディケータは、更新するごとに自分のOnCalculate()関数でprev_calculated==0を受け取ります。この特性を覚えておいてください。この特性を考慮した方法は、以下に記載します。 

そして、もう一つ注意があります。OnCalculate()ではprev_calculated==0は2つの状況を意味します。

  1. これがインディケータの初回起動である。
  2. または、履歴がダウンロード済である。

両方の状況では、チャート上では全てのバーは再計算されなければいけません。これが1度だけ(ダウンロード時に)実行する必要があるのは、正常の範囲内です。そしてオフラインチャートでは、更新ごとにprev_calculated==0を受け取り(およそ2-3秒に1度)、インディケータは全てのバーを再計算します。これはリソースの大きな無駄遣いです。その為、少しトリックを使いましょう。インディケータがオフラインチャートにある場合、インディケータはチャート上の最初のバーの時間とバーの数(rates_total変数)を比較し保存します。

ステップ1:OnCalculate()の最初に静的変数と1つの擬似変数を宣言します。

                const long &volume[],
                const int &spread[])
  {
//---
   static int static_rates_total=0;
   static datetime static_time_close=0;
   int pseudo_prev_calculated=prev_calculated;
//---
   if(!mode_offline && !crash) // work in the online chart 
     {

ステップ2:インディケータの数値計算のコードのブロックで、prev_calculated変数をpseudo_prev_calculatedに置き換えます。

      CollectTicks();
     }
//---
   int i,limit;
//---
   if(rates_total<=InpSignalSMA || !ExtParameters)
      return(0);
//--- last counted bar will be recounted
   limit=rates_total-pseudo_prev_calculated;
   if(pseudo_prev_calculated>0)
      limit++;
//--- macd counted in the 1-st buffer
   for(i=0; i<limit; i++)
      ExtMacdBuffer[i]=iMA(NULL,0,InpFastEMA,0,MODE_EMA,PRICE_CLOSE,i)-
                       iMA(NULL,0,InpSlowEMA,0,MODE_EMA,PRICE_CLOSE,i);
//--- signal line counted in the 2-nd buffer
   SimpleMAOnBuffer(rates_total,pseudo_prev_calculated,0,InpSignalSMA,ExtMacdBuffer,ExtSignalBuffer);
//---
   if(mode_offline) // work in the offline chart 

ステップ3:ここでインディケータがオフラインチャートで動作している場合、擬似変数の値を計算しましょう。 

      CollectTicks();
     }
//---
   if(mode_offline) // work in the offline chart 
     {
      if(time[0]>static_time_close) // new bar
        {
         if(static_time_close==0)
            pseudo_prev_calculated=0;
         else // search bar at which time[0]==static_time_close
           {
            for(int i=0;i<rates_total;i++)
              {
               if(time[i]==static_time_close)
                 {
                  pseudo_prev_calculated=rates_total-i;
                  break;
                 }
              }
           }
        }
      else
        {
         pseudo_prev_calculated=rates_total;
        }
      //---
      static_rates_total=rates_total;
      static_time_close=time[0];
     }
//---
   int i,limit;

私達のインディケータはオフラインチャートで、自分の値を経済的に計算します。その他にも、このモードではインディケータは接続の断絶を正常に処理します。断絶後には追加のバーが新たに計算されます。

2.6. 履歴のダウンロードオフラインチャート

あとは私達のインディケータがあるオンラインチャートで履歴のダウンロード(prev_calculated==0の時)が行われた場合に何をすべきか決めます。このような状況はターミナルのグローバル変数を介して解決することを提案します。動作アルゴリズムは次の通りです。オンラインチャートでは、prev_calculated==0を受け取る場合(初回起動や履歴のダウンロードがあったかは重要ではない)、グローバル変数を作成します。インディケータはオフラインチャート上では、更新ごとにグローバル変数の有無をチェックします。グローバル変数がある場合(つまりオンラインチャートでprev_calculated==0があった場合)、インディケータは完全に再計算され、グローバル変数を削除します。

インディケータのキャップに、ターミナルの将来のグローバル変数名を保存する変数を追加し、OnInit()にこの名前を生成します。

MqlRates rate;                //
long     ChartOffID=-1;       // ID of the offline chart
string   NameGlVariable="";   // name global variable
//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit()
  {
   mode_offline=IsOffline(ChartID());
   if(!mode_offline && Period()!=PERIOD_M1)
     {
      Print("The period on the online chart must be \"M1\"!");
      crash=true;
     }
   NameGlVariable=Symbol()+(string)ExtOffPeriod;
//--- init MACD indicator
   IndicatorDigits(Digits+1);
//--- drawing settings

prev_calculated==0の条件で、インディケータがオンラインチャートにある場合、ターミナルのグローバル変数の作成コードを追加します。

         if(!ChartApplyTemplate(ChartOffID,"IndMACDDoubleDuty"))
           {
            Print("Error apply template: ",GetLastError());
            first_start=false;
            crash=true;
            return(rates_total);
           }
         //---
         ResetLastError();
         if(GlobalVariableSet(NameGlVariable,0.0)==0) // creates a new global variable
           {
            Print("Failed to creates a new global variable ",GetLastError());
           }
        }
      //---
      if(prev_calculated==0 && !first_start) // a deeper history downloaded or history blanks filled
        {
         Print("a deeper history downloaded or history blanks filled. first_start=",first_start);
         if(CreateHeader(ExtOffPeriod))
            first_start=false;
         else
           {
            crash=true;
            return(rates_total);
           }
         //---
         FirstWriteHistory(ExtOffPeriod);
         first_start=false;
         //---
         ResetLastError();
         if(GlobalVariableSet(NameGlVariable,0.0)==0) // creates a new global variable
           {
            Print("Failed to creates a new global variable ",GetLastError());
           }
        }
      //---
      CollectTicks();

そして最後の変更、オフラインチャート上のインディケータのグローバル変数の有無のチェックです。

      else
        {
         pseudo_prev_calculated=rates_total;
        }
      //---
      if(GlobalVariableCheck(NameGlVariable))
        {
         pseudo_prev_calculated=0;
         GlobalVariableDel(NameGlVariable);
        }
      //---
      Print("rates_total=",rates_total,"; prev_calculated=",
            prev_calculated,"; pseudo_prev_calculated=",pseudo_prev_calculated);
      static_rates_total=rates_total;
      static_time_close=time[0];
     }
//---
   int i,limit;

最後の変更を加えたインディケータIndMACDDoubleDuty.mq4の最終バージョンです。  

 

3. 練行足バーを表示するインディケータ

1.オフラインチャートを作成するインディケータ"IndCreateOffline"と、2.オフラインチャート上でのインディケータの起動で使用されたものと同じ技術で練行足バーを構築します。つまり、最終的に私達はオフラインチャートを入手しますが、この場合、*.hstの履歴ファイルは同じサイズのバーを持ちます。これらのバーを『レンガ』とも呼びます。『レンガ』のサイズはインディケータのプロパティで設定され、ポイントで測定されます。

練行足バー  

図3. 練行足バー 

構築前に、練行足バー構築の為のいくつかの*.hst履歴ファイルの形成ルールを考慮する必要があります。

3.1. 練行足バーの構築ルール

ルール1:*.hst履歴ファイルへのOHLCの記録は、特に高値と安値に関して正確である必要があります。そうでない場合には、ターミナルは正確ではない記録を表示しません。陽線の*.hst履歴ファイルへの正確/不正確な記録の例

正確/不正確な記録例  

図4. 正確/不正確な記録例 

ルール2:時間的縛りがない練行足バーを構築しますが、*.hst履歴ファイルのフォーマット自体は、timeパラメータ(これは期間の開始時間)を必要とします。従ってtimeパラメータは、必ず記録する必要があります。

ルール3:timeパラメータは、全てのバーで異なっている必要があります。全てのバーに対し同じtimeパラメータを記録すると、このような記録は不正確となり、ターミナルはチャートを表示しません。しかし、ここには便利な特性もあります。timeパラメータは1秒の差で記録することができます。例えば、1つのバーをtime=2016.02.10 09:08:00で記録をし、次のバーをtime=2016.02.10 09:08:01で記録します。

3.2. ヒストリカルデータ上における"レンガ"の形成

この方法は完璧なアルゴリズムである必要はありません。この記事の主な課題は、*.hst履歴ファイルを形成する方法を紹介することなので、簡単なアプローチを選択しました。インディケータ"IndRange.mq4"では、履歴に基づいてレンガを描く際に、現在の期間のHigh[i]とLow[i]の値を分析します。つまり、インディケータ"IndRange.mq4"がM5のチャートに設置されている場合、インディケータ"IndRange.mq4"は初回起動時に現在の期間のM5の履歴を分析します。 

勿論、希望によって履歴ベースの描画アルゴリズムをアップグレードし、一番小さい時間軸、М1での価格変動を考慮するということもできます。一般的な動作スキーム:

  • High[i]が前のhighのレンガよりも1つ分大きい場合、前のレンガよりも上に1つ以上のレンガが描かれます。
  • Low[i]が前のlowのレンガよりも1つ分小さい場合、前のレンガよりも上に1つ以上のレンガが描かれます。

前のレンガが陽線

図5. 前のレンガが陽線 


 前のレンガが陰線

図6. 前のレンガが陰線

面白いニュアンスがあります。バーの詳細(Open、High、Low、Close、Timeそしてボリューム)は、インディケータの『キャップ』で宣言されるrate構造体で保存されます。

MqlRates rate;                   //

そしてこの構造体は*.hst履歴ファイルへの記録ごとに初期化されず、ただ上書きされます。このおかげで、図5と図6で示されているアルゴリズムを簡単に実装し、一般式を与えることができます。

  • High[i]が前のレンガのhighよりも大きい場合(この場合、前のレンガが陽線か陰線であるかは重要ではない)、新しいバーの詳細はこのようなスキームで計算されます。  

Open = High; Low = Open; Close = Low + Renko size; High = Close

 

  • High[i]が前のレンガのhighよりも小さい場合(この場合、前のレンガが陽線か陰線であるかは重要ではない)、新しいバーの詳細はこのようなスキームで計算されます。  

Low = OpenHigh = OpenClose = High - Renko sizeLow = Close

これらの式はインディケータ"IndRange.mq4"とFirstWriteHistory()関数で表示されます。

//+------------------------------------------------------------------+
//| First Write History                                              |
//+------------------------------------------------------------------+
bool FirstWriteHistory(const int offline_period)
  {
   int      i,start_pos;
   int      cnt=0;
//--- write history file
   periodseconds=offline_period*60;
   start_pos=Bars-1;
   rate.open=Open[start_pos];
   rate.close=Close[start_pos];
   rate.low=Low[start_pos];
   rate.high=High[start_pos];
   rate.tick_volume=(long)Volume[start_pos];
   rate.spread=0;
   rate.real_volume=0;
//--- normalize open time
   rate.time=D'1980.07.19 12:30:27';
   for(i=start_pos-1; i>=0; i--)
     {
      if(IsStopped())
         break;
      while((High[i]-rate.high)>SizeRenko*Point())
        {
         rate.time+=1;
         rate.open=rate.high;
         rate.low=rate.open;
         rate.close=NormalizeDouble(rate.low+SizeRenko*Point(),Digits);
         rate.high=rate.close;
         last_fpos=FileTell(HandleHistory);
         uint byteswritten=FileWriteStruct(HandleHistory,rate);
         //--- check the number of bytes written 
         if(byteswritten==0)
            PrintFormat("Error read data. Error code=%d",GetLastError());
         else
            cnt++;
        }
      while((Low[i]-rate.low)<-SizeRenko*Point())
        {
         rate.time+=1;
         rate.open=rate.low;
         rate.high=rate.open;
         rate.close=NormalizeDouble(rate.high-SizeRenko*Point(),Digits);
         rate.low=rate.close;
         last_fpos=FileTell(HandleHistory);
         uint byteswritten=FileWriteStruct(HandleHistory,rate);
         //--- check the number of bytes written 
         if(byteswritten==0)
            PrintFormat("Error read data. Error code=%d",GetLastError());
         else
            cnt++;
        }
     }
   FileFlush(HandleHistory);
   PrintFormat("%d record(s) written",cnt);
   return(true);
  }

 

インディケータ"IndRange.mq4"は、常に*.hst 履歴ファイルの名前を次のルールに基づき形成します:『現在の通貨ペア名』+『7』+".hst"。例えば、"EURUSD"の通貨ペアに対しては、履歴ファイルは"EURUSD7.hst"という名前を持ちます。

3.3. オンラインのインディケータの動作

インディケータのオンライン動作時、高値の代わりに終値(ゼロバー上の終値(一番右))を分析する必要があります。

//+------------------------------------------------------------------+
//| Collect Ticks                                                    |
//+------------------------------------------------------------------+
bool CollectTicks()
  {
   static datetime last_time;//=TimeLocal()-5;
   long     chart_id=0;
   datetime cur_time=TimeLocal();
//---
   while((Close[0]-rate.high)>SizeRenko*Point())
     {
      rate.time+=1;
      rate.open=rate.high;
      rate.low=rate.open;
      rate.close=NormalizeDouble(rate.low+SizeRenko*Point(),Digits);
      rate.high=rate.close;
      last_fpos=FileTell(HandleHistory);
      uint byteswritten=FileWriteStruct(HandleHistory,rate);
      //--- check the number of bytes written  
      if(byteswritten==0)
         PrintFormat("Error read data. Error code=%d",GetLastError());
     }
   while((Close[0]-rate.low)<-SizeRenko*Point())
     {
      rate.time+=1;
      rate.open=rate.low;
      rate.high=rate.open;
      rate.close=NormalizeDouble(rate.high-SizeRenko*Point(),Digits);
      rate.low=rate.close;
      last_fpos=FileTell(HandleHistory);
      uint byteswritten=FileWriteStruct(HandleHistory,rate);
      //--- check the number of bytes written 
      if(byteswritten==0)
         PrintFormat("Error read data. Error code=%d",GetLastError());
     }
//--- refresh window not frequently than 1 time in 2 seconds
   if(cur_time-last_time>=3)
     {
      FileFlush(HandleHistory);
      ChartSetSymbolPeriod(ChartOffID,Symbol(),i_period);
      last_time=cur_time;
     }
   return(true);
  }


4. 非標準通貨ペア(ドル指数USDx)を作成するインディケータ

インディケータ"IndUSDx.mq4"のアルゴリズムへの重要事項:今の私達の目的は、非標準通貨ペアの*.hst 履歴ファイルの形成方法を紹介することなので、ドル指標のインディケータは、指標計算式の完全な正確性を求めません。その結果、オフラインチャートを入手し、このチャートのバーは計算されたドル指標を表示します。また、インディケータ"IndUSDx.mq4"は、最初にチャートにインディケータを設置した時、またはチャート期間の変更後に、一度だけオフラインチャートを作成します。

ドル指標の計算式と通貨ペアのセットは、 ドル指標の簡単なインディケータのコードをベースにしています。 

ドル指標の計算式には、"EURUSD"、"GBPUSD"、"USDCHF"、"USDJPY"、"AUDUSD"、"USDCAD"、"NZDUSD"のTime、Open、Closeのデータを取得する必要があります。データへのアクセスと保存をしやすくする為に、OHLS構造体を導入します(これはインディケータの『キャップ』で宣言されています)。

//--- structuts
struct   OHLS
  {
   datetime          ohls_time[];
   double            ohls_open[];
   double            ohls_close[];
  };

OHLS構造体では、要素としてTime、Open、Closeの保存の為の配列が表示されます。構造体の記載時にすぐにいくつかのオブジェクト(計算される通貨ペアのデータを保存するOHLS構造体)が宣言されます。

//--- structuts
struct   OHLS
  {
   datetime          ohls_time[];
   double            ohls_open[];
   double            ohls_close[];
  };
OHLS     OHLS_EURUSD,OHLS_GBPUSD,OHLS_USDCHF,OHLS_USDJPY,OHLS_AUDUSD,OHLS_USDCAD,OHLS_NZDUSD;
//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit()

OnInit()ではSelectSymbols()関数が呼び出されます。

//+------------------------------------------------------------------+
//| Select Symbols                                                   |
//+------------------------------------------------------------------+
bool SelectSymbols()
  {
   bool rezult=true;
   string arr_symbols[7]={"EURUSD","GBPUSD","USDCHF","USDJPY","AUDUSD","USDCAD","NZDUSD"};
   for(int i=0;i<ArraySize(arr_symbols);i++)
      rezult+=SymbolSelect(arr_symbols[i],true);
//---
   return(rezult);
  }

SelectSymbols()関数は、SymbolSelectを使い、ドル指標式に関係する通貨ペアをマーケットウォッチウィンドウで選択します。

初回起動時に、OnCalculate()ではCopyCloseSymbols()関数が呼び出されます。ここでは計算される通貨ペアのデータのリクエストと通貨ペアの構造体の入力が行われます。

//+------------------------------------------------------------------+
//| CopyClose Symbols                                                |
//+------------------------------------------------------------------+
bool CopyCloseSymbols(const int rates)
  {
   int copied=0;
   int copy_time=0,copy_open=0,copy_close=0;
   copy_time=CopyTime("EURUSD",Period(),0,rates,OHLS_EURUSD.ohls_time);
   copy_open=CopyOpen("EURUSD",Period(),0,rates,OHLS_EURUSD.ohls_open);
   copy_close=CopyClose("EURUSD",Period(),0,rates,OHLS_EURUSD.ohls_close);
   if(copy_open!=rates || copy_close!=rates || copy_time!=rates)
     {
      Print("Symbol \"EURUSD\". Get time ",copy_time,", get open ",copy_open,", get close ",copy_close," of ",rates);
      return(false);
     }

   copy_time=CopyTime("GBPUSD",Period(),0,rates,OHLS_EURUSD.ohls_time);
   copy_open=CopyOpen("GBPUSD",Period(),0,rates,OHLS_GBPUSD.ohls_open);
   copy_close=CopyClose("GBPUSD",Period(),0,rates,OHLS_GBPUSD.ohls_close);
   if(copy_open!=rates || copy_close!=rates || copy_time!=rates)
     {
      Print("Symbol \"GBPUSD\". Get time ",copy_time,", get open ",copy_open,", get close ",copy_close," of ",rates);
      return(false);
     }

   copy_time=CopyTime("USDCHF",Period(),0,rates,OHLS_EURUSD.ohls_time);
   copy_open=CopyOpen("USDCHF",Period(),0,rates,OHLS_USDCHF.ohls_open);
   copy_close=CopyClose("USDCHF",Period(),0,rates,OHLS_USDCHF.ohls_close);
   if(copy_open!=rates || copy_close!=rates || copy_time!=rates)
     {
      Print("Symbol \"USDCHF\". Get time ",copy_time,", get open ",copy_open,", get close ",copy_close," of ",rates);
      return(false);
     }

   copy_time=CopyTime("USDJPY",Period(),0,rates,OHLS_EURUSD.ohls_time);
   copy_open=CopyOpen("USDJPY",Period(),0,rates,OHLS_USDJPY.ohls_open);
   copy_close=CopyClose("USDJPY",Period(),0,rates,OHLS_USDJPY.ohls_close);
   if(copy_open!=rates || copy_close!=rates || copy_time!=rates)
     {
      Print("Symbol \"USDJPY\". Get time ",copy_time,", get open ",copy_open,", get close ",copy_close," of ",rates);
      return(false);
     }

   copy_time=CopyTime("AUDUSD",Period(),0,rates,OHLS_EURUSD.ohls_time);
   copy_open=CopyOpen("AUDUSD",Period(),0,rates,OHLS_AUDUSD.ohls_open);
   copy_close=CopyClose("AUDUSD",Period(),0,rates,OHLS_AUDUSD.ohls_close);
   if(copy_open!=rates || copy_close!=rates || copy_time!=rates)
     {
      Print("Symbol \"AUDUSD\". Get time ",copy_time,", get open ",copy_open,", get close ",copy_close," of ",rates);
      return(false);
     }

   copy_time=CopyTime("USDCAD",Period(),0,rates,OHLS_EURUSD.ohls_time);
   copy_open=CopyOpen("USDCAD",Period(),0,rates,OHLS_USDCAD.ohls_open);
   copy_close=CopyClose("USDCAD",Period(),0,rates,OHLS_USDCAD.ohls_close);
   if(copy_open!=rates || copy_close!=rates || copy_time!=rates)
     {
      Print("Symbol \"USDCAD\". Get time ",copy_time,", get open ",copy_open,", get close ",copy_close," of ",rates);
      return(false);
     }

   copy_time=CopyTime("NZDUSD",Period(),0,rates,OHLS_EURUSD.ohls_time);
   copy_open=CopyOpen("NZDUSD",Period(),0,rates,OHLS_NZDUSD.ohls_open);
   copy_close=CopyClose("NZDUSD",Period(),0,rates,OHLS_NZDUSD.ohls_close);
   if(copy_open!=rates || copy_close!=rates || copy_time!=rates)
     {
      Print("Symbol \"NZDUSD\". Get time ",copy_time,", get open ",copy_open,", get close ",copy_close," of ",rates);
      return(false);
     }
//---
   return(true);
  }

CopyCloseSymbols()関数の通貨ペアの履歴が指定した値よりも小さい場合、通貨ペア名とダウンロードされた履歴の実際値を伴ったメッセージが出力されます。

構造体の記入に成功した場合、主要機能(*.hst履歴ファイルを埋めるFirstWriteHistory()関数)の呼び出しが行われます。

//+------------------------------------------------------------------+
//| First Write History                                              |
//+------------------------------------------------------------------+
bool FirstWriteHistory(const int rates)
  {
   int      i;
   int      cnt=0;
   rate.tick_volume=0;
   rate.spread=0;
   rate.real_volume=0;
   for(i=0;i<rates;i++)
     {
      rate.time=OHLS_EURUSD.ohls_time[i];
      rate.open=(100*MathPow(OHLS_EURUSD.ohls_open[i],0.125)+100*MathPow(OHLS_GBPUSD.ohls_open[i],0.125)+
                 100*MathPow(OHLS_USDCHF.ohls_open[i],0.125)+100*MathPow(OHLS_USDJPY.ohls_open[i],0.125)+
                 100*MathPow(OHLS_AUDUSD.ohls_open[i],0.125)+100*MathPow(OHLS_USDCAD.ohls_open[i],0.125)+
                 100*MathPow(OHLS_NZDUSD.ohls_open[i],0.125))/8.0;

      rate.close=(100*MathPow(OHLS_EURUSD.ohls_close[i],0.125)+100*MathPow(OHLS_GBPUSD.ohls_close[i],0.125)+
                  100*MathPow(OHLS_USDCHF.ohls_close[i],0.125)+100*MathPow(OHLS_USDJPY.ohls_close[i],0.125)+
                  100*MathPow(OHLS_AUDUSD.ohls_close[i],0.125)+100*MathPow(OHLS_USDCAD.ohls_close[i],0.125)+
                  100*MathPow(OHLS_NZDUSD.ohls_close[i],0.125))/8.0;

      if(rate.open>rate.close)
        {
         rate.high=rate.open;
         rate.low=rate.close;
        }
      else
        {
         rate.high=rate.close;
         rate.low=rate.open;
        }
      last_fpos=FileTell(HandleHistory);
      uint byteswritten=FileWriteStruct(HandleHistory,rate);
      //--- check the number of bytes written 
      if(byteswritten==0)
         PrintFormat("Error read data. Error code=%d",GetLastError());
      else
         cnt++;
     }
   FileFlush(HandleHistory);
   PrintFormat("%d record(s) written",cnt);
   return(true);
  }

インディケータ"IndUSDx.mq4"の動作結果: 

 インディケータ

図7. インディケータ"IndUSDx.mq4 

  

まとめ

オフラインチャートでも、オンラインチャートでも、インディケータの再計算を経済的に行うことができることがわかりました。確かにその為には、オフラインチャート更新の特性、特にオフラインチャートの更新時に全てのインディケータが、OnCalculate()でprev_calculate==0という値を受け取るという特性を考慮する為に、インディケータのコードに変更を加える必要があります。

また、記事の中では*.hst 履歴ファイルの形成方法と、その為のチャート上に表示されるバーのパラメータの根本的な変更方法をご紹介しました。 


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

モスクワ証券取引所(MOEX)の為のトレードロボット作成は何から始めたら良いか モスクワ証券取引所(MOEX)の為のトレードロボット作成は何から始めたら良いか
モスクワ証券取引所のトレーダーの多くが。自分のトレードアルゴリズムを自動化したいと考えていても、何から始めたら良いかわからずにいます。MQL5言語は膨大な取引機能を提供するだけでなく、アルゴトレードにおける最初の一歩を最大限に簡単なものにするクラスも提供しています。
トレーダーの為の正規表現 トレーダーの為の正規表現
正規表現(英語ではregular expressions) とは、正規表現のパターンやマスクと呼ばれる指定されたルールに従ったテキストを処理する為の特別な言語です。この記事では、MQL5のRegularExpressionsライブラリを使用した取引レポートの処理をご紹介し、それを使った最適化結果をデモンストレーションします。
グラフィカルインタフェースVI:チェックボックスコントロール、編集コントロールとその混合型(チャプター 1) グラフィカルインタフェースVI:チェックボックスコントロール、編集コントロールとその混合型(チャプター 1)
本稿は、MetaTrader端末でグラフィカルインタフェースを作成するためのライブラリの開発に関するシリーズの第六部の始まりです。第1章ではチェックボックスコントロール、編集コントロールとその混合型についてお話します。
手動取引のサポーターを作成する 手動取引のサポーターを作成する
近年、為替市場の為のトレードロボットの数は、雪だるま式に増えています。これらのトレードロボットの中には、様々な概念や戦略がありますが、負けない人工知能の作成は誰も成し遂げていません。その為、多くのトレーダーは手動取引を支持しています。しかし、このようなスペシャリストの為に、トレードパネルと呼ばれるロボットアシスタントが作成されています。この記事では、トレードパネルの作成例を『ゼロから』ご紹介していきます。