最近は、新しい仕事が忙しく、また、仕事後に毎日プールで泳いでいるのでなかなか作業が進みません。
また、新年度になってから飲み会も増えました。職場で気の合う独身兄貴たちと4人で定期飲みしています♨
ということで、今日は少し進めます。
はじめに
前回は、PythonでEAのバックテスト結果を分析するための環境を作りました。

今回は、いよいよPythonを使って「零号機」の分析を始めます。
まず調べるのは、
- 総取引数
- 勝率
- 総損益
- 総利益・総損失
- PF(Profit Factor)
- 平均利益・平均損失
- 最大ドローダウン
- 最終残高
- 月別損益
といった基本的な成績です。
いきなり複雑な分析をするのではなく、まずは、
「零号機はどんなシステムなのか」
を数字で確認していきます。
コード全文は最後に掲載しています。
今回の流れ
今回行う処理は次のとおりです。
MT4
↓
バックテスト
↓
report.csv
↓
Python(pandas)
↓
決済済み取引を抽出
↓
基本成績を計算
↓
月別損益を集計
分析には、前回MT4のバックテストから出力したreport.csvを使用します。
1.CSVをpandasで読み込む
まずはpandasを読み込みます。
import pandas as pd
pandasは、CSVなどの表形式データをPythonで扱うためのライブラリです。
続いて、MT4から出力したCSVを読み込みます。
df = pd.read_csv(
"/mnt/c/MT4/report.csv",
encoding="utf-8-sig"
)
pd.read_csv()はCSVファイルを読み込むための関数です。
読み込んだデータは、
df
という変数に入れています。
dfはDataFrameの略としてよく使われます。
DataFrameは、Excelの表のような、
行 × 列
のデータをPython上で扱うためのものです。
2.決済済みの取引だけを取り出す
MT4から出力したCSVには、エントリーしたときの行と決済したときの行が含まれています。
今回知りたいのは、
「1回の取引が最終的にいくらの損益になったのか」
です。
そこで、取引種別がcloseになっている行だけを取り出します。
trades = df[
df["取引種別"] == "close"
].copy()
ここでは、
df["取引種別"] == "close"
によって、
「取引種別がcloseである行」
という条件を作っています。
その条件に一致したデータだけをdfから取り出し、
trades
という新しいDataFrameにしています。
最後の、
.copy()
は、抽出したデータを独立したDataFrameとして扱うために付けています。
これ以降、基本的にはtradesを分析していきます。
3.時間を日時型へ変換する
CSVから読み込んだ「時間」を、Pythonが日時として扱える形へ変換します。
trades["時間"] = pd.to_datetime(
trades["時間"],
format="%Y.%m.%d %H:%M"
)
ここで使っている、
pd.to_datetime()
は、データを日時型へ変換するpandasの関数です。
例えば、
2026.05.01 14:30
というデータについて、
format="%Y.%m.%d %H:%M"
で、
%Y → 年
%m → 月
%d → 日
%H → 時
%M → 分
という形式であることをPythonに伝えています。
日時型へ変換しておくことで、この後、
- 年
- 月
- 曜日
- 時間帯
などを取り出して分析できるようになります。
4.年月の列を作る
月別成績を分析するため、新しく「年月」という列を作ります。
trades["年月"] = (
trades["時間"]
.dt.strftime("%Y-%m")
)
例えば、
2026.05.01 14:30
なら、
2026-05
という年月だけを取り出します。
この列を使って、後ほど月ごとの損益を集計します。
5.勝ち取引と負け取引を分ける
続いて基本成績を計算します。
まず総取引数です。
total_trades = len(trades)
len()を使って、tradesに何行あるのかを数えています。
今回は1行が1回の決済済み取引なので、そのまま総取引数になります。
次に、勝ち取引と負け取引を分けます。
wins = trades[
trades["損益"] > 0
]
losses = trades[
trades["損益"] < 0
]
つまり、
損益 > 0
→ 勝ち
損益 < 0
→ 負け
として分類しています。
それぞれの件数は、
win_count = len(wins)
loss_count = len(losses)
で取得できます。
6.勝率を計算する
勝率は、
win_rate = (
win_count
/ total_trades
* 100
)
で計算します。
つまり、
勝ち取引数 ÷ 総取引数 × 100
です。
今回の零号機では、勝率は約62%となりました。
ただし、
勝率が高い=優秀なシステム
とは限りません。
1回の勝ちで100円稼いでも、1回の負けで1,000円失っていれば、勝率が高くても資産は減ってしまいます。
そこで、利益と損失についても調べます。
7.総利益・総損失・PFを計算する
まず、勝ち取引の利益をすべて合計します。
gross_profit = wins["損益"].sum()
負け取引についても同様です。
gross_loss = losses["損益"].sum()
そして、この2つからPF(Profit Factor)を計算します。
profit_factor = (
gross_profit / abs(gross_loss)
if gross_loss != 0
else float("nan")
)
PFは、
総利益 ÷ 総損失の絶対値
です。
例えば、
総利益 100,000円
総損失 -80,000円
なら、
PF = 100,000 ÷ 80,000
= 1.25
となります。
PFが1を上回っていれば、対象期間では総利益が総損失を上回っていたことになります。
今回の零号機は、
PF = 1.35
でした。
なお、コードでは、
if gross_loss != 0
という条件も入れています。
これは総損失が0だった場合に、
○○ ÷ 0
という計算をしてエラーになることを防ぐためです。
8.総損益・平均利益・平均損失を計算する
続いて、
total_profit = trades["損益"].sum()
average_profit = wins["損益"].mean()
average_loss = losses["損益"].mean()
を計算します。
sum()は合計、
mean()は平均です。
つまり、
total_profit
→ 全取引の損益合計
average_profit
→ 勝った取引1回あたりの平均利益
average_loss
→ 負けた取引1回あたりの平均損失
です。
零号機では、
平均利益:約829円
平均損失:約-1,010円
となりました。
つまり、平均すると、
1回の負けが1回の勝ちより大きい
という特徴があります。
その代わり、勝率が約62%あることで全体として利益を残していることが分かります。
勝率が低下したときに成績が大きく悪化する可能性があるため、この点は今後詳しく分析したいところです。
9.最大ドローダウンを計算する
次にドローダウンを確認します。
ドローダウンとは、資産が過去の最高値からどれだけ下落したかを表す指標です。
利益だけでは分からない「そのEAを運用する途中で、どれくらい資産が減る可能性があったのか」を確認するために使います。
まず、
trades["過去最高残高"] = (
trades["残高"].cummax()
)
で、その時点までの最高残高を計算します。
cummax()は、
その時点までに登場した最大値
を順番に求める処理です。
例えば残高が、
50,000
55,000
53,000
58,000
54,000
と動いた場合、過去最高残高は、
50,000
55,000
55,000
58,000
58,000
となります。
次に、
trades["ドローダウン"] = (
trades["残高"]
- trades["過去最高残高"]
)
として、
過去最高残高から現在の残高がどれだけ下がっているのか
を計算します。
最後に、
max_balance_drawdown = (
trades["ドローダウン"].min()
)
で、最も大きく下落した値を取得します。
今回計算しているのは、
決済後の残高を基準にしたドローダウン
です。
ドローダウンについては、後の記事で期間や回復までの取引数なども含めて、改めて詳しく分析します。
10.月別損益を計算する
最後に、年月ごとの損益を集計します。
monthly_profit = (
trades
.groupby("年月")["損益"]
.sum()
.reset_index()
)
ここでは、
.groupby("年月")
によって同じ年月の取引をグループにまとめています。
そのうえで、
["損益"].sum()
として、各月の損益を合計しています。
つまり、
2015-01の取引
↓
損益を全部合計
↓
2015-01の月間損益
2015-02の取引
↓
損益を全部合計
↓
2015-02の月間損益
という処理です。
最後の、
.reset_index()
によって、集計結果を通常の表として扱いやすい形に戻しています。
分析結果
ここまでのコードを実行した結果、零号機の基本成績は次のようになりました。

まず、バックテスト期間全体の総取引数は1,720回でした。
そのうち、
- 勝ち取引:1,064回
- 負け取引:656回
- 勝率:61.86%
となっています。
勝率だけを見ると、6割以上の取引で利益を出しています。
しかし、勝率が高いからといって、それだけで優秀なシステムとは判断できません。
そこで次に、PF(Profit Factor:プロフィットファクター)を見てみます。
PFとは、上記で説明したように、
総利益 ÷ 総損失の絶対値
で計算され、「得られた利益の総額が、失った金額の何倍だったのか」を表す指標です。
今回の零号機は、
PF:1.33
でした。
実際に、
総利益:911,061円
総損失:-687,567円
※小数点以下は四捨五入。以降「金額」については同じ。
なので、
911,061 ÷ 687,567
≒ 1.33
となります。
つまり今回のバックテスト期間では、1円の損失に対して約1.33円の利益を得ていたことになります。
PFは1を上回っていますが、この数字だけで零号機を評価することはできません。
取引回数やドローダウン、取引コスト、相場環境による成績の違いなども含めて、これから詳しく調べていきます。
勝率は高いが、1回の負けが大きい
もう一つ気になるのが、平均利益と平均損失です。
平均利益:+856円
平均損失:-1,048円
平均すると、1回の負けが1回の勝ちより約192円大きいことが分かりました。
つまり零号機は、
1回あたりの利益は損失より小さいが、勝つ回数を多くすることで利益を残している
という性格を持っています。
実際、
勝率:61.86%
平均利益:+856円
平均損失:-1,048円
という組み合わせによって、最終的には、
総損益:+223,495円
となりました。(初期残高50,000円)
一方で、この構造では勝率が低下した場合、成績が悪化する可能性があります。
「零号機は、どのような状況になると勝率が落ちるのか」
これは今後分析していきたいポイントの一つです。
最大ドローダウンは約3万円
今回のコードで計算した最大ドローダウンは、
-29,742円
でした。
ドローダウンとは、資産がそれまでの最高値からどれだけ落ち込んだかを表す指標です。
利益だけを見れば、零号機は最終的に約22万円の利益を出しています。
しかし、その道中では、ピークから約3万円資産が減少する局面もあったことになります。
なお、ここで計算しているのは、各取引の決済後の残高を基準としたドローダウンです。
ドローダウンについては、後の第10回の記事で「いつ始まり、どこまで落ち、回復までどれくらいかかったのか」まで詳しく分析していきます。
月別では利益月と損失月を繰り返している
月別損益も確認してみます。
例えばバックテスト開始直後は、
2015-01 +15,586円
2015-02 -422円
2015-03 -12,472円
2015-04 -2,755円
2015-05 +6,954円
となっています。
最初の5か月だけでも、利益月と損失月がかなり入り交じっています。
そして直近を見ると、
2026-03 +2,366円
2026-04 -7,282円
2026-05 -63円
2026-06 -1,808円
2026-07 -2,429円
となっており、直近では5か月中4か月がマイナスとなっています。
したがって、単純に「安定して右肩上がり」と評価するのは少し早そうです。
長期間では、
初期資金:約50,000円
最終残高:273,494円
総損益 :+223,495円
と利益を積み上げていますが、その過程では利益月と損失月を繰り返しています。
また、2015年3月には「-12,472円」という比較的大きな月間損失も発生しています。
「利益を出した」という最終結果だけではなく、
いつ利益を出し、いつ損失を出しているのか
を見る必要がありそうです。
今回分かったこと
今回の分析から、零号機について最初の特徴が見えてきました。
勝率は61.86%と比較的高い。一方で、平均損失は平均利益より大きい。それでも1,720回の取引を通して、約22.3万円の利益を残している。
ただし、その利益は一直線に積み上がったわけではありません。
月によって成績にはかなりばらつきがあり、最大ドローダウンも約3万円発生しています。
つまり、この段階で分かったのは、
「零号機は利益を出せる」ことよりも、「利益を出しているが、その中身にはかなりムラがある」こと
なのかもしれません。
では、そのムラはどこから生まれているのでしょうか。
エントリーした時間帯や曜日なのか。
決済した時間帯や曜日なのか。
それとも、ポジションを保有していた時間や相場環境なのか。
ここから一つずつ、零号機の性格を観測していきます。
今回の完成コード
今回作成した部分をまとめると、次のコードになりました。
コード全文
import pandas as pd
# CSV読み込み
df = pd.read_csv(
"/mnt/c/MT4/report.csv",
encoding="utf-8-sig"
)
# 決済済み取引(close)だけ抽出
trades = df[
df["取引種別"] == "close"
].copy()
# 日時を変換
trades["時間"] = pd.to_datetime(
trades["時間"],
format="%Y.%m.%d %H:%M"
)
# 年月列を作成
trades["年月"] = (
trades["時間"]
.dt.strftime("%Y-%m")
)
# 基本集計
total_trades = len(trades)
wins = trades[
trades["損益"] > 0
]
losses = trades[
trades["損益"] < 0
]
# 勝ち数・負け数
win_count = len(wins)
loss_count = len(losses)
# 勝率
win_rate = (
win_count
/ total_trades
* 100
)
# 利益合計・損失合計
gross_profit = wins["損益"].sum()
gross_loss = losses["損益"].sum()
# プロフィットファクタ
profit_factor = (
gross_profit / abs(gross_loss)
if gross_loss != 0
else float("nan")
)
# 損益・平均利益・平均損失
total_profit = trades["損益"].sum()
average_profit = wins["損益"].mean()
average_loss = losses["損益"].mean()
# 最大ドローダウン
trades["過去最高残高"] = (
trades["残高"].cummax()
)
trades["ドローダウン"] = (
trades["残高"]
- trades["過去最高残高"]
)
max_balance_drawdown = (
trades["ドローダウン"].min()
)
# 表示
print("===== EA分析結果 =====")
print(f"総取引数: {total_trades}")
print(f"勝ち取引: {win_count}")
print(f"負け取引: {loss_count}")
print(f"勝率: {win_rate:.2f}%")
print(f"総損益: {total_profit:.2f}")
print(f"総利益: {gross_profit:.2f}")
print(f"総損失: {gross_loss:.2f}")
print(f"PF: {profit_factor:.2f}")
print(f"平均利益: {average_profit:.2f}")
print(f"平均損失: {average_loss:.2f}")
print(
f"最大ドローダウン: "
f"{max_balance_drawdown:.2f}"
)
print(
f"最終残高: "
f"{trades['残高'].iloc[-1]:.2f}"
)
# 月別損益
monthly_profit = (
trades
.groupby("年月")["損益"]
.sum()
.reset_index()
)
print("\n===== 月別損益 =====")
print(monthly_profit)
次回 ― 成績をグラフで見る
今回は数字として基本成績を確認しました。
ただ、月別損益を数字だけで並べても、長期間の傾向はなかなか分かりません。
そこで次回は、今回計算したデータをグラフ化してみます。
数字の羅列からでは見えなかった、
零号機の利益の積み上がり方
を視覚的に確認していきます。

おまけ
最近この本を読みました。
ユクスキュルの「生物から見た世界」を優しくかみ砕いた本です。
生物たちによって、必要な情報しか見る必要がなく、ゆえに「必要な情報だけが表示された世界」を見ている。
つまり、世界をそのまま感知できていない。
例えば、ダニなんかは、二酸化炭素と体温だけが見えているようです。
そう思うと、「世界をちゃんと認知できていなくて可哀そう」なんて思いがちですが、
人間も同じ生物ですから、世界をそのまま認知できていないことになります。
例えば赤外線や紫外線を見ることはできません。
我々もひょっとすると恐ろしく狭い認知世界の中で生きているのかもしれません。
面白い一冊でした。
(ちなみにこの本の後に「生物から見た世界」を詠みましたが、書いてあることが難しく途中で挫折しました)
次の記事

コメント