2015年4月20日月曜日

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とか、ほしい・・・・。