ランダムウォークへの挑戦⑤ | Pythonで月別損益をグラフ化する(2026/7/5)

未分類

先日ボーナスが入りました。
最近は物欲がないので特にボーナスが出たからと言って買いたいものがないのですが、
パソコンの作業効率向上のためにモニターをもう一台買おうか悩んでいます。

それでは今回も作業進めていきます。

はじめに

前回は、MT4から出力したバックテスト結果をPythonで読み込み、「零号機」の基本成績を分析しました。

ランダムウォークへの挑戦④ |Pythonで「零号機」の基本成績を分析する(2026/6/11)
最近は、新しい仕事が忙しく、また、仕事後に毎日プールで泳いでいるのでなかなか作業が進みません。また、新年度になってから飲み会も増えました。職場で気の合う独身兄貴たちと4人で定期飲みしています♨ということで、今日は少し進めます。はじめに前回は…

総取引数、勝率、PF、平均利益・平均損失、最大ドローダウンなどを確認し、さらに月別損益も集計しました。

ただ、月別損益を数字だけで並べても、

「どの時期に大きく勝ち、どの時期に大きく負けているのか」

は少し分かりにくいです。

そこで今回は、前回作成した月別損益を棒グラフにして視覚化します。

数字の一覧からでは見えにくかった、零号機の成績の「ムラ」を観測してみます。


今回やること

今回の処理はシンプルです。

月別損益を集計
      ↓
matplotlibで棒グラフを作成
      ↓
monthly_profit.pngとして保存
      ↓
利益月・損失月のばらつきを確認

Pythonでグラフを作成するために、今回はmatplotlibというライブラリを使います。


1.matplotlibを追加する

まず、WSLでUbuntuに入り、分析用フォルダへ移動します。

cd /mnt/c/Users/***/ea-analysis

そして、matplotlibを追加します。

uv add matplotlib

matplotlibは、Pythonでグラフを作成するための代表的なライブラリです。

今回のような棒グラフだけでなく、折れ線グラフや散布図なども作成できます。


2.matplotlibを読み込む

Python側では、次のコードを追加します。

import matplotlib.pyplot as plt

ここでは、

matplotlib.pyplot

を、

plt

という短い名前で使えるようにしています。

以降、

plt.figure()
plt.bar()
plt.savefig()

といった形でグラフを操作します。


3.月別損益を集計する

グラフに使うデータを作ります。

monthly_profit = (
    trades
    .groupby("年月")["損益"]
    .sum()
    .reset_index()
)

前回も使ったgroupby()です。

ここでは、

.groupby("年月")

で同じ年月の取引をまとめ、

["損益"]

で損益列を対象にして、

.sum()

で月ごとの損益を合計しています。

例えば、

2026-01
  +500
  -200
  +300

という取引があれば、

2026-01 → +600円

として1か月分にまとめられます。

最後に、

.reset_index()

を使って、groupby()によってインデックスになった「年月」を、再び通常の列に戻しています。

これによって、集計結果は、

      年月        損益
0  2015-01   15586.10
1  2015-02    -422.18
2  2015-03  -12471.63

のようなDataFrameになります。

このあとグラフを作成するときに、

monthly_profit["年月"]
monthly_profit["損益"]

と、それぞれの列を指定して利用できます。


4.グラフを作る領域を用意する

続いて、

plt.figure(
    figsize=(14, 6)
)

でグラフを作成する領域を用意します。

figsizeはグラフの大きさです。

figsize=(14, 6)

なので、

横:14
縦:6

の比率で作成します。

今回のバックテストは10年以上あり、月数も多いため、横長のグラフにしています。


5.棒グラフを作成する

実際に棒グラフを作るのが、

plt.bar(
    monthly_profit.index,
    monthly_profit.values
)

です。

plt.bar()は棒グラフを作る関数です。

ここでは、

monthly_profit.index

を横軸、

monthly_profit.values

を縦軸にしています。

今回のmonthly_profitは、

年月        損益
2015-01    15586.10
2015-02     -422.18
2015-03   -12471.63
...

という構造なので、

横軸 → 年月
縦軸 → 月間損益

というグラフになります。


6.タイトルと軸の名前を付ける

続いて、

plt.title("Monthly Profit")
plt.xlabel("Month")
plt.ylabel("Profit")

で、グラフのタイトルと軸の名前を設定します。

それぞれ、

Monthly Profit
→ 月別損益

Month
→ 月

Profit
→ 損益

という意味です。


7.横軸の文字を90度回転する

月数が多いため、そのまま年月を表示すると文字同士が重なってしまいます。

そこで、

plt.xticks(
    rotation=90
)

とします。

rotation=90は、

横軸の文字を90度回転する

という意味です。

これによって年月を縦向きに表示し、できるだけ読みやすくしています。


8.レイアウトを自動調整する

続いて、

plt.tight_layout()

を実行します。

これは、

グラフの文字やラベルが枠からはみ出さないように、自動的に余白を調整する

ための処理です。

今回のように横軸の文字を90度回転させる場合には特に役立ちます。


9.グラフを画像として保存する

完成したグラフを保存します。

plt.savefig(
    "results/monthly_profit.png",
    dpi=150
)

plt.savefig()は、

作成したグラフを画像ファイルとして保存する

ための関数です。

今回は、

results/monthly_profit.png

として保存します。

また、

dpi=150

で画像の解像度を指定しています。


10.グラフを閉じる

保存後には、

plt.close()

を実行します。

これは、作成したグラフを閉じる処理です。

今後、同じPythonプログラムの中で時間帯別・曜日別・保有時間別など複数のグラフを作成するため、前のグラフを閉じておきます。


実行結果

コードを実行すると、

results
└─ monthly_profit.png

が作成されました。

棒グラフにすると、数字だけを眺めていたときよりも、零号機の成績にかなりばらつきがあることが分かります。


分析

月別損益をグラフにすると、数字の一覧だけを見ていたときよりも、「零号機」の特徴がかなり分かりやすくなりました。

まず目につくのは、月によって損益のばらつきがかなり大きいことです。

多くの月では損益が0円付近から数千円程度の範囲に収まっていますが、その中に大きな利益を出した月、大きな損失を出した月が点在しています。

特にバックテスト前半には、約+29,000円の大きな利益を出した月がある一方で、約-18,000円の損失を出した月もあります。

その後の期間を見ても、+10,000円を超える利益月や、-10,000円を下回る損失月が時折発生しています。

つまり、零号機は毎月安定して少しずつ利益を積み上げるタイプというより、

「普段は比較的小さな損益を繰り返しながら、ときどき大きく勝ったり負けたりする」

という特徴がありそうです。

また、グラフ全体を見ると、特定の時期だけ一貫して利益が出ているわけでもありません。

利益月と損失月が入り交じりながら、長期間のバックテストでは最終的にプラスになっています。

前回の分析では、

  • 勝率:61.86%
  • PF:1.33
  • 総損益:+223,494.50円

という結果でした。

しかし、今回のグラフを見ると、その約22万円の利益が均等に積み上がったわけではないことが分かります。

ここで気になるのが、

なぜ、これほど成績の良い月と悪い月が生まれるのか?

ということです。

このグラフだけでは、その原因までは分かりません。

時間帯による違いなのか、曜日による違いなのか。それとも、そのときの相場環境によるものなのか。

現時点で言えるのは、

「零号機の成績には、時期によって明確なムラがある」

ということまでです。

今回の完成コード

第4回までに作成したコードに、今回のグラフ作成処理を追加すると、ここまでのコードは次のようになります。

コード全文はこちら
import os
import pandas as pd
import matplotlib.pyplot as plt


# resultsフォルダが存在しない場合は作成
os.makedirs(
    "results",
    exist_ok=True
)


# 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)


# ==============================
# 月別損益グラフ
# ==============================

plt.figure(
    figsize=(14, 6)
)

plt.bar(
    monthly_profit["年月"],
    monthly_profit["損益"]
)

plt.title("Monthly Profit")
plt.xlabel("Month")
plt.ylabel("Profit")

plt.xticks(
    rotation=90
)

plt.tight_layout()

plt.savefig(
    "results/monthly_profit.png",
    dpi=150
)

plt.close()

print(
    "月別損益グラフを "
    "results/monthly_profit.png "
    "として保存しました。"
)

次回 ― 時間帯別に分析する

月別損益を見ることで、

「成績には時期によってムラがある」

ことが分かりました。

では、もっと細かく見るとどうでしょうか。

次回は、

「どの時間帯で勝ち、どの時間帯で負けているのか」

を分析します。

零号機が得意な時間帯、苦手な時間帯が存在するのか。

さらに一段細かく観測していきます。

ランダムウォークへの挑戦⑥ | Pythonでエントリー時間と決済時間から損益を分析する(2026/7/22)
最近は暑すぎてプールが気持ちいいです。今日は夏休みをとって、昼間から泳ぎに行きました。それでは今日も作業を進めていきます。はじめに前回は、零号機の月別損益をグラフ化しました。その結果、長期的には利益を残しているものの、毎月均等に利益を積み上…

おまけ

今、「知って得する、すごい法則77」という本を読んでいます。

1つの法則につき数ページなので、サクサク読めます。

知っている法則も多いですが、ある程度体系的にまとまっているので、意味が逆の法則を場面場面で利用し分ける目線など面白いです。

これを実務で利用するぞ!というより、読んでいて面白い本です。

次の記事

ランダムウォークへの挑戦⑥ | Pythonでエントリー時間と決済時間から損益を分析する(2026/7/22)
最近は暑すぎてプールが気持ちいいです。今日は夏休みをとって、昼間から泳ぎに行きました。それでは今日も作業を進めていきます。はじめに前回は、零号機の月別損益をグラフ化しました。その結果、長期的には利益を残しているものの、毎月均等に利益を積み上…

関連記事

ランダムウォークへの挑戦㉓|最高PFは本当に強いのか。Relative Band Distanceを時間分割で比較する(2026/8/30)
ランダムウォークへの挑戦㉒|Q3の境界をずらしても優位性は残るのか(2026/8/30)
ランダムウォークへの挑戦㉑|Q3の優位性は時間が変わっても残るのか(2026/8/29)
ランダムウォークへの挑戦⑳|零号機の利益は「一部の大勝ち」に支えられているのか(2026/8/20)
ランダムウォークへの挑戦⑲|BAND_DISTANCEをATRで割って「相対的なブレイクの強さ」を分析する(2026/8/19)
ランダムウォークへの挑戦⑱|バンドからの距離で零号機のエントリー位置を分析する(2026/8/19)
ランダムウォークへの挑戦⑰|ATR×BB幅で零号機の「得意な相場」を16種類に分解する(2026/8/14)
ランダムウォークへの挑戦⑯|ボリンジャーバンドの幅で零号機の得意な相場を探る(2026/8/11)
ランダムウォークへの挑戦⑮|零号機は「荒れた相場」に強いのか ― ATRと取引成績の関係をPythonで分析する(2026/8/9)
ランダムウォークへの挑戦⑭ | 零号機の「記憶」を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をコピーしました