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);
}

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