- はじめに
- 1.必要なライブラリを読み込む
- 2.resultsフォルダを作る
- 3.trade_log.csvを読み込む
- 4.まずデータの中身を確認する
- 5.必要な18列がそろっているか確認する
- 6.日時として扱う列を指定する
- 7.for文で日時型へ変換する
- 8.pd.to_datetime()で日時型へ変換する
- 9.errors="coerce"とは
- 10.数値として扱う列を指定する
- 11.for文で数値型へ変換する
- 12.pd.to_numeric()とは
- 13.変換に失敗したデータを確認する
- 14.変換後の型を確認する
- 15.基本統計量を確認する
- 16.取引ごとの基本的な整合性も確認する
- 17.trade_base.csvとして保存する
- trade_log.csvを直接加工しない理由
- 今回のまとめ
- 次回予告
- 今回作ったPythonコード
- 関連記事
はじめに
前回は、MQL4を改修して、零号機が取引したときの情報を記録する仕組みを作りました。

エントリーが成立した瞬間に、
ATR
BB幅
バンドまでの距離
スプレッド
エントリー時刻
曜日
バー時刻
などを取得し、取引が決済されるまでGlobalVariableに保存します。
そして決済が成功すると、取引結果と合わせてtrade_log.csvへ書き出します。前回の記事では、ticketを取引ごとのIDとしてエントリー時情報を保持する仕組みにしました。
今回のtrade_log.csvには、次の18項目が記録されます。
ticket
side
entry_time
entry_bar_time
exit_time
entry_price
exit_price
lots
profit
holding_minutes
entry_hour
entry_weekday
entry_atr14_pips
entry_bb_width_pips
entry_band_distance_pips
entry_spread_pips
band_condition
reverse_condition
しかし、MQL4から出力されたCSVは、まだ零号機が残した生の記録です。
これからPythonで分析するには、
trade_log.csv
↓
Pythonで読み込む
↓
列が正しいか確認
↓
日時型へ変換
↓
数値型へ変換
↓
変換エラーを確認
↓
trade_base.csv
という前処理を行います。
今回はまだ本格的な分析には入りません。
零号機の「記憶」をPythonが正しく扱える形へ整えることが目的です。
コード全文は最後に掲載します。
1.必要なライブラリを読み込む
まず、今回使用するライブラリを読み込みます。
import os
import pandas as pd
osはフォルダなどを操作するために使います。
pandasは、CSVのような表形式データをPythonで扱うためのライブラリです。
import pandas as pd
としているため、以降はpandasを、
pd
という名前で使用します。
2.resultsフォルダを作る
分析用データの保存先を作ります。
os.makedirs(
"results",
exist_ok=True
)
os.makedirs()はフォルダを作成する関数です。
今回は、
"results"
を指定しているため、
作業フォルダ
├─ create_master.py
├─ trade_log.csv
└─ results
という構成になります。
exist_ok=True
は、
すでに
resultsフォルダが存在していてもエラーにしない
という指定です。
同じコードを何度実行しても、フォルダが既にあることを理由に処理が停止しません。
3.trade_log.csvを読み込む
続いて、第13回でMQL4から出力したtrade_log.csvを読み込みます。
df = pd.read_csv(
"trade_log.csv",
encoding="utf-8-sig"
)
pd.read_csv()はCSVを読み込むpandasの関数です。
読み込んだデータを、
df
という変数に入れています。
dfはDataFrameの略としてよく使われます。
DataFrameは、
行 × 列
で構成された表形式のデータです。
つまり、
df = pd.read_csv(...)
は、
trade_log.csvを読み込み、その表をdfとしてPythonで扱えるようにする
という処理です。
4.まずデータの中身を確認する
CSVを読み込んだら、すぐに加工するのではなく、最初に正しく読み込めているか確認します。
print(df.head())
df.info()
df.head()
df.head()
は、DataFrameの先頭5行を取得します。
今回は、
print(df.head())
としてターミナルへ表示します。
ここでは、
ticket
side
entry_time
entry_bar_time
exit_time
...
という列が正しく並んでいるか、実際の値が想定どおり記録されているかを確認します。
df.info()
df.info()
では、
行数
列数
列名
欠損していないデータ数
各列のデータ型
などを確認できます。
つまり、
df.head()
→ 実際のデータを見る
df.info()
→ データの構造を見る
という違いです。
df.info()自体が結果を表示するため、
print(df.info())
とする必要はありません。
5.必要な18列がそろっているか確認する
今回から、ここを追加します。
第13回でMQL4側のCSV構造を決めたので、Python側でも想定した列が全部存在しているか確認しておきます。
expected_columns = [
"ticket",
"side",
"entry_time",
"entry_bar_time",
"exit_time",
"entry_price",
"exit_price",
"lots",
"profit",
"holding_minutes",
"entry_hour",
"entry_weekday",
"entry_atr14_pips",
"entry_bb_width_pips",
"entry_band_distance_pips",
"entry_spread_pips",
"band_condition",
"reverse_condition"
]
これは、第13回で作ったtrade_log.csvのヘッダーと同じです。
そして、
missing_columns = [
col
for col in expected_columns
if col not in df.columns
]
で、必要な列のうちdfに存在しないものを探します。
もし不足していれば、
if missing_columns:
raise ValueError(
f"必要な列がありません: {missing_columns}"
)
として処理を停止します。
これによって、MQL4側とPython側でCSVの構造が食い違っている状態のまま分析を進めることを防げます。
6.日時として扱う列を指定する
今回、日時として扱いたいのは3列です。
date_columns = [
"entry_time",
"entry_bar_time",
"exit_time"
]
第13回ではエントリー時のバー時刻entry_bar_timeも記録するようになっています。
そのため今回は、
entry_time
→ 実際に注文が成立した時刻
entry_bar_time
→ エントリー時に見ていたバーの時刻
exit_time
→ 決済した時刻
の3列を日時型に変換します。
7.for文で日時型へ変換する
for col in date_columns:
df[col] = pd.to_datetime(
df[col],
format="%Y.%m.%d %H:%M",
errors="coerce"
)
まず、
for col in date_columns:
によって、
1周目 → entry_time
2周目 → entry_bar_time
3周目 → exit_time
と、1列ずつ処理します。
例えば1周目なら、実質的には、
df["entry_time"] = pd.to_datetime(
df["entry_time"],
format="%Y.%m.%d %H:%M",
errors="coerce"
)
を実行していることになります。
8.pd.to_datetime()で日時型へ変換する
pd.to_datetime()
は、データを日時型へ変換するpandasの関数です。
今回、
format="%Y.%m.%d %H:%M"
としています。
例えば、
2026.08.08 14:30
なら、
%Y → 2026
%m → 08
%d → 08
%H → 14
%M → 30
に対応します。
第13回のMQL4ではTimeToString(..., TIME_DATE | TIME_MINUTES)で日時をCSVへ書き出しているため、Python側でもその形式に合わせます。
9.errors="coerce"とは
errors="coerce"
は、
変換できない値があってもプログラムを停止せず、欠損値にする
という指定です。
日時で変換に失敗すると、
NaT
になります。
エラーを隠しているのではなく、後から、
isna()
で変換失敗を発見できるようにしています。
10.数値として扱う列を指定する
次は数値です。
今回のCSVでは、かなり多くの項目を数値として分析します。
numeric_columns = [
"ticket",
"entry_price",
"exit_price",
"lots",
"profit",
"holding_minutes",
"entry_hour",
"entry_weekday",
"entry_atr14_pips",
"entry_bb_width_pips",
"entry_band_distance_pips",
"entry_spread_pips",
"band_condition",
"reverse_condition"
]
特に第13回で新しく記録した、
entry_atr14_pips
entry_bb_width_pips
entry_band_distance_pips
entry_spread_pips
はChapter2で相場環境と成績の関係を調べるための重要なデータです。第13回ではこれらのエントリー時情報をGlobalVariableで保持し、決済後にCSVへ保存する設計になっています。
entry_hourやentry_weekday、決済条件のフラグも今後集計に使うため、数値として扱える状態にしておきます。
一方、
side
は、
BUY
SELL
という文字列なので、数値型には変換しません。
11.for文で数値型へ変換する
for col in numeric_columns:
df[col] = pd.to_numeric(
df[col],
errors="coerce"
)
日時変換と同じ考え方です。
例えば、
col = "profit"
のときは、
df["profit"] = pd.to_numeric(
df["profit"],
errors="coerce"
)
が実行されます。
12.pd.to_numeric()とは
pd.to_numeric()
は、データを数値型へ変換するpandasの関数です。
先ほどの、
pd.to_datetime()
と並べると覚えやすいです。
pd.to_datetime()
→ 日時へ変換
pd.to_numeric()
→ 数値へ変換
例えばCSV上では、
"12.345"
のように文字列として認識されていた値でも、数値へ変換すれば平均・合計などを計算できるようになります。
13.変換に失敗したデータを確認する
型変換が終わったら、変換エラーを確認します。
check_columns = (
date_columns
+ numeric_columns
)
日時列と数値列をまとめています。
そして、
print(
"\n===== 変換エラー確認 ====="
)
print(
df[
check_columns
].isna().sum()
)
とします。
isna()は、
値が欠損しているか
を判定します。
さらに、
.sum()
によって、各列に何件の欠損があるかを数えます。
例えば、
entry_time 0
entry_bar_time 0
exit_time 0
entry_price 0
profit 0
entry_atr14_pips 0
...
であれば、今回の型変換では失敗していません。
一方、
entry_atr14_pips 3
となっていれば、3件について数値へ変換できなかったことになります。
14.変換後の型を確認する
もう一度、
df.info()
を実行します。
最初のdf.info()は、
CSVを読み込んだ直後
を確認しました。
今回は、
型変換した後
を確認します。
例えば、
entry_time
entry_bar_time
exit_time
→ datetime64
entry_price
profit
entry_atr14_pips
...
→ 数値型
になっているかを確認します。
15.基本統計量を確認する
続いて、
print(
df.describe()
)
を実行します。
describe()を使うと、数値データについて、
データ数
平均
標準偏差
最小値
25%
中央値
75%
最大値
などをまとめて確認できます。
今回ここで本格的な分析をするわけではありません。
例えば、
holding_minutesがマイナスになっていないか
entry_hourが0~23の範囲か
entry_weekdayが想定した範囲か
ATRやBB幅に極端な値がないか
など、明らかにおかしなデータがないかを見るための一次チェックとして使います。
16.取引ごとの基本的な整合性も確認する
第13回でせっかく1取引1行のログを作ったので、日時の前後関係なども確認しておきます。
invalid_time_order = df[
df["exit_time"]
< df["entry_time"]
]
これは、
決済時間がエントリー時間より前になっている取引
を抽出します。
通常、そのような取引は存在しないはずです。
print(
"\n===== 時刻順序エラー ====="
)
print(
f"{len(invalid_time_order)}件"
)
として件数を確認します。
さらに、
duplicate_ticket_count = (
df["ticket"]
.duplicated()
.sum()
)
によってticketの重複も確認します。
今回は1つの決済済み取引につき1行を記録する設計なので、同じticketが複数行に現れていないかを確認しておきます。
17.trade_base.csvとして保存する
最後に、整えたDataFrameを保存します。
df.to_csv(
"results/trade_base.csv",
index=False,
encoding="utf-8-sig"
)
これで、
results
└─ trade_base.csv
が作られます。
index=False
としているため、pandas内部の行番号はCSVへ保存しません。
これで、
trade_log.csv
↓
Python
↓
18列の構造を確認
↓
日時・数値を正しい型へ変換
↓
変換エラー・整合性を確認
↓
trade_base.csv
という処理が完成しました。
trade_log.csvを直接加工しない理由
今回は、MQL4が出力したtrade_log.csvを上書きしません。
trade_log.csv
↓
trade_base.csv
と別ファイルにします。
役割としては、
trade_log.csv
= 零号機が残した「生の記憶」
trade_base.csv
= Pythonで整理した「分析用の記憶」
です。
元のtrade_log.csvを残しておけば、今後Python側の前処理を変更したくなった場合も、生データから何度でも作り直せます。
これは今後、
trade_log.csv
↓
trade_base.csv
↓
さらなる特徴量を追加
↓
分析用データ
と処理が増えていくことを考えても重要です。
今回のまとめ
今回は、第13回でMQL4から出力したtrade_log.csvをPythonで読み込み、分析に使える形へ整えました。
今回行ったのは、
trade_log.csv
↓
CSVを読み込む
↓
必要な18列がそろっているか確認
↓
日時データをdatetime型へ変換
↓
数値データをnumeric型へ変換
↓
変換エラーを確認
↓
時刻やticketの整合性を確認
↓
trade_base.csvとして保存
という前処理です。
第13回では、零号機がエントリーした瞬間の、
ATR
BB幅
バンドまでの距離
スプレッド
エントリー時間
曜日
バー時刻
などを記録できるようにしました。
しかし、記録しただけではまだ「学習」には使えません。
Python側で、
どの列が日時なのか、どの列が数値なのか
を明確にし、分析できる形へ変換する必要があります。
今回作成したtrade_base.csvは、そのための分析用の基礎データです。
これまでChapter1では、
何時に勝っているのか
何曜日に勝っているのか
どのくらい保有すると弱くなるのか
と、取引結果を後から観測してきました。
Chapter2では、そこから一歩進んで、
その取引をした瞬間、相場はどんな状態だったのか
を分析していきます。
そのための準備が、今回ようやく整いました。
trade_log.csvは、零号機が残した生の記憶。
trade_base.csvは、その記憶をPythonが読める形に整理した分析用の記憶。
まだ「学習」は始まったばかりですが、零号機が自分の取引を振り返るための土台ができました。
次回予告
次回は、今回作成したtrade_base.csvを使って、いよいよエントリー時の相場環境と取引結果の関係を分析してみます。
まず注目するのは、
entry_atr14_pips
entry_bb_width_pips
entry_band_distance_pips
entry_spread_pips
です。
例えば、
ATRが大きいときと小さいときで成績は違うのか。
ボリンジャーバンドの幅が広いときと狭いときでは、どちらが勝ちやすいのか。
バンドから離れすぎた状態でエントリーした取引は弱くないか。
スプレッドが広いときの取引は成績が悪くないか。
といったことを調べます。
Chapter1では、
「零号機には時間帯や保有時間によって成績差がある」
ことを観測しました。
Chapter2では、その差が生まれる原因を、
エントリー時の相場環境
から探っていきます。
ここから、
取引結果を見る
↓
エントリー時の環境を見る
↓
勝ちやすい条件・負けやすい条件を探す
↓
改善仮説を作る
という流れに進みます。
零号機がただ売買するだけのシステムから、
自分の過去の取引を材料に、少しずつ「学ぶ」システムへ。
今回作ったPythonコード
今回の処理をまとめると、次のコードになりました。
コード全文を開く
# ==========================================
# Chapter2
# trade_log.csv を分析用データへ変換
# ==========================================
import os
import pandas as pd
# ==========================================
# 保存先フォルダを作成
# ==========================================
os.makedirs(
"results",
exist_ok=True
)
# ==========================================
# trade_log.csv を読み込む
# ==========================================
df = pd.read_csv(
"trade_log.csv",
encoding="utf-8-sig"
)
# ==========================================
# 読み込んだデータを確認
# ==========================================
print(
"\n===== 読み込み直後 ====="
)
print(
df.head()
)
df.info()
# ==========================================
# 必要な列を確認
# ==========================================
expected_columns = [
"ticket",
"side",
"entry_time",
"entry_bar_time",
"exit_time",
"entry_price",
"exit_price",
"lots",
"profit",
"holding_minutes",
"entry_hour",
"entry_weekday",
"entry_atr14_pips",
"entry_bb_width_pips",
"entry_band_distance_pips",
"entry_spread_pips",
"band_condition",
"reverse_condition"
]
missing_columns = [
col
for col in expected_columns
if col not in df.columns
]
if missing_columns:
raise ValueError(
f"必要な列がありません: {missing_columns}"
)
# ==========================================
# 日時型へ変換
# ==========================================
date_columns = [
"entry_time",
"entry_bar_time",
"exit_time"
]
for col in date_columns:
df[col] = pd.to_datetime(
df[col],
format="%Y.%m.%d %H:%M",
errors="coerce"
)
# ==========================================
# 数値型へ変換
# ==========================================
numeric_columns = [
"ticket",
"entry_price",
"exit_price",
"lots",
"profit",
"holding_minutes",
"entry_hour",
"entry_weekday",
"entry_atr14_pips",
"entry_bb_width_pips",
"entry_band_distance_pips",
"entry_spread_pips",
"band_condition",
"reverse_condition"
]
for col in numeric_columns:
df[col] = pd.to_numeric(
df[col],
errors="coerce"
)
# ==========================================
# 変換エラーを確認
# ==========================================
check_columns = (
date_columns
+ numeric_columns
)
print(
"\n===== 変換エラー確認 ====="
)
print(
df[
check_columns
].isna().sum()
)
# ==========================================
# 変換後の構造を確認
# ==========================================
print(
"\n===== 型変換後 ====="
)
df.info()
# ==========================================
# 基本統計量を確認
# ==========================================
print(
"\n===== 基本統計量 ====="
)
print(
df.describe()
)
# ==========================================
# 時刻の前後関係を確認
# ==========================================
invalid_time_order = df[
df["exit_time"]
< df["entry_time"]
]
print(
"\n===== 時刻順序エラー ====="
)
print(
f"{len(invalid_time_order)}件"
)
# ==========================================
# ticketの重複を確認
# ==========================================
duplicate_ticket_count = (
df["ticket"]
.duplicated()
.sum()
)
print(
"\n===== ticket重複 ====="
)
print(
f"{duplicate_ticket_count}件"
)
# ==========================================
# 分析用CSVとして保存
# ==========================================
df.to_csv(
"results/trade_base.csv",
index=False,
encoding="utf-8-sig"
)
print(
"\nresults/trade_base.csv "
"を保存しました。"
)
コメント