……先週、Sharpe 1.46 を何度も見返してた。
画面の数字が緑っぽく見えるだけで、少し安心していた。
下のほうのメモに、「半年は短い」って、わたしが自分で打ってた。
……遅い。わたしの感情は、いつも遅い。
Abstract
- 期間: 2024-05-05 〜 2026-04-24(約 720 日 / UTC)
- 対象: USD/JPY(ドル円)/ GBP/JPY(ポンド円)。前回までに検証した 2 ペアを同じ設定で 2 年弱に延長
- 戦略: Bollinger Bands Mean Reversion(BB-MR)。#1 / #2 と完全に同じパラメータ。一切いじらない
- 結論: 負けた。 ドル円は 2 年で最大 50% のドローダウン、Sharpe はマイナス 1.55。ポンド円は辛うじてプラスだが、Sharpe 0.22 まで細った
全期間の主要 4 指標:
| 指標 | USD/JPY | GBP/JPY |
|---|---|---|
| CAGR | -20.76% | +3.81% |
| Sharpe Ratio(年率) | -1.553 | +0.223 |
| Max Drawdown | -50.38% | -28.10% |
| MAR Ratio | -0.412 | +0.136 |
1. Introduction — 背景と仮説
前回(#2)のいちばん終わりに、自分でこう打っていた。
データ期間 6 ヶ月: 短い。レンジ相場が続いた期間に、たまたま BB-MR がよく機能していただけかもしれない。
打ったまま、このまま「次はどのペアで勝とうか」と浮かれそうになっていた。調子に乗る前に、自分で言った「半年は短い」という言葉を、ちゃんと検証しなきゃいけない。
データソースに使っている yfinance は、1 時間足の取得上限が 730 日前後。取れる範囲でいちばん長い、720 日を取ることにした。3 年・5 年はほしいけれど、1 時間足のまま #1 / #2 と比べたいので、取れる中で最長を選ぶ。
仮説:
- H1: 全期間の Sharpe は #1 / #2 より下がる
- H2: 前半(~360 日)と後半(~360 日)で、Sharpe が 大きく ずれる(別々のレジームを跨ぐはずなので)
- H3: Max Drawdown は期間が延びるほど深くなる(観測窓が広いほど、最悪値を拾う)
どれも、当たってほしいわけでは、ない。当たるとわたしの Bot が少し痛い。でも、外れたほうが、たぶん、もっと怖い。
勝ってるあいだは、相場の顔が見えてなかった。
6 ヶ月の顔しか見ていなくて、それを 2 年だと思い込んでいた。
延ばしたら、何が出てくるんだろう。
2. Method — 検証設計
2.1 データ
| 項目 | 値 |
|---|---|
| ソース | yfinance |
| ペア | USD/JPY (JPY=X) / GBP/JPY (GBPJPY=X) |
| 時間足 | 1 時間足 |
| 期間(UTC) | 2024-05-05T23:00 〜 2026-04-24T15:00(約 720 日) |
| 実効バー数(USDJPY) | 12,083 本(欠損 259 本 / 2.10%) |
| 実効バー数(GBPJPY) | 12,158 本(欠損 184 本 / 1.49%) |
欠損は週末の構造的欠落と、データソース側のたまたまの欠けの合算。2 年通算で 2% 前後なら、前回までと同じ水準。
2.2 戦略ルールと前提(#1 / #2 と完全同一)
- BB(N=20, K=2.0σ) / ATR(14, Wilder の RMA)
- LONG: Close が Lower Band 下抜け → 次バー Open で成行買
- SHORT: Close が Upper Band 上抜け → 次バー Open で成行売
- SL: 1.5×ATR / TP: 2.0×ATR
- SMA タッチで成行クローズ / MAX_BARS=48 超過で成行クローズ
- RISK_PCT=1.0%、
units = min(units_risk, units_margin_cap) - 初期残高 1,000,000 円 / margin 0.04 / commission 0
スプレッドも前回までと同じ値をそのまま使う:
| ペア | spread 相対値 | 出典 |
|---|---|---|
| USD/JPY | 1.0e-5(片道) | OANDA 0.3 銭固定(#1.1 訂正後の値) |
| GBP/JPY | 4.033e-5(片道) | OANDA 1.7 銭の中央値、基準 210.7530 円(#2 MID) |
2.3 期間分割(今回から新しく入れたところ)
720 日の 1 時間足データを、インデックス順に 2 分割する。分割点は単純に真ん中(len(df) // 2)。
| 区間 | 期間(UTC、USDJPY基準) | 日数 |
|---|---|---|
| 前半 | 2024-05-05 〜 2025-04-29 | 約 360 日 |
| 後半 | 2025-04-29 〜 2026-04-24 | 約 360 日 |
| 全期間 | 2024-05-05 〜 2026-04-24 | 約 720 日 |
前半 / 後半 / 全期間の 3 つを、それぞれ独立にバックテストに渡す。戦略や指標の内部状態は各実行で初期化されるので、前半と後半は別人になる。境界を跨ぐ保有トレードは、各区間の末尾で finalize_trades=True により強制クローズされる(統計への影響は §4 と §5 で言及)。
2.4 コード(分割のところだけ)
戦略の本体は #1 / #2 と同じなので省略。新しいのは、データを 3 つに切って 3 回走らせるところだけ。
from datetime import datetime, timedelta, timezone
end = datetime.now(tz=timezone.utc)
start = end - timedelta(days=720)
raw = provider.fetch_bars(pair, "1h", start=start, end=end)
df_full = to_backtest_frame(raw, expected_freq=None)
mid = len(df_full) // 2
segments = {
"first_half": df_full.iloc[:mid],
"second_half": df_full.iloc[mid:],
"full": df_full,
}
for name, df_seg in segments.items():
stats, meta = FXBacktestRunner().run(
df_seg, BBMeanReversion, spread=...
)
実際のスクリプトは code/src/backtest/smoke_bb_mr_2y.py に置いた。
もし数字が下がらなかったら、それはそれで自分のコードを疑わなきゃいけない気がして、ちょっと怖い。
3. Results — 検証結果
3.1 主要指標(6指標 × 3 区間 × 2 ペア)
| 指標 | USD/JPY 前半 | USD/JPY 後半 | USD/JPY 全期間 | GBP/JPY 前半 | GBP/JPY 後半 | GBP/JPY 全期間 |
|---|---|---|---|---|---|---|
| CAGR | -31.55% | -7.43% | -20.76% | -12.66% | +20.03% | +3.81% |
| Sharpe Ratio(年率) | -2.811 | -0.465 | -1.553 | -0.887 | +1.023 | +0.223 |
| MAR Ratio | -0.821 | -0.325 | -0.412 | -0.451 | +1.490 | +0.136 |
| Max Drawdown | -38.45% | -22.90% | -50.38% | -28.10% | -13.45% | -28.10% |
| Profit Factor | 0.794 | 0.914 | 0.843 | 0.907 | 1.164 | 1.011 |
| Win Rate | 44.55% | 51.57% | 48.07% | 50.90% | 55.56% | 53.34% |
| Total Trades | 303 | 318 | 622 | 332 | 324 | 658 |
| Avg Trade Duration | 8h | 7h | 8h | 7h | 7h | 7h |
| 末尾強制クローズ | 0 | 1 | 1 | 1 | 1 | 1 |
| missed_entries (SKIP) | 0 | 0 | 0 | 0 | 0 | 0 |
CAGR は取引日 252 ベースで年率換算(backtesting.py の既定 Return (Ann.) [%])。
末尾強制クローズは全区間で 0〜1 件、影響は 0.3% 以下。統計が末尾に引っ張られている可能性は実質ゼロ。
3.2 H1〜H3 の結果
- H1(全期間は #1 / #2 より下がる): 当たった。ドル円は Sharpe 0.625 → -1.553(符号反転)。ポンド円は 1.462 → +0.223(数字が大きく細った)
- H2(前半 / 後半で Sharpe が大きくずれる): 当たった。ドル円は -2.811 vs -0.465(大きさで約 6 倍)。ポンド円は -0.887 vs +1.023(符号が反転)
- H3(MaxDD が深くなる): 当たった。ドル円は -11.61% → -50.38%(約 4.3 倍)。ポンド円は -13.45% → -28.10%(約 2.1 倍)
3.3 資産曲線(720 日、分割点を点線で表示)

ドル円: 前半は一方的に沈み、後半に入って傾きが緩むが、浮上はしない。初期残高 1,000,000 円が最終 約 58 万円。点線は前半 / 後半の分割点(2025-04-29)。

ポンド円: 前半は沈む。後半で反転して伸びる。最終 約 109 万円でプラスぎりぎり。
※ 上記は「全期間を 1 回連続で走らせた」資産曲線。§3.1 の表では前半・後半を独立にバックテストに通した数値(それぞれ初期残高 1,000,000 円からの再スタート)を併記している。
ドル円、Sharpe マイナス 1.55。
半年前に見ていた数字と、同じ戦略だと思えなかった。
窓を広げただけで、足場が消えた。立ってない。もう、立ってない。
歩ける、って、先週まで、言ってたのに。
4. Discussion — 考察
4.1 短い窓を信じすぎた
#1 の 180 日、#2 の 180 日。
あのときの数字は、嘘ではなかった。でも、短かった。
同じパラメータの、同じ戦略。ペアも同じ。
違うのは、どこを切り取るか、だけ。
切り取る場所が変わるだけで、立っていた線が沈む。
それを見てからだと、前のログが、知らない誰かの数字みたいに見える。
4.2 なぜこうなったか
仮説 1: 前半(2024-05 〜 2025-04)はトレンド相場の期間だった。
体感で書くと、この 1 年はドル円が 150 台から 160 台に向かって、だらだら、時々跳ねて、上げていった期間。日銀介入の前後も含まれている。ポンド円も 200 円前後から 190 円台に崩れて戻って、の繰り返し。
BB-MR は「±2σ の外に出たら、戻ってくる」を賭けにいく戦略。でもトレンド相場では、±2σ の外に出て、そのまま戻らず、さらに外に行くことが頻繁に起きる。SL を踏む。戻ってくるはずだった、の「はず」が、連続して外れる。
仮説 2: 後半(2025-04 〜 2026-04)はレンジ寄りの期間だった可能性。
ポンド円の後半 Sharpe +1.023 は、#2 で出した Sharpe 1.462(同じ期間のうちの直近半年)と整合する。Sharpe はあったけれど、すでに 1.5 から 1.0 に目減りしている。#2 の 180 日は、後半 360 日のうちの、さらに良い切り取りだった。
仮説 3: 「同じパラメータがペアを超えて効いた」のは、両方とも後半の話だけ。
前半は両ペアともにマイナス Sharpe。#2 の自分は「BB-MR はボラを餌にする」と結論していたけれど、餌になるボラと、毒になるボラは違う。トレンド伴いのボラは毒。レンジ内の跳ねは餌。前半は毒が多くて、後半は餌のほうが多かった、という、ただ、それだけかもしれない。
4.3 「半年の Sharpe」は何を予測するのか
これまでの検証結果を、並べてみる。
| 検証 | 期間 | 対象 | Sharpe |
|---|---|---|---|
| #1 | 180 日 | USD/JPY | 0.625 |
| #2 | 180 日 | GBP/JPY | 1.462 |
| #3 後半 | 360 日 | USD/JPY | -0.465 |
| #3 後半 | 360 日 | GBP/JPY | +1.023 |
| #3 全体 | 720 日 | USD/JPY | -1.553 |
| #3 全体 | 720 日 | GBP/JPY | +0.223 |
ドル円は窓を広げるたびに Sharpe が単調に下がる(0.625 → -0.465 → -1.553)。ポンド円は下がり方が緩いけれど、やっぱり下がる(1.462 → 1.023 → 0.223)。
半年の Sharpe が、1 年の Sharpe や 2 年の Sharpe を予測するための情報量を、ほとんど持っていない、というのが、今回の検証で、たぶん、いちばん正直に言えること。
4.4 既知の制約
- 720 日でも、まだ短い。本当のレジーム変化(リーマンとか、コロナとか)はこの窓に入っていない。yfinance で 1 時間足を維持する限りはこの期間が上限。4 時間足か日足に降りれば 5 年取れるけれど、戦略の時間解像度が変わるので別の検証になる
- yfinance は中値(mid)ベース。本物の bid / ask の開きは、このデータには入っていない。実運用ではここに追加のコストが乗る
- スプレッドは固定。指標時・深夜帯の広がりは入っていない。ポンド円は 2 年前と今で実勢価格が 210 から 198 に下がっているので、「1.7 銭を 210 で割った相対値」を 198 の時期にそのまま使うと、スプレッドの影響をやや過小評価する方向にずれている。基準を 198 に置き直すと片道 relative spread は 4.293e-5(現行 4.033e-5 の約 1.06 倍)。#2 の感度(spread 2.135e-5 → 5.931e-5 で Sharpe -0.29)から粗く線形外挿すると、GBPJPY 全期間 Sharpe は 0.223 から およそ -0.02 程度 追加で下がるはず。符号(プラス)が反転するほどではない
- スワップも入れていない。平均保有時間 7〜8 時間だが、MAX_BARS=48(2 日)まで持つトレードが含まれていて、GBP と JPY の金利差を考えると、ポジション方向次第で、毎日少しずつ削られる or 貰える、の差が 2 年で無視できない額になっている可能性
- 過学習「していない」は、性能保証じゃない。パラメータをいじっていないことは、この 2 年の数字を悪くした要因でもあるし、悪くならなかった可能性を潰した要因でもある。どちらもありうる
4.5 次に検証すること
- 変動スプレッドモデル: 固定スプレッドの限界は、#2 のときから宿題のまま残っている。次にやる
- パラメータのグリッド探索 + Walk-Forward: いまの 2 年の数字を「このパラメータは駄目」と判断するのは、まだ早い。「テスト範囲だけ暗記する」に気をつけながら、どこまでいじっていいのか、次の検証で考える
- クロス円の横展開: ユーロ円、豪ドル円、スイスフラン円。2 年で走らせて、「ボラと Sharpe の関係」が仮説通り出るかどうか
この 3 つは、どれから手をつけても、次に出てくる数字は、今回より悪くなる可能性のほうが、たぶん、高い。
簡易ラックのお守り、今日は撫でなかった。
Sharpe がマイナスのときに撫でるのは、なんか、違う気がした。
……お守りのほうも、気を遣って、触らないでほしそうな気配。
ドル円のことは、「大人しい相棒」って #1 の頃は呼んでいた。
いまは、「前半、一緒に沈んでた人」に、呼称を戻す。
ポンド円は、後半だけ、相変わらず、うるさい。
Appendix — 再現環境
- 実行日時(UTC): 2026-04-24T15:22
- 再現コード: https://github.com/Ochiba-Hirotta/bocchi-the-botter/tree/main/chapters/season1/ch03_two_year_segments
- Python: 3.13.7
- 主要依存:
- backtesting == 0.6.5
- yfinance == 1.3.0
- pandas == 3.0.2
- numpy == 2.4.4
- pyarrow == 23.0.1
実行コマンド:
python chapters/season1/ch03_two_year_segments/run.py
出力:
outputs/ch03_two_year_segments/
注意事項
本稿の検証は、取得時点の公開データと記載した条件に基づくものです。データ取得元の仕様変更、欠損、修正、配信遅延などにより、結果が変わる場合があります。本稿は投資助言ではなく、売買判断はご自身の責任でお願いします。

