今回はサブウインドウに表示するオシレーター系インジケータのRCIを作成します。
いつも通りに今回も他カスタムインジケータをベースにして新しいカスタムインジケータを作成します。
それでは
以前作成したRSIのカスタムインジケータのソースコードのTestSubRSI.mq4をコピペして、
ファイル名をTestSubSelfRCI.mq4に変更しましょう。
RCIの計算式からロジックを考える
RCIは
事前定義されているテクニカルインジケータ関数に無いので、自作する必要があります。
前回自作した時は単純移動平均だったので、そんなに考える事無く作る事が出来ました。
RCI(Rank Correlation Index)のように考えて分かるような類では無い場合は、まずは計算式を調べる必要があります。
d:日付順位と価格順位の順位差を2乗し、合計した数値
n:算出期間
この計算式からまずどんなデータが必要かを考えます。
①日付データ
②価格データ
③日付順位(ランク)
④価格順位(ランク)
まずはこの4つのデータが必要です。
データは算出期間分取得しなければなりません。
更にランク付けしなければならない為、
データをソートしなければなりません。
また価格データで同値が複数ある場合、価格順位(ランク)は中間値になります。
(中間値の情報提供ありがとうございました。)
ソートしてランク付けしたデータを取得出来なければRCIを算出する事が出来ません。
struct(構造体)型を宣言する
4種類のデータ配列が必要で、それをソートしなければならない事が分かっているので、
データ取得用の変数は
struct(構造体)を使用します。
struct(構造体)はデータ管理し易くなる非常に便利なものですので、
プログラミング未経験者はなるべく使うようにしましょう。
構造体は簡単に言うと、複数のデータをひとまとめにして取り扱う事が出来る型です。
構造体を使うにはまず、構造体の型を宣言する必要があります。
必要なデータは4つですが、価格順位は同値調整が必要なので価格順位(調整後)を含めて5つデータを用意します。
これは関数の外を書きます。
struct_rci_dataという名の
構造体型が宣言されました。
このstruct_rci_dataという型を使って変数を宣言すると使えるようになります。
変数宣言方法は、他変数宣言とやり方は一緒です。
実際の使い方は、RCIを算出する時に説明します。
enum列挙型を宣言する
RCIは3本表示して使う人が多いので、最初から3本表示出来るように作ってしまいます。
1本表示にして、チャートにRCIインジケータを3回適用すれば済む話ですが、
せっかく自作するのなら使い勝手が良いように最初から3本表示させます。
ついでに表示本数を1~3本から選択出来るようにします。
このように指定したデータを選択して使うようにさせるには
enum列挙型を使います。
enum列挙型も既に紹介済みなので、詳細説明は割愛します。
今までは事前に用意された
enum列挙型を使用していましたが、
今回は上記構造体と同様に、
enum列挙型を自作して宣言します。
ソースコード:
enum ENUM_LINECOUNT {
RCI_DISPLINE_1 = 1,
RCI_DISPLINE_2,
RCI_DISPLINE_3
};
これは関数の外にソースコードを書きます。
ENUM_LINECOUNT という名の
enum列挙型が宣言されました。
インプットパラメータを追加する
インプットパラメータは短期・中期・長期の算出期間設定と、
RCI表示本数設定にします。
ソースコード:
input int _InputCalPeriod_S = 9;
input int _InputCalPeriod_M = 26;
input int _InputCalPeriod_L = 52;
sinput ENUM_LINECOUNT _InputLineCount = RCI_DISPLINE_3;
RCI表示本数の型は、先程宣言した自作
enum列挙型を使用しています。
enum列挙型の変数を宣言した場合、
設定出来る値はそのenum列挙の定数リストしか使えません。
なので初期値はENUM_LINECOUNT列挙の定数リストのRCI_DISPLINE_3を設定しています。
インプットパラメータを変えただけなので、今時点でコンパイルをすると_InputCalPeriodを使用している箇所でエラーになります。
なるべくコンパイルが通る状態を保っていたいので、_InputCalPeriodを使用している部分を削除しましょう。
まずはOnInit()関数内で使用している部分を削除します。
続いてOnCalculate()関数内で使用している部分を丸ごと削除します。
これでコンパイルが通るようになった筈ですので、動かしてプロパティを確認してみましょう。
パラメータの入力画面はこのようになります。
それから_InputLineCountだけinputでは無く、
sinputを使用していますが、
これはストラテジーテスターでパラメータの最適化する必要が無いものに対して使います。
パラメータの最適化については別途説明しますが、基本的にEAで行う作業です。
今はカスタムインジケータ作成をしているので、特別気にする必要はありません。
インジケータプロパティ設定
インジケータが最大3本表示されるので、インジケータプロパティを調整します。
カスタムインジケータのバッファ数(indicator_buffers)の値はプログラムの処理の途中で変更する事は出来ない為、
最大表示数の3を設定します。
RCIの範囲は -100 ~ 100 なので、サブウインドウスケール下限値設定(indicator_minimum)は-100を設定します。
レベルライン(indicator_level1~3)は、中心値と80%の閾値用に設定します。
コンパイルして、動作チェックしてみましょう。
サブウインドウの範囲が -100 ~ 100になって、レベルラインが3本表示されていますね。
プロパティの"色の設定"で、インジケータが3本になって各種デフォルト色が設定されている事を確認します。
インジケータ短縮名設定
今回はインプットパラメータが多い為、インジケータの短縮名を作成する処理が長くなります。
処理が長いので、短縮名を作成する関数を作成しましょう。
ソースコード:
string GetShortName()
{
string file_name;
string str_array[];
string input_str;
string ret_name;
int get_arraycount;
get_arraycount = StringSplit( __FILE__ , '.' , str_array );
if ( get_arraycount > 0 ) {
file_name = str_array[0];
}
input_str = (string)_InputCalPeriod_S;
if ( _InputLineCount >= RCI_DISPLINE_2 ) {
input_str += StringFormat( ",%d" , _InputCalPeriod_M );
}
if ( _InputLineCount >= RCI_DISPLINE_3 ) {
input_str += StringFormat( ",%d" , _InputCalPeriod_L );
}
ret_name = StringFormat( "%s(%s)" , file_name , input_str );
return ret_name;
}
GetShortName()関数を定義して、OnInit()関数内の処理を持ってきました。
少し処理を増やしていますが、これはインプットパラメータでRCI表示本数によって、短縮名が変わるようにしています。
次はOnInit()の処理を変更して、GetShortName()関数を呼ぶようにしましょう。
OnInit()関数内がだいぶスッキリしましたね。
それではコンパイルして動作チェックしましょう、インプットパラメータでRCI表示本数を変えると短縮名が変わります。
■RCI3本表示設定時
■RCI1本表示設定時
カスタムインジケータ用動的配列追加
表示するインジケータが最大3本になったので、カスタムインジケータ用動的配列追加を追加します。
動的配列を追加したら、バインドする事も忘れずに行います。
OnInit()関数内で処理を追加します。
コンパイルしても動作確認出来る内容はありませんが、コンパイルが通る事だけ確認しておきます。
RCI算出関数を定義する
以前紹介した自作SMA()関数と同様に、RCI算出するCalRCI()関数を定義します。
引数と戻り値は
自作SMA()関数と全く一緒です。
インプット変数配列と算出期間から
配列範囲外にアクセスさせない処理も全く一緒です。
ここからRCI算出処理を追加していきます。
構造体配列を宣言する
まずこの
ページ序盤で宣言した自作構造体のstruct_rci_data型の変数を宣言します。
ソースコード:
struct_rci_data temp_st_rci[];
int arrayst_count = ArrayResize(
temp_st_rci ,
in_time_period ,
0
);
CalRCI()関数内の配列範囲内チェック処理内にこの追加します。
この
ページの序盤でも説明しましたが、RCIを算出するには
①日付データ
②価格データ
③日付順位(ランク)
④価格順位(ランク)
上記データを算出期間分取得しなければなりません。
その為、
構造体型変数のtemp_st_rciは動的配列で宣言しています。
必要な配列サイズは算出期間分なので、
ArrayResize()関数で算出期間分の配列を確保しています。
算出期間のin_time_periodが5の場合、動的配列が5つ確保されるので、上記イメージのような配列が確保されます。
構造体メンバにデータ設定する
構造体内の変数を構造体メンバと呼びます。
構造体メンバにアクセスするには構造体型変数のあとにピリオド(.)を付けてアクセスします。
データを取得してtemp_st_rciの各メンバにデータを設定します。
ソースコード:
int temp_rank = 1;
int arr_count = 0;
for ( int icount = in_index ; icount < end_index ; icount++ ) {
temp_st_rci[arr_count].date_value = Time[icount];
temp_st_rci[arr_count].rate_value = in_array[icount];
temp_st_rci[arr_count].rank_date = temp_rank;
temp_rank++;
arr_count++;
}
先程追加した処理の後に、上記処理を追加します。
日付データは
定義済み変数のTime[]の値を設定します。
価格データは
以前紹介した自作SMA()関数と同様に、引数の変数配列から設定します。
日付順位(ランク)は、インデックスの小さい方が新しい日付になるので、設定順の順番でランク付けします。
仮に1時間足のドル円で動作させた場合、上記のようになります
(まだCalRCI()関数の呼び出す処理がない為、あくまで例です)。
構造体型変数配列をソートする
価格順位(ランク)は価格データを基にランク付けしなければならない為、まずは価格データで配列をソートします。
ソースコード:
for ( int main_count = 0; main_count < arrayst_count - 1 ; main_count++ ) {
for ( int sub_count = main_count + 1; sub_count < arrayst_count ; sub_count++ ) {
if ( temp_st_rci[main_count].rate_value < temp_st_rci[sub_count].rate_value ) {
struct_rci_data temp_swap = temp_st_rci[main_count];
temp_st_rci[main_count] = temp_st_rci[sub_count];
temp_st_rci[sub_count] = temp_swap;
}
}
}
価格データのrate_valueを比較して、比較先よりも値が小さい場合、構造体型変数配列をスワップしています。
これを配列全てに対して行っています。
(ちなみに余談ですが、構造体では無く多次元配列でデータを持っていた場合、
ArraySort()関数で簡単にソート出来ます。)
価格ソートが終わったら、ソートされた順番でランク付けをします。
価格順位(調整後ランク)は後で調整するので、今は価格順位(ランク)と同じ値を設定します。
ソースコード:
for( int main_count = 0; main_count < arrayst_count ; main_count++ ) {
int temp_set_rank = main_count + 1;
temp_st_rci[main_count].rank_rate = temp_set_rank;
temp_st_rci[main_count].rank_adjust_rate = (double)temp_set_rank;
}
価格順位(ランク)は、価格ソート後なのでインデックスの小さい方が高い価格になるので、設定順の順番でランク付けします。
同値ランクの中間値を設定する
価格データで同値が複数ある場合、価格順位(ランク)は中間値に調整しなければならないので、
価格順位でソートしたデータを上からチェックして、同値の価格データの価格順位を調整します。
ソースコード:
for ( int main_count = 0 ; main_count < arrayst_count - 1 ; main_count++ ) {
double sum_rank = (double)temp_st_rci[main_count].rank_rate;
int same_count = 0;
for ( int sub_count = main_count + 1 ; sub_count < arrayst_count ; sub_count++ ) {
if ( temp_st_rci[main_count].rate_value == temp_st_rci[sub_count].rate_value ) {
sum_rank += (double)temp_st_rci[sub_count].rank_rate;
same_count++;
} else {
break;
}
}
if ( same_count >= 1 ) {
double set_adjust_rank = sum_rank / ((double)same_count + 1);
for( int ad_count = 0 ; ad_count <= same_count; ad_count++ ) {
temp_st_rci[ad_count + main_count].rank_adjust_rate = set_adjust_rank;
}
main_count += same_count;
}
}
これでRCI算出に必要なデータが揃いました。
やっとRCIを算出する事が出来ますw
上記ソースコードのif文判定で注意しなければならない処理があります。
if ( temp_st_rci[main_count].rate_value == temp_st_rci[sub_count].rate_value ) {
一見すると何も問題なさそうですが、double型の値の同値(完全一致)判定は本来であれば使うべきではありません。
今回はrate_valueに設定される値が、小数点第5位までの精度という事が分かっているので完全一致判定を使っても問題が発生しませんが、
判定する値が除算によって求めれらた値の場合、除算結果が割り切れない値の時に完全一致判定がうまく動作しない事があります。
詳細は
doubleのリファレンスを見て下さい。
また
break処理は最も近いループから抜けます。
ループを一つ抜けるだけで、全てのループから抜ける訳ではないので注意して下さい。
RCIを算出する
もう特に説明は要らないと思いますが、RCIの計算式通りにプログラムを書くだけです。
d:日付順位と価格順位の順位差を2乗し、合計した数値
n:算出期間
価格順位は、価格順位(調整後)の値を使用します。
ソースコード:
double sum_d = 0;
double temp_diff = 0;
for( int main_count = 0; main_count < arrayst_count ; main_count++ ) {
temp_diff = (double)temp_st_rci[main_count].rank_date - temp_st_rci[main_count].rank_adjust_rate;
sum_d += MathPow( temp_diff , 2 );
}
int temp_div = in_time_period * ( (int)MathPow( in_time_period , 2 ) - 1 );
if ( temp_div > 0) {
ret = 100 * ( 1 - ( 6 * sum_d / (double)temp_div ) );
}
もうこれぐらいのプログラムであれば説明不要ですよね??
MathPow()関数が出てきましたが、これは
数学関数で
べき乗の計算を行ってくれます。
RCI算出部分で、sum_dとtemp_divを
double型で
キャストしているのは、
int型変数を除算すると小数点以下が丸められてしまうからです。
CalRCI()関数はこれでやっと完成です。
RCIラインを表示する
完成したCalRCI()関数を呼び出してRCIラインを表示させましょう。
OnCalculate()関数内のiRSI()関数呼び出しを削除した所に以下ソースコードを追加します。
ソースコード:
double get_value;
get_value = CalRCI (
Close,
_InputCalPeriod_S,
icount
);
_IndBuffer1[icount] = get_value;
if ( _InputLineCount >= RCI_DISPLINE_2 ) {
get_value = CalRCI (
Close,
_InputCalPeriod_M,
icount
);
_IndBuffer2[icount] = get_value;
}
if ( _InputLineCount >= RCI_DISPLINE_3 ) {
get_value = CalRCI (
Close,
_InputCalPeriod_L,
icount
);
_IndBuffer3[icount] = get_value;
}
コンパイルして動作チェックしてみましょう。
RCI3本表示と、2本表示と、1本表示の3パターンでチェックしてインプットパラメータが機能しているか確認します。
完成したサンプルソースコード
最後に出来上がったソースコードをまとめると、
ソースコード:
#property copyright "yuki"
#property link "https://yukifx.web.fc2.com/"
#property version "1.00"
#property strict
#property indicator_separate_window
#property indicator_buffers 3
#property indicator_color1 clrWhite
#property indicator_width1 1
#property indicator_color2 clrYellow
#property indicator_width2 1
#property indicator_color3 clrMagenta
#property indicator_width3 1
#property indicator_minimum -100
#property indicator_maximum 100
#property indicator_level1 0
#property indicator_level2 80
#property indicator_level3 -80
#property indicator_levelcolor clrGray
#property indicator_levelstyle STYLE_DOT
#property indicator_levelwidth 1
#define IND_MIN_INDEX 2
double _IndBuffer1[];
double _IndBuffer2[];
double _IndBuffer3[];
struct struct_rci_data {
datetime date_value;
double rate_value;
int rank_date;
int rank_rate;
double rank_adjust_rate;
};
enum ENUM_LINECOUNT {
RCI_DISPLINE_1 = 1,
RCI_DISPLINE_2,
RCI_DISPLINE_3
};
input int _InputCalPeriod_S = 9;
input int _InputCalPeriod_M = 26;
input int _InputCalPeriod_L = 52;
sinput ENUM_LINECOUNT _InputLineCount = RCI_DISPLINE_3;
int OnInit()
{
IndicatorSetString( INDICATOR_SHORTNAME , GetShortName() );
SetIndexBuffer( 0, _IndBuffer1 );
SetIndexBuffer( 1, _IndBuffer2 );
SetIndexBuffer( 2, _IndBuffer3 );
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++ ) {
double get_value;
get_value = CalRCI (
Close,
_InputCalPeriod_S,
icount
);
_IndBuffer1[icount] = get_value;
if ( _InputLineCount >= RCI_DISPLINE_2 ) {
get_value = CalRCI (
Close,
_InputCalPeriod_M,
icount
);
_IndBuffer2[icount] = get_value;
}
if ( _InputLineCount >= RCI_DISPLINE_3 ) {
get_value = CalRCI (
Close,
_InputCalPeriod_L,
icount
);
_IndBuffer3[icount] = get_value;
}
}
return( rates_total );
}
double CalRCI(
const double &in_array[],
int in_time_period ,
int in_index
)
{
double ret = 0;
int array_count = ArraySize(in_array);
int end_index = in_index + in_time_period;
if ( end_index < array_count ) {
struct_rci_data temp_st_rci[];
int arrayst_count = ArrayResize(
temp_st_rci ,
in_time_period ,
0
);
int temp_rank = 1;
int arr_count = 0;
for ( int icount = in_index ; icount < end_index ; icount++ ) {
temp_st_rci[arr_count].date_value = Time[icount];
temp_st_rci[arr_count].rate_value = in_array[icount];
temp_st_rci[arr_count].rank_date = temp_rank;
temp_rank++;
arr_count++;
}
for ( int main_count = 0; main_count < arrayst_count - 1 ; main_count++ ) {
for ( int sub_count = main_count + 1; sub_count < arrayst_count ; sub_count++ ) {
if ( temp_st_rci[main_count].rate_value < temp_st_rci[sub_count].rate_value ) {
struct_rci_data temp_swap = temp_st_rci[main_count];
temp_st_rci[main_count] = temp_st_rci[sub_count];
temp_st_rci[sub_count] = temp_swap;
}
}
}
for( int main_count = 0; main_count < arrayst_count ; main_count++ ) {
int temp_set_rank = main_count + 1;
temp_st_rci[main_count].rank_rate = temp_set_rank;
temp_st_rci[main_count].rank_adjust_rate = (double)temp_set_rank;
}
for ( int main_count = 0 ; main_count < arrayst_count - 1 ; main_count++ ) {
double sum_rank = (double)temp_st_rci[main_count].rank_rate;
int same_count = 0;
for ( int sub_count = main_count + 1 ; sub_count < arrayst_count ; sub_count++ ) {
if ( temp_st_rci[main_count].rate_value == temp_st_rci[sub_count].rate_value ) {
sum_rank += (double)temp_st_rci[sub_count].rank_rate;
same_count++;
} else {
break;
}
}
if ( same_count >= 1 ) {
double set_adjust_rank = sum_rank / ((double)same_count + 1);
for( int ad_count = 0 ; ad_count <= same_count; ad_count++ ) {
temp_st_rci[ad_count + main_count].rank_adjust_rate = set_adjust_rank;
}
main_count += same_count;
}
}
double sum_d = 0;
double temp_diff = 0;
for( int main_count = 0; main_count < arrayst_count ; main_count++ ) {
temp_diff = (double)temp_st_rci[main_count].rank_date - temp_st_rci[main_count].rank_adjust_rate;
sum_d += MathPow( temp_diff , 2 );
}
int temp_div = in_time_period * ( (int)MathPow( in_time_period , 2 ) - 1 );
if ( temp_div > 0) {
ret = 100 * ( 1 - ( 6 * sum_d / (double)temp_div ) );
}
}
return ret;
}
string GetShortName()
{
string file_name;
string str_array[];
string input_str;
string ret_name;
int get_arraycount;
get_arraycount = StringSplit( __FILE__ , '.' , str_array );
if ( get_arraycount > 0 ) {
file_name = str_array[0];
}
input_str = (string)_InputCalPeriod_S;
if ( _InputLineCount >= RCI_DISPLINE_2 ) {
input_str += StringFormat( ",%d" , _InputCalPeriod_M );
}
if ( _InputLineCount >= RCI_DISPLINE_3 ) {
input_str += StringFormat( ",%d" , _InputCalPeriod_L );
}
ret_name = StringFormat( "%s(%s)" , file_name , input_str );
return ret_name;
}