マーケット用の任意の非標準チャートのインディケータを作成するには
目次
- 概論
- 1. オフラインチャートを作成する、インディケータ"IndCreateOffline"
- 1.1. インディケータの"キャップ"の編集
- 1.2. チャートタイプの制御
- 1.3. 履歴ファイルのヘッダー作成機能
- 1.4. *.hstファイルの最初の履歴記録
- 1.5. オンライン相場の記録
- 1.6. OnCalculate()関数の編集
- 2. オフラインチャートでのインディケータの起動
- 2.1. ChartSetSymbolPeriod()を使ったオフラインチャートの更新
- 2.2. 操作モード
- 2.3. オフラインチャートへのインディケータのコピー
- 2.4. MACD.mq4への統合
- 2.5. オフラインチャートでのインディケータの簡単な計算
- 2.6. 履歴のダウンロードオフラインチャート
- 3. 練行足バーを表示するインディケータ
- 4. 非標準通貨ペア(ドル指数USDx)を作成するインディケータ
ローソク足から練行足チャートまで
今日、トレーダーの中で最も人気のチャートタイプと言えば、市場の現在の状況を簡単に評価することができるローソク足でしょう。ローソク足チャートは、一つのローソクが捕捉する対象期間の価格変動についての明瞭な情報を与えてくれます。しかし、一部のトレーダー達は、チャートが時間の要素を含んでいて、価格変動にのみ対処をするようになっていることを欠点として捉えています。そして、『三目並べ』や『練行』、『カギ』、『レンジバー』、等量チャートなどが登場しました。
オフラインチャートと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ファイルへ相場履歴を記録する為にあります。
これをすぐに行うのが面倒であっても、最初にファイルの説明を追加する必要があります。これは時間が経つにつれて、私たちのインディケータの役割を思い出すのに役立ちます。
#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 |
私たちのインディケータ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変数に作製したハンドルが格納されます。
*.hstファイルの作成とそのヘッダーの記入後に、ファイルへ相場履歴の最初の記録を行う必要があります。つまり、現時点での全ての履歴でファイルを埋める必要があります。ファイルへの最初の履歴の記録は、FirstWriteHistory()関数が行います(これはインディケータで見ることができます)。
私達のインディケータでは、いつ『最初の履歴記録』が発生するのでしょうか?これはインディケータの初回ダウンロード時に起こると予想するのが論理的です。
最初のダウンロードは、インディケータ内のprev_calculated変数の値で制御することができます(制御する必要があります)。prev_calculated==0という値は、最初のダウンロードが行われることを意味します。しかし同時に、prev_calculated==0は、ダウンロードは初回ではないが、履歴はダウンロードされているということを意味することもあります。履歴のダウンロードの際に何をすべきかは、OnCalculate()のコード編集の際にお話ししましょう。
*.hstファイルの作成とヘッダーの記入、そして最初の履歴の記録が終わったら、オンラインレートの記録へ移ることができます。これはCollectTicks()関数が行います(これはインディケータで見ることができます)。
上記のインディケータの変更は、IndCreateOfflineStep2.mq4ファイルの中で見ることができます。
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()関数の動作アルゴリズム:
図1. OnCalculate()関数のアルゴリズム
インディケータの最終バージョンは、IndCreateOffline.mq4ファイルで参照できます。
2. オフラインチャートでのインディケータの起動
2.1. ChartSetSymbolPeriod()を使用したオフラインチャートの更新
重要:オフラインチャートの更新には、ChartRedraw()の代わりに現在のパラメータのChartSetSymbolPeriod()を呼び出す必要があります。ChartSetSymbolPeriod()の呼び出しは、3秒に1度以上の頻度の間隔を持つCollectTicks()関数で行われます。
また、オフラインチャートの動作ニュアンスを考慮する必要があります。オフラインチャートに設定されたインディケータは、更新するごとに自分のOnCalculate()関数でprev_calculated==0を受け取ります。この特性を覚えておいてください。この特性を考慮した方法は、以下に記載します。
私達が欲する事は、同じインディケータが通常のオンラインチャートでも、オフラインチャートでも動作することです。この時、インディケータが設置されているのがオンラインかオフラインかによって、インディケータの動作が変わります。インディケータがオンラインチャートにある場合、インディケータの機能は上記のインディケータ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ファイルに履歴を記録し、オフラインチャートを更新します。
//--- 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ファイルで参照することができます。
IndMACDDoubleDutyインディケータに、新しい機能を追加します。『操作』モードにありながら、インディケータが作成したオフラインチャートに自分のコピーを送信するようにします。ここでは、ChartSaveTemplate関数とChartApplyTemplate関数が役に立ちます。OnCalcalculate()のアルゴリズムは、このようになります。
図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ファイルで参照することができます。
私達のインディケータは自分自身をオフラインチャートに設置しますが、まだ何も表示しないし、何も計算しません。これを修正しましょう。私達のインディケータを標準の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の始めに記載したニュアンスを思い出してください。
また、オフラインチャートの動作ニュアンスを考慮する必要があります。オフラインチャートに設定されたインディケータは、更新するごとに自分のOnCalculate()関数でprev_calculated==0を受け取ります。この特性を覚えておいてください。この特性を考慮した方法は、以下に記載します。
そして、もう一つ注意があります。OnCalculate()ではprev_calculated==0は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;
私達のインディケータはオフラインチャートで、自分の値を経済的に計算します。その他にも、このモードではインディケータは接続の断絶を正常に処理します。断絶後には追加のバーが新たに計算されます。
あとは私達のインディケータがあるオンラインチャートで履歴のダウンロード(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履歴ファイルの形成ルールを考慮する必要があります。
ルール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で記録します。
この方法は完璧なアルゴリズムである必要はありません。この記事の主な課題は、*.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 = Open; High = Open; Close = High - Renko size; Low = 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"という名前を持ちます。
インディケータのオンライン動作時、高値の代わりに終値(ゼロバー上の終値(一番右))を分析する必要があります。
//+------------------------------------------------------------------+ //| 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
- 無料取引アプリ
- 8千を超えるシグナルをコピー
- 金融ニュースで金融マーケットを探索