ランダムウォークへの挑戦⑭ | 零号機の「記憶」をPythonへ渡す ― trade_log.csvを分析用データに整える(2026/8/5)

ランダムウォーク 未分類

はじめに

前回は、MQL4を改修して、零号機が取引したときの情報を記録する仕組みを作りました。

ランダムウォークへの挑戦⑬ | 零号機に「記憶」を与える ― mql4でエントリーから決済までの相場環境を記録する(2026/8/2)
はじめに前回からChapter2「学習」に入りました。Chapter1「観測」では、約11年半のバックテスト結果をPythonで分析し、月別損益エントリー・決済時間帯エントリー・決済曜日保有時間連勝・連敗ドローダウンなど、さまざまな角度から…

エントリーが成立した瞬間に、

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_hourentry_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 "
    "を保存しました。"
)

関連記事

ランダムウォークへの挑戦⑭ | 零号機の「記憶」をPythonへ渡す ― trade_log.csvを分析用データに整える(2026/8/5)
ランダムウォークへの挑戦⑬ | 零号機に「記憶」を与える ― mql4でエントリーから決済までの相場環境を記録する(2026/8/2)
ランダムウォークへの挑戦⑫ | Chapter2「学習」始動 ー零号機は何を学ぶべきなのかー(2026/8/2)
ランダムウォークへの挑戦⑪ | 零号機の性格診断──Chapter1「観測」 完結(2026/8/1)
ランダムウォークへの挑戦⑩ | Pythonで資産推移とドローダウンを分析する(2026/8/1)
ランダムウォークへの挑戦⑨ | Pythonで取引の連勝・連敗を分析する(2026/7/28)
ランダムウォークへの挑戦⑧ | Pythonでポジションの保有時間別に成績を分析する(2026/7/26)
ランダムウォークへの挑戦⑦ | Pythonでエントリー曜日と決済曜日から零号機を分析する(2026/7/23)
ランダムウォークへの挑戦⑥ | Pythonでエントリー時間と決済時間から損益を分析する(2026/7/22)
ランダムウォークへの挑戦⑤ | Pythonで月別損益をグラフ化する(2026/7/5)
ランダムウォークへの挑戦④ |Pythonで「零号機」の基本成績を分析する(2026/6/11)
ランダムウォークへの挑戦③ | 環境設定(2026/5/26)
ランダムウォークへの挑戦➁ | 小目標3つ設定(2026/5/25)
ランダムウォークへの挑戦① | はじめに(2026/5/25)

コメント

タイトルとURLをコピーしました