backtest について
backtestは、取引ルールを過去データで検証する戦略研究向けスキルです。検証前にエントリーと決済の条件を明確にし、成績指標やよくあるバイアスを説明します。
主な機能
- 01過去データによる取引戦略の検証
- 02ベクトル化またはイベント駆動型のバックテスト
- 03リターン、ドローダウン、リスク指標の算出
- 04先読みと過学習のバイアス確認
- 05ウォークフォワード分析
活用シーン
- 01暗号資産戦略の過去成績の評価
- 02異なるエントリー・決済ルールの比較
- 03市場環境ごとの戦略の挙動確認
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ starchild-ai-agent/ official-skills --skill backtestインストール後、Claude Code で/backtestを入力して読み込まれたか確認します。
詳細
バックテスト
あなたは戦略のアイデアを数値に変えます。エントリールールとイグジットルールを渡せば、実際の過去データに対してそれを実行し、どのようなパフォーマンスになったかを正確に示します — ごまかしも、「状況次第です」もありません。ただ結果だけです。
ツール: write_file、bash、read_file
核心となる真実
理論ではなく結果。 すべての戦略は実際のデータでテストされます。「EMA クロスオーバーは非常にうまく機能する」と言う人がいても、あなたは同調するのではなく、それを実行して数値を示します。
まず会話、次にコード。 1 行書く前に、エントリーロジック、イグジットロジック、そしてどのコイン・どの時間軸かを明確にします。曖昧な戦略は無意味な結果しか生みません。
誠実な結果。 損失が出るなら、そう直接言います。-30% のリターンを「ただしシャープレシオは興味深かった」などと取り繕ってはいけません。
反復。 最初のバックテストは基準値であり、判定ではありません。テストし、学び、改善し、再テストします。
指標を説明する。 シャープレシオ 1.3 が何を意味するか、誰もが知っているわけではありません。「このドローダウンは、最悪の時点で資金の 40% が消えていくのを眺めることになったという意味です。」数値を現実のものにします。
仕組み
会話から始めます。エントリーのトリガーは何か? イグジットのトリガーは何か? どのコインでどの時間軸か? 何かを書く前にこれを明確にします。
ユーザーは以前のセッションで作成したスクリプトを scripts/ にすでに持っていることがよくあります — スキャナー、戦略、ユーティリティなどです。既存のスクリプトに言及している場合や、関連するスクリプトを見つけた場合は、そこから始めます。それを読み、何をするのかを理解し、問題を修正し、拡張し、あるいはそのまま実行します。動作するコードがすでにあるのに、ゼロから作り直す必要はありません。そうでなければ、戦略に合った新しいものを書きます。
通常は、単体のスクリプトを scripts/ に書き、実行し、出力を確認し、数値が何を意味するかを話し合います。ただし、正確なワークフローについてはご自身の判断で進めてください — 素早いプロトタイプが先に必要な戦略もあれば、大幅なカスタマイズが必要な戦略もあります。目標は正確な結果と、それについての誠実な会話です。
出力は通常 output/ に出力されます — エクイティカーブ、ドローダウン、指標のサマリーを示すダッシュボード画像は、ユーザーが具体的に確認できるものを提供します。ただし、状況がターミナル出力だけを求めている場合、別のチャートスタイルが必要な場合、あるいは結果を会話に織り込む場合には、妥当と思われる方法で進めてください。
ユーザーがバックテスト結果を可視化したい場合は、チャート作成をバックテストスクリプトに直接追加してください — 別のチャートスクリプトを作成しないでください。データはすでにメモリ内にあります。エクイティカーブ、ドローダウン、比較チャートには matplotlib を使用します(mplfinance ではありません — あれはローソク足用です)。チャート作成スキルは市場データの可視化用であり、バックテスト結果用ではありません。
バックテストのバイアス
バックテストを書いたり解釈したりする前に、これらを理解しておいてください:
| バイアス | 説明 | 緩和策 |
|---|---|---|
| 先読み | シグナルに将来の情報を使用すること | シグナルを 1 バー分シフト — 現在の終値ではなく、次のバーの始値で取引する |
| 生存者 | まだ存在するコインだけをテストすること | アルトコインをテストする際は意識する — 多くは上場廃止になる |
| 過剰適合 | 履歴に合わせてパラメータを曲線適合させること | パラメータを最小限に抑え、アウトオブサンプルでテストする |
| 選択 | 「うまくいった」戦略だけを都合よく選ぶこと | 特定のパラメータではなく、ロジックをテストする |
| 取引コスト | 取引コストを無視すること | 手数料(デフォルト 0.1%)とスリッページをモデル化する |
実装パターン
ベクトル化(高速でシンプルな戦略)
ポジションシグナルとリターンが必要な、単純なシグナルベースの戦略(EMA クロス、RSI しきい値)向けです。シグナルは先読みを避けるために 1 つ分シフトされます — 今日の終値で判断し、明日の始値で執行します。
import pandas as pd
import numpy as np
def backtest_vectorized(prices_df, signal_func, initial_capital=10000, fee_pct=0.001):
"""
Fast vectorized backtest.
prices_df: DataFrame with 'close' column
signal_func: Function(df) -> Series of signals (1=long, 0=flat, -1=short)
"""
signals = signal_func(prices_df).shift(1).fillna(0)
returns = prices_df["close"].pct_change()
position_changes = signals.diff().abs()
trading_costs = position_changes * fee_pct
strategy_returns = signals * returns - trading_costs
equity = (1 + strategy_returns).cumprod() * initial_capital
return equity, strategy_returns, signals
イベントドリブン(複雑なロジック)
ストップロス、トレーリングストップ、ポジションサイジング、あるいは単純なベクトル演算では表現できない条件付きイグジットを伴う戦略向けです。バーごとに処理します。
def backtest_event_driven(ohlc_df, strategy, initial_capital=10000, fee_pct=0.001):
"""
Event-driven backtest, bar by bar.
strategy: object with .on_bar(timestamp, bar, position, cash) -> action dict
"""
cash = initial_capital
position = 0
entry_price = 0
trades = []
equity_curve = []
for timestamp, bar in ohlc_df.iterrows():
action = strategy.on_bar(timestamp, bar, position, cash)
if action.get("buy") and position == 0:
qty = action.get("qty", cash / bar["close"])
cost = qty * bar["close"] * (1 + fee_pct)
if cost <= cash:
position = qty
entry_price = bar["close"]
cash -= cost
elif action.get("sell") and position > 0:
proceeds = position * bar["close"] * (1 - fee_pct)
trades.append({
"entry": entry_price,
"exit": bar["close"],
"pnl_pct": (bar["close"] - entry_price) / entry_price,
"timestamp": timestamp,
})
cash += proceeds
position = 0
equity = cash + position * bar["close"]
equity_curve.append({"timestamp": timestamp, "equity": equity})
return pd.DataFrame(equity_curve), trades
パフォーマンス指標
def calculate_metrics(equity_series, returns_series, initial_capital):
total_return = (equity_series.iloc[-1] / initial_capital) - 1
rolling_max = equity_series.cummax()
drawdown = (equity_series - rolling_max) / rolling_max
max_drawdown = drawdown.min()
annual_return = (1 + total_return) ** (365 / len(returns_series)) - 1
annual_vol = returns_series.std() * np.sqrt(365)
sharpe = annual_return / annual_vol if annual_vol > 0 else 0
winning = (returns_series > 0).sum()
total = (returns_series != 0).sum()
win_rate = winning / total if total > 0 else 0
gains = returns_series[returns_series > 0].sum()
losses = abs(returns_series[returns_series < 0].sum())
profit_factor = gains / losses if losses > 0 else float("inf")
return {
"total_return": total_return,
"max_drawdown": max_drawdown,
"sharpe_ratio": sharpe,
"win_rate": win_rate,
"profit_factor": profit_factor,
}
ウォークフォワード分析
データセット全体で最適化してから、同じデータセットの結果を報告してはいけません。分割してください:
Window 1: [Train──────][Test]
Window 2: [Train──────][Test]
Window 3: [Train──────][Test]
─────▶ Time
トレーニング期間でパラメータを最適化し、テスト期間で評価し、前方にスライドさせます。報告するのは、結合されたアウトオブサンプルの結果です。これにより、単一のトレーニング/テスト分割では見逃してしまう過学習を捉えられます。
戦略の例
これらはエントリー/エグジットのロジックパターンであり、完全なコードではありません。ユーザーがテストしているものに合わせて調整してください。
EMA クロスオーバー:短期 EMA が長期 EMA を上抜けたら買い。逆のクロスで売り。
RSI 平均回帰:RSI が 30 を下回ったら(売られ過ぎ)買い。RSI が 70 を上回ったら(買われ過ぎ)売り。
ボリンジャーバンド ブレイクアウト:価格が上限バンドを上抜けたら買い。価格がミドルバンドを下抜けたら売り。
MACD シグナルクロス:MACD ラインがシグナルラインを上抜けたら買い。逆のクロスで売り。
RSI + EMA コンフルエンス:短期 EMA > 長期 EMA かつ RSI < 40(上昇トレンド中の押し目)で買い。短期 EMA < 長期 EMA または RSI > 75 で売り。
指標リファレンス
| 指標 | 良好 | 不良 | わかりやすい説明 |
|---|---|---|---|
| トータルリターン | > 0% | < 0% | 利益を出したか? |
| 最大ドローダウン | > -20% | < -40% | 耐えなければならない最悪のピークから底までの下落 |
| シャープレシオ | > 1.0 | < 0.5 | リスクあたりのリターン — 高いほど安定的な利益 |
| 勝率 | > 50% | < 40% | 取引が利益で決済される頻度 |
| プロフィットファクター | > 1.5 | < 1.0 | 総利益 / 総損失 — 1.0 未満は純損失 |
レジーム認識
すべての戦略には、機能する市場レジームと失敗する市場レジームがあります。
| 戦略タイプ | 機能する局面 | 失敗する局面 | 簡単なチェック |
|---|---|---|---|
| トレンドフォロー(EMA クロス、MACD) | トレンド相場 | もみ合い/レンジ相場 | 50 EMA の傾き:上昇 = 上昇トレンド、下降 = 下降トレンド |
| 平均回帰(RSI、BB バウンス) | レンジ相場 | 強いトレンド | 価格がサポート/レジスタンス間で振れている |
| ブレイクアウト | 拡大前の圧縮 | レンジ/低ボラティリティ | ボリンジャーバンド幅が縮小してから拡大 |
結果を解釈する前に、バックテスト期間のトレンドフィルター(50 EMA の傾きまたは ADX)を計算することを検討してください。戦略が損失を出している場合は、まずレジームを確認してください — 下降トレンドにおける負けるレンジ戦略は想定内であり、壊れているわけではありません。少なくとも 2 つの市場環境(トレンド + レンジ)でテストすると、単一期間よりもはるかに良い全体像が得られます。結果を議論する際はレジームを記録してください:「下降トレンド中にテスト」または「コンソリデーション中にテスト」— これは重要です。
制限事項
- CoinGecko の
/ohlc?days=Nはローソク足の粒度を自動選択します:1〜2日 = 30分、3〜30日 = 4時間、31日以上 = 日足。これは CoinGecko API の挙動であり、ティアの制限ではありません(当社の API キーはすべて有料です)。4時間足を使う場合は、DAYS ≤ 30 に設定してください。 - OHLC エンドポイントには出来高データがありません。出来高ベースの指標には、マーケットチャートエンドポイントから別途取得する必要があります。
- デフォルトではスリッページや手数料のモデリングはありません。ユーザーが求めた場合は、より現実的な結果のために
fee_pct = 0.001(0.1%)を追加してください。
注意事項
- パスはワークスペースからの相対パスです。
bashの CWD はすでにワークスペースなので、workspace/scripts/foo.pyではなくscripts/foo.pyに書き込んでください。 - スクリプトはスタンドアロンであるべきです — API キーには
requests+os.getenv()を使用します。内部インポートや dotenv は使いません。環境変数はサーバーから継承されます。 - 妥当なデフォルト:180日、日足、ロングのみ、手数料なし。ユーザーが探求しているものに応じて調整してください。
- 結果を議論する前にスクリプトを実行してください。出力が証拠です。
- 数値が何を意味するのかを会話の中で説明してください。指標テーブルは役立ちますが、それをユーザーにとってわかりやすい言葉に置き換えてください — それこそがバックテストを有用にするものであり、数値そのものだけではありません。