Abstract
- 期間: 2025-10-24 〜 2026-04-22(約180日 / UTC)
- 対象: USDJPY(米ドル円) / 時間足: 1h
- 戦略: Bollinger Bands Mean Reversion(BB-MR)
- 結論: プラス。……微益。
| 指標 | 値 |
|---|---|
| CAGR | 8.87% |
| Sharpe Ratio(年率) | 0.625 |
| Max Drawdown | -11.61% |
| MAR Ratio | 0.76 |
1. Introduction — 背景と仮説
最初の検証は、ドル円にした。
ほんとうは、ポンド円(GBP/JPY)でやる気だった。教室でいちばんうるさい人、みたいな通貨だから。テンションが高い瞬間は、いずれ落ち着く。落ち着く瞬間を待てば、勝てるんじゃないかと思っていた。
でも、いちばん最初の検証は、ドル円にした。理由は2つ。
流動性が高い。板が厚いから、スリッページの想定が立てやすい。バックテストの前提を作るとき、「このコストでだいたい合ってるはず」と言い切れる材料が多い。
スプレッドが狭い。OANDA証券のドル円は0.2銭。コスト試算が、とにかく楽。
仮説はシンプル。「価格が極端に動いたら、戻ってくる」 という、平均回帰。ボリンジャーバンドの±2σに触れた瞬間をエントリーとして、戻りを待つ。
教科書に載るような、いちばん素直な戦略から始めたかった。
いきなり凝ったことをして「これが効いた」って言われても、
たぶん、わたしは信じない。
2. Method — 検証設計
2.1 データ
| 項目 | 値 |
|---|---|
| ソース | yfinance |
| ペア | USDJPY |
| 時間足 | 1時間足 |
| 期間(UTC) | 2025-10-24T12:40 〜 2026-04-22T12:40 |
| 実効バー数 | 2,969 本(欠損 116 本 / 3.91%) |
欠損の3.91%は、FXが24時間×5日稼働で、週末48hぶんが構造的に欠落するため。これは想定内で、24×5前提でバー数を換算し直している。
2.2 戦略ルール(BB-MR)
インジケータ
- Bollinger Bands: BB(N=20, K=2.0σ)
- ATR: ATR(14, Wilderの RMA)
エントリー(バー確定時に判定、次バー Open で約定)
- LONG: Close が Lower Band を下抜け → 次バー Open で成行買
- SHORT: Close が Upper Band を上抜け → 次バー Open で成行売
エグジット(優先順)
- SL:
1.5 × ATR(backtesting.py が同バー内で自動判定) - TP:
2.0 × ATR(同上) - SMA タッチ(Low ≤ SMA ≤ High)→ 次バー Open で成行クローズ
- MAX_BARS=48 超過 → 次バー Open で成行クローズ
ポジションサイジング
units = min(units_risk, units_margin_cap)
units_risk = equity × RISK_PCT / (SL_ATR_MULT × ATR)
RISK_PCT=1.0%。余力不足時はエントリー見送り(SKIP)。
2.3 前提条件
| 項目 | 値 | 備考 |
|---|---|---|
| 初期残高 | 1,000,000 円 | |
| commission | 0 | デモ段階 |
| spread | 1.33e-5 |
0.2銭 → 0.002円 → / 150 ≈ 1.33e-5 |
| margin | 0.04 | 25倍レバレッジ上限 |
| trade_on_close | False | バー確定時判定、次バー Open で約定 |
| exclusive_orders | True | 同時保有なし(hedging off) |
| finalize_trades | True | 末尾未決済は最終バー Close で強制クローズ |
スプレッド、これで合ってるはず。
0.2銭……前に見たどこかの記事で覚えた数字。OANDAの公式かな、たぶん。
backtesting.pyのドキュメントも、ざっと読んだ。AIもそう言ってたし。
2.4 コード(戦略の核)
class BBMeanReversion(Strategy):
BB_N, BB_K = 20, 2.0
ATR_N = 14
SL_ATR_MULT, TP_ATR_MULT = 1.5, 2.0
RISK_PCT = 0.01
MAX_BARS = 48
def next(self) -> None:
close = self.data.Close[-1]
atr = self._atr[-1]
# LONG: Close が Lower Band を下抜けたら次バー Open で買う
if crossover(self._bb_lower, self.data.Close):
sl = close - self.SL_ATR_MULT * atr
tp = close + self.TP_ATR_MULT * atr
units = compute_units(
equity=self.equity, atr=atr, price=close,
risk_pct=self.RISK_PCT, sl_atr_mult=self.SL_ATR_MULT,
margin=self._margin, spread=self._spread,
)
if units > 0:
self.buy(size=units, sl=sl, tp=tp)
# SHORT 側は省略(対称)
3. Results — 検証結果
3.1 主要指標(6指標フル)
| 指標 | 値 | メモ |
|---|---|---|
| CAGR | 8.87% | 「お小遣いの範囲。でも、銀行よりはマシ」 |
| Sharpe Ratio(年率) | 0.625 | 「微益。まだ、信用しきれない」 |
| MAR Ratio | 0.76 | 「リスクの四分の三ぶん、稼げてる」 |
| Max Drawdown | -11.61% | 「胃が、ちょっと痛い」 |
| Profit Factor | 1.055 | 「ギリギリ、1を超えてる」 |
| Win Rate | 55.35% | 「コイン投げよりは、マシ」 |
- Total Trades: 159
- Avg. Trade Duration: 約 6 時間
- 末尾強制クローズ: 0 件(データ終端での未決済なし)
- missed_entries(余力不足でのSKIP): 0 件
3.2 資産曲線

初期1,000,000円 → 最終1,062,565円。180日で +6.26%。
……複利で回したら、年率換算でだいたい8.87%(取引日ベース)。銀行よりは、マシ、かもしれない。
……ブレイクイーブン寄りの、微益。
凄くもないし、悲惨でもない。
最初の検証がいちばん地味な結果って、
なんか、自分に似てる気がした。
4. Discussion — 考察
4.1 結果の解釈
ドル円はGBP/JPYと比べてボラティリティが小さい。2σを越えるような極端な動きが、そもそも少ない。だからエントリーは打てても、TP(+2×ATR)まで届かないケースがわりとあった。
SL:TP = 1.5 : 2.0 という R:R も、保守的な側。勝率55%でコストを引いて、ようやく PF 1.055。派手な勝ちはしない代わりに、大きく崩れもしない、という形になった。
大負けしたトレードが見当たらないのは、SLがATR基準で絶対価格に固定されているから。ボラが拡大した局面でも、損切り幅が先に決まっているので、1回のトレードでの損失は想定リスク(RISK_PCT=1%)のあたりに収まっている。
……と、ここまで考えて、気づく。これ、全部ドル円だから成立してる話かもしれない。
4.2 既知の制約
- 期間が短い: 180日は、過学習の判定をするには不十分。
- walk-forward をやっていない: 「たまたま勝てた」なのか「たぶん勝てる」なのかを切り分けるには、期間をずらした再検証が必要。
- パラメータは初期値のまま: BB(20, 2σ)も、SL/TP倍率も、最適化していない。ここからいじり始めると、テスト範囲だけ暗記する状態に近づいていく。そのとき、過学習っていう言葉を思い出したい。
- バックテストのSharpeは実運用で下がるのが通例。オーバーフィッティング、スリッページ、流動性、レジームシフト。今回の0.625も、実運用に出すと0付近に落ちる可能性がある。
4.3 次に検証すること
- GBP/JPY に同じ戦略を当てる。教室でいちばんうるさい人に、同じ手が通じるか。
- 期間を 2〜3 年に延長する。180日では短すぎる。
- ATR倍率のパラメータ感度を調べる。SL:TPを変えると勝率とPFがどう動くか。ただし「動かしすぎない」範囲で。
タピオカプロテインに、今日はきなこを入れてみた。
……なんか、土の味がした。
AIに「これ、どう思う?」って聞いたら、「材料の相性は悪くないと思います」って返ってきた。
……そう?
ターミナルを閉じる前に、資産曲線をもう一度見た。
ゆるやかに右上に向かう線。最大11%の谷がひとつ。
次は、ポンド円。
Appendix — 再現環境
- 実行日時(UTC): 2026-04-22T12:40:05
- 再現コード: https://github.com/Ochiba-Hirotta/bocchi-the-botter/tree/main/chapters/season1/ch01_usdjpy_bb_mr
- 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/ch01_usdjpy_bb_mr/run.py
注意事項
本稿の検証は、取得時点の公開データと記載した条件に基づくものです。データ取得元の仕様変更、欠損、修正、配信遅延などにより、結果が変わる場合があります。本稿は投資助言ではなく、売買判断はご自身の責任でお願いします。
