ランダムウォークへの挑戦⑰|ATR×BB幅で零号機の「得意な相場」を16種類に分解する(2026/8/14)

ランダムウォーク 未分類

はじめに

前回は、エントリー時のボリンジャーバンド幅と零号機の取引成績の関係を分析しました。

ランダムウォークへの挑戦⑯|ボリンジャーバンドの幅で零号機の得意な相場を探る(2026/8/11)
前回は、エントリー時のATRと取引成績の関係を分析しました。ATRとは、簡単にいえば「そのときの相場がどれくらい動いているか」を表す指標です。分析の結果、最もATRが低いQ1では勝率自体はそれほど悪くないものの、PF(プロフィットファクター…

その結果、BB幅別のPFは、

Q1:1.16
Q2:1.29
Q3:1.48
Q4:1.32

となり、BB幅が最も狭いQ1で成績が低下していました。

さらに第15回で分析したATRとの相関係数を調べたところ、

0.836

という強い正の相関が確認されました。

ここで新たな疑問が生まれます。

零号機は「低ATR」だから弱いのか?

「BB幅が狭い」から弱いのか?

それとも「低ATR × 狭いBB幅」という相場そのものが苦手なのか?

ATRとBB幅を別々に分析しているだけでは、この疑問には答えられません。

そこで今回は、ATRとBB幅を組み合わせて分析します。


ATRとBB幅を組み合わせる

これまでは、

  • ATR → Q1~Q4
  • BB幅 → Q1~Q4

と、それぞれを四分位に分けて分析してきました。

今回は、この2つを組み合わせます。

ATRが4種類、BB幅も4種類なので、

4 × 4 = 16種類

の相場環境ができます。

例えば、

ATR Q1 × BB Q1
ATR Q1 × BB Q2
ATR Q1 × BB Q3
ATR Q1 × BB Q4

ATR Q2 × BB Q1
……

ATR Q4 × BB Q4

という分類です。

これによって、「低ATR」という条件の中でも、BB幅によって成績が変わるのかを確認できます。


今回確認する項目

16種類の相場環境について、

  • 取引回数
  • 勝率
  • 総損益
  • 平均損益
  • PF(Profit Factor)

を計算します。

特に重要なのが取引回数です。

前回、ATRとBB幅には0.836という強い相関が確認されました。

そのため、

ATRは非常に低いのにBB幅は非常に広い

といった組み合わせは、そもそもほとんど存在しない可能性があります。

取引数が数件しかないグループでPFが高くなっても、それだけで「零号機が得意な相場」と判断することはできません。

今回はPFだけでなく、そのPFが何回の取引から算出されたものなのかも確認します。


Pythonで分析する

まず必要なライブラリを読み込みます。

import os
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt

今回も分析元には、

results/trade_base.csv

を使用します。

df = pd.read_csv(
    "results/trade_base.csv"
)

print(
    "読み込み件数:",
    len(df)
)

分析対象データを作る

今回必要なのは、

entry_atr14_pips
entry_bb_width_pips
profit

です。

ATRまたはBB幅が記録されていない取引は分析から除外します。

atr_bb_df = df[
    df["entry_atr14_pips"].notna()
    &
    df["entry_bb_width_pips"].notna()
].copy()

件数も確認します。

print(
    "\nATR × BB幅 分析対象件数:",
    len(atr_bb_df)
)

ATRをQ1~Q4に分類する

まずATRを四分位に分けます。

atr_bb_df["atr_quartile_no"] = pd.qcut(
    atr_bb_df["entry_atr14_pips"],
    q=4,
    labels=False,
    duplicates="drop"
)

続いて、

atr_bb_df["atr_group"] = (
    atr_bb_df["atr_quartile_no"]
    .apply(
        lambda x:
            f"Q{int(x) + 1}"
            if pd.notna(x)
            else np.nan
    )
)

とすることで、

Q1 → ATRが低い25%
Q2 → 次の25%
Q3 → 次の25%
Q4 → ATRが高い25%

に分類します。


BB幅もQ1~Q4に分類する

同じ方法でBB幅も分類します。

atr_bb_df["bb_quartile_no"] = pd.qcut(
    atr_bb_df["entry_bb_width_pips"],
    q=4,
    labels=False,
    duplicates="drop"
)

atr_bb_df["bb_group"] = (
    atr_bb_df["bb_quartile_no"]
    .apply(
        lambda x:
            f"Q{int(x) + 1}"
            if pd.notna(x)
            else np.nan
    )
)

これで各取引に、

atr_group
bb_group

という2つの相場環境情報が追加されました。


まず取引数を見る

いきなりPFを見る前に、16種類それぞれに何件の取引が存在するのか確認します。

trade_count_matrix = pd.crosstab(
    atr_bb_df["atr_group"],
    atr_bb_df["bb_group"]
)

trade_count_matrix = trade_count_matrix.reindex(
    index=["Q1", "Q2", "Q3", "Q4"],
    columns=["Q1", "Q2", "Q3", "Q4"],
    fill_value=0
)

print(
    "\nATR × BB幅 取引数"
)

print(
    trade_count_matrix
)

結果は、

BB      Q1   Q2   Q3   Q4
ATR
Q1      ...
Q2      ...
Q3      ...
Q4      ...

という4×4の表になります。

ここは今回かなり重要な結果です。

ATRとBB幅の相関が強いため、

Q1 × Q1
Q2 × Q2
Q3 × Q3
Q4 × Q4

といった対角線付近に取引が集中すると予想されます。

逆に、

ATR Q1 × BB Q4
ATR Q4 × BB Q1

のような組み合わせがどの程度存在するのかにも注目します。


16種類の取引成績を計算する

続いて、ATRとBB幅の組み合わせごとに基本成績を集計します。

summary = (
    atr_bb_df
    .groupby(
        [
            "atr_group",
            "bb_group"
        ],
        observed=True
    )
    .agg(
        trades=(
            "profit",
            "count"
        ),
        total_profit=(
            "profit",
            "sum"
        ),
        average_profit=(
            "profit",
            "mean"
        )
    )
)

勝率を計算する

win_rate = (
    atr_bb_df
    .assign(
        win=atr_bb_df["profit"] > 0
    )
    .groupby(
        [
            "atr_group",
            "bb_group"
        ],
        observed=True
    )
    ["win"]
    .mean()
    * 100
)

summary["win_rate"] = win_rate

PFを計算する

総利益を計算します。

gross_profit = (
    atr_bb_df[
        atr_bb_df["profit"] > 0
    ]
    .groupby(
        [
            "atr_group",
            "bb_group"
        ],
        observed=True
    )
    ["profit"]
    .sum()
)

続いて総損失です。

gross_loss = (
    atr_bb_df[
        atr_bb_df["profit"] < 0
    ]
    .groupby(
        [
            "atr_group",
            "bb_group"
        ],
        observed=True
    )
    ["profit"]
    .sum()
    .abs()
)

PFを計算します。

summary["profit_factor"] = (
    gross_profit
    / gross_loss
)

表示を整える

summary = summary[
    [
        "trades",
        "win_rate",
        "total_profit",
        "average_profit",
        "profit_factor"
    ]
]

print(
    "\nATR × BB幅 取引成績"
)

print(
    summary
)

CSVにも保存します。

summary.to_csv(
    "results/atr_bb_analysis.csv",
    encoding="utf-8-sig"
)

trade_count_matrix.to_csv(
    "results/atr_bb_trade_counts.csv",
    encoding="utf-8-sig"
)

PFを4×4の表にする

16個の数字を縦に並べるだけでは分かりにくいため、PFもマトリクス形式にします。

pf_matrix = (
    summary["profit_factor"]
    .unstack(
        "bb_group"
    )
)

pf_matrix = pf_matrix.reindex(
    index=["Q1", "Q2", "Q3", "Q4"],
    columns=["Q1", "Q2", "Q3", "Q4"]
)

print(
    "\nATR × BB幅 PF"
)

print(
    pf_matrix
)

これによって、

              BB
          Q1   Q2   Q3   Q4
ATR Q1    PF   PF   PF   PF
    Q2    PF   PF   PF   PF
    Q3    PF   PF   PF   PF
    Q4    PF   PF   PF   PF

という形で確認できます。


PFをヒートマップにする

今回は16種類を比較するため、棒グラフよりも4×4のヒートマップの方が全体像を把握しやすくなります。

fig, ax = plt.subplots(
    figsize=(8, 6)
)

im = ax.imshow(
    pf_matrix.values,
    aspect="auto"
)

ax.set_xticks(
    range(len(pf_matrix.columns))
)

ax.set_xticklabels(
    pf_matrix.columns
)

ax.set_yticks(
    range(len(pf_matrix.index))
)

ax.set_yticklabels(
    pf_matrix.index
)

ax.set_xlabel(
    "BB Width Quartile"
)

ax.set_ylabel(
    "ATR Quartile"
)

ax.set_title(
    "Profit Factor: ATR x BB Width"
)

for i in range(
    len(pf_matrix.index)
):
    for j in range(
        len(pf_matrix.columns)
    ):
        value = pf_matrix.iloc[i, j]

        if pd.notna(value):
            ax.text(
                j,
                i,
                f"{value:.2f}",
                ha="center",
                va="center"
            )

fig.colorbar(
    im,
    ax=ax,
    label="Profit Factor"
)

plt.tight_layout()

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

plt.close()

これで、零号機がどの相場環境で高いPFを出しているのかを視覚的に確認できます。


取引数もヒートマップにする

PFだけを見ると、取引数の少ないグループを過大評価する危険があります。

そこで取引数についても同じ形式で可視化します。

fig, ax = plt.subplots(
    figsize=(8, 6)
)

im = ax.imshow(
    trade_count_matrix.values,
    aspect="auto"
)

ax.set_xticks(
    range(len(trade_count_matrix.columns))
)

ax.set_xticklabels(
    trade_count_matrix.columns
)

ax.set_yticks(
    range(len(trade_count_matrix.index))
)

ax.set_yticklabels(
    trade_count_matrix.index
)

ax.set_xlabel(
    "BB Width Quartile"
)

ax.set_ylabel(
    "ATR Quartile"
)

ax.set_title(
    "Trade Count: ATR x BB Width"
)

for i in range(
    len(trade_count_matrix.index)
):
    for j in range(
        len(trade_count_matrix.columns)
    ):
        ax.text(
            j,
            i,
            str(
                trade_count_matrix.iloc[i, j]
            ),
            ha="center",
            va="center"
        )

fig.colorbar(
    im,
    ax=ax,
    label="Trades"
)

plt.tight_layout()

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

plt.close()

出力されるファイル

今回の分析では、

results/
├─ atr_bb_analysis.csv
├─ atr_bb_trade_counts.csv
├─ atr_bb_pf_heatmap.png
└─ atr_bb_trade_count_heatmap.png

の4ファイルが作成されます。


出力結果

分析結果

ATRとBB幅をそれぞれ4分割し、16種類の相場環境に分けた結果がこちらです。

ATRBB幅取引数勝率総損益平均損益PF
Q1Q130161.79%+4,132.88+13.731.06
Q1Q210367.96%+4,092.13+39.731.14
Q1Q32965.52%+4,731.23+163.152.05
Q1Q41100.00%+138.59+138.59
Q2Q111554.78%+9,169.58+79.741.21
Q2Q220267.82%+42,628.24+211.031.88
Q2Q38964.04%+3,171.08+35.631.09
Q2Q42369.57%+2,764.72+120.211.45
Q3Q11553.33%+6,868.94+457.932.97
Q3Q211353.98%-13,746.25-121.650.77
Q3Q320762.32%+47,323.33+228.621.66
Q3Q49662.50%+23,261.36+242.311.55
Q4Q120.00%-1,654.25-827.13
Q4Q21266.67%+7,509.05+625.752.94
Q4Q310657.55%+18,428.37+173.851.41
Q4Q431161.09%+54,470.61+175.151.27

※PFが算出されていないグループは、取引数が極端に少なく、利益または損失の一方しか存在しないためです。


取引は16種類に均等には存在しなかった

まず取引数のヒートマップを見ると、非常にはっきりした特徴があります。

取引数が多かったのは、

ATR Q1 × BB Q1:301回
ATR Q2 × BB Q2:202回
ATR Q3 × BB Q3:207回
ATR Q4 × BB Q4:311回

でした。

つまり、ATRとBB幅が同じ四分位に属する「対角線上」に取引が集中しています。

反対に、

ATR Q1 × BB Q4:1回
ATR Q4 × BB Q1:2回

しかありません。

これは前回確認した、

ATRとBB幅の相関係数:0.836

という結果ときれいに一致します。

ATRが低いときにはBB幅も狭く、ATRが高いときにはBB幅も広い。

やはり両者はかなり近い相場環境を観測しているようです。


「低ATR × 狭いBB幅」は零号機の苦手環境

まず注目したいのが、

ATR Q1 × BB Q1

です。

301回と十分な取引数がありますが、

勝率:61.79%
総損益:+4,132.88
平均損益:+13.73
PF:1.06

となりました。

PFはほぼ1です。

第15回では低ATR、第16回では狭いBB幅でPFが低下することを確認しました。

今回その2つを組み合わせても、

「ATRが低く、なおかつBB幅も狭い相場では、零号機の優位性がかなり小さくなる」

という結果になりました。

しかも301回という比較的大きなサンプルで確認できています。

これは今回得られた結果の中でも、かなり重要だと思います。


勝率61.8%でも、ほとんど利益が残らない

もう一つ興味深いのが勝率です。

ATR Q1 × BB Q1でも勝率は、

61.79%

あります。

決して低い数字ではありません。

それにもかかわらず、301回取引して総損益はわずか+4,132.88、平均損益は+13.73、PFは1.06でした。

つまり零号機は、この相場でも「勝てない」わけではありません。

むしろ、

勝つ回数はそれなりに多いが、勝ったときに利益を伸ばせない

可能性があります。

これは第15回のATR分析で立てた、

「低ATRでは勝てないのではなく、勝っても利益を伸ばしにくいのではないか」

という仮説とも整合します。

低ボラティリティ相場では、エントリー自体はある程度成功しても、その後価格が十分に伸びず、零号機のトレンドフォロー型の出口と相性が悪いのかもしれません。


一方で「Q2 × Q2」「Q3 × Q3」はかなり強い

対角線上を比較すると、さらに面白い結果になります。

Q1 × Q1:PF 1.06(301回)
Q2 × Q2:PF 1.88(202回)
Q3 × Q3:PF 1.66(207回)
Q4 × Q4:PF 1.27(311回)

最も低ボラティリティのQ1×Q1からQ2×Q2になると、PFが一気に1.88まで上昇しました。

Q3×Q3でも1.66と高い水準です。

ところが、最もボラティリティの高いQ4×Q4では1.27まで低下しています。

この結果から、

値動きが小さすぎる相場は苦手。

適度に値動きがある相場では強い。

しかし、値動きが極端に大きくなればさらに強くなるわけではない。

という零号機の特徴が、前回までよりもはっきりしてきました。

単純な「高ボラティリティほど有利」という関係ではなさそうです。


最大PF2.97をそのまま評価してはいけない

ヒートマップだけを見ると、

ATR Q3 × BB Q1:PF 2.97

が最も目立ちます。

また、

ATR Q4 × BB Q2:PF 2.94
ATR Q1 × BB Q3:PF 2.05

という非常に高いPFも確認できます。

しかし、それぞれの取引数を見ると、

Q3 × Q1:15回
Q4 × Q2:12回
Q1 × Q3:29回

しかありません。

特に15回や12回の結果だけで、

「この相場が零号機の最強環境だ」

と判断するのは危険です。

ATRとBB幅には強い相関があるため、このような大きく食い違った組み合わせ自体が珍しく、サンプル数を確保できていません。

今回のヒートマップは、PFだけを見ると誤った結論を出してしまう典型例ともいえます。

だからこそ、PFと取引数のヒートマップをセットで確認することに意味がありました。


ただし、無視できない「Q3 × Q2」

今回、私が最も気になったのはこちらです。

ATR Q3 × BB Q2

取引数は113回。

その結果は、

勝率:53.98%
総損益:-13,746.25
平均損益:-121.65
PF:0.77

となりました。

今回の16分類の中で、ある程度の取引数が存在しながらPFが1を下回った唯一のグループです。

これは単なる数回の偶然として片付けにくい結果です。

さらに興味深いのは、

ATR Q3 × BB Q3:PF 1.66
ATR Q3 × BB Q4:PF 1.55

であることです。

同じATR Q3でも、BB幅がQ2の場合だけ成績が大きく悪化しています。

つまり、

ATRだけでは説明できない情報を、BB幅が持っている可能性

が出てきました。

前回、

「ATRとBB幅は相関0.836なので、同じ現象を見ているだけなのではないか?」

という疑問を持ちました。

今回の結果を見る限り、

大部分は同じボラティリティ環境を捉えているが、完全に同じ情報ではない

と考えるのが現時点では妥当そうです。


今回見えてきた零号機の性格

ここまでの分析から、零号機の特徴が少しずつ具体的になってきました。

現時点では、

① 低ボラティリティ環境は苦手

② 中程度のボラティリティ環境では収益効率が高い

③ ボラティリティが極端に高ければ高いほど良いわけではない

④ ATRとBB幅は強く関連するが、組み合わせによっては成績が大きく変化する

という姿が見えてきています。

特に①については、第15回のATR、第16回のBB幅、そして今回のATR×BB幅という複数の分析で、同じ方向の結果が確認されました。

これは単独の分析結果よりも興味深いものです。

一方で、Q3×Q2のPF0.77については、まだ理由が分かりません。

ここで、

「Q3×Q2をエントリー禁止にすればEAが強くなる」

と考えるのはまだ早いでしょう。

それをしてしまうと、過去データに合わせて条件を追加していく「過剰最適化」へ近づいてしまいます。

今はまだ、零号機の特徴を観測している段階です。


今回のまとめ

今回はATRとBB幅を組み合わせ、零号機の取引を16種類の相場環境に分解しました。

まず取引数を見ると、Q1×Q1、Q2×Q2、Q3×Q3、Q4×Q4という対角線上に取引が集中しました。

これはATRとBB幅の相関係数が0.836だったこととも整合しており、両者がかなり近い相場環境を捉えていることを改めて確認できました。

一方、取引成績には重要な違いもありました。

特に、

Q1 × Q1:301回、PF 1.06

という結果から、低ATRかつBB幅の狭い低ボラティリティ環境では、零号機の優位性がかなり小さくなることが分かりました。

反対に、

Q2 × Q2:202回、PF 1.88
Q3 × Q3:207回、PF 1.66

と、中程度のボラティリティでは良好な成績となっています。

そして今回最も気になるのが、

Q3 × Q2:113回、PF 0.77

です。

ATRとBB幅が完全に同じ情報なら、このような違いは生まれにくいはずです。

ATRとBB幅は強く関連している。

しかし、その「ズレ」の中に零号機の得意・不得意を説明する情報が隠れている可能性がある。

今回の分析で、そんな新しい仮説が生まれました。


次回|バンドからどれだけ離れてエントリーしたか

ここまで、

ATR

BB幅

ATR × BB幅

と、エントリー時の「相場環境」を分析してきました。

次は少し視点を変えます。

零号機では、

entry_band_distance_pips

というデータも記録しています。

これは、エントリー時に価格がボリンジャーバンドからどの程度離れていたのかを表す値です。

同じボラティリティ環境でも、

バンドをわずかに抜けたところでエントリーした場合

と、

バンドから大きく離れたところでエントリーした場合

では、その後の値動きが違うかもしれません。

大きく抜けた方が強いトレンドの始まりを捉えているのか。

それとも、すでに価格が動きすぎており、その後反転しやすくなるのか。

次回は、

「バンドからの距離」と零号機の取引成績

を分析します。

これまでの「相場がどれくらい動いていたか」という分析から、いよいよ「零号機がどの位置でエントリーしたのか」へ進みます。

今回のコード全文

今回のコード全文はこちら
import os
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt


# ==============================
# データ読み込み
# ==============================

df = pd.read_csv(
    "results/trade_base.csv"
)

print(
    "読み込み件数:",
    len(df)
)


# ==============================
# resultsフォルダ作成
# ==============================

os.makedirs(
    "results",
    exist_ok=True
)


# ==============================
# 分析対象データ
# ==============================

atr_bb_df = df[
    df["entry_atr14_pips"].notna()
    &
    df["entry_bb_width_pips"].notna()
].copy()

print(
    "\nATR × BB幅 分析対象件数:",
    len(atr_bb_df)
)


# ==============================
# ATRを四分位に分類
# ==============================

atr_bb_df["atr_quartile_no"] = pd.qcut(
    atr_bb_df["entry_atr14_pips"],
    q=4,
    labels=False,
    duplicates="drop"
)

atr_bb_df["atr_group"] = (
    atr_bb_df["atr_quartile_no"]
    .apply(
        lambda x:
            f"Q{int(x) + 1}"
            if pd.notna(x)
            else np.nan
    )
)


# ==============================
# BB幅を四分位に分類
# ==============================

atr_bb_df["bb_quartile_no"] = pd.qcut(
    atr_bb_df["entry_bb_width_pips"],
    q=4,
    labels=False,
    duplicates="drop"
)

atr_bb_df["bb_group"] = (
    atr_bb_df["bb_quartile_no"]
    .apply(
        lambda x:
            f"Q{int(x) + 1}"
            if pd.notna(x)
            else np.nan
    )
)


# ==============================
# 取引数マトリクス
# ==============================

group_order = [
    "Q1",
    "Q2",
    "Q3",
    "Q4"
]

trade_count_matrix = pd.crosstab(
    atr_bb_df["atr_group"],
    atr_bb_df["bb_group"]
)

trade_count_matrix = trade_count_matrix.reindex(
    index=group_order,
    columns=group_order,
    fill_value=0
)

print(
    "\nATR × BB幅 取引数"
)

print(
    trade_count_matrix
)


# ==============================
# 基本成績
# ==============================

summary = (
    atr_bb_df
    .groupby(
        [
            "atr_group",
            "bb_group"
        ],
        observed=True
    )
    .agg(
        trades=(
            "profit",
            "count"
        ),
        total_profit=(
            "profit",
            "sum"
        ),
        average_profit=(
            "profit",
            "mean"
        )
    )
)


# ==============================
# 勝率
# ==============================

win_rate = (
    atr_bb_df
    .assign(
        win=atr_bb_df["profit"] > 0
    )
    .groupby(
        [
            "atr_group",
            "bb_group"
        ],
        observed=True
    )
    ["win"]
    .mean()
    * 100
)

summary["win_rate"] = win_rate


# ==============================
# 総利益
# ==============================

gross_profit = (
    atr_bb_df[
        atr_bb_df["profit"] > 0
    ]
    .groupby(
        [
            "atr_group",
            "bb_group"
        ],
        observed=True
    )
    ["profit"]
    .sum()
)


# ==============================
# 総損失
# ==============================

gross_loss = (
    atr_bb_df[
        atr_bb_df["profit"] < 0
    ]
    .groupby(
        [
            "atr_group",
            "bb_group"
        ],
        observed=True
    )
    ["profit"]
    .sum()
    .abs()
)


# ==============================
# PF
# ==============================

summary["profit_factor"] = (
    gross_profit
    / gross_loss
)


# ==============================
# 列順
# ==============================

summary = summary[
    [
        "trades",
        "win_rate",
        "total_profit",
        "average_profit",
        "profit_factor"
    ]
]

print(
    "\nATR × BB幅 取引成績"
)

print(
    summary
)


# ==============================
# CSV保存
# ==============================

summary.to_csv(
    "results/atr_bb_analysis.csv",
    encoding="utf-8-sig"
)

trade_count_matrix.to_csv(
    "results/atr_bb_trade_counts.csv",
    encoding="utf-8-sig"
)


# ==============================
# PFマトリクス
# ==============================

pf_matrix = (
    summary["profit_factor"]
    .unstack(
        "bb_group"
    )
)

pf_matrix = pf_matrix.reindex(
    index=group_order,
    columns=group_order
)

print(
    "\nATR × BB幅 PF"
)

print(
    pf_matrix
)


# ==============================
# PFヒートマップ
# ==============================

fig, ax = plt.subplots(
    figsize=(8, 6)
)

im = ax.imshow(
    pf_matrix.values,
    aspect="auto"
)

ax.set_xticks(
    range(len(pf_matrix.columns))
)

ax.set_xticklabels(
    pf_matrix.columns
)

ax.set_yticks(
    range(len(pf_matrix.index))
)

ax.set_yticklabels(
    pf_matrix.index
)

ax.set_xlabel(
    "BB Width Quartile"
)

ax.set_ylabel(
    "ATR Quartile"
)

ax.set_title(
    "Profit Factor: ATR x BB Width"
)

for i in range(
    len(pf_matrix.index)
):
    for j in range(
        len(pf_matrix.columns)
    ):
        value = pf_matrix.iloc[i, j]

        if pd.notna(value):
            ax.text(
                j,
                i,
                f"{value:.2f}",
                ha="center",
                va="center"
            )

fig.colorbar(
    im,
    ax=ax,
    label="Profit Factor"
)

plt.tight_layout()

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

plt.close()


# ==============================
# 取引数ヒートマップ
# ==============================

fig, ax = plt.subplots(
    figsize=(8, 6)
)

im = ax.imshow(
    trade_count_matrix.values,
    aspect="auto"
)

ax.set_xticks(
    range(len(trade_count_matrix.columns))
)

ax.set_xticklabels(
    trade_count_matrix.columns
)

ax.set_yticks(
    range(len(trade_count_matrix.index))
)

ax.set_yticklabels(
    trade_count_matrix.index
)

ax.set_xlabel(
    "BB Width Quartile"
)

ax.set_ylabel(
    "ATR Quartile"
)

ax.set_title(
    "Trade Count: ATR x BB Width"
)

for i in range(
    len(trade_count_matrix.index)
):
    for j in range(
        len(trade_count_matrix.columns)
    ):
        ax.text(
            j,
            i,
            str(
                trade_count_matrix.iloc[i, j]
            ),
            ha="center",
            va="center"
        )

fig.colorbar(
    im,
    ax=ax,
    label="Trades"
)

plt.tight_layout()

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

plt.close()


# ==============================
# 完了
# ==============================

print(
    "\nATR × BB幅分析が完了しました。"
)

print(
    "出力ファイル:"
)

print(
    "results/atr_bb_analysis.csv"
)

print(
    "results/atr_bb_trade_counts.csv"
)

print(
    "results/atr_bb_pf_heatmap.png"
)

print(
    "results/atr_bb_trade_count_heatmap.png"
)

関連記事

ランダムウォークへの挑戦⑰|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をコピーしました