- はじめに
- 今回記録する情報
- なぜエントリー時と決済時の両方を記録するのか
- 今回作る仕組み
- 1.取引ごとの「記憶」の名前を作る
- 2.エントリー時のATRを取得する
- 3.その他のエントリー時情報も記憶する
- 4.なぜGlobalVariableが必要なのか
- 5.決済時の相場環境は、その場で取得する
- 6.決済成功後にログを書く
- 7.エントリー時の「記憶」を取り出す
- 8.決済時の相場環境を取得する
- 9.1取引=CSVの1行にする
- 10.FileOpen()でCSVを開く
- 11.FileSeek()で末尾へ移動する
- 12.FileWrite()で取引を記録する
- 13.書き込みが終わったら記憶を削除する
- 今回作った仕組み
- 零号機は「結果」だけでなく「過程」を記録する
- 次回 ― 記憶をPythonへ渡す
- 関連記事
はじめに
前回からChapter2「学習」に入りました。
Chapter1「観測」では、約11年半のバックテスト結果をPythonで分析し、
- 月別損益
- エントリー・決済時間帯
- エントリー・決済曜日
- 保有時間
- 連勝・連敗
- ドローダウン
など、さまざまな角度から零号機の特徴を調べてきました。
その結果、時間帯や保有時間などによって成績に差があることが分かりました。

しかし、Chapter1で分かったのは、
「何が起きていたのか」
までです。
例えば、4~24時間保有した取引の成績が悪かったとしても、
なぜ、その取引は長時間保有することになり、最終的に負けたのか
までは分かりません。
エントリー時から相場の値動きが小さかったのか。
エントリー後にボラティリティが低下したのか。
あるいは、ボリンジャーバンドの状態が変化したのか。
原因を調べるためには、取引結果だけでなく、取引を開始したときと終了したときの相場環境も必要になります。
そこで今回はMQL4を改修し、
「どのような相場でエントリーし、どのような相場で決済したのか」
を記録できるようにします。
零号機に「記憶」を与える作業です。
今回記録する情報
今回の改修では、1回の取引について大きく3種類の情報を残します。
① 取引そのものの情報
ticket
side
entry_time
exit_time
entry_price
exit_price
lots
profit
holding_minutes
band_condition
reverse_condition
これは、
- BUYかSELLか
- いつエントリー・決済したか
- いくらで売買したか
- 何分保有したか
- 最終的にいくら勝った・負けたか
- どの条件で決済したか
といった、取引そのものの記録です。
② エントリー時の相場環境
atr14_entry_pips
bb_width_entry_pips
band_distance_entry_pips
spread_entry_pips
entry_hour
entry_weekday
entry_bar_time
③ 決済時の相場環境
今回はさらに、
atr14_exit_pips
bb_width_exit_pips
band_distance_exit_pips
spread_exit_pips
も記録します。
これによって、
エントリー時ATR
↓
ポジション保有
↓
決済時ATR
のように、取引中に相場環境がどう変化したのかも分析できるようになります。
なぜエントリー時と決済時の両方を記録するのか
例えば、4~8時間保有して負けた取引があったとします。
エントリー時と決済時のATRが、
Entry ATR = 15 pips
Exit ATR = 6 pips
だったとします。
このような取引が多数見つかれば、
長時間保有したから負けた
のではなく、
エントリー後にボラティリティが低下した結果、ポジションが長時間捕まり、最終的に負けた
という別の可能性が見えてきます。
逆に、
Entry ATR = 8 pips
Exit ATR = 16 pips
という取引が勝ちやすければ、
エントリー後のボラティリティ拡大が零号機に有利
という仮説も立てられます。
Chapter1では、
保有時間 → 損益
という関係までしか見られませんでした。
Chapter2では、
エントリー時の相場
↓
相場環境の変化
↓
決済時の相場
↓
取引結果
まで見られるようにします。
今回作る仕組み
ただし、エントリー時と決済時では情報の保存方法を変えます。
【エントリー】
OrderSend()
↓
注文成立
↓
ticket取得
↓
ATR・BB幅・バンド距離・スプレッド等を取得
↓
GlobalVariableへ保存
【ポジション保有】
GlobalVariableに
エントリー時の相場環境を保持
【決済】
OrderClose()
↓
決済成功
↓
その場で決済時の相場環境を取得
↓
GlobalVariableから
エントリー時の相場環境を取得
↓
取引結果とまとめる
↓
trade_log.csvへ保存
↓
GlobalVariableを削除
ここが今回の設計のポイントです。
エントリー時の情報は保存しておかなければ後から失われます。
そのため、GlobalVariableを使います。
一方、決済時の情報は決済した瞬間に取得できるため、GlobalVariableへ保存する必要はありません。
1.取引ごとの「記憶」の名前を作る
まず、GlobalVariableの名前を作る関数を用意します。
string EntryKey(
int ticket,
string suffix
)
{
return(
IntegerToString(ticket)
+ "_"
+ suffix
);
}
例えば、
ticket = 259597667
suffix = ATR14_PIPS
なら、
259597667_ATR14_PIPS
という名前になります。
同じticketに対して、
259597667_ATR14_PIPS
259597667_BB_WIDTH_PIPS
259597667_BAND_DISTANCE_PIPS
259597667_SPREAD_PIPS
259597667_ENTRY_HOUR
259597667_ENTRY_WEEKDAY
259597667_ENTRY_BAR_TIME
というGlobalVariableを作ることができます。
つまり、
ticketを取引ごとのIDとして、エントリー時の情報を紐付ける
という仕組みです。
2.エントリー時のATRを取得する
例えばATR14は、
double atr14 =
iATR(
_Symbol,
0,
14,
0
);
で取得します。
ATRは、簡単にいえば最近の値動きの大きさを見るための指標です。
ATRが大きければ値動きの大きな相場、ATRが小さければ比較的値動きの小さな相場と考えられます。
取得したATRは、
GlobalVariableSet(
EntryKey(
ticket,
"ATR14_PIPS"
),
atr14 / PipPoint
);
として保存します。
GlobalVariableSet()がGlobalVariableへ値を保存する関数です。
また、
atr14 / PipPoint
によって、値をpips単位へ変換しています。
3.その他のエントリー時情報も記憶する
同じ方法で、
ATR14_PIPS
BB_WIDTH_PIPS
BAND_DISTANCE_PIPS
SPREAD_PIPS
ENTRY_HOUR
ENTRY_WEEKDAY
ENTRY_BAR_TIME
の7種類を保存します。
例えば、
ATR14_PIPS
なら値動きの大きさ、
BB_WIDTH_PIPS
ならボリンジャーバンドの広がり、
SPREAD_PIPS
ならエントリー時の取引コスト、
といった具合です。
この段階では、
どの情報が本当に零号機の成績に影響しているのかは分かりません。
それを調べるのがChapter2です。
4.なぜGlobalVariableが必要なのか
例えば14時にエントリーして、18時に決済したとします。
14:00 Entry
ATR = 8 pips
↓
4時間保有
↓
18:00 Exit
ATR = 15 pips
18時にiATR()を実行して取得できるのは、18時時点のATRです。
14時時点のATRではありません。
そのため、
14:00
エントリー時の情報を取得
↓
GlobalVariableへ保存
↓
18:00
GlobalVariableから取得
という処理が必要になります。
GlobalVariableを、エントリーから決済まで取引の状態を覚えておく一時的な記憶領域として使うわけです。
5.決済時の相場環境は、その場で取得する
一方、決済時のATRなどは保存しておく必要がありません。
決済するタイミングで、
double atr14_exit =
iATR(
_Symbol,
0,
14,
0
);
のように取得できます。
同様に、
BB幅
バンドまでの距離
スプレッド
も決済時に計算します。
そして、
Entry ATR
Exit ATR
Entry BB Width
Exit BB Width
という形でCSVに一緒に保存します。
これによって、後からPythonで、
df["atr_change_pips"] = (
df["atr14_exit_pips"]
- df["atr14_entry_pips"]
)
のような分析も可能になります。
正なら、
エントリー後にATRが拡大した
負なら、
エントリー後にATRが縮小した
ことを意味します。
6.決済成功後にログを書く
取引情報は、OrderClose()が成功した後に記録します。
例えば、
if(OrderClose(...))
{
Write_Trade_Log(
ticket,
Buy_Exit_Band_Flag,
Buy_Exit_Reverse_Flag
);
}
という流れです。
決済注文を出しても、必ず成功するとは限りません。
そのため、
OrderClose()
↓
成功確認
↓
Write_Trade_Log()
という順番にすることで、実際に決済された取引だけをCSVへ記録します。
7.エントリー時の「記憶」を取り出す
Write_Trade_Log()では、ticketを使ってエントリー時に保存した情報を取得します。
例えば、
double atr14_entry =
GlobalVariableGet(
EntryKey(
ticket,
"ATR14_PIPS"
)
);
とすれば、そのticketに対応するエントリー時ATRを取得できます。
これを、
ATR
BB幅
バンド距離
スプレッド
時間
曜日
バー時刻
について行います。
8.決済時の相場環境を取得する
同じWrite_Trade_Log()の中で、決済時の相場環境も取得します。
ここでは、
atr14_exit_pips
bb_width_exit_pips
band_distance_exit_pips
spread_exit_pips
を計算します。
つまりWrite_Trade_Log()には、
GlobalVariableから取得したEntry情報
+
その場で計算したExit情報
+
MT4の取引結果
が集まることになります。
そして、それらを1行のCSVとして保存します。
9.1取引=CSVの1行にする
trade_log.csvでは、
1回の取引を1行
として保存します。
イメージとしては、
ticket
side
entry_time
exit_time
entry_price
exit_price
lots
profit
holding_minutes
band_condition
reverse_condition
atr14_entry_pips
atr14_exit_pips
bb_width_entry_pips
bb_width_exit_pips
band_distance_entry_pips
band_distance_exit_pips
spread_entry_pips
spread_exit_pips
entry_hour
entry_weekday
entry_bar_time
という構造です。
ここで列名を、
entry
exit
で明確に分けておくのがポイントです。
例えば単に、
ATR14_PIPS
としてしまうと、後から見たときにエントリー時なのか決済時なのか分からなくなります。
そのためCSVでは、
atr14_entry_pips
atr14_exit_pips
のようにしておきます。
10.FileOpen()でCSVを開く
CSVへの書き込みには、
FileOpen()
を使います。
今回の保存先は、
trade_log.csv
です。
基本的な処理は、
FileOpen()
↓
FileSeek()
↓
FileWrite()
↓
FileFlush()
↓
FileClose()
となります。
11.FileSeek()で末尾へ移動する
すでにCSVに取引データが存在する場合、新しい取引はその下へ追加します。
取引1
取引2
取引3
↑
ここまで記録済み
そこで、
FileSeek()
を使ってファイル末尾へ移動してから、
FileWrite()
を実行します。
すると、
取引1
取引2
取引3
取引4 ← 追加
という形で取引履歴を蓄積できます。
12.FileWrite()で取引を記録する
FileWrite()には、今回記録する項目を順番に渡します。
重要なのは、CSVのヘッダーとFileWrite()の順番を必ず一致させることです。
例えば、
atr14_entry_pips
atr14_exit_pips
bb_width_entry_pips
bb_width_exit_pips
というヘッダーなら、FileWrite()側も同じ順番にします。
ここがずれると、
ATRの列にBB幅が入っている
といった非常に厄介なデータになってしまいます。
今後Pythonで分析するデータなので、ここは特に注意します。
13.書き込みが終わったら記憶を削除する
CSVへの保存が完了すれば、その取引についてGlobalVariableへ保存していた情報は不要です。
そこで、
void DeleteEntryState(
int ticket
)
{
string suffixes[7] =
{
"ATR14_PIPS",
"BB_WIDTH_PIPS",
"BAND_DISTANCE_PIPS",
"SPREAD_PIPS",
"ENTRY_HOUR",
"ENTRY_WEEKDAY",
"ENTRY_BAR_TIME"
};
for(
int i = 0;
i < ArraySize(suffixes);
i++
)
{
string key = EntryKey(
ticket,
suffixes[i]
);
if(GlobalVariableCheck(key))
{
GlobalVariableDel(key);
}
}
}
として削除します。
ここで削除するのはエントリー時の7項目だけです。
決済時の相場環境はGlobalVariableに保存していないので、削除する必要はありません。
今回作った仕組み
今回の処理を改めて整理すると、
① Entry
OrderSend()
↓
ticket取得
↓
エントリー時の相場環境を取得
↓
GlobalVariableSet()
② Position
エントリー時情報を
GlobalVariableで保持
③ Exit
OrderClose()
↓
決済成功
↓
決済時の相場環境を取得
④ Log
エントリー時情報
+
決済時情報
+
取引結果
↓
trade_log.csv
⑤ Cleanup
DeleteEntryState()
↓
GlobalVariableDel()
となります。
これで零号機は、
「どんな相場でエントリーし、その後どんな相場になり、最終的にどうなったのか」
を記録できるようになります。
零号機は「結果」だけでなく「過程」を記録する
今回も、売買ロジックそのものを改善したわけではありません。
エントリー条件や決済条件は変えていないため、今回の改修だけでバックテストの成績が良くなるわけではありません。
しかし、Chapter2を進めるための重要な材料が増えました。
これまでは、
取引
↓
結果
を中心に見ていました。
これからは、
エントリー時の相場環境
↓
取引
↓
決済時の相場環境
↓
結果
として分析できます。
例えば、
ATRは拡大したのか
BB幅は拡大したのか
バンドとの距離はどう変化したのか
といった相場環境の変化と損益を結び付けることができます。
これは、Chapter1で見つけた「4~24時間の取引が弱い」といった現象の原因を探すうえでも使えるはずです。
零号機は今回、単に「取引した」という記憶だけでなく、
その取引の前後で相場がどう変わったのかを振り返るための記憶
を持てるようになりました。
次回 ― 記憶をPythonへ渡す
MQL4側の記録機能ができたので、次はそのデータをPythonへ渡します。
次回は、
trade_log.csv
↓
Python
↓
trade_base.csv
という処理を作ります。
trade_log.csvは、零号機が記録した生の取引データです。
Pythonのpandasで読み込み、
- 列構成の確認
- 日時型への変換
- 数値型への変換
- 欠損値の確認
- 異常値の確認
- 分析しやすい形式への整理
を行います。
さらに今回、
Entry ATR / Exit ATR
Entry BB Width / Exit BB Width
の両方を保存できるようにしたので、Python側では将来的に、
ATRの変化量
BB幅の変化量
といった新しい特徴量も作れるようになります。
MQL4の役割は、経験を正確に記録すること。
Pythonの役割は、その経験からパターンを探すこと。
次回は、零号機の「記憶」を分析可能なデータへ変換していきます。

コメント