ドル円にボリンジャーバンドを巻いてみた

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 で成行売

エグジット(優先順)

  1. SL: 1.5 × ATR(backtesting.py が同バー内で自動判定)
  2. TP: 2.0 × ATR(同上)
  3. SMA タッチ(Low ≤ SMA ≤ High)→ 次バー Open で成行クローズ
  4. 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 — 再現環境

実行コマンド:

python chapters/season1/ch01_usdjpy_bb_mr/run.py

注意事項

本稿の検証は、取得時点の公開データと記載した条件に基づくものです。データ取得元の仕様変更、欠損、修正、配信遅延などにより、結果が変わる場合があります。本稿は投資助言ではなく、売買判断はご自身の責任でお願いします。