2015年4月24日金曜日

レンジ・エクスパンション・インデックス(REI)を作ってみた。

レンジ・エクスパンション・インデックスが見当たらなかったため、作成してみました。
海外サイトには古いMT4向けのコードはあったのですが、新MT4向けではなかったため1から作成しました。

英語版Wikiに説明が乗っていまして、こちらの方が正確そうなため、英語版Wikiをベースに作成しています。
http://en.wikipedia.org/wiki/Range_expansion_index

すごくざっくり説明すると、改良版モメンタムです。
モメンタムを計算する際、5本~8本前の足の高値低値を比較することで波形の類似性を確認して、類似している場合は同じトレンドに乗っていると判断してだましシグナルを回避する思想のようです。

REI1.PNG
チャート画像にて上がREI、下がモメンタムです。上昇トレンドにおいて、REIでは0を割っていないのに対して、モメンタムでは途中一度100を割って、だましのシグナルが発生していることが見て取れます。

REIでは、レンジが+-60付近を超えている場合の反対方向への動きはだましの可能性が高いと判断するようです。
2015.4.27 修正
私が読み間違えていたみたいです。+-60超えは買われすぎ・売られすぎです。
モメンタム(相場の推進力の強弱を表す)系のオシレータです。0のラインを下回っていたら弱気、上回っていたら強気と読み取るようです。そのため0のラインを超えた方向に売買。買われすぎの場合は反転を狙うというのが基本です。チャートと見比べると、確かに相場が行き過ぎている事はつかみやすいのですが、波形が崩れるとすぐ0のラインを超えてしまいます。
波形が崩れるという事は相場反転の合図ではあるのですが、シグナルが早めに出る傾向になり一時的な損を抱えることも多そうです。そのため、高値つかみ安値つかみを避ける指標として使用するのがよさそうと感じました。

//------------------------------------------------------------------
// レンジ・エクスパンション・インデックス
// http://en.wikipedia.org/wiki/Range_expansion_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   -100
#property indicator_maximum   100
#property indicator_level1    60
#property indicator_level2    -60
#property indicator_levelcolor clrSilver
#property indicator_levelstyle STYLE_DOT

#property indicator_buffers 1

#property indicator_label1  "REI"
#property indicator_type1   DRAW_LINE
#property indicator_color1  clrYellow
#property indicator_style1  STYLE_SOLID
#property indicator_width1  1

// 入力パラメータ REI期間
extern int ReiPeriod = 8;

// バッファー
double reiBuffer[];

//------------------------------------------------------------------
//初期化
int OnInit()
{
SetIndexBuffer(0, reiBuffer);
IndicatorShortName("REI (" + IntegerToString(ReiPeriod) + ")");
SetIndexDrawBegin(0, ReiPeriod + 8);

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-- )
{
if( i >=  rates_total - ReiPeriod - 8 - 1 )
{
reiBuffer[i] = 0 ;
continue;
}

double sigmaS1 = 0 ;
double sigmaS2 = 0 ;

// s1 = sigma(j=1 to k) nj*mj*sj
// s2 = sigma(j=1 to k) |High[j] - High[j - 2]| + |Low[j] - Low[j - 2]|

for( int j = i; j < i + ReiPeriod; j++ )
{
// nj = if ((High[j – 2] < Close[j - 7]) && (High[j - 2] < Close[j - 8]) && (High[j] < High[j - 5])
// && (High[j] < High[j - 6])) n_j = 0  else n_j = 1

// 2本前の高値が、7本or8本前の終値未満かつ、現在の高値が5本or6本前の高値未満の場合 値を採用しない。
int nj = high[j + 2] < close[j + 7] && high[j + 2] < close[j + 8] && high[j] < high[j + 5] && high[j] < high[j + 6] ? 0 : 1;

// mj = if ((Low[j – 2] > Close[j – 7]) && (Low[j – 2] > Close[j – 8]) && (Low[j] > Low[j – 5])
// && (Low[j] > Low[j – 6])) m_j = 0 else m_j = 1
// 2本前の安値が、7本or8本前の終値より大きいかつ、現在の安値が5本or6本前の安値より大きい場合 値を採用しない。
int mj = low[j + 2] > close[j + 7] && low[j + 2] > close[j + 8] && low[j] > low[j + 5] && low[j] > low[j + 6] ? 0 : 1;

//上記で波形の近似性を見ている。

// 高値安値のモメンタムの合計
// s_j = High[j] - High[j - 2] + Low[j] - Low[j - 2]
double sj = high[j] - high[j + 2] + low[j] - low[j + 2];

// 高値安値のモメンタムの絶対値合計
sigmaS1 += nj * mj * sj;
sigmaS2 += MathAbs(high[j] - high[j + 2]) + MathAbs(low[j] - low[j + 2]);
}

// モメンタム合計/絶対値合計*100にて%化している。
reiBuffer[i] = sigmaS2 != 0 ? sigmaS1 * 100 / sigmaS2 : 0;
}

return (rates_total - 1);
}


※利益の増加や損失の減少を保証するものではありません。ご自身の判断にてお取引いただきますようお願いいたします。

2015年4月23日木曜日

EAを作ってみよう5 デバッグログでデバッグ!そして・・発見!

EAですが、バックテストから、正しく動作しているかテストは必須です。
ストラテジーテスターの操作履歴を追いかけるのもありですが、わかりやすくファイルに出力できれば、テストがはかどります。

今回はCSV形式で取引結果をファイルに出力し、シグナルと取引が正しいかどうか確認したいと思います。
前回の記事で書いた通り#ifdefを使用してデバッグコードを埋め込みます。
シグナルとポジション数をCSVに出力してシグナル通りに売買が行われているか確認したいと思います。Sample1のコードを下記のように追加&修正しました。

// デバッグモード
#define DEBUG

��略)

//------------------------------------------------------------------
// 気配値表示処理
void OnTick()
{
// 前回取引時間
static datetime beforeTime = TimeCurrent();

// 前回シグナル
static int beforeSignal = 0;

int signal = GetSignal();

// 前回までの売買シグナルと違う値の場合、ポジションをいったん全決済する。
if( signal != 0 && signal != beforeSignal )
{
m_wrapper.CloseOrderAll(Slippage);
beforeSignal = signal;
}

// 購入戦略。シグナルが出ている間、1分毎に時間をずらして購入していく。
datetime currentTime= TimeCurrent();
m_wrapper.RefreshPositions();
if( MathAbs(currentTime - beforeTime) >= 60 && m_wrapper.GetPositionCount() < MaxPosition )
{
m_wrapper.SendOrder( signal == 1 ? OP_BUY : OP_SELL, Lot, 0, Slippage, Limit, 0);
currentTime = currentTime;
#ifdef DEBUG
//SendOrderの結果をCSVに出力する。
WriteTradeLog(signal);
#endif
}
//トレーリングを行う。
if( m_wrapper.GetPositionCount() > 0 )
{
Trainling(Limit);
}
}

��略)
#ifdef DEBUG
//------------------------------------------------------------------
// 取引ログを出力する。
void WriteTradeLog(int signal)
{
//ログファイル名が重複するのを避けるため、後ろに番号をつける。
static string filename = "";
if( filename == "" )
{
for( int i = 0 ; i < 9999; i++ )
{
filename = "sample1Log_" + IntegerToString(i) + ".csv";
if( !FileIsExist(filename) ) break ;
}
}
int handle = FileOpen(filename,  FILE_CSV|FILE_READ|FILE_WRITE, ',');

// とりあえずログなのでファイルオープンできなかった場合は、何もしない。
if( handle < 0 ) return ;

// ヘッダ出力
if( FileSize(handle) == 0 )
{
FileWrite(handle, "日本時間","シグナル","合計ポジション数", "マジックNo一致数", "シンボル一致数",
"買","売","指値買","指値売","逆指値買","逆指値売");
}

// 最終行へ追加
FileSeek(handle, 0, SEEK_END);

int total = OrdersTotal();
int matchingMagicCount = 0 ;
int matchingSymbol = 0 ;

int orderCount[6];
ArrayInitialize(orderCount, 0);

// 現時点でのオーダー数を取得する。
for( int i = 0 ; i <total; i++)
{
if( OrderSelect(i, SELECT_BY_POS) )
{
if( OrderMagicNumber() != MagicNumber ) continue;
matchingMagicCount++;

if( OrderSymbol() != Symbol() ) continue;
matchingSymbol++;
int orderType = OrderType();
orderCount[orderType]++;
}
}

FileWrite(handle, TimeToStr(TimeGMT() + 32400), signal,
total, matchingMagicCount, matchingSymbol,
orderCount[0], orderCount[1], orderCount[2],
orderCount[3], orderCount[4], orderCount[5]);

FileClose(handle);
}
#endif


コンパイルが通ったので早速バックテストを再度実行します。
ここで生成したファイルはWindows7の場合、次のフォルダに出力されていました。
c:\Users\(ユーザ名)\AppData\Roaming\MetaQuotes\Terminal\(口座毎のシステムID)\tester\files

sample1Log(バックテスト実行日時).csvファイルが作成されていれば成功です。

さっそく中身を見てみましょう。
debuglog1.PNG
・・・・・おっと、いきなりシグナルが0状態で売ポジションが1になっています。
バグ発見!
このようにわかりやすい形でファイルを出力することによりテストが行いやすくなります。
ログを工夫することでさまざまなデータをとることが可能です。

バグを修正したコードがこちらです。
//------------------------------------------------------------------
// 気配値表示処理
void OnTick()
{
// 前回取引時間
static datetime beforeTime = TimeCurrent();
static datetime signalChangingTime = TimeCurrent();

// 前回シグナル
static int beforeSignal = 0;

int signal = GetSignal();

// 前回までの売買シグナルと違う値の場合、ポジションをいったん全決済する。
if( signal != 0 && signal != beforeSignal )
{
m_wrapper.CloseOrderAll(Slippage);
beforeSignal = signal;
signalChangingTime = TimeCurrent();
}

// 購入戦略。シグナルが出ている間、1分毎に時間をずらして購入していく。
datetime currentTime= TimeCurrent();
m_wrapper.RefreshPositions();
if( signal != 0 &&
MathAbs(currentTime - beforeTime) >= 60 &&
MathAbs(currentTime - signalChangingTime) <= 60 * MaxPosition &&
m_wrapper.GetPositionCount() < MaxPosition )
{
m_wrapper.SendOrder( signal == 1 ? OP_BUY : OP_SELL, Lot, 0, Slippage, Limit, 0);
beforeTime = currentTime;
#ifdef DEBUG
//SendOrderの結果をCSVに出力する。
WriteTradeLog(signal);
#endif
}
//トレーリングを行う。
if( m_wrapper.GetPositionCount() > 0 )
{
Trainling(Limit);
}
}


修正した結果のログはこちらです。
debuglog2.PNG

シグナルの方向と売買の方向が一致するようになりました。

実は、最大ポジション数を増やしても取引数が増えない問題が残っています。バックテスト固有の問題かもしれませんが、モデルを「全ティック」にして実行しても、OnTick内のTimeCurrent()で取得できる時間は、設定されている時間足(たとえば15分であれば、15分毎)になってしまい、時間の比較関数が正しく動作していません。
もうちょっと調べてみないといけない項目のようです。



(追記)
 時間について、少し調査してみましたが、TimeCurrent()の値が時間足の値になっているわけではなさそうです。
 もう少し調べてみます。
 バックテスト中はTimeCurrent()とTimeGMT()が同じ値が戻ってくるというのもちょっとショックです・・・。、今作っている休日判断ロジックがバックテスト中は正しく動作しない様子です・・・。(泣)
��追記2)
 落ちとしては、ストラテジーテスタのエキスパート設定を開きパラメータをリセットしたら正しく動作しました・・。ソース上でデフォルト値を変更しても、エキスパート設定でリセットしないとデフォルト値は反映されない事をすっかり失念していました。バグとしてもう一か所、初期化時にMaxPositionの設定をしていない箇所がありましたので修正しました。
//------------------------------------------------------------------
//初期化
int OnInit()
{
if( Period() < PERIOD_M5 ) return (INIT_PARAMETERS_INCORRECT);
m_wrapper = new CTradingWrapper(Symbol(), MagicNumber, MaxPosition);
return(INIT_SUCCEEDED);
}

テストは大切!!!ということを改めて感じました。

2015年4月22日水曜日

EAをつくってみよう4 バックテストを実行してみよう。

サンプルのEAを作成したため、次はバックテストを行ってみたいと思います。

MT4のバックテストはEA開発者だけではなくEAを使う人にも必須の作業のようで、あちこちのページに方法が書かれています。
FX-ON 2.4 メタトレーダーのバックテスト方法

このページを参考にバックテストを実施する為の準備を行います。
さっそく作成したEAのバックテストを実行してみましょう。

backtest1.PNG
エキスパートアドバイザーの選択は、前回の記事で作成したSample1を選択します。
あんまり長い期間だと時間がかかるため、「日付と時間を使用」にチェックを入れて、「差出人」2000.01.01 「終了日」2014.12.31と14年間を選択します。(差出人????? 誤訳かリソース設定ミスかと思いますが開始日のことです。私の使用しているMT4 - OANDAだけでしょうか??? )

2,3分放置すると終了します。
テスターの下部になるグラフを表示します。
backtest2.PNG

・・・・・・・・・・・・・・・・。
ま、こんなもんですよね。

さて、素直に動作すればよいのですが、実際にはロジックミス等が発生して取引しないことも多々あります。
新しいMT4にはデバッガが搭載されていて、動作させながらのデバッグも可能です。
豊嶋先生のコラムにやり方が具体的に記載されています。MT5向けですが、新しいMT4ではメタエディタがMT5と統一されているため、MT4でも対応可能です。(豊嶋先生のブログにはいつもお世話になっていてEA開発者として尊敬するばかりです。本も数冊買わせていただきました。)

ただし、デバッグを行うときには、現在の選択口座がデモ口座になっているか注意したほうがよさそうです。自動売買を行うチェックを間違えて入れっぱなしにしていた時、大参事になる可能性があります。

こちらのデバッグだと、現在の値を見てシグナルを発生させたりしますが、たまにしかシグナルを出さないようなEAだとテストに時間がかかります。
その場合は、コード内にPrint文を埋め込んでバックテストの操作履歴タブにて確認していきます。

あんまり長い期間を行うと操作履歴が大量に発生しますので、「日付と時間を使用」を予想される数トレード分ぐらいにしてから実行して確認していきます。
Sample1の中のGetSignal関数に正しく5分おきに過去シグナルを更新しているか確認するPrint文を埋め込んでみました。
// デバッグモード ON
#define DEBUG

//------------------------------------------------------------------
// 売買シグナルを取得する。
// return 買1 シグナルなし0 売-1
int GetSignal()
{
static double ma1 = 0 ;
static double ma2 = 0 ;
static MqlDateTime old;

MqlDateTime now;
TimeCurrent(now);

static int signal = 0 ;

//確定したモメンタムが100の境界を超えたかどうか。
if( now.min != old.min && now.min % 5 == 0 )
{
#ifdef DEBUG
Print("now=", StructToTime(now), " old=", StructToTime(old));
#endif
// 必要な時だけ、パラメーターを計算するようにする。
double m1 = iMomentum(NULL, PERIOD_CURRENT, MoPeriod, MoPrice, 1) ;
double m2 = iMomentum(NULL, PERIOD_CURRENT, MoPeriod, MoPrice, 2) ;

signal = m1 > 100 && m2 < 100 ? 1 : m1 < 100 && m2 > 100 ? -1 : 0;
}

// 現在のモメンタムがシグナル値と同じ方向にあるかどうか。
// 同じ方向にない場合、シグナルを取り消す。
double m0 = iMomentum(NULL, 0, MoPeriod, MoPrice, 0) ;
if( signal > 0 && m0 < 100 ) signal = 0;
if( signal < 0 && m0 > 100 ) signal = 0;

old = now;
return signal ;
}


backtest3.PNG

操作履歴タブには、埋め込んだPrint文の出力結果が表示されています。
バックテストでテストをする場合、基本はPrint文でコツコツと確認していく方法になりそうです。

また、Print文は#ifdef DEBUG ~ #endifで囲った中に記載しています。これは#define DEBUGがどこかに定義してある場合、この中を有効にするというプリプロセッサとなります。
このようにしておくことで、#define DEBUGを入れたり消したりすることでプログラムの動作を変えることができます。if文での判定でも同じ動きをしますが、デバッグコードの場合、#ifdefで行うのが一般的です。これは#fidefはコンパイル時に判定する為、条件が成立しない場合は最終的な実行ファイルにはコードの内容が含まれません。そのため、デバッグコードによるパフォーマンスの低下を避けることが出来ます。

ちなみにストラテジテスターの操作履歴は、ファイルとしても残っています。
Windows7の場合は下記でした。
c:\Users\(ユーザ名)\AppData\Roaming\MetaQuotes\Terminal\(口座毎のシステムID)\tester\logs

口座IDはメタエディタのナビゲーターの適当なフォルダのプロパティを表示すると、フォルダのプロパティが表示されます。
backtest4.png

フォルダプロパティの場所の項目にWindows上でのフォルダが表示されていますので、そこからたどることが可能です。
backtest5.PNG

操作履歴ベースでのファイルだと、正しくシグナルが出ているかどうか判定が難しいです。
そこで、次回はバックテスト中のシグナル結果をcsvに出力する方法を書いていきたいと思います。

2015年4月21日火曜日

ストキャスティクスRSI オシレーターを作成してみた。

ぱっと見ですが、MT4のインジケーターにストキャスティクス RSI(StochRSI)が見当たらなかった為、作ってみました。

ストキャスティクスRSIはRSIの値をストキャスティクス計算式に押し込んだグラフとなります。ストキャスティクスやRSIは価格に対する高値低値の割合をベースに計算していますが、RSIの値をストキャスティクスとしてグラフ化することによって、相場の勢いの変化をみるできます。トレンド時の押し目狙いなどに効果を発揮します。また、バイナリオプションでのハイロー選択の指標にも有効そうです。
StochRSI.PNG
画像のように小さな値幅でだらだらと一方向に動き続けるような相場ではダミーのシグナルが発生していますが、前回ストキャス値がどこまで行っているかを見ることにより手を出すべきかどうかを判断できます。

コードはMT4についていたStochastic.mq4を改造する形で作成しました。
RSIの値とStochRSIのグラフを描画するインジケータです。
//------------------------------------------------------------------
// ストキャスティクス RSI オシレーター

#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_buffers    5

#property indicator_label1  "RSI"
#property indicator_type1   DRAW_LINE
#property indicator_color1  clrYellow
#property indicator_style1  STYLE_SOLID
#property indicator_width1  1

#property indicator_label2  "main"
#property indicator_type2   DRAW_LINE
#property indicator_color2  clrLightSeaGreen
#property indicator_style2  STYLE_SOLID
#property indicator_width2  1

#property indicator_label3  "signal"
#property indicator_type3   DRAW_LINE
#property indicator_color3  clrIndianRed
#property indicator_style3  STYLE_SOLID
#property indicator_width3  1

#property indicator_type4   DRAW_NONE
#property indicator_type5   DRAW_NONE

#property indicator_level1     20.0
#property indicator_level2     80.0
#property indicator_levelcolor clrSilver
#property indicator_levelstyle STYLE_DOT
//--- input parameters
input int InpKPeriod = 5; // K Period
input int InpDPeriod = 3; // D Period
input int InpSlowing = 3; // Slowing

// RSI 期間
input int RSIPeriod = 14;
// RSI 適用価格
input ENUM_APPLIED_PRICE RSIPrice = PRICE_CLOSE;


//--- buffers
double ExtMainBuffer[];
double ExtSignalBuffer[];
double ExtHighesBuffer[];
double ExtLowesBuffer[];
double rsiBuffer[];

//---
int draw_begin1 = 0;
int draw_begin2 = 0;

//------------------------------------------------------------------
//初期化
int OnInit()
{
string short_name;

SetIndexBuffer(0, rsiBuffer);
SetIndexBuffer(1, ExtMainBuffer);
SetIndexBuffer(2, ExtSignalBuffer);
SetIndexBuffer(3, ExtHighesBuffer);
SetIndexBuffer(4, ExtLowesBuffer);
//--- indicator lines
//--- name for DataWindow and indicator subwindow label

short_name = "RSI(" + IntegerToString(RSIPeriod) + ")_Sto(" +
IntegerToString(InpKPeriod) + "," + IntegerToString(InpDPeriod) + "," + IntegerToString(InpSlowing) + ")";
IndicatorShortName(short_name);

draw_begin1 = InpKPeriod + InpSlowing;
draw_begin2 = draw_begin1 + InpDPeriod;
SetIndexDrawBegin(0, RSIPeriod);
SetIndexDrawBegin(1, draw_begin1);
SetIndexDrawBegin(2, draw_begin2);

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    i, k, pos;
//--- check for bars count
if (rates_total <= InpKPeriod + InpDPeriod + InpSlowing)
return(0);
//--- counting from 0 to rates_total
ArraySetAsSeries(ExtMainBuffer, false);
ArraySetAsSeries(ExtSignalBuffer, false);
ArraySetAsSeries(ExtHighesBuffer, false);
ArraySetAsSeries(ExtLowesBuffer, false);
ArraySetAsSeries(rsiBuffer, false);
//---
pos = InpKPeriod - 1;
if (pos + 1 < prev_calculated)
pos = prev_calculated - 2;
else
{
for (i = 0; i < pos; i++)
{
ExtLowesBuffer[i] = 0.0;
ExtHighesBuffer[i] = 0.0;
rsiBuffer[i] = 0;
}
}
//--- calculate HighesBuffer[] and ExtHighesBuffer[]
for (i = pos; i < rates_total && !IsStopped(); i++)
{
rsiBuffer[i] = iRSI(Symbol(), 0, RSIPeriod, RSIPrice, rates_total - i - 1);

double dmin = 1000000.0;
double dmax = -1000000.0;
for (k = i - InpKPeriod + 1; k <= i; k++)
{
if (dmin>rsiBuffer[k]) dmin = rsiBuffer[k];
if (dmax < rsiBuffer[k]) dmax = rsiBuffer[k];
}
ExtLowesBuffer[i] = dmin;
ExtHighesBuffer[i] = dmax;
}
//--- %K line
pos = InpKPeriod - 1 + InpSlowing - 1;
if (pos + 1 < prev_calculated)
pos = prev_calculated - 2;
else
{
for (i = 0; i < pos; i++)
ExtMainBuffer[i] = 0.0;
}
//--- main cycle
for (i = pos; i < rates_total && !IsStopped(); i++)
{
double sumlow = 0.0;
double sumhigh = 0.0;
for (k = (i - InpSlowing + 1); k <= i; k++)
{
sumlow += (rsiBuffer[k] - ExtLowesBuffer[k]);
sumhigh += (ExtHighesBuffer[k] - ExtLowesBuffer[k]);
}
if (sumhigh == 0.0)
ExtMainBuffer[i] = 100.0;
else
ExtMainBuffer[i] = sumlow / sumhigh*100.0;
}
//--- signal
pos = InpDPeriod - 1;
if (pos + 1 < prev_calculated)
pos = prev_calculated - 2;
else
{
for (i = 0; i < pos; i++)
ExtSignalBuffer[i] = 0.0;
}
for (i = pos; i < rates_total && !IsStopped(); i++)
{
double sum = 0.0;
for (k = 0; k < InpDPeriod; k++)
sum += ExtMainBuffer[i - k];
ExtSignalBuffer[i] = sum / InpDPeriod;
}
//--- OnCalculate done. Return new prev_calculated.
return(rates_total);
}


元のコードと比較すると、ちょっとだけ、初期化処理を自分でわかりやすいようにまとめました。初期化の中で次の関数をコールしています。
SetIndexDrawBegin
void SetIndexDrawBegin(
int index, // インジケーターの番号
int begin // 開始位置
);
これは、たとえば移動平均などで、ある程度のデータがそろってからではないと、描画できないような場合、描画開始する位置をずらすという指示をしています。

計算処理の部分は、Stochastic.mq4がArraySetAsSeries関数にて、時系列配列から普通の配列に変更してから計算していたため、その流儀に合わせて作成しています。・・・っていうより、まぁストキャス計算時のhigh/low/closeの値をiRSIからの値に置き換えただけです・・・(^^;;

FX-ONとかで無料インジケータとして公開したら需要あります??? ほしい方がいらっしゃったら試してみようかな・・・。


2015年4月20日月曜日

MQL4でEndOfMonth 月末日付を取得する。

MQL4ってEndOfMonth関数ないのですね・・・・・。
真面目に計算する方法もありますが、MQL4にある程度日付に関する関数がありますので、それを活用します。

月頭の0時0分0秒から1秒引いた際の日付を取得しています。

//------------------------------------------------------------------
//指定月の最終日を返す
int EndOfMonth(
int year,      // 年
int month      // 月
)
{
MqlDateTime work;
work.year = year;
work.mon = month + 1;
work.day = 1;
work.hour = 0;
work.min = 0 ;
work.sec = 0 ;

if( work.mon >= 13)
{
work.mon = 1;
work.year = work.year + 1;
}
return TimeDay( StructToTime(work) - 1 );
}


EAを作ってみよう3 とりあえず作ってみよう。モメンタム&トレイリングEA

まぁなにあともあれ、作って動かしてみないとよくわかりません。
ということで、適当に作ってみることにしました。

とりえあず、EAの設計ポイントとして、
・売買シグナルの生成
・ポジションオープン戦略
・ポジションクローズ戦略

があると仮定しました。
今回は下記の戦略で考えてみました。

・売買シグナルの生成
 確定したモメンタムが100未満なら売りシグナル、100より大きければ買シグナル
 ただし、現在のモメンタムが確定したモメンタムと逆方向の場合シグナルを取り消す。

・ポジションオープン戦略
 同じ方向にシグナルが出ている間、1分毎にポジションを増やす。
 最大ポジション数は外部から設定する。

・ポジションクローズ戦略
 外部から与えられたリミット設定を元に、損失を固定する。
 さらに、上値が拡大した場合、ストップ値を移動して損失を減らしていく。
 (トレイリング)
 
//------------------------------------------------------------------
//サンプル EA
#property copyright "Copyright 2015,  Daisuke"
#property link      "http://mt4program.blogspot.jp/"
#property version   "1.00"
#property strict

#include 

//入力パラメータ マジックナンバー 他のEAと当らない値を使用する。
input int   MagicNumber = 37851250;

//入力パラメータ 売買ロット
input double Lot = 0.1;

//入力パラメータ 損切最少値(Pips)
input int Limit = 10;

//入力パラメータ 最大ポジション数
input int MaxPosition = 1;

//入力パラメータ 許容スリップページ(pips)
input uint Slippage = 2;

//入力パラメータ モメンタム期間
input int MoPeriod = 14;

//入力パラメータ モメンタム価格
input ENUM_APPLIED_PRICE MoPrice = PRICE_CLOSE;

// トレード補助クラス
CTradingWrapper *m_wrapper;

//------------------------------------------------------------------
//初期化
int OnInit()
{
if( Period() < PERIOD_M5 ) return (INIT_PARAMETERS_INCORRECT);

m_wrapper = new CTradingWrapper(Symbol(), MagicNumber, 3);
return(INIT_SUCCEEDED);
}
//------------------------------------------------------------------
//終了処理
void OnDeinit(const int reason)
{
delete m_wrapper;
}
//------------------------------------------------------------------
// 気配値表示処理
void OnTick()
{
//(1)
// 前回取引時間
static datetime beforeTime = TimeCurrent();

// 前回シグナル
static int beforeSignal = 0;

int signal = GetSignal();

// 前回までの売買シグナルと違う値の場合、ポジションをいったん全決済する。
if( signal != 0 && signal != beforeSignal )
{
m_wrapper.CloseOrderAll(Slippage);
beforeSignal = signal;
}

// 購入戦略。シグナルが出ている間、1分毎に時間をずらして購入していく。
datetime currentTime= TimeCurrent();
m_wrapper.RefreshPositions();
if( MathAbs(currentTime - beforeTime) >= 60 && m_wrapper.GetPositionCount() < MaxPosition )
{
m_wrapper.SendOrder( signal == 1 ? OP_BUY : OP_SELL, Lot, 0, Slippage, Limit, 0);
currentTime = currentTime;
}
//トレーリングを行う。
if( m_wrapper.GetPositionCount() > 0 )
{
Trainling(Limit);
}
}

//------------------------------------------------------------------
// ストップ値トレイリング
void Trainling(
uint limit     //ストップ幅Pips
)
{
for( int i = 0 ; i < OrdersTotal(); i++)
{
if( OrderSelect(i, SELECT_BY_POS) )
{
if( OrderMagicNumber() == MagicNumber && OrderSymbol() == Symbol())
{
int orderType = OrderType();
double stop = OrderStopLoss();

// サンプルなので通信エラーは考えていない。
if( orderType == OP_BUY)
{
double newStop = Ask - limit * m_wrapper.GetPipsValue();
if( newStop > stop )
if( OrderModify(OrderTicket(), 0, newStop, 0, 0)) return ;
}
else if( orderType == OP_SELL)
{
double newStop = Bid + limit * m_wrapper.GetPipsValue();
if( newStop < newStop )
if( OrderModify(OrderTicket(), 0, newStop, 0, 0)) return ;
}
}
}
}
}

//------------------------------------------------------------------
// 売買シグナルを取得する。
// return 買1 シグナルなし0 売-1
int GetSignal()
{
static double ma1 = 0 ;
static double ma2 = 0 ;
static MqlDateTime old;

MqlDateTime now;
TimeCurrent(now);

static int signal = 0 ;

// (2)
//確定したモメンタムが100の境界を超えたかどうか。
if( now.min != old.min && now.min % 5 == 0 )
{
// 必要な時だけ、パラメーターを計算するようにする。
double m1 = iMomentum(NULL, PERIOD_CURRENT, MoPeriod, MoPrice, 1) ;
double m2 = iMomentum(NULL, PERIOD_CURRENT, MoPeriod, MoPrice, 2) ;

signal = m1 > 100 && m2 < 100 ? 1 : m1 < 100 && m2 > 100 ? -1 : 0;
}

// 現在のモメンタムがシグナル値と同じ方向にあるかどうか。
// 同じ方向にない場合、シグナルを取り消す。
double m0 = iMomentum(NULL, 0, MoPeriod, MoPrice, 0) ;
if( signal > 0 && m0 < 100 ) signal = 0;
if( signal < 0 && m0 > 100 ) signal = 0;

old = now;
return signal ;
}



ソースコード補足です。
��1)staticとdatetimeについて
 staticを使用してローカル変数を永続化しています。
 って書くと難しいですが、通常、関数内の{}で閉じられた範囲で宣言された変数は、関数の呼び出しの度にリセットされますが、staticをつけておくと、前回の値を記憶するようになります。
static datetime beforeTime = TimeCurrent();
 と書いておくと、最初の一回だけ、TimeCurrent();からの値で、beforeTimeは初期化されますが、以後はこの構文は飛ばされます。
 関数内の前回値を記憶しておきたい場合などに使用します。

 また、今回datetime型を使用しています。これは1970/01/01 00:00:00からの秒数を整数値で表した型となります。
 ちなみにロシア時間基準のようです。

��2)シグナル生成時の軽量化
 今回組み込み関数のiMomentumを使用しています。
double iMomentum(
string symbol, // 価格ペア名 NULLの場合、挿入されているチャートから判断
int timeframe, // 時間足
int period, // 期間
int applied_price, // 対象価格
int shift // シフト
);

 シグナル生成処理は気配値更新の度に呼び出されるように記述しています。ただし、過去のモメンタムについては確定している物であり毎回計算する必要性がありません。この場合、前回判断時間と今回判断時間を比較して、更新必要があるときにのみ計算するようにします。
 今後説明するバックテストを効率よく行うためにも、時間を確認して必要なときのみ計算を行うテクニックはEAでは必須のようです。
 ちなみに、if( now.min != old.min && now.min % 5 == 0 )の有り無しで、バックテストの実行時間差は私の環境ではこのようになりました。

2000/1/1~2014/12/31までのバックテストにて、最適化なし
時間判断なし 2:13
時間判断あり 2:05

 モメンタムの計算だけでは、それほど大きな差にはならないようです。ただ、最適化を行うと、この8秒が何十倍になると思うとやっておくのに越したことはなさそうです。

 バックテストについては別途書きたいと思いますが、何回かやってみて、一番思った事は

爆速SSDがほしい!!!!!

Intel SSD 750とか、ほしい・・・・。