どうも潤奈です( ・Д・)
皆さんも自作EAなどをバックテストで色々分析していると思います。
その際に最後に表示されるレポートには最大ドローダウンや相対ドローダウンは表示されますが、その最大ドローダウン期間は表示されていません。
実際に運用する際に、「このEAは過去のバックテストでこれだけのドローダウン期間発生していた事がある」という事を知っておけば、「なかなかドローダウンから復活しないぞ?大丈夫か?」という心配が少しでも解消すると思いコードを考えてみました( ・Д・)
コード全文
今回必要なコードは3ブロックに分かれます。
1 2 3 4 5 |
//①関数の外部で宣言(パラメーター辺り) //---ドローダウン期間の取得変数 datetime DrawdownPeriod; //ドローダウン期間を記録 datetime DrawdownStart; //ドローダウン開始時間 datetime DrawdownEnd; //ドローダウン終了時間 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
//②OnTick関数内に記述 void OnTick() { //その他ロジック static double Balance=AccountBalance(); //初期口座残高を記録 static datetime RecordTime=TimeCurrent(); //計測開始時間を記録 //前回記録した口座残高を更新している時 if(Balance<AccountBalance()){ datetime Record=TimeCurrent()-RecordTime; //計測開始時刻から現在までの期間を計算 //ドローダウン期間を更新した時 if(DrawdownPeriod<Record){ DrawdownPeriod=Record; //最大ドローダウン期間を更新 DrawdownStart=RecordTime; //計測開始した時の時間を記録 DrawdownEnd=TimeCurrent(); //計測終了した時の時間を記録 } RecordTime=TimeCurrent(); //現在時刻を計測開始時刻に記録 Balance=AccountBalance(); //現在残高を記録 } } |
1 2 3 4 5 6 7 8 9 10 11 |
//③OnTester関数内に記述 double OnTester(){ //開始時間と終了時間をプリント出力 Print("開始時間=",DrawdownStart," 終了時間=",DrawdownEnd); //最大ドローダウン期間を年月日時分でプリント出力 Print(TimeYear(DrawdownPeriod)-1970,"年",TimeMonth(DrawdownPeriod)-1,"月",TimeDay(DrawdownPeriod)-1,"日",TimeHour(DrawdownPeriod),"時",TimeMinute(DrawdownPeriod),"分"); return(DrawdownPeriod); //最大ドローダウン期間(秒)で返す } |
では順番に説明します。
使用する変数を宣言
使用する変数を関数の外で宣言します。記述する位置に関してはOnTick関数より上の位置で、どの関数内に入ってなければ大丈夫です。
1 2 3 4 5 |
//①関数の外部で宣言(パラメーター辺り) //---ドローダウン期間の取得変数 datetime DrawdownPeriod; //ドローダウン期間を記録 datetime DrawdownStart; //ドローダウン開始時間 datetime DrawdownEnd; //ドローダウン終了時間 |
それぞれdatetime型で宣言します。
この段階では値を入れずに宣言だけですので、中に入る値は1970年1月1日(MT4仕様)になります。
計測コードを記述
OnTick関数内に最大ドローダウン期間を計測するコードを記述します。
EAではこの中にロジックなど他のコードが入っていると思いますので、良いところへ入れて下さいw
では分解して説明します。
1 2 3 4 5 6 7 |
//②OnTick関数内に記述 void OnTick() { //その他ロジック static double Balance=AccountBalance(); //初期口座残高を記録 static datetime RecordTime=TimeCurrent(); //計測開始時間を記録 |
まずはstaticを付けて変数を宣言します。
こうする事で最初の1回だけこの変数が宣言されるようになるので、以降のティック処理時に値が更新される事を防ぎます。
次に説明する順番として、中の一部を非表示にしています。まずはその上下の説明します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
//前回記録した口座残高を更新している時 if(Balance<AccountBalance()){ datetime Record=TimeCurrent()-RecordTime; //計測開始時刻から現在までの期間を計算 /*一時非表示 //ドローダウン期間を更新した時 if(DrawdownPeriod<Record){ DrawdownPeriod=Record; //最大ドローダウン期間を更新 DrawdownStart=RecordTime; //計測開始した時の時間を記録 DrawdownEnd=TimeCurrent(); //計測終了した時の時間を記録 } */ RecordTime=TimeCurrent(); //現在時刻を計測開始時刻に記録 Balance=AccountBalance(); //現在残高を記録 } } |
初期動作
if文でBalance変数に入っている初期口座残高と現在の口座残高を比較して、残高が増えていれば中の処理に進みます。
次に、Record変数に現在時刻 TimeCurrent()から計測開始時刻を引く事で、その期間が算出されます。
最後にif文を抜ける前にRecordTime変数に現在時刻と、Balance変数に現在口座残高を記録して抜けます。
これが次のドローダウン期間の計測開始時刻と口座残高になります。
口座残高を更新時動作(最大ドローダウン期間更新)
口座残高を更新するとif文内の処理に進みます。
そして現在時刻と計測開始時刻の期間をRecord変数に記録し、次のif文でDrawdownPeriod変数と比較し、期間を更新していれば中の処理へ進みます。
1 2 3 4 5 6 |
//ドローダウン期間を更新した時 if(DrawdownPeriod<Record){ DrawdownPeriod=Record; //①最大ドローダウン期間を更新 DrawdownStart=RecordTime; //②計測開始した時の時間を記録 DrawdownEnd=TimeCurrent(); //③計測終了した時の時間を記録 } |
①DrawdownPeriod変数に最大ドローダウン期間を更新します。
②DrawdownStart変数に計測開始した時の時間を記録します。
③DrawdownEnd変数に現在時刻を記録します。
バックテスト結果を出力コード
バックテストを完了した時に処理されるOnTester関数に計測したデータを出力するコードを記述します。
1 2 3 4 5 6 7 8 9 10 11 |
//③OnTester関数内に記述 double OnTester(){ //開始時間と終了時間をプリント出力 Print("開始時間=",DrawdownStart," 終了時間=",DrawdownEnd); //最大ドローダウン期間を年月日時分でプリント出力 Print(TimeYear(DrawdownPeriod)-1970,"年",TimeMonth(DrawdownPeriod)-1,"月",TimeDay(DrawdownPeriod)-1,"日",TimeHour(DrawdownPeriod),"時",TimeMinute(DrawdownPeriod),"分"); return(DrawdownPeriod); //最大ドローダウン期間(秒)で返す } |
まず一つはPrint関数で最大ドローダウン期間を計測した時の開始時間と終了時間を出力させます。
次に最大ドローダウン期間を年月日時分の表示で出力させますが、少し工夫が必要です。
DrawdownPeriod変数には1970.01.01 00:00:00の時間から加算された時間がドローダウン期間として記録されています。
それぞれ年月日時分を取得する時に年は1970年、月は1月、日は1日を引く事で加算されているドローダウン期間を出力する事が出来ます。
年はTimeYear()で年を取得して1970を引く。
月はTimeMonth()で月を取得して1を引く。
日はTimeDay()で日を取得して1を引く。
時はTimeHour()で時を取得する。元は0なのでそのまま。
分はTimeMinute()で分を取得する。元は0なのでそのまま。
Print関数でバックテスト後に出力される場所は「操作履歴」タブで以下の様に出力されます。
最後にreturnにDrawdownPeriodの値を返す事で、バックテストの最適化した時にOnTesterに最大ドローダウン期間が秒単位で表示されますので、どのパラメーターが最大ドローダウン期間が短いか確認する事が出来ます。
その他
OnTester()関数はdouble型なので、datetime型のDrawdownPeriod変数をリターンで直接返そうとするとデータの型が違うので以下のような注意が出ますが出力した感じは大丈夫そうです。
エラーが一つでも気になる方は、一度datetime型をlong型の変数に代入して、long型からdouble型に変換して出力させるようなコードにしたらエラーは消えました。
ただしlong型からdouble型に変換する際に精度の損失が発生する可能性があるようです。これに関しては現在の私の知識では確実な事が言えませんので、参考程度にして下さい。
まとめ
実際にEAを運用する時にはドローダウンも気になりますが、そのドローダウンの期間も気になります。
私の開発したDorayakiも長期バックテストを見るとずっと右肩上がりに見えますが、今回のコードを使って検証してみると口座残高を更新しない期間が4ヵ月と13日あったという結果を見る事が出来ます。
つまり1~2ヵ月利益が更新出来なくても過去のバックテストでは発生していた事ですので、まだまだ許容範囲として捉える事が出来ます。(※個人の感想です)
では( ・Д・)
コメント