グリッドボットを作ったとしても、パラメータ設定が悪ければ利益は出ません。「どの価格帯に設定するか」「グリッドを何本引くか」「どれだけの資金を投入するか」——これらの判断を根拠なく行うのは、目隠しで道を歩くようなものです。
そこで重要になるのがバックテスト(過去データを使った戦略の事後検証)です。過去の価格データにグリッド戦略を適用し、どの設定が最も良いリターン・リスク比率を示したかを定量的に評価します。
本記事では、グリッドボットのパラメータ最適化の考え方と、Pythonを使ったバックテストの実装方法を解説します。バックテストはあくまで「過去の検証」であり、将来の利益を保証するものではないことをご留意ください。
1. バックテストの基本概念と限界
1-1. バックテストとは何か
バックテストとは、ある戦略を過去の価格データに適用し、もし当時その戦略を実行していたらどうなっていたかを事後的にシミュレーションすることです。グリッドボットのバックテストでは、以下を確認します。
- 設定した価格帯の中に価格が収まっていた割合(時間帯)
- 各グリッドで何回売買が成立したか(取引回数)
- 手数料控除後の合計損益
- 最大ドローダウン(最大の含み損)
1-2. バックテストの3つの限界
- 過去は未来を保証しない: 過去のレンジが再現される保証はありません
- 過学習(オーバーフィット)リスク: 過去データに最適化しすぎると、未来のデータには機能しなくなります
- スリッページと流動性が反映されない: 実際の取引では指値が思った価格で通らないことがあります
2. バックテスト用データの取得
2-1. ccxtでの過去OHLCVデータ取得
バックテストには十分な量の過去データが必要です。ccxtで過去データを取得する場合、一度のAPIコールで取得できる本数に制限があります(Binanceの場合、最大1,500本)。長期間のデータを取得するには複数回のAPIコールが必要です。
import ccxt, pandas as pd, time
def fetch_all_ohlcv(exchange, symbol, timeframe, start_timestamp):
all_data = []
since = start_timestamp
while True:
data = exchange.fetch_ohlcv(symbol, timeframe=timeframe, since=since, limit=1000)
if not data:
break
all_data.extend(data)
since = data[-1][0] + 1
time.sleep(exchange.rateLimit / 1000)
if data[-1][0] >= exchange.milliseconds():
break
df = pd.DataFrame(all_data, columns=['timestamp','open','high','low','close','volume'])
df['datetime'] = pd.to_datetime(df['timestamp'], unit='ms')
df.drop_duplicates(subset='timestamp', inplace=True)
df.set_index('datetime', inplace=True)
return df
exchange = ccxt.binance()
since = exchange.parse8601('2024-01-01T00:00:00Z')
df = fetch_all_ohlcv(exchange, 'BTC/USDT', '1h', since)
print(f"取得本数: {len(df)}")
2-2. データの品質チェック
取得したデータには欠損(ギャップ)が含まれることがあります。欠損率を計算し、5%以上の欠損がある場合はデータの信頼性に注意が必要です。
3. グリッドバックテストエンジンの実装
3-1. バックテストの基本アルゴリズム
OHLCVの各バーで価格がグリッドラインを通過したか判定し、通過したグリッドで売買が成立したとみなして差益を累積します。
import pandas as pd
def backtest_grid(df, lower, upper, grid_count, budget, fee_rate=0.001):
ratio = (upper / lower) ** (1 / grid_count)
grid_prices = [lower * (ratio ** i) for i in range(grid_count + 1)]
qty_per_grid = budget / grid_count / df['close'].iloc[0]
total_pnl = 0
inventory = 0
for i in range(1, len(df)):
prev_close = df['close'].iloc[i - 1]
curr_low, curr_high = df['low'].iloc[i], df['high'].iloc[i]
for j, price in enumerate(grid_prices[:-1]):
if curr_low <= price < prev_close:
total_pnl -= price * qty_per_grid * fee_rate
inventory += qty_per_grid
elif prev_close <= price < curr_high and inventory >= qty_per_grid:
pnl = (price - grid_prices[j]) * qty_per_grid - price * qty_per_grid * fee_rate * 2
total_pnl += pnl
inventory -= qty_per_grid
unrealized = inventory * (df['close'].iloc[-1] - lower)
return {'total_realized_pnl': round(total_pnl, 4), 'unrealized_pnl': round(unrealized, 4)}
result = backtest_grid(df, lower=80000, upper=100000, grid_count=20, budget=1000000)
print(result)
3-2. 複数パラメータのグリッドサーチ
results = []
for grid_count in [10, 15, 20, 30]:
for lower_offset in [-0.1, -0.15, -0.2]:
current_price = df['close'].iloc[0]
lower = current_price * (1 + lower_offset)
upper = current_price * 1.1
r = backtest_grid(df, lower, upper, grid_count, budget=1000000)
r.update({'grid_count': grid_count, 'lower_offset': lower_offset})
results.append(r)
results_df = pd.DataFrame(results).sort_values('total_realized_pnl', ascending=False)
print(results_df.head(10))
4. パラメータ最適化の考え方
4-1. 評価指標の選定
単純に利益額が最大のパラメータを選ぶのは危険です。以下の指標を総合的に評価することが推奨されます。
- シャープレシオ: リターン ÷ リターンの標準偏差。リスク調整済みリターンの代表的な指標
- 最大ドローダウン: 最高点から最低点への下落率。小さいほど安定
- プロフィットファクター: 総利益 ÷ 総損失。1.5以上を目安にする場合が多い
- 取引回数: 少なすぎると機会損失が大きく、多すぎると手数料コストが増大
4-2. ウォークフォワードテストの重要性
過学習を防ぐには、最適化に使うデータ(インサンプル)と検証に使うデータ(アウトオブサンプル)を分けることが重要です。たとえば、2023年1〜12月のデータでパラメータを最適化し、2024年1〜6月のデータで検証します。インサンプルで良い結果が出ても、アウトオブサンプルで大幅に悪化する場合は過学習の可能性があります。
5. Freqtradeを使ったバックテストの活用
5-1. Freqtradeのバックテスト環境
Freqtradeはグリッド戦略のバックテストを含む高機能な検証環境を提供しています。バックテスト結果には取引回数・総損益・最大ドローダウン・シャープレシオなどが自動計算されます。
freqtrade backtesting --strategy GridStrategy --config config.json --timerange 20240101-20241231
5-2. ハイパーオプトによる自動パラメータ最適化
Freqtradeには hyperopt コマンドによる自動パラメータ最適化機能があります。シャープレシオを最大化する場合は以下のように実行します。
freqtrade hyperopt --strategy GridStrategy --hyperopt-loss SharpeHyperOptLoss --epochs 200 --config config.json
6. バックテスト結果の解釈と実運用への注意
6-1. バックテストが良くても実運用でうまくいかない理由
- 相場環境の変化: バックテスト期間がレンジ相場中心だったが、実運用期間はトレンド相場だった
- スリッページの未考慮: 実際の約定価格がバックテストの想定価格より不利なケース
- 感情的なパラメータ変更: ドローダウン中に設定を変更してしまう人的ミス
6-2. ドライランでの実運用前検証
本番投入前にドライラン(仮想売買)で少なくとも2〜4週間の実運用テストを実施することを強く推奨します。Freqtradeのドライランモードや、ccxtのテストネット環境を活用してください。
まとめ
- バックテストは有用だが過信は禁物。過学習と相場環境変化に注意
- ccxtでOHLCVデータを取得し、Pythonでシミュレーションエンジンを実装できる
- 評価指標はシャープレシオ・最大ドローダウン・プロフィットファクターを総合的に見る
- ウォークフォワードテストで過学習を検出する
- Freqtradeのhyperoptで自動最適化が可能
- 本番投入前に必ずドライランテストを実施すること
よくある質問(FAQ)
Q: バックテストのデータは何年分あれば十分ですか?
A: 一般的に1〜2年以上のデータを使用することが推奨されます。ただし仮想通貨は相場サイクルが変わりやすいため、直近1年のデータを重視しつつ長期データで安定性を検証するアプローチが有効です。
Q: バックテストでは収益が出たのに実運用で損失が出た場合、どう対応すべきですか?
A: まず相場環境がバックテスト期間と異なっていないかを確認します。レンジ相場向けの設定がトレンド相場で機能しなくなっているケースが多いです。パラメータを現在の相場に合わせて再設定するか、ボットを一時停止する判断も重要です。
Q: グリッドサーチで計算時間がかかりすぎます。
A: まず探索範囲を絞り込むために大まかなサーチを実施し、有望な範囲を特定してから細かいサーチを行う「2段階サーチ」が効率的です。Python multiprocessingによる並列化も有効です。
※本記事は情報提供を目的としており、投資を推奨するものではありません。暗号資産への投資は元本割れのリスクがあります。投資判断はご自身の責任で行ってください。