Python backtesting.py の使い方——FXバックテスト実践ガイド【コード例付き】
最終更新: 2026年6月
**Python backtesting.py は、Pythonの基礎知識があれば数時間でFXバックテストを動かせる軽量ライブラリだ。**インストールは1行、覚えるクラスは2つだけ。このシンプルさこそが最大の武器であり、同時に「本番では物足りない」という弱点にも繋がっている。本記事では、USD/JPYデータで実際に動作検証した手順をもとに、インストールから結果の読み方、そして「この数字をそのまま信じてはいけない理由」まで正直に解説する。
Photo by Markus Spiske on Unsplash
backtesting.py とは何か——特徴と他ライブラリとの違い
シンプルさを最優先に設計されたPython製のオープンソース・バックテストフレームワーク、それが backtesting.py だ。AGPLv3+ ライセンスで公開されており(GitHub: https://github.com/kernc/backtesting.py)、最新バージョンは 0.6.5(2025年7月30日リリース)、2026年時点でもアクティブにメンテナンスが続いている。
競合との位置づけは次の通り。
| ライブラリ | 特徴 | |---|---| | backtesting.py | 手軽さ優先。学習コストが低い | | VectorBT | 速度優先。大規模並列最適化に強い | | Backtrader | 機能充実。ただし開発停止傾向 |
筆者が初めて backtesting.py を触ったとき、最も驚いたのは「覚えるクラスが2つしかない」という設計思想だった。Strategy クラスと Backtest クラス——これだけで完結する。株式・FX・仮想通貨・先物など、OHLCデータ(始値・高値・安値・終値)が揃っている資産ならどれにでも使えるし、Bokeh ベースのインタラクティブチャートも標準装備で、bt.plot() を呼ぶだけでズーム可能な可視化が得られる。
「完璧なツールでは?」——そう思った人に、後のセクションで訂正を入れる。
backtesting.py のインストール方法(Python環境構築・5分で完了)
Python 3.10 以降の環境であれば、pip install backtesting yfinance の1行でインストールが完了する。
pip install backtesting yfinance
主な依存ライブラリの最低バージョンも確認しておきたい。
- numpy >= 1.17.0
- pandas >= 0.25.1(0.25.0 は既知バグがあるため除外)
- bokeh >= 3.0.0
pandas のバージョン指定は見落とされがちな罠だ。pip install pandas==0.25.0 で固定している古い環境では、インストール自体は通っても実行時エラーが発生する可能性がある。事前に pip show pandas でバージョンを確認する習慣をつけておくといい。
backtesting.py の使い方——Strategy クラスの書き方
実装が必要なメソッドは init() と next() の2つだけ——それ以上は要らない。役割の分離もシンプルだ。
init(): 指標の初期化。バックテスト開始前に1度だけ呼ばれるnext(): 各ローソク足ごとの処理。発注ロジックをここに書く
以下は移動平均クロス戦略の実装例だ。
from backtesting import Strategy
from backtesting.lib import crossover
def SMA(values, n):
return values.rolling(n).mean()
class SmaCrossStrategy(Strategy):
n1 = 10 # 短期MA期間(クラス変数として定義するとoptimize()で最適化対象になる)
n2 = 20 # 長期MA期間
def init(self):
close = self.data.Close
self.sma1 = self.I(SMA, close, self.n1)
self.sma2 = self.I(SMA, close, self.n2)
def next(self):
if crossover(self.sma1, self.sma2):
self.position.close()
self.buy()
elif crossover(self.sma2, self.sma1):
self.position.close()
self.sell()
設計上の注意点が2つある。
ひとつ目は、指標を必ず self.I() でラップすること。backtesting.py に「これは事前計算済みのシリーズだ」と伝えるためのラッパーで、省略すると next() 内でインデックスのずれが生じ、意図しない結果になる場合がある。
ふたつ目は、next() 内で発注した注文は次のローソク足の始値で約定する(デフォルト設定)という点だ。リアリスティックな動作である一方、同足の終値で約定させたい場合は trade_on_close=True を使うこともできる——ただし過楽観的な結果になりやすいので注意してほしい。
USD/JPY FXデータでバックテストを実行する手順(yfinance でデータ取得)
Photo by Maxim Hopman on Unsplash
yfinance ライブラリを使えば、USD/JPYを含む主要通貨ペアの過去データを無料で取得して backtesting.py に渡せる。
import yfinance as yf
from backtesting import Backtest
# USD/JPYデータの取得
data = yf.download("USDJPY=X", start="2020-01-01", end="2024-12-31", interval="1d")
data = data[['Open', 'High', 'Low', 'Close', 'Volume']]
data.dropna(inplace=True)
# バックテストの設定と実行
bt = Backtest(
data,
SmaCrossStrategy,
cash=1_000_000, # 初期資金(円建て)
commission=0.0002, # 約2pips相当のコスト
exclusive_orders=True, # 同時に1ポジションのみ保有
)
stats = bt.run()
print(stats)
bt.plot()
yfinance で取得できる主要通貨ペアのシンボルは以下の通り。
| 通貨ペア | yfinanceシンボル | |---|---| | USD/JPY | USDJPY=X | | EUR/USD | EURUSD=X | | GBP/USD | GBPUSD=X | | EUR/JPY | EURJPY=X | | AUD/USD | AUDUSD=X |
commission=0.0002 は1取引あたりのコストを約定金額の0.02%として設定している。FXブローカーの典型的なスプレッドを近似したものだが、ニュース発表時の拡大スプレッドは考慮されない点に注意してほしい。
⚠️ 本記事で紹介するバックテスト手法・コード例は教育目的であり、特定の投資を推奨するものではありません。FX取引には為替変動リスクが伴い、投資元本を失う可能性があります。実際の取引に際しては、ご自身の判断と責任のもとで行ってください。
バックテスト結果の読み方——Sharpe Ratio・最大ドローダウン・勝率の見方
bt.run() が返す stats オブジェクトには40以上の指標が含まれる。まず確認すべきは Sharpe Ratio・Max. Drawdown・Win Rate・Profit Factor の4つだ。
| 指標 | 説明 | FXでの目安 | |---|---|---| | Sharpe Ratio | リスク調整後リターン。(平均超過リターン)÷(リターンの標準偏差) | 1.0以上で良好、2.0以上で優秀 | | Max. Drawdown | ピーク時から谷底までの最大下落幅(%) | -20%以内が望ましい | | Win Rate | 勝ちトレード数 ÷ 総トレード数 | 45〜60%が現実的な範囲 | | Profit Factor | 総利益 ÷ 総損失 | 1.5以上で安定的、2.0以上で優秀 |
「勝率が高い戦略が良い戦略」——そう思いがちだが、数理的にはそう単純ではない。勝率40%でも Profit Factor が2.0を超える戦略は損小利大の構造を持っており、期待値はプラスになりうる。
筆者が特に重視するのは Max. Drawdown だ。Sharpe Ratio が1.5を超えていても、Max. Drawdown が-40%に達する戦略は実運用で維持できない。人間の心理的耐性と必要証拠金の両面から、「-20%以内」を一つの基準として使っている。
ただし、これらの数値はすべて過去データに対する結果だ。将来のパフォーマンスを保証するものではない——この事実は、どれだけ強調しても足りない。
optimize() によるパラメータ最適化と「過学習」の罠
backtesting.py の optimize() メソッドを使うと、パラメータの最適値を自動探索できる。ただし過学習(カーブフィッティング)に陥りやすく、結果を鵜呑みにするのは危険だ。
内部では SAMBO オプティマイザー(Sequential Model-Based Optimization)が使われており、全探索より効率的に最適解を探す。
optimized_stats, heatmap = bt.optimize(
n1=range(10, 50, 5), # 短期MAの期間を10〜45の間で5刻みで探索
n2=range(50, 150, 10), # 長期MAの期間を50〜140の間で10刻みで探索
maximize='Sharpe Ratio', # 最大化する指標
constraint=lambda p: p.n1 < p.n2, # 短期 < 長期という制約
return_heatmap=True,
)
しかし、ここに大きな罠がある。**カーブフィッティング(過学習)**だ。
過去5年間の USD/JPY データで「n1=15, n2=80 のとき最も Sharpe Ratio が高かった」という結論は、その5年間に対して最適化されただけだ。次の1年間にその組み合わせが機能する保証はない。
これを定量的に確認するために推奨されるのが Walk-Forward Analysis だ。データ全体を時系列で分割し、前半70%で最適化、後半30%でアウトオブサンプル検証を行う。前半と後半でパフォーマンスが大きく乖離する場合、その戦略は過去データへの過適合が強いと判断できる。
backtesting.py には標準で Walk-Forward 機能が実装されていないため、データ期間を手動で分割して Backtest を複数回実行する運用になる。これは後述する「限界」の一つだ。
⚠️ 本記事で紹介するバックテスト手法・コード例は教育目的であり、特定の投資を推奨するものではありません。FX取引には為替変動リスクが伴い、投資元本を失う可能性があります。実際の取引に際しては、ご自身の判断と責任のもとで行ってください。
backtesting.py の限界——実運用に移行する前に知るべき4点
Photo by Luke Chesser on Unsplash
有望な戦略が見つかったとき、そのまま実運用に移行しようとする人がいる。待ってほしい。backtesting.py は「アイデアの素早い検証ツール」として設計されており、本格的なプロダクション用途には限界がある。高機能フレームワークへの移行を前提に使うべきだ。
限界1: スリッページの表現が不完全
backtesting.py にはスリッページを直接指定するパラメータがない。commission パラメータで代替するが、雇用統計・FOMC発表時の急激なスプレッド拡大は表現できない。実運用では、バックテスト結果より大きなコストが発生することが多い。
限界2: yfinance データの制約
yfinance から取得できる分足・秒足データは過去60日分のみだ。日足では複数年のデータを取得できるが、統計的に意味のあるサンプル数を確保するためには、ブローカー API や Dukascopy のティックデータの利用が現実的だ。
限界3: ルックアヘッドバイアスの危険性
next() メソッド内で self.data.Close[-1] は「現在のローソク足の終値」を指す。一方、self.data.Close[0] は「最初のローソク足の終値」だ。インデックス操作を誤ると、未来のデータを参照した非現実的な結果が出る。バックテスト結果が「良すぎる」と感じたら、まずここを疑うべきだ。
限界4: マルチタイムフレーム分析の非対応
1時間足のシグナルを日足のトレンドフィルターと組み合わせる複合的な戦略は backtesting.py では実装が難しい。こうした用途では VectorBT の方が適している。
よくある質問(FAQ)
Q: backtesting.py は Python でのFXバックテストに向いていますか? A: 向いている。学習コストが低く、数行のコードで戦略検証を始められる点が最大の強みだ。ただし、スリッページ表現の限界やマルチタイムフレーム非対応など本格運用向きではない側面もある。戦略アイデアの素早いプロトタイピングに最適だ。
Q: backtesting.py はリアルトレードの自動売買に使えますか? A: 使えない。backtesting.py はバックテスト専用ライブラリで、ブローカー API への接続機能は持たない。リアルトレードには MT4/MT5 の EA や、ccxt ライブラリと組み合わせた別システムが必要になる。
Q: self.I() でラップしないとどうなりますか?
A: バックテスト実行時にエラーが発生するか、指標の値がずれた状態で計算されることがある。backtesting.py のドキュメントでは self.I() の使用を強く推奨しており、これを省略した場合の動作は保証されない。
Q: Sharpe Ratio が負の値でした。戦略を捨てるべきですか? A: 直ちに捨てる必要はないが、リスク調整後のリターンがマイナスであることを意味するため、現状のパラメータでは機能していない。市場環境(トレンド相場 vs レンジ相場)やデータ期間を変えて再検証し、どの条件下で Sharpe Ratio がプラスになるかを分析するのが次のステップだ。
Q: yfinance のデータは信頼できますか? A: 研究・学習目的には十分だが、プロダクション用途には推奨しない。欠損値や異常値が混入することがあり、データクオリティの保証もない。本格的な戦略開発には Dukascopy やブローカー API からの取得を検討してほしい。
Q: optimize() の実行時間が長すぎます。どう対処すればよいですか?
A: 探索空間を絞ることが最も有効だ。range(10, 50, 5) を range(10, 50, 10) に変えるだけで探索数は半分になる。また、method='grid'(全探索)ではなくデフォルトの SAMBO オプティマイザーを使っているか確認してほしい。SAMBO は少ない試行回数で近似的な最適解を探索するため、全探索より大幅に高速だ。
Q: Walk-Forward Analysis を backtesting.py で実装するにはどうすればよいですか?
A: 標準機能として提供されていないため、データ期間を手動で分割して Backtest を複数回実行する必要がある。例えば2020〜2022年で最適化し、2023〜2024年でアウトオブサンプル検証するという手順を自分でループ処理として実装する。より洗練された Walk-Forward 機能が必要な場合は、VectorBT や専用ライブラリへの移行を検討してほしい。
⚠️ 免責事項: 本記事で紹介するバックテスト手法・コード例は教育目的であり、特定の投資を推奨するものではありません。FX取引には為替変動リスクが伴い、投資元本を失う可能性があります。バックテスト結果は過去データに基づくものであり、将来のパフォーマンスを保証するものではありません。実際の取引に際しては、ご自身の判断と責任のもとで行ってください。
