2015年5月29日金曜日

MT4プログラミング 注文クラス修正版

FX-ON様に作成したEAを送付しました。
現在フォワードテスト中。まだ取引が発生していませんが、そのうち効果がでてくるかと。また公開が近くなったら紹介記事を書いていきたいと思います。

注文クラスを修正したので、またアップしておきます。
シグナルによる単純な売買に対応した注文クラスです。

カウントダウン方式に対応したのと、ModifyOrderにストップロス値が消えてしまう障害があったのを修正しています。
うーんバグが残るもんですね。とりあえずバックテスト的には動作しているので、いったんこれでOKかなぁと思っているのですが・・。

//------------------------------------------------------------------
// 注文関係ラッパークラス
#property copyright "Copyright 2015,  Daisuke"
#property link      "http://mt4program.blogspot.jp/"
#property version   "1.30"
#property strict

#include <Arrays/ArrayInt.mqh>
#include <Custom/TradingDefines.mqh>
#include <stdlib.mqh>
#include <stderror.mqh>

//------------------------------------------------------------------
// 注文関係ラッパークラス
class CTradingWrapper
{
private:
// マジックナンバー
int m_magicNumber;

// 最大ポジション数
int m_maxPosition;

// 最大スプレット
double m_spreadFilter;

// Pips倍率
int m_pipsRate;

// 対象通貨ペア
string m_symbol;

// 保有ポジション
CArrayInt m_positions ;

// タイムアウト値
uint m_timeout;

// 少数桁数
int m_digit;

// 取扱い最小値
double m_point ;

// デフォルトのコメント
string m_defualtComment;

// カウントダウン方式の場合true
bool m_isCountdown;
public:
//------------------------------------------------------------------
// コンストラクタ
CTradingWrapper(
string symbol,       //対象通貨ペア
int magicNumber,     //マジックナンバー
int maxPosition,     //最大ポジション数
double spreadFilter, //最大スプレット
uint timeout,        //タイムアウト
bool isCountdown,    // カウントダウン方式
string defualtComment //デフォルトコメント
);

//------------------------------------------------------------------
// デストラクタ
~CTradingWrapper();

//------------------------------------------------------------------
// 発注する
// Return   発注成功時インデックス それ以外-1
int SendOrder(
int cmd,                //売買種別
double volume,          //売買ロット
double price,           //価格
uint slippage,          //許容スリッピング(Pips単位)
uint stoploss,          //ストップロス(Pips単位)
uint takeprofit,        //利確値(Pips単位)
string comment,         //コメント
datetime expiration,    //注文有効期限
color arrowColor        //注文矢印の色
);

//------------------------------------------------------------------
// 成行き決済を行う。
// Return   発注成功時ture それ以外false
bool CloseOrder(
int index,                    //決済するインデックス
uint slippage,                //許容スリッピング
color arrowColor = clrNONE    //注文矢印の色
);

//------------------------------------------------------------------
// 全ポジションを成行き決済を行う。
// Return   発注成功時ture それ以外false
bool CloseOrderAll(
uint slippage,                //許容スリッピング
color arrowColor = clrNONE    //注文矢印の色
);

//------------------------------------------------------------------
// 注文を変更する。
// Return   発注成功時ture それ以外false
bool ModifyOrder(
int index,              //決済するインデックス
uint stoploss,          //ストップロス(Pips単位)
uint takeprofit,        //利確値(Pips単位)
color arrowColor        //注文矢印の色
);

//------------------------------------------------------------------
// すべての注文を変更する。
bool ModifyOrderAll(
uint stoploss,          //ストップロス(Pips単位)
uint takeprofit,        //利確値(Pips単位)
color arrowColor        //注文矢印の色
);

//------------------------------------------------------------------
// マジックナンバーを取得する。
// Return   マジックナンバー
int GetMagicNumber() { return m_magicNumber; };

//------------------------------------------------------------------
// 現在のポジション数を取得する。
// Return   現在のポジション数を取得する。
int GetPositionCount(){ return m_positions.Total(); };

//------------------------------------------------------------------
// 最大ポジション数を取得する。
// Return   最大ポジション数を取得する。
int GetMaxPosition(){ return m_maxPosition; };

//------------------------------------------------------------------
// 通貨ペアを取得する。
// Return   最大ポジション数を取得する。
string GetSymbol(){ return m_symbol; };

//------------------------------------------------------------------
// 指定番号のチケットを取得する。
// Return   チケット番号 取得失敗時-1
int GetTicket(
int index         //インデックス
);

//------------------------------------------------------------------
// 指定番号の売買種別 を取得する。
// Return   売買種別
int GetOrderType(
int index         //インデックス
);

//------------------------------------------------------------------
// 指定番号の現在利益を取得する。
// Return   現在利益(Pips)
double GetOrderProfit(
int index         //インデックス
);

//------------------------------------------------------------------
// 指定番号の取引開始時間 を取得する。
// Return   取引開始時間
datetime GetOrderOpenTime(
int index         //インデックス
);

//------------------------------------------------------------------
// 1pipsあたりの値を取得する。
// Return   1pipあたりの値
double GetPipsValue() { return m_point * m_pipsRate; } ;

//------------------------------------------------------------------
// 指定pipsあたりの値を取得する。
// Return   指定pipあたりの値
double GetPipsValue(
double pips    // 指定Pips
){ return pips * m_point * m_pipsRate; } ;

//------------------------------------------------------------------
// ポジションリストを更新する。
void RefreshPositions();

private:

//------------------------------------------------------------------
// 設定パラメータが安全かどうか
// Return 安全ture それ以外false
bool IsSafeParameter();

};

//------------------------------------------------------------------
// コンストラクタ
CTradingWrapper::CTradingWrapper(
string symbol,             //対象通貨ペア
int magicNumber = 0,       //マジックナンバー
int maxPosition = 3,       //最大ポジション数
double spreadFilter = 1,   //最大スプレット
uint timeout = 1000,       //タイムアウト(ms)
bool isCountdown = false,  // カウントダウン方式
string defualtComment=NULL //デフォルトコメント
)
{
m_symbol = symbol;
m_magicNumber = magicNumber;
m_maxPosition = maxPosition;
m_timeout = timeout;
m_positions.Resize(m_maxPosition);
m_defualtComment = defualtComment;
m_isCountdown = isCountdown;

//Pips計算 小数点桁数が3or5の場合、Point()*10=1pips
m_digit = (int)MarketInfo(m_symbol, MODE_DIGITS);
m_point = (double)MarketInfo(Symbol(), MODE_POINT);

m_pipsRate = m_digit == 3 || m_digit == 5 ? 10 : 1;

for( int i = 0 ; i < OrdersTotal(); i++)
{
if( OrderSelect(i, SELECT_BY_POS) )
{
if( OrderMagicNumber() == m_magicNumber && OrderSymbol() == m_symbol)
{
m_positions.Add(OrderTicket());
}
}
}
m_spreadFilter = GetPipsValue(spreadFilter);
}

//------------------------------------------------------------------
// デストラクタ
CTradingWrapper::~CTradingWrapper()
{
}

//------------------------------------------------------------------
// 発注する
// Return   発注成功時インデックス それ以外-1
int CTradingWrapper::SendOrder(
int cmd,                      //売買種別
double volume,                //売買ロット
double price,                 //価格(成行き時は現在値を自動設定)
uint slippage,                //許容スリッピング(Pips単位)
uint stoploss = 0,            //ストップロス(Pips単位)
uint takeprofit = 0,          //利確値(Pips単位)
string comment = NULL,        //コメント
datetime expiration = 0,      //注文有効期限
color arrowColor = clrNONE    //注文矢印の色
)
{
if( !this.IsSafeParameter() ) return -1;
if( m_positions.Total() >= m_positions.Max() ) return -1 ;

if( comment == NULL ) comment = m_defualtComment;

// 計算用 負数フラグ
int flag = cmd == OP_BUY || cmd == OP_BUYLIMIT || cmd == OP_BUYSTOP ? 1 : -1;

uint start = GetTickCount();
while(!IsStopped())
{
if( (GetTickCount() - start ) > m_timeout ) return -1;

double bid = MarketInfo(m_symbol, MODE_BID);
double ask = MarketInfo(m_symbol, MODE_ASK);

// スプレットが大きすぎる場合は取引しない。
if( NormalizeDouble(MathAbs(ask - bid), m_digit) > m_spreadFilter ) return -1;

if( cmd == OP_SELL ) price = bid;
if( cmd == OP_BUY ) price = ask;

double closeTarget = cmd == OP_BUY || cmd == OP_BUYLIMIT || cmd == OP_BUYSTOP ? ask : bid;

//pips値からストップロス値、利益確定値を取得する。
double stoplossValue = 0 ;
double takeprofitValue = 0;
if( stoploss != 0 )
{
stoplossValue = NormalizeDouble(closeTarget - this.GetPipsValue(stoploss) * flag, m_digit );
}
if( takeprofit != 0)
{
takeprofitValue = NormalizeDouble(closeTarget + this.GetPipsValue(takeprofit) * flag, m_digit );
}

if( IsTradeAllowed() )
{
if( AccountFreeMarginCheck(m_symbol, cmd, volume) < 0 )
{
int errorCode = GetLastError();
Print("AccountFreeMarginCheck Error[",errorCode ,"]");
return -1;
}

int tiket = -1;
if( m_isCountdown )
{
tiket = ::OrderSend(m_symbol, cmd, volume, price,
slippage * m_pipsRate, 0, 0,
comment, m_magicNumber, expiration, arrowColor);
}
else
{
tiket = ::OrderSend(m_symbol, cmd, volume, price,
slippage * m_pipsRate, stoplossValue, takeprofitValue,
comment, m_magicNumber, expiration, arrowColor);
}

if( tiket >= 0 )
{
m_positions.Add(tiket);

if( m_isCountdown )
{
ModifyOrder(m_positions.Total() - 1, stoploss, takeprofit);
}

return m_positions.Total() - 1;
}
else
{
int errorCode = GetLastError();

// リトライしても仕方がないエラーの時は終了してしまう。
if( errorCode == ERR_INVALID_PRICE ||
errorCode == ERR_INVALID_STOPS ||
errorCode == ERR_INVALID_TRADE_VOLUME ||
errorCode == ERR_NOT_ENOUGH_MONEY )
{
return -1;
}
}
}
Sleep(100);
}
Print("SendOrder Timeout");
return -1;
}

//------------------------------------------------------------------
// 成行き決済を行う。
// Return   発注成功時ture それ以外false
bool CTradingWrapper::CloseOrder(
int index,                    //決済するインデックス
uint slippage,                //許容スリッピング
color arrowColor = clrNONE    //注文矢印の色
)
{
uint start = GetTickCount();
while(!IsStopped())
{
if( (GetTickCount() - start ) > m_timeout ) return false;

if( IsTradeAllowed() )
{
// オーダーが選択できない状態(すでに存在しない)場合は、リストから削除して終了する。
int targetTicket = m_positions.At(index);
if( !OrderSelect(targetTicket, SELECT_BY_TICKET) )
{
m_positions.Delete(index);
return false;
}

if( OrderClose(OrderTicket(),OrderLots(), OrderClosePrice(), slippage * m_pipsRate, arrowColor) )
{
m_positions.Delete(index);
return true;
}
else
{
int errorCode = GetLastError();

// リトライしても仕方がないエラーの時は終了してしまう。
if( errorCode == ERR_INVALID_TICKET)
{
m_positions.Delete(index);
return false;
}
}
}
Sleep(100);
}

Print("CloseOrder Timeout");

return false;
}

//------------------------------------------------------------------
// 全ポジションを成行き決済を行う。
// Return   発注成功時ture それ以外false
bool CTradingWrapper::CloseOrderAll(
uint slippage,                // 許容スリッピング
color arrowColor = clrNONE    // 注文矢印の色
)
{
RefreshPositions();
bool result = true;
for( int i = m_positions.Total() - 1; i >= 0; i--)
{
result &= this.CloseOrder(i, slippage, arrowColor);
}

return result;
}

//------------------------------------------------------------------
// 注文を変更する。
// Return   発注成功時ture それ以外false
bool CTradingWrapper::ModifyOrder(
int index,                 //決済するインデックス
uint stoploss,             //ストップロス(Pips単位)
uint takeprofit = 0,       //利確値(Pips単位)0は変更なし
color arrowColor = clrNONE //注文矢印の色
)
{
uint start = GetTickCount();
while(!IsStopped())
{
if( (GetTickCount() - start ) > m_timeout ) return false;

if( IsTradeAllowed() )
{
// オーダーが選択できない状態(すでに存在しない)場合は、リストから削除して終了する。
int targetTicket = m_positions.At(index);
if( !OrderSelect(targetTicket, SELECT_BY_TICKET) )
{
m_positions.Delete(index);
return false;
}

// 実際のポジション以外は無視
int cmd = OrderType();
if( cmd != OP_BUY && cmd != OP_SELL )
{
return false;
}

double bid = MarketInfo(m_symbol, MODE_BID);
double ask = MarketInfo(m_symbol, MODE_ASK);

// 計算用 負数フラグ
int flag = cmd == OP_BUY  ? 1 : -1;

double closeTarget = cmd == OP_BUY ? ask : bid;
//pips値からストップロス値、利益確定値を取得する。
double stoplossValue = 0 ;
double takeprofitValue = 0;
double nowStop = OrderStopLoss();

if( stoploss != 0 )
{
stoplossValue = NormalizeDouble(closeTarget - this.GetPipsValue(stoploss) * flag, m_digit );
}
else
{
// 0の場合変更しない。
if( nowStop > 0 ) stoplossValue = nowStop;
}

if( nowStop > 0 )
{
// 注文ストップ値は下方方向への変更は許可しない。
if((cmd == OP_BUY && stoplossValue <= OrderStopLoss() ) ||
(cmd == OP_SELL && stoplossValue >= OrderStopLoss() ) ) stoplossValue = nowStop ;
}

if( takeprofit != 0)
{
closeTarget = OrderOpenPrice();
takeprofitValue = NormalizeDouble(closeTarget + this.GetPipsValue(takeprofit) * flag, m_digit );
}
else
{
// 0の場合変更しない。
double profit = OrderTakeProfit();
if( stoplossValue > 0 && profit > 0 ) takeprofitValue = profit;
}

// 変更がない場合はコマンドを実行しない。
if( stoplossValue == 0 && takeprofitValue == 0 ) return true;

if( OrderModify(targetTicket, OrderOpenPrice(), stoplossValue, takeprofitValue, 0, arrowColor ))
{
return true;
}
else
{
int errorCode = GetLastError();

// 変更なしだった
if( errorCode == ERR_NO_RESULT ) return true;

// リトライしても仕方がないエラーの時は終了してしまう。
if( errorCode == ERR_INVALID_PRICE ||
errorCode == ERR_INVALID_STOPS ||
errorCode == ERR_INVALID_TRADE_VOLUME ||
errorCode == ERR_NOT_ENOUGH_MONEY)
{
return false;
}
}
}
}
Print("ModifyOrder Timeout");
return false;
}

//------------------------------------------------------------------
// すべての注文を変更する。
bool CTradingWrapper::ModifyOrderAll(
uint stoploss,             //ストップロス(Pips単位)
uint takeprofit = 0,       //利確値(Pips単位)
color arrowColor = clrNONE //注文矢印の色
)
{
RefreshPositions();
bool result = true;
for( int i = m_positions.Total() - 1; i >= 0; i--)
{
result &= this.ModifyOrder(i, stoploss, takeprofit, arrowColor);
}

return result;
}

//------------------------------------------------------------------
// 設定パラメータが安全かどうか
// Return 安全ture それ以外false
bool CTradingWrapper::IsSafeParameter()
{
if( m_symbol == NULL ) return false;
if( m_maxPosition == 0 ) return false;
return true;
}

//------------------------------------------------------------------
// ポジションリストを更新する。
void CTradingWrapper::RefreshPositions()
{
m_positions.Clear();
for( int i = 0 ; i < OrdersTotal(); i++)
{
if( OrderSelect(i, SELECT_BY_POS) )
{
if( OrderMagicNumber() == m_magicNumber && OrderSymbol() == m_symbol)
{
m_positions.Add(OrderTicket());
}
}
}
}

//------------------------------------------------------------------
// 指定番号のチケットを取得する。
// Return   チケット番号 取得失敗時-1
int CTradingWrapper::GetTicket(
int index         //インデックス
)
{
if(index < 0 || index >= m_positions.Total() ) return -1;
return m_positions.At(index);
}

//------------------------------------------------------------------
// 指定番号の売買種別 を取得する。
// Return   売買種別
int CTradingWrapper::GetOrderType(
int index         //インデックス
)
{
int ticket = GetTicket(index);
if( ticket < 0 ) return OP_NONE;
if( OrderSelect(ticket, SELECT_BY_TICKET) ) return OrderType();

return OP_NONE;
}

//------------------------------------------------------------------
// 指定番号の現在利益を取得する。
// Return   現在利益(Pips)
double CTradingWrapper::GetOrderProfit(
int index         //インデックス
)
{
int ticket = GetTicket(index);
if( ticket < 0 ) return 0;
if( OrderSelect(ticket, SELECT_BY_TICKET) )
{
if( OrderType() == OP_BUY )
{
return (OrderClosePrice() - OrderOpenPrice()) / GetPipsValue();
}
else
{
return (OrderOpenPrice() - OrderClosePrice()) / GetPipsValue();
}
}

return 0;
}

//------------------------------------------------------------------
// 指定番号の取引開始時間 を取得する。
// Return   取引開始時間
datetime CTradingWrapper::GetOrderOpenTime(
int index         //インデックス
)
{
int ticket = GetTicket(index);
if( ticket < 0 ) return 0;
if( OrderSelect(ticket, SELECT_BY_TICKET) )
{
return OrderOpenTime();
}
return 0;
}


2015年5月28日木曜日

EA開発者視点でのMT4バックテストの注意点

※こちらの記事もご覧ください。
[MT4プログラミング]小ネタ バックテスト時のiCloseは1000本前までに限定される。
http://mt4program.blogspot.jp/2015/10/mt4-iclose1000.html

EA開発時にバックテストをおこないますが、バックテスト時の独自挙動が存在します。

開発時に注意する点として特に問題となることを自分向けのメモとしてまとめます。

■OnTimerが動作しない。
 EventSetTimerで設定してもOnTimerは動作しません。
 EAとしてOnTimerで何かするのはやめておいた方がよさそうです。
 私はEA稼働時のコメントの更新の為だけに使用してポジションの状態に影響しないようにしました。

 ポジションの状態に時間ごとに影響させたい場合OnTick内で、定期的に呼び出すしか方法はなさそうです。これも気配値の更新タイミングで呼び出し時間がずれますが、通常は大きな影響はないと思われます。たとえば60秒ごとに呼び出す場合はこんな感じ。
void OnTick()
{
static datetime beforeTime = TimeCurrent();
if( TimeCurrent() - beforeTime > 60)
{
//なんか定期的な処理
beforeTime = TimeCurrent();
}
}


■TimeGMTが機能しない。
TimeGMT・・・GMTが取れる超便利な関数ですよね。
ところが、バックテストだとTimeCurrentと同じ値が取れてきます。バックテスト用データのチャート時間の値であってGMTではありません。バックテスト用にGMTを補正する必要があります。さらにMT4でよく採用されているGMT+2ですが、アメリカ夏時間帯になると+3になるデータが多い為、夏時間の判定も必要です。

以前紹介したMT4プログラムの小ネタ アメリカ夏時間を判定するプログラムを使って夏時間かどうかを判定した上でバックテストを行う必要が出てきます。
こんな感じで、バックテスト中のみGMTに対するオフセット値を適用するコードが必要です。
   if( !IsTesting() ) return TimeGMT();

// バックテストの場合TimeGMTの値はサーバータイムが帰ってくる。
// 夏時間が有効なヒストリカルデータの場合、夏時間は+1時間されて
// 帰ってくる。
datetime gmt = TimeGMT();
gmt = gmt - (int)(m_gmtOffset * ONEHOUR);

//切り替えタイミングの場合 おかしくなるが日曜日早朝切り替えの為
//EA動作には影響しない。
if( m_type == ST_TYPE_AMERICA && IsAmericaSummerTime(gmt) )
{
gmt -= ONEHOUR;
}

バックテスト用のパラメータコードとしてGMT補正が必要になるのではれば、結局のところTimeGMTいらないという結果に・・・(バックテストせずにEA動かす人なんてそうそういないですよね?)

IsTestingはバックテスト中かどうかをbool値で返してくれる関数です。このようにバックテスト時だけどうしても動作を変える必要がある部分などでは重宝します。

■初値で判断するなら、初値時しか動かない判定が必要。
 バックテストで、初値のみでテストしている場合、実運用上で正しく初値のみでしか動作しないようにしないといけません。
 全チックでやっても初値のみでやってもテスト結果としては大差ないのが基本です。(実際やってみると判定式を入れていたとしてもずれることがあるので、何かしらのアルゴリズムが入っているのかもしれませんが・・・)切り替え時だけ動作させる判定式はこんな感じになります。

   static datetime beforeTime = 0 ;
datetime currentTime = iTime(m_symbol, PERIOD_M5, 0);
if( beforeTime == currentTime )
{
return ;
}
beforeTime = currentTime;


■最適化をする場合は、利益だけを見てはいけない。
 最適化を何回かやっていると最大の利益が出るように調整していく事になるかと思います。利益の差が大きいEAの場合、たまたま引っかかったでかい利益のポジションの影響を受けることがあります。
 そのため、ドローダウンや勝率、取引数などを含めて確認することが必要と感じました。
 また、期間をいくつかに分けて確認することも重要で、長い期間だとうまくいっても実は直近NGだったりすることが多々ありました。

■履歴データを追いかけるときは注意
 バックテストでは普通に全履歴がとれますが、実運用では取れません。
 MT4「ターミナル」内「口座履歴」のタブにて表示期間を「全期間」に指定する必要があります。

■最後は力技
 ただ今デバッグ中ですが、デバッグになると割と力技です。
 チャートとインジケータを見比べて取引がアルゴリズム通りかどうか結局一個一個見ていく必要がありました。そして結構バグがある・・・・・泣
 このあたりは、まだまだ人の手による目視確認が有効でした。
 この場合も、切り分けて確認する必要があります。私の場合、売りだけ・買いだけといったモードをEAに組み込み買いのアルゴリズムのみを確認・売りのアルゴリズムを確認といった形で行いました。チャートの確認だけなら長い時間は必要ないため、ひと月ぐらいのスパンで何回も実行することが重要でした。

 ただ今自作EAをFX-ON様に登録すべく全ティックでのバックテストを実施中です。重いーw



2015年5月27日水曜日

MT4プログラミング この休み複雑すぎるだろ!イースターの計算


さて、EAを作成していると休みの日は取引したくない!ってことありませんか?
その場合、コツコツと計算していくわけですが、大概の休みは決まった日か月の何番目の何曜日とか割と簡単な計算で求めることができます。

ところが、イースターだけは別格です。
「春分の日の後の最初の満月から数えて最初の日曜日」

春分の日の計算も近似で求める必要があるなど大概面倒なのですが、満月ってなんですか?満月ってどうやって求めるといいんですか? ふざけてるんですか???

と言いたくなる法則です。
それでもネットの海を検索するとガウスさん(ガウス平面とか最小二乗法とかのガウスさんです!)が考えてくれた数式が見つかりました。それ以降さまざまな数学者が公式を出しています。こんな超有名数学者じゃないと作れない式を使わないと計算できない恐ろしい休日なのです。

イギリス国立天文台のサイトから式を拾ってアルゴリズムにしてみました。1900-2099まで適用可能のようです。EAで使う分には十分ですよね。

さらっと見たところ結果は正しいと思われますが式の内容は全く理解していません(汗

//------------------------------------------------------------------
// イースターを取得する
void GetEastar(
int year,            //取得年度
MqlDateTime &dt      //イースター日
)
{
int mod = year % 19;
int d = 255 - mod * 11;
while( d >= 51 ) d-=30;
if( d > 48 ) d -= 1;
int e = (year + (int)(year / 4) + d + 1) % 7;
int q = d + 7 - e;

if( q <= 31 )
{
dt.mon = 3;
dt.day = q;
}
else
{
dt.mon = 4;
dt.day = q - 31;
}
}



2015年5月26日火曜日

MT4プログラミング いまいち使えない子のCArrayDoubleを改造する。CArrayDoubleで移動平均

今日も小ネタです。

Build600からオブジェクト指向に対応しているMQL4です。
オブジェクト指向対応版の配列クラスCArrayDoubleがコードも合わせてMT4に添付されています。

配列操作が便利にはなるのですが、標準関数であるiMAOnArrayの引数が当然のごとくdouble[]を要求してくるのでCArrayDoubleで値を保持してしまうと移動平均が取れないといった問題があります。

オブジェクト指向の機能として継承という機能があります。CArrayDbouleを拡張してiMAOnArrayによる移動平均を計算できるようにしたいと思います。

継承についてはWikiの説明を見てください。継承 (プログラミング)

//------------------------------------------------------------------
// CArrayDouble 各種計算追加クラス
#property copyright "Copyright 2015,  Daisuke"
#property link      "http://mt4program.blogspot.jp/"
#property version   "1.00"
#property strict

#include <Arrays/ArrayDouble.mqh>

//------------------------------------------------------------------
// CArrayDouble 各種計算追加クラス
class CCustomArrayDouble : public CArrayDouble
{
private:

public:
//------------------------------------------------------------------
// コンストラクタ
CCustomArrayDouble();

//------------------------------------------------------------------
// デストラクタ
~CCustomArrayDouble();

//------------------------------------------------------------------
// 移動平均を取得する。
double GetMa(
int period,             // 移動平均期間
int maShift,            // 移動平均シフト
ENUM_MA_METHOD method,  // 移動平均種別
int shift               // シフト
);
};

//------------------------------------------------------------------
// コンストラクタ
CCustomArrayDouble::CCustomArrayDouble() : CArrayDouble()
{
ArraySetAsSeries(m_data, true);
}

//------------------------------------------------------------------
// デストラクタ
CCustomArrayDouble::~CCustomArrayDouble()
{
}

//------------------------------------------------------------------
// 移動平均を取得する。
double CCustomArrayDouble::GetMa(
int period,             // 移動平均期間
int maShift,            // 移動平均シフト
ENUM_MA_METHOD method,  // 移動平均種別
int shift               // シフト
)
{
// 高速化のため単純移動平均は自力で計算する。
if( method == MODE_SMA )
{
double sum = 0;
int count = 0 ;
for( int i = shift; i < shift + period && i < Total(); i++ )
{
sum += At(i);
count ++ ;
}
return count == 0 ? 0 : sum / count;
}
else
return iMAOnArray(m_data, 0, period, maShift, method, shift);
}


プログラム的には移動平均を求める関数を追加した形となります。コンストラクタで、 ArraySetAsSeries(m_data, true);
を呼び出しています。これは、MT4のインジケータ等のバッファが基本シリーズ配列なためインデックスに対する動きを合わせる為に呼び出しています。iMAOnArrayの動作が変わります。iMAOnArrayですが結構重いです。配列に対して特定インデックスの単純移動平均を求めるような場合は総和をnで割った方が速度が速いため自力で計算しています。10倍ぐらい早くなります。バックテストなどでは結構効いてきます。

使い方はこんな感じになります。CArrayDoubleの機能を保持したまま、移動平均を求める機能を追加しています。継承の典型ですね。
CCustomArrayDouble buffer;
buffer.Resize(10);
buffer.Add(1.0);
buffer.Add(1);
//...略
double ma = buffer.GetMa(10, 0, MODE_SMA, 0);


まぁMT4のプログラムでここまでする必要があるのか?と言われると、一回きりなら不要と答えます。長々と作っていく場合こーいったライブラリを整理しておくと将来的な時間削減につながると思って対応しています。地道な作業の積み重ねですね。


2015年5月25日月曜日

MT4プログラム 毎月第1金曜日は米雇用統計・・いいえ違います。雇用統計発表日を計算する。

FXで相場が大きく動くアメリカ雇用統計です。
雇用統計に向けて仕掛けるにしても、逆に雇用統計前にポジションをきれいにするにしても、雇用統計発表時間は判定したいですよね。

毎週金曜日のアメリカ時間8:30を取引しないというアルゴリズムでもいいのですが、何にもない日で利益の機会を逃してしまう事も多々あります。

さて、アメリカ雇用統計ですが、第一金曜日とよく日本のページには記載されていますが、たまに第2金曜日になる事ありませんか?

実は、アメリカ 雇用統計 発表日は第1金曜日ではありません。
非農業部門雇用者数(アメリカwiki)によると、12を含む週が終わった後の第三金曜日に発表されるとなります。Wikiには特に書いてありませんが、1月は1~3が金曜日になった場合は、次の週に回すようです。また7月は独立記念日があるため、木曜日に回る事があります。2月が短いために3月はほぼ第2金曜日になったりします。

判定プログラムを作るとたぶんこんな感じになるかと。
#define ONEDAY (3600 * 24)
// 曜日
#define SUNDAY 0
#define MONDAY 1
#define TUESDAY 2
#define WEDNESDAY 3
#define THURSDAY 4
#define FRIDAY 5
#define SATURDAY 6

//------------------------------------------------------------------
// 指定された日が雇用統計日か判定する。
// return true
bool IsEmplymentStatisticDay(
int year,      // 年
int month,     //月
int day        //日
)
{
// 12を含む週終了後の3回目の金曜日
// 1月だけは1~3が金曜日の場合、次の金曜日

MqlDateTime current;
current.year = year;
current.mon = month;
current.day = day;
current.hour = 12;
current.min = 0 ;
current.sec = 0;
TimeToStruct(StructToTime(current), current);

if( current.mon == 1 )
{
// 1月は4~10日の金曜日が雇用統計で確定。
if( 4 <= current.day && current.day <= 10 && current.day_of_week == FRIDAY )
{
return true;
}
}
else if( current.mon == 7 && (current.day == 2 || current.day == 3) && current.day_of_week == THURSDAY)
{
// 7/4は独立記念日の為、重なった場合は木曜日にずれる。
// 7/3が金曜日の場合も振替休日のためずれる。
return true;
}
else if( current.mon == 7 && (current.day == 3 || current.day == 4) && current.day_of_week == FRIDAY)
{
return false;
}
else if(  1 <= current.day && current.day <= 10 && (current.day_of_week == THURSDAY || current.day_of_week == FRIDAY ))
{
//前の月の12日を含む週から3番目
MqlDateTime beforeMonth;
beforeMonth.year = current.year;
beforeMonth.mon = current.mon - 1;
beforeMonth.day = 12;
beforeMonth.hour = 12;
beforeMonth.min = 0;
beforeMonth.sec = 0;

datetime beforeMonthTime = StructToTime(beforeMonth);
TimeToStruct( beforeMonthTime, beforeMonth);

datetime emplymentDay = beforeMonthTime + (5 - TimeDayOfWeek(beforeMonthTime) + 21) * ONEDAY;

if( current.day == TimeDay(emplymentDay) )
{
return true;
}
}
return false;
}


ちなみに2013年の10月~11月は予算承認問題で全く違う日に発表されています。
バックテスト用にEAを停止していたか動かしていたか?という判断で別途判定する必要があります。