前回、
1-2.トレンド系インジケータ作成(終値ライン)初期コード解説編 のページの続きになります。
カスタムインジケータの初期コードの内容を全て理解した所で、ようやく本題に入れます。
随分と長くなってしまったので、3ページを渡って説明する羽目になってしまいました。
前回説明済みなので、上記の初期コードで分からない所は無い筈ですよね??
カスタムインジケータプロパティ設定
まずはカスタムインジケータのプロパティ設定をします。
この設定をしないとカスタムインジケータを表示させる事が出来ません。
このプロパティ設定は、他プロパティ設定と同様に関数の外に書きます。
まだインジケータプロパティ設定しかしていないので、チャート上には何も表示されませんが、
インジケータのプロパティを開くとカスタムインジケータが追加され、デフォルト値が変化していると思います。
今回は使用するカスタムインジケータの本数は1本だけなので、indicator_buffersの値を1にしています。
カスタムインジケータ用動的配列作成とバインド
次に、カスタムインジケータ表示用の動的配列を宣言します。
ソースコード:
double _IndBuffer1[];
この動的配列も関数外に書きます。
この変数配列の値がインジケータ表示で使われる事になります。
そして先程プロパティ設定で用意したインジケータバッファに、この変数配列をバインド(紐付け)します。
これはプログラム実行中に1度だけ行う必要があるので、OnInitイベント関数内で行います。
SetIndexBuffer関数を使って動的配列をバインドするのですが、
インジケータのプロパティ画面を見てわかる通り、最初の
インジケータバッファの番号が0から始まっています。
なのでSetIndexBufferの第1引数で指定するインジケータバッファインデックスは0を指定します。
またインジケータバッファにバインドされた動的配列は、
定義済み変数の動的配列と同様に、
自動的に動的配列の領域が確保されるので、自分で動的配列のサイズ変更する処理は必要ありません。
終値ラインインジケータ表示
カスタムインジケータの最低限の設定が終わったので、インジケータ表示用動的配列に終値を設定してみましょう。
終値を設定すると終値ラインが表示されます。
これもプログラム実行中に処理しなければならない内容なので、OnCalculateイベント関数内にプログラムを書きます。
直近4つの終値を設定したので、4本分の終値ラインが表示されました。
では、forループでバーの本数分の設定を行いましょう。
ソースコード:
int end_index = Bars;
for( int icount = 0 ; icount < end_index ; icount++ ) {
_IndBuffer1[icount] = Close[icount];
}
終値ラインが全て表示されるようになりましたが、実は未だ未完成です。
現状では、確定済みのバーに対するインジケータが毎回更新されています。
このカスタムインジケータは確定済みのバーに対しては1度だけデータを設定し、未確定の先頭のバーだけ毎回更新すれば良いので、
先頭のバーだけ常時更新するようにします。
ただ、OnCalculateイベント関数はtick更新時に処理を取りこぼす事がある為、一つ前のバーのインジケータも常時更新した方が無難です。
なので先頭と一つ前のバーだけ常時更新させるようにします。
ソースコード:
int end_index = Bars - prev_calculated;
if ( end_index <= 2) {
end_index = 2;
}
for( int icount = 0 ; icount < end_index ; icount++ ) {
_IndBuffer1[icount] = Close[icount];
}
これで無駄な再計算は行われなくなりましたが、最後に不具合対策を行います。
不具合対策(バッファオーバーフロー対策)
インジケータバッファにバインドした動的配列は自動的に動的配列の領域が確保されると言いましたが、
動的配列が確保されない時もあります。
それはヒストリカルデータが無い時です。
ヒストリカルデータが無い場合、自動で直近のデータが取得されますが、データ取得完了する前にOnCalculateイベント関数が呼ばれた場合、
動的配列が自動確保されていない状態で動的配列にアクセスする事になります。
その結果バッファオーバーフローによる不具合が発生します。
ソースコード:
int end_index = Bars - prev_calculated;
if ( end_index <= 2) {
end_index = 2;
}
if ( Bars <= 2 ) {
return 0;
}
for( int icount = 0 ; icount < end_index ; icount++ ) {
_IndBuffer1[icount] = Close[icount];
}
少し雑な不具合対策処理ですが、配列アクセスする前に
ArraySize関数を使って配列要素数をチェックする方法等もあります。
一般的には配列要素数をチェックして、配列外アクセスさせない処理を入れますが、今回のケースでは動的配列のサイズと
Barsが一緒である事を知っていて、
尚且再計算させない配列があるのと、分かり易いプログラムにしたかったので、このような形になりました。
やり方は人それぞれですので、プログラミング未経験者の方はこのやり方に拘る必要はありません、ある程度理解してきたらもっとスマートなコードを書いても問題ありません。
ソースコードのハードコーディングを整理(defineマクロ)
(この項目にはプログラミング未経験者向けの説明が含まれています)
上の項目のソースコードで、インデックス判定に2という数値を使用しています。
ちなみに
2という数値を直接書く行為はマジックナンバーと呼ばれます。ただ、EAのマジックナンバーと勘違いしてしまう可能性があるのでハードコーディングと呼びます。
ハードコーディングは良くないコードの書き方ですので整理しましょう。
(とはいえ、適当に作った時はついハードコーディングしてしまい、そのまま放置して忘れてしまう事があります。仕事でやる時はそんな事はありませんが)
ハードコーディングはコードを書いた人にしか意味と影響範囲が分からないので、他人が見たり製作者が数カ月後に見直した時に何の数値が分からなくなってしまいます。
影響範囲が分からない為、変更した時に変更漏れが発生する事もあります。
ちなみに紛らわしいのですが、
EAの識別用マジックナンバーとは無関係です。
defineマクロを使ってマジックマンバーを置き換えましょう。
まずは
defineを定義をします。
ソースコード:
#define IND_MIN_INDEX 2
このマクロは、プロパティ設定と同様に関数の外に書きます。
IND_MIN_INDEXというマクロを定義しましたが、この名称は作成者が自由に名付けて下さい。
それではハードコーディングの2を、IND_MIN_INDEXに置き換えましょう。
ソースコード:
int end_index = Bars - prev_calculated;
if ( end_index <= IND_MIN_INDEX ) {
end_index = IND_MIN_INDEX;
}
if ( Bars <= IND_MIN_INDEX ) {
return 0;
}
for( int icount = 0 ; icount < end_index ; icount++ ) {
_IndBuffer1[icount] = Close[icount];
}
このIND_MIN_INDEXは、コンパイル時(正確にはコンパイルの直前)に2へ置き換えられます。
つまりソースコード上からハードコーディングは無くなりましたが、翻訳された後の機械語には全く影響を与えません。
完成したサンプルソースコード
最後に出来上がったソースコードをまとめると、
ソースコード:
#property copyright "yuki"
#property link "https://yukifx.web.fc2.com/"
#property version "1.00"
#property strict
#property indicator_chart_window
#property indicator_buffers 1
#property indicator_color1 clrWhite
#property indicator_width1 2
#define IND_MIN_INDEX 2
double _IndBuffer1[];
int OnInit()
{
SetIndexBuffer( 0, _IndBuffer1 );
return( INIT_SUCCEEDED );
}
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[])
{
int end_index = Bars - prev_calculated;
if ( end_index <= IND_MIN_INDEX ) {
end_index = IND_MIN_INDEX;
}
if ( Bars <= IND_MIN_INDEX ) {
return 0;
}
for( int icount = 0 ; icount < end_index ; icount++ ) {
_IndBuffer1[icount] = Close[icount];
}
return( rates_total );
}
実際に手を加えたのは19行だけですので非常に短いソースコードです。
これは最低限の内容だけですので、ここから好きなようにカスタマイズしていく事になります。
課題
上記サンプルソースコード変更して、高値ラインのカスタムインジケータを作成して下さい。
■ヒント
・
High[]