2015年5月1日金曜日

MT4プログラムの小ネタ アメリカ夏時間を判定するプログラム

自作EAのバックテストによる調整に時間をとられていて、プログラムネタが少々つきかけてます。(^^;;
ゴールデンウィーク中にまた貯めたいと思いますので、今日は小ネタで。

EA作っていると、特定の時間帯は取引を避けたり逆に取引したりしたくなることありませんか?
大体アメリカの重要指標の発表時間帯(現地8:30or11:00)だったりします。あちらは夏時間を採用していて特定の期間だけ標準時より1時間早くなったりするので面倒です。夏時間になったらEAを止めて時間補正パラメータを入れ替えるとかもありでしょうが、ここははやりプログラムで判定してあげる方が親切ですしバックテストの精度も上がるかと思います。

で、TimeGMT()で取得した値を返すとアメリカが夏時間かどうかを判定するプログラムです。
なおバックテストではTimeGMT()が正しくGMTを返してこない為、工夫が必要です。このあたりのお話はまた別途したいと思います。
EU夏時間も同じような計算で取得可能です。

// 曜日
#define SUNDAY 0
#define MONDAY 1
#define TUESDAY 2
#define WEDNESDAY 3
#define THURSDAY 4
#define FRIDAY 5
#define SATURDAY 6

// 一時間(秒)
#define ONEHOUR   (3600)

// GMTと東アメリカ標準時間の差(-5時間)
#define GMT_TO_AMERICA (-18000)

//------------------------------------------------------------------
// サマータイム指定日時がサマータイムかどうか
// return 指定GMT時間がサマータイムの場合 true
bool IsAmericaSummerTime(
datetime gmt         // GMT
)
{
datetime americaTime = gmt + GMT_TO_AMERICA;

MqlDateTime dt;
TimeToStruct(americaTime, dt);

//夏時間判定
datetime startSummerTime;
datetime endSummerTime;
MqlDateTime work;
int week;
if( dt.year < 2007 )
{
// 標準時ベースで4月第1日曜日 AM2:00 ~ 10月最終日曜日AM1:00
work.year = dt.year;
work.mon = 4;
work.day = 1;
work.hour = 2;
work.min = 0 ;
work.sec = 0 ;
week = TimeDayOfWeek(StructToTime(work));

if( week != SUNDAY )
work.day = work.day + 7 - week;
startSummerTime = StructToTime(work) ;

work.mon = 10;
work.day = EndOfMonth(work.year, work.mon);
work.hour = 2;
work.min =  0;
work.sec = 0 ;
week = TimeDayOfWeek(StructToTime(work));
work.day = work.day - week;

endSummerTime = StructToTime(work) ;
}
else
{
// 標準時ベースで3月第2日曜日 AM2:00 ~ 11月第1日曜日AM1:00
work.year = dt.year;
work.mon = 3;
work.day = 8;
work.hour = 2;
work.min = 0 ;
work.sec = 0 ;

week = TimeDayOfWeek(StructToTime(work));
if( week != SUNDAY )
work.day = work.day + 7 - week;
startSummerTime = StructToTime(work) ;

work.mon = 11;
work.day = 1;
work.hour = 2;
work.min = 0 ;
work.sec = 0 ;
week = TimeDayOfWeek(StructToTime(work));
if( week != SUNDAY )
work.day = work.day + 7 - week;
endSummerTime = StructToTime(work) ;
}

return startSummerTime <= americaTime && americaTime < endSummerTime ;
}


なお、関数内で「MQL4でEndOfMonth 月末日付を取得する。」を使用しています。

まぁ、こーいった面倒なところの判定を勝手にやってくれるようにできるのがEAのいいところです。
テクニカルだけ見て裁量取引しているとたまに重要指標発表の時間帯のことをすっかり忘れて手痛い目に合う事がありますよね??^^;;


2015年4月30日木曜日

オシレータにも鈍感力を!期間が変化するRSI VLDMI

今日はVLDMI(Variable Length Dynamic Momentum Index)です。

今回は、はっきりとした論文等が見つからなかったため、調べた結果の私なりの解釈で実装したVLDMIです。
ざっくりいうと期間が動的に変化するRSI!ってことで、私の認識間違ってたらご指摘ください^^;;

さておき、Variable Lengthというのは、相場が凪いでいる時は鈍感に、動きだしたら敏感にするという考え方です。この考え方はいろんなオシレータの改良に使えると考えています。
今回はVLDMIを実装してみてRSIと比較してみました。

VLDMI.PNG
水色の線がVLDMI(14)、青色の線がRSI(14)になります。RSIに比べてVLDMIが動き出したときには敏感に反応していることが分かります。※()の中は期間を表しています。
VLDMIは期間を大体50%~200%の範囲で変動させます。つまりVLDMI(14)は 期間を7~24を標準偏差の変化率から動的に変更しているRSIという事になります。

VLDMIの見方ですがRSIと同様に70超えで買われすぎ30以下で売られすぎです。
チャートを眺めた感じ、このオシレータ5分足には結構使えるかな?といったところです。短期トレード向けか、押し目狙いのオシレータとなります。あと、ダイバージェンス現象がRSIと比べかなりはっきり出ます。そのあたりを参考にしている人も良いのではないでしょうか?

//------------------------------------------------------------------
// VLDMI Variable Length Dynamic Momentum Index
#property copyright "Copyright 2015,  Daisuke"
#property link      "http://mt4program.blogspot.jp/"
#property version   "1.00"
#property strict

#property indicator_separate_window
#property indicator_minimum   0
#property indicator_maximum   100
#property indicator_level1    70
#property indicator_level2    30
#property indicator_levelcolor clrSilver
#property indicator_levelstyle STYLE_DOT

//バッファーを指定する。
#property indicator_buffers 2

//プロット数を指定する。
#property indicator_plots   1

// CDMAインデックス
#property indicator_label1  "VLDMI"
#property indicator_type1   DRAW_LINE
#property indicator_color1  clrAqua
#property indicator_style1  STYLE_SOLID
#property indicator_width1  1

#property indicator_type2   DRAW_NONE

// 入力パラメータ RSI算出基準期間
input int VariableLengthBase = 14;

// 入力パラメータ RSI算出価格
input ENUM_APPLIED_PRICE RsiPrice = PRICE_CLOSE;

// 入力パラメータ 標準偏差期間
input int StdPeriod = 5;

// 入力パラメータ 標準偏差平均期間
input int StdAvgPeriod = 10;

//入力パラメータ 表示移動
input int   StdShift = 0;

//入力パラメータ 移動平均種別
input ENUM_MA_METHOD StdMethod = MODE_EMA;

//入力パラメータ 適用価格
input ENUM_APPLIED_PRICE StdPrice = PRICE_CLOSE;

// バッファー
double vldmiBuffer[];
double stdDevBuffer[];

//------------------------------------------------------------------
//初期化
int OnInit()
{
SetIndexBuffer(0, vldmiBuffer);
SetIndexBuffer(1, stdDevBuffer);
SetIndexDrawBegin(0, (int)(VariableLengthBase * 2.5));
IndicatorShortName("VLDMI(" + IntegerToString(VariableLengthBase) + ")");

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[])            //スプレット
{
for( int i = rates_total - prev_calculated - 1 ; i >= 0; i-- )
{
// 標準偏差を求める。
stdDevBuffer[i] = i >= rates_total - StdPeriod - 1 ?
0 : iStdDev(NULL,PERIOD_CURRENT, StdPeriod, StdShift, StdMethod, StdPrice, i);

// 標準偏差のStdAvgPeriod間の平均を求める。
double stdDevAvg = iMAOnArray(stdDevBuffer, rates_total, StdAvgPeriod, 0, MODE_SMA, i);
if( stdDevAvg == 0 || stdDevBuffer[i] == 0)
{
vldmiBuffer[i] = 0 ;
continue;
}

// RSI対象期間を求める。対象期間 = 基準期間 / ( 標準偏差 / 標準偏差の平均 )
int variableLength = (int)(VariableLengthBase * stdDevAvg / stdDevBuffer[i] );
vldmiBuffer[i] = iRSI(NULL, PERIOD_CURRENT, variableLength, RsiPrice, i);
}

return (rates_total - 1);
}


 やっていることは割と単純で、標準関数で用意されているiStdDevを使用して標準偏差を求めて、iMAOnArrayの単純平均を利用して平均値を出しています。その後、現在の標準偏差/平均値を元に対象期間を変化させています。最後に用意されているiRSIを使用してRSI値を求めてます。

iStdDev
double iStdDev(
string symbol, // 対象通貨ペア
int timeframe, // 時間足
int ma_period, // 移動平均期間
int ma_shift, // 移動平均シフト量
int ma_method, // 移動平均種別
int applied_price, // 移動平均対象価格
int shift // シフト量
);
標準で用意されているiStdDevですが、なぜか移動平均のパラメータを要求されます。移動平均に対する標準偏差をとる関数のようです。自分で標本標準偏差を計算してVLDMIを表示させてみたのですが、あまりグラフに変化が無かったため、iStdDevを使っています。
厳密に標準偏差を取りたい場合や、High/Log/Closeの値も含めてばらつきを見たい場合などは自力で計算する必要があります。

後の二つは紹介だけ。
iMAOnArray
double iMAOnArray(
double array[], // 対象配列
int total, // 配列の要素数
int ma_period, // 移動平均期間
int ma_shift, // 移動平均シフト量
int ma_method, // 移動平均対象価格
int shift // シフト量
);

iRSI
double iRSI(
string symbol, // 対象通貨ペア
int timeframe, // 時間足
int period, // 期間
int applied_price, // 対象価格
int shift // シフト量
);

 余談ですが、対象期間を求めるような式のように割り算が続く場合は、なるべく掛け算になるように式を置き換えています。四則演算の速度は + = - > × >>> ÷ とダントツ割り算が遅くなります。繰り返し演算する場合はなるべく割り算を減らした方がパフォーマンスが良いです。まぁ今のPCは性能が高いので誤差の範囲!って言われてしまうとその通りなのですが・・癖です(笑)

 あと、このオシレータ、標準偏差の計算に時間がかかるためか、MT4のチャート要素数が多くなると、時間足の切り替え動作などが結構重くなります。標準偏差の計算時にルート演算が挟まっている為と思いますが、チャートのバー数などを極端に大きくしている場合などはご注意ください。

 さて、そろそろゴールデンウィークですね。
 そろそろ、手持ちのネタが尽きつつあるので、ゴールデンウィーク中に何か探したいと思います。


2015年4月28日火曜日

青赤荒れ狂う! MT4でS/Rオシレータのシグナルを青赤帯でチャートに表示する。その2

前回に続いてS/Rオシレータに青赤帯を出したいと思います。
組み込んだコードがこちらです。

修正箇所は赤字にしてあります。
//------------------------------------------------------------------
// ウィドナー博士のサポートレジスタンス オシレーター
#property copyright "Copyright 2015,  Daisuke"
#property link      "http://mt4program.blogspot.jp/"
#property version   "1.00"
#property strict

#include <Arrays/ArrayDouble.mqh>

//オブジェクト名
#define WSRO_OBJECT_NAME "WSRO"

#property indicator_separate_window
#property indicator_minimum    0
#property indicator_maximum    100

//バッファーを指定する。
#property indicator_buffers 2

//プロット数を指定する。
#property indicator_plots   2

//サポートオシレータ情報
#property indicator_label1  "Support"
#property indicator_type1   DRAW_LINE
#property indicator_color1  clrAqua
#property indicator_style1  STYLE_SOLID
#property indicator_width1  1

//レジスタンスオシレータ
#property indicator_label2  "Resistance"
#property indicator_type2   DRAW_LINE
#property indicator_color2  clrIndianRed
#property indicator_style2  STYLE_SOLID
#property indicator_width2  1

//入力パラメータ 期間
input int Period = 6;

//入力パラメータ HL期間(奇数)
input int HLPeriod = 9;

//入力パラメータ 売買シグナルをチャート上に表示するかどうか
input bool IsShowBuySellRect = true;

//入力パラメータ 売買シグナル 買時
input color BuyColor = C'0,0,128';

//入力パラメータ 売買シグナル 売時
input color SellColor = C'78,0,0';

//インジケーター バッファ
double wsoBuffer[];
double wroBuffer[];

//S1-Sn
CArrayDouble supports;
//R1-Rn
CArrayDouble resistances;
//------------------------------------------------------------------
//初期化
int OnInit()
{
if( HLPeriod % 2 == 0 || HLPeriod < 5) return (INIT_PARAMETERS_INCORRECT);
if( Period < 3 ) return (INIT_PARAMETERS_INCORRECT);

//インジケータバッファを初期化する。
SetIndexBuffer(0,wsoBuffer);
SetIndexBuffer(1,wroBuffer);

SetIndexDrawBegin(0, HLPeriod + 1);
SetIndexDrawBegin(1, HLPeriod + 1);

//サポート/レジスタンス保持用の配列クリア
supports.Clear();
supports.Resize(Period);
resistances.Clear();
resistances.Resize(Period);

//インジケータ短縮名を設定する。
string short_name = "WSRO( " + IntegerToString(Period)+" , "+  IntegerToString(HLPeriod) + " )";
IndicatorShortName(short_name);

return(INIT_SUCCEEDED);
}

//------------------------------------------------------------------
//終了処理
void OnDeinit(const int reason)
{
//オブジェクトを作成する。
long chartId = ChartID();

int total = ObjectsTotal(chartId);
//生成したオブジェクトを削除する。
//0から削除するとインデックス位置がずれて
//正しく削除できないため、後ろから削除するようにする。
for( int i = total - 1; i >= 0 ; i--)
{
string name = ObjectName(chartId, i);

// 先頭文字列がWSRO_OBJECT_NAMEと一致する場合、削除する。
if( StringFind(name, WSRO_OBJECT_NAME) == 0 )
{
ObjectDelete(chartId, name);
}
}
}

//------------------------------------------------------------------
//計算イベント
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[])            //スプレット
{
// 最終計算時間
static datetime lastCalculate = 0;

//元となる値を計算する。
for( int i = (rates_total - prev_calculated - 1); i>=0 ; i-- )
{
if( i >=  rates_total - Period - 2 )
{
wsoBuffer[i] = 0 ;
wroBuffer[i] = 0 ;
continue;
}

// 同一時間足の値は再計算不要
if( lastCalculate != time[i] )
{
lastCalculate = time[i];
int centerIndex =  i + 1 + (HLPeriod - 1) / 2;

//サポート
//if Low( four days back ) = Min(lows of previous nine days) then SL=Low(four days back)
if( iLowest(NULL, PERIOD_CURRENT, MODE_LOW, HLPeriod, i + 1) == centerIndex )
{
// 新しいサポートが構築された場合、Period数保持する。
if( supports.Total() >= Period )
{
supports.Delete(supports.Total() - 1);
}
supports.Insert(iLow(NULL, PERIOD_CURRENT, centerIndex), 0);
}

//レジスタンス
//if High( four days back ) = Max(high of previous nine days) then RL=High(four days back)
if( iHighest(NULL, PERIOD_CURRENT, MODE_HIGH, HLPeriod, i + 1) == centerIndex )
{
// 新しいレジスタンスが構築された場合、Period数保持する。
if( resistances.Total() >= Period )
{
resistances.Delete(resistances.Total() - 1);
}
resistances.Insert(iHigh(NULL, PERIOD_CURRENT, centerIndex), 0);
}
}

//wsoの計算 1 - ((int)S1/C + (int)S2/C +(int)S3/C +(int)S4/C +(int)S5/C +(int)S6/C) / 6
if( supports.Total() == Period )
{
double wso = 0 ;
for( int j = 0 ; j < supports.Total(); j++ )
{
wso += (int) (supports.At(j) / close[i]);
}
wsoBuffer[i] = (1 - wso / supports.Total()) * 100;
}
else
{
wsoBuffer[i] = 0;
}

if( resistances.Total() == Period )
{
//wroの計算 1 - ((int)R1/C + (int)R2/C +(int)R3/C +(int)R4/C +(int)R5/C +(int)R6/C) / 6
double wro = 0 ;
for( int j = 0 ; j < resistances.Total(); j++ )
{
wro += (int) ( resistances.At(j) / close[i]);
}
wroBuffer[i] = (1 - wro / resistances.Total()) * 100;
}
else
{
wroBuffer[i] = 0;
}

// シグナルをチャート上に表示する。
static datetime lastSignalTime = 0 ;
if( IsShowBuySellRect && wsoBuffer[i] == 100 && wroBuffer[i] == 100 )
{
if( lastSignalTime != time[i] ) CreateRectangleObject(time[i], SellColor);
lastSignalTime = time[i];
}
else if( IsShowBuySellRect && wsoBuffer[i] == 0 && wroBuffer[i] == 0 )
{
if( lastSignalTime != time[i] ) CreateRectangleObject(time[i], BuyColor);
lastSignalTime = time[i];
}
else
{
DeleteRectangleObject(time[i]);
}
}

return(rates_total - 1);
}

//------------------------------------------------------------------
//売買シグナルを矩形で描画する。
bool CreateRectangleObject(
datetime time,       //表示時間(横軸)
color backColor      //背景色
)
{
//オブジェクトを作成する。
long chartId = ChartID();

int period = PeriodSeconds(PERIOD_CURRENT);

string name = WSRO_OBJECT_NAME + TimeToStr(time);

//チャート縦幅いっぱいに表示する。
double min = 0;
double max = ChartGetDouble(chartId, CHART_PRICE_MAX) * 4;

if( !ObjectCreate(chartId, name, OBJ_RECTANGLE, 0, time, max, time + period, min) )
{
return false;
}
ObjectSetInteger(chartId, name, OBJPROP_COLOR, backColor);

return true;
}


//------------------------------------------------------------------
//オブジェクトを削除する。
bool DeleteRectangleObject(
datetime time        //表示時間(横軸)
)
{
//オブジェクトを作成する。
long chartId = ChartID();
string name = WSRO_OBJECT_NAME + TimeToStr(time);

if( ObjectDelete(chartId, name) )
{
#ifdef DEBUG
Print("Ojbect Deleted " + name);
#endif
}

return true;
}


赤字になっている部分が、前回との差分です。
組み込んだ関数を、シグナルが出ている時に作成するというコードを作成しています。

また、OnDeinitで作成したオブジェクトを消しています。これをしないと、例えばチャートの時間足を変更した場合などに作った図形がそのまま表示されてしまいます。後始末は忘れず行いましょう。
なお、ソースのコメントにも書いてありますが、ObjectsTotal()関数で、チャート上のオブジェクト数を取得して、インデックスの最大値から減らしていく形で消していっています。0から消していってしまうと、MT4が内部で持っているオブジェクトの配列と位置があわなくなり正しく消せなくなるので注意しましょう。

さて、さっそくシグナルをだしたS/Rオシレータを見てみたいと思います。EURUSDの1時間足です。
signal1.png
それっぽく赤い帯が出ていますね。入力パラメータのSellColorで指定してある色で描画しています。矩形を表示すると少し透過効果がでるようです。
うーん。なんとなくいい感じですが、結構広い幅でシグナルが出てしまっています。

signal4.pngsignal3.png

もう少し敏感に反応してほしいので、HLPeriodの値を9から5に変えてみました。(過去5本のうち2本目が最高値・安値だった場合サポートレジスタンスラインとするという設定になります)
まぁありかも?っていうふうな感じでしょうか?

このようにチャートの上に帯が出ているとわかりやすくなるのはないかと感じました。

ちなみに、時間を変えてもう一枚。
signal2.png
うーん。やっぱりこのオシレータ。だらだらと一方向に動き続ける局面だとダミーのシグナルを出し続けてしまいます。何かしらの別のやつと組み合わせないと大変なことになりそうです^^;
ただ、有効に機能している時間帯は有効に機能し続ける傾向にあるようにも感じます。前回シグナルタイミンが正しく判定できていた場合は、今回も有効かも?みたいな感じで使うのもありかも??

余談ですが、今日はMacbookで作業中・・。いやー仮想化ツールって便利だなー。MAC上からもMT4使えてます。とりあえず問題無く動作している雰囲気です。
etc1.png


青赤荒れ狂う! MT4でS/Rオシレータのシグナルを青赤帯でチャートに表示する。その1

WSRO Singnal.PNG

前回の「相場は重力に縛られるのか?S/Rオシレータを作ってみた。」で作ったS/Rオシレータですが効果がありそうでなさそうなよくわかりません。
そこで、検証を目でわかりやすく確認する為、100/0に張り付いている期間をチャート上に帯で表示するようにしてみましょう。

いつぞか書いた「MT4のチャート上に矢印を表示する。」を参考にシグナルが出ている期間を帯上に表示したいと思います。

まず、矩形を描画する為の関数を作成します。

//オブジェクト名
#define WSRO_OBJECT_NAME "WSRO"
//------------------------------------------------------------------
//売買シグナルを矩形で描画する。
bool CreateRectangleObject(
datetime time,       //表示時間(横軸)
color backColor      //背景色
)
{
//オブジェクトを作成する。
long chartId = ChartID();

int period = PeriodSeconds(PERIOD_CURRENT);

string name = WSRO_OBJECT_NAME + TimeToStr(time);

//チャート縦幅いっぱいに表示する。
double min = 0;
double max = ChartGetDouble(chartId, CHART_PRICE_MAX) * 4;

if( !ObjectCreate(chartId, name, OBJ_RECTANGLE, 0, time, max, time + period, min) )
{
return false;
}
ObjectSetInteger(chartId, name, OBJPROP_COLOR, backColor);

return true;
}


前回の記事では、最新のバーにシグナルを一個だけ表示していましたが、今回は、シグナルが出るたびにチャートに表示したいと思います。
そこで、WSRO_OBJECT_NAMEという定数値と描画する時間足の時間を元に名前を生成しました。
ObjectCreateの第3引数にはOBJ_RECTANGLEを指定しています。これは矩形を表しています。
矩形を描画する場合は、第5引数以降に、左上の時間、左上価格、右下時間、右下価格の4つを指定します。

今回、左上の座標として、シグナル発生時間、現在の価格の4倍としています。チャートの縦幅いっぱいに帯を表示する為、十分大きな値として現在価格の4倍を指定しました。
右下の座標は、シグナル発生時間+表示チャートの時間足、0としています。

オブジェクトを作成した後、ObjectSetInteger(chartId, name, OBJPROP_COLOR, backColor);で背景色を設定しています。背景色は関数の外部から渡せるようにしています。

次に、オブジェクトを削除する関数も作成しておきます。
これは最新の値を表示する場合、現在価格によってシグナルが発生したりしなかったりすることを考慮して、現在チャートに表示している帯を消せるようにする為です。

//------------------------------------------------------------------
//オブジェクトを削除する。
bool DeleteRectangleObject(
datetime time        //表示時間(横軸)
)
{
//オブジェクトを作成する。
long chartId = ChartID();
string name = WSRO_OBJECT_NAME + TimeToStr(time);

if( ObjectDelete(chartId, name) )
{
#ifdef DEBUG
Print("Ojbect Deleted " + name);
#endif
}

return true;
}


こちらの方も、名前を時間から生成して、オブジェクトを削除しています。すでに存在しないObjectを削除しようとした場合、ObjectDelete関数がfalseを返してきます。あらかじめOjbectFindで調べてから消してもいいかな?とも思ったのですが、どうせObjectDelete内で同じことやっているんだろうなと思い、あらかじめ調べるコードは省略しました。

さて、この2関数で表示する準備は完了です。
次回の記事で、これを実際にS/Rオシレータに組み込み表示したいと思います。

2015年4月27日月曜日

相場は重力に縛られるのか?S/Rオシレータを作ってみた。

インジケータ作ってみたシリーズが思いのほか好評なため、もう一個作ったやつを公開します。

その前に、前回のレンジ・エクスパンション・インデックス(REI)を作ってみた。の記事を修正しています。私の解釈が間違っていた部分がありました。ソースコードは修正していません。

WSRO1.PNG

今回はS/R オシレータです。これまた、ぱっとMT4用のコードが見つからなかった為、自作してしまいました。

こちらはネットにウィドナー博士の英語論文が落ちてましたので、それを参考にしています。

とはいっても英語なんてこれっぽちもできません。Google先生に頼んでニュアンスで理解したところによると、「どんな勢い良くリンゴを放り投げても引力という抵抗を受けていつかは落ちる。それと同じで相場も勢いをつけて移動しても反対方向に落ちてくるからそのタイミングをとらえましょう。」という考え方のようです。
・・・・その意味でいくと重力圏を抜けて宇宙に飛び出してニュー○イプ化してしまった感のあるApple株みたいなこともあるので、そうなの?と思わないでもないですが、とりあえず作ってみました。

S/R オシレータは「Support and resistance oscillator」の略です。サポートラインとレジスタンスラインに対する位置を推進力、引力にとらえて算出するようです。上昇基調の時の考え方としては

サポートラインを上回る→推進力がある。
レジスタンスラインを上回る→引力を受ける。
推進力と引力が釣り合う→落ちてくるタイミング

という事みたいです。


今回は計算式にちょっと自信がありません。間違ってたら遠慮なく教えてください^^;;
//------------------------------------------------------------------
// ウィドナー博士のサポートレジスタンス オシレーター
#property copyright "Copyright 2015,  Daisuke"
#property link      "http://mt4program.blogspot.jp/"
#property version   "1.00"
#property strict

#include <Arrays/ArrayDouble.mqh>

#property indicator_separate_window
#property indicator_minimum    0
#property indicator_maximum    100

//バッファーを指定する。
#property indicator_buffers 2

//プロット数を指定する。
#property indicator_plots   2

//サポートオシレータ情報
#property indicator_label1  "Support"
#property indicator_type1   DRAW_LINE
#property indicator_color1  clrAqua
#property indicator_style1  STYLE_SOLID
#property indicator_width1  1

//レジスタンスオシレータ
#property indicator_label2  "Resistance"
#property indicator_type2   DRAW_LINE
#property indicator_color2  clrIndianRed
#property indicator_style2  STYLE_SOLID
#property indicator_width2  1

//入力パラメータ 期間
input int Period = 6;

//入力パラメータ HL期間(奇数)
input int HLPeriod = 9;

//インジケーター バッファ
double wsoBuffer[];
double wroBuffer[];

//S1-Sn
CArrayDouble supports;
//R1-Rn
CArrayDouble resistances;

//------------------------------------------------------------------
//初期化
int OnInit()
{
if( HLPeriod % 2 == 0 || HLPeriod < 5) return (INIT_PARAMETERS_INCORRECT);
if( Period < 3 ) return (INIT_PARAMETERS_INCORRECT);

//インジケーターバッファを初期化する。
SetIndexBuffer(0,wsoBuffer);
SetIndexBuffer(1,wroBuffer);

SetIndexDrawBegin(0, HLPeriod + 1);
SetIndexDrawBegin(1, HLPeriod + 1);


supports.Clear();
supports.Resize(Period);
resistances.Clear();
resistances.Resize(Period);

string short_name = "WSRO( " + IntegerToString(Period)+" , "+  IntegerToString(HLPeriod) + " )";
IndicatorShortName(short_name);


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[])            //スプレット
{
// 最終計算時間
static datetime lastCalculate = 0;

//元となる値を計算する。
for( int i = (rates_total - prev_calculated - 1); i>=0 ; i-- )
{
if( i >=  rates_total - Period - 2 )
{
wsoBuffer[i] = 0 ;
wroBuffer[i] = 0 ;
continue;
}

// 同一時間足の値は再計算不要
if( lastCalculate != time[i] )
{
lastCalculate = time[i];
int centerIndex =  i + 1 + (HLPeriod - 1) / 2;

//サポート
//if Low( four days back ) = Min(lows of previous nine days) then SL=Low(four days back)
if( iLowest(NULL, PERIOD_CURRENT, MODE_LOW, HLPeriod, i + 1) == centerIndex )
{
// 新しいサポートが構築された場合、Period数保持する。
if( supports.Total() >= Period )
{
supports.Delete(supports.Total() - 1);
}
supports.Insert(iLow(NULL, PERIOD_CURRENT, centerIndex), 0);
}

//レジスタンス
//if High( four days back ) = Max(high of previous nine days) then RL=High(four days back)
if( iHighest(NULL, PERIOD_CURRENT, MODE_HIGH, HLPeriod, i + 1) == centerIndex )
{
// 新しいレジスタンスが構築された場合、Period数保持する。
if( resistances.Total() >= Period )
{
resistances.Delete(resistances.Total() - 1);
}
resistances.Insert(iHigh(NULL, PERIOD_CURRENT, centerIndex), 0);
}
}

//つまりHLPeriodの期間内の中央値-1が高値・安値だった場合サポート・レジストラインとして登録する。
//ここで単純に価格で比較すると、最新の値が行ったり来たりすると
//過去のサポート・レジスタンスが次々更新される症状が発生してしまう。

//wsoの計算 1 - ((int)S1/C + (int)S2/C +(int)S3/C +(int)S4/C +(int)S5/C +(int)S6/C) / 6
if( supports.Total() == Period )
{
double wso = 0 ;
for( int j = 0 ; j < supports.Total(); j++ )
{
wso += (int) (supports.At(j) / close[i]);
}
wsoBuffer[i] = (1 - wso / supports.Total()) * 100;
}
else
{
wsoBuffer[i] = 0;
}

if( resistances.Total() == Period )
{
//wroの計算 1 - ((int)R1/C + (int)R2/C +(int)R3/C +(int)R4/C +(int)R5/C +(int)R6/C) / 6
double wro = 0 ;
for( int j = 0 ; j < resistances.Total(); j++ )
{
wro += (int) ( resistances.At(j) / close[i]);
}
wroBuffer[i] = (1 - wro / resistances.Total()) * 100;
}
else
{
wroBuffer[i] = 0;
}
}

return(rates_total - 1);
}


動作としては、
過去9本のうち4本目が最安値だった場合、サポートラインとします。
同様に過去9本のうち4本目が最高値だった場合、レジスタンスラインとします。
サポート・レジスタンスともに新しく更新されるまで過去6個分保持します。
上記6個と現在価格の位置をカウントして超えている割合を%表示したものがS/Rオシレータとなります。
��たぶん・・・汗)

WSRO2.PNG

水色のラインがサポートラインに対する現在価格の位置、赤色のラインがレジスタンスに対する現在価格の位置となります。両方のラインが100になった場合売り、0になった場合は買いというシグナルです。
添付画像で行くと赤丸で囲った部分が0になっていますが、そのあと反転していることが読み取れます。

チャートとS/Rオシレータを眺めていて、思った事ですが、やはりこのオシレータ価格が一方方向に動き続けると0/100に張り付きます。特に相場が凪いでいた状態から動き出すとあっという間に0/100に達して相場が波打つまで、そのままとなります。俗にいう力をためている状態というのは認識できていなさそうです。

ただし、一度100/0に張り付いた後、それが離れるタイミングでは確かに相場が逆に動き出すことも多そうです。ですので、もし、このオシレータを参考にする場合、100/0に達した後、それが崩れるタイミングを見てエントリーというのがよさそうと感じました。

ソースコード的な部分では、今回CArrayDoubleを使用しています。
これは、MT4についてきている浮動小数点配列クラスで、Include/Arraysフォルダに含まれています。
中身を見ると、doubleの配列に対して、AddやInsertなどデータ操作系が行えるようになっており、配列操作を簡単化できます。簡単によく使うメンバ関数を紹介します。

bool CArrayDouble::Resize(const int size)
引数で指定したサイズ分まで配列を確保します。
元のサイズより小さい場合は値が削除されます。
この値は、要素数の最大値となります。(実際には16個分を最低値として16の倍数で確保します)

bool CArrayDouble::Add(const double element)
配列に要素を追加します。Resizeで指定した値まで追加可能です。

bool CArrayDouble::Insert(const double element,const int pos)
posで指定した位置に要素を挿入します。Resizeで指定した値まで追加可能です。

bool CArrayDouble::Delete(const int index)
indexで指定した位置の要素を削除します。

double CArrayDouble::At(const int index) const
indexで指定した位置の要素を取得します。

int CArray:Total(void) const
現在の要素数を取得します。
Resizeで指定した値は最大値で、実際に値の入っている要素数はTotalで取得します。
CArrayDouble はCArrayを継承していますので、CArrayのメンバであるTotalが使用可能です。

��しかし、みなさんMT4のインジケータで定番の奴が見つからない時どうしてるんでしょうか??
��英語版のサイトをうろつくと見つかることもあるのですが、コードが古い為動かなかったりします・・。
��まぁこーやって作っていくと考え方の勉強になるため、とても役には立っているのですが・・。