LLMによるコード生成を活用した取引戦略の高速プロトタイピング
Python自動売買バックテスト
戦略概要
本記事では、LLM(Large Language Model)のコード生成能力を活用し、取引戦略のアイデアを高速にプロトタイプ・バックテストする手法を解説します。従来は数週間〜数ヶ月かかっていた戦略開発サイクルを、数時間〜数日に短縮できます。
LLMでなければ実現できなかった理由
| 従来の手法 | LLM活用後 |
|---|---|
| 戦略アイデア → コード化に数日 | 自然言語で記述 → 数分でコード生成 |
| バックテストコードの手動実装 | フレームワーク連携コードを自動生成 |
| エラー修正に時間を要する | 対話的デバッグで即時修正 |
| 1つずつ手動でパラメータ調整 | パラメータ探索コードを自動生成 |
| 専門的なプログラミング知識が必須 | 自然言語で戦略を表現可能 |
2026年現在、Claude Opus 4.6やGPT-4oを活用したトレーディングシステムでは、開発サイクルが従来比50%以上高速化され、一部事例ではROI 102%を記録しています。
プロンプトによる戦略仕様の記述方法
戦略をLLMに正しく伝えるためのプロンプト設計が重要です。
基本的なプロンプト構造
# 戦略仕様をプロンプトで記述する例
strategy_prompt = """
## 戦略名
RSIモメンタム戦略
## エントリー条件
- RSI(14)が30を下回った後、30を上抜けしたとき買いエントリー
- RSI(14)が70を上回った後、70を下抜けしたとき売りエントリー
## エグジット条件
- エントリーから5%の利益で利確
- エントリーから2%の損失でストップロス
- 最大保有期間: 10日
## 対象銘柄
- 日経225構成銘柄
- 1日の出来高が100万株以上
## バックテスト期間
- 2023年1月1日〜2025年12月31日
## 出力
- backtraderで動作するPythonコードを生成してください
- シャープレシオ、最大ドローダウン、勝率を計算してください
"""
効果的なプロンプトの要素
| 要素 | 説明 | 例 |
|---|---|---|
| 明確な条件 | 曖昧さを排除した数値条件 | 「RSIが30を下回る」ではなく「RSI(14)が30未満になる」 |
| 時間軸の指定 | 足の種類を明示 | 「日足ベース」「1時間足」 |
| リスク管理 | ストップロス・利確条件 | 「エントリー価格から2%下落でストップ」 |
| 出力形式 | 期待するコード形式 | 「backtraderのStrategy形式」「vectorbtのシグナル配列」 |
バックテストフレームワークとの連携
backtraderでのコード自動生成
import anthropic
import backtrader as bt
def generate_backtrader_strategy(strategy_description: str) -> str:
"""LLMでbacktrader戦略コードを生成"""
client = anthropic.Anthropic()
prompt = f"""以下の戦略仕様に基づいて、backtraderのStrategyクラスを生成してください。
## 戦略仕様
{strategy_description}
## 出力要件
1. bt.Strategyを継承したクラス
2. __init__でインジケーターを定義
3. nextメソッドでエントリー・エグジットロジックを実装
4. 動作確認済みの完全なコード
5. 日本語コメント付き
## 注意
- self.dataで価格データにアクセス
- self.buy() / self.sell() / self.close()で注文
- sizer設定も含める
"""
response = client.messages.create(
model="claude-sonnet-4-20250514",
max_tokens=4000,
messages=[{"role": "user", "content": prompt}]
)
return response.content[0].text
# 使用例
strategy_spec = """
移動平均クロスオーバー戦略:
- 短期SMA(20)が長期SMA(50)を上抜けで買い
- 短期SMAが長期SMAを下抜けで売り
- ストップロス: エントリーから3%
"""
generated_code = generate_backtrader_strategy(strategy_spec)
print(generated_code)
生成されるコードの例
import backtrader as bt
class SMACrossStrategy(bt.Strategy):
"""移動平均クロスオーバー戦略"""
params = (
("short_period", 20),
("long_period", 50),
("stop_loss", 0.03),
)
def __init__(self):
# 移動平均インジケーター
self.sma_short = bt.indicators.SMA(
self.data.close, period=self.params.short_period
)
self.sma_long = bt.indicators.SMA(
self.data.close, period=self.params.long_period
)
# クロスオーバー検出
self.crossover = bt.indicators.CrossOver(
self.sma_short, self.sma_long
)
# トラッキング用
self.entry_price = None
def next(self):
if not self.position:
# ポジションなし
if self.crossover > 0: # ゴールデンクロス
self.buy()
self.entry_price = self.data.close[0]
else:
# ポジションあり
if self.crossover < 0: # デッドクロス
self.close()
elif self.entry_price:
# ストップロスチェック
pnl_pct = (self.data.close[0] - self.entry_price) / self.entry_price
if pnl_pct < -self.params.stop_loss:
self.close()
vectorbtでの高速バックテスト
vectorbtはNumba最適化により、従来ライブラリの100倍の速度でバックテストを実行できます。
import vectorbt as vbt
import numpy as np
import pandas as pd
def generate_vectorbt_signals(strategy_description: str, price_data: pd.Series) -> str:
"""LLMでvectorbt用シグナル生成コードを作成"""
client = anthropic.Anthropic()
prompt = f"""以下の戦略に基づいて、vectorbt用のエントリー・エグジットシグナル配列を生成するPythonコードを書いてください。
## 戦略仕様
{strategy_description}
## 入力データ
- price_data: pd.Series(終値の時系列データ)
## 出力形式
- entries: np.ndarray(Trueでロングエントリー)
- exits: np.ndarray(Trueでエグジット)
## 注意
- vectorbt.indicators を活用
- NumPy/Pandasでベクトル演算
- ループは使わない(パフォーマンスのため)
"""
response = client.messages.create(
model="claude-sonnet-4-20250514",
max_tokens=3000,
messages=[{"role": "user", "content": prompt}]
)
return response.content[0].text
# 生成されたコードの実行例
def run_vectorbt_backtest(price_data: pd.Series):
"""vectorbtでバックテスト実行"""
# RSIベースのシグナル生成
rsi = vbt.RSI.run(price_data, window=14)
# シグナル配列
entries = (rsi.rsi_crossed_below(30)).shift(1) & (rsi.rsi > 30)
exits = (rsi.rsi_crossed_above(70)).shift(1) & (rsi.rsi < 70)
# バックテスト実行
portfolio = vbt.Portfolio.from_signals(
price_data,
entries=entries,
exits=exits,
init_cash=1000000, # 100万円
fees=0.001, # 0.1%手数料
)
# 結果取得
stats = portfolio.stats()
return {
"total_return": stats["Total Return [%]"],
"sharpe_ratio": stats["Sharpe Ratio"],
"max_drawdown": stats["Max Drawdown [%]"],
"win_rate": stats["Win Rate [%]"],
}
Claude Code / Cursorを使った対話的開発
Claude Codeでの戦略開発ワークフロー
# Claude Codeで戦略開発を開始
claude
# プロンプト例
> RSIとボリンジャーバンドを組み合わせた逆張り戦略を作成してください。
> RSIが30以下かつ価格がボリンジャーバンド下限を下回ったら買い、
> RSIが70以上かつ価格がボリンジャーバンド上限を上回ったら売りです。
> backtraderで実装し、日経225のデータでバックテストしてください。
対話的デバッグの例
ユーザー: このコードでバックテストを実行するとエラーが出ます
"IndexError: array index out of range"
Claude: このエラーは、インジケーターの計算期間中にnextメソッドが
呼ばれていることが原因です。以下の修正を提案します:
```python
def next(self):
# インジケーターの準備ができるまで待機
if len(self.data) < self.params.long_period:
return
# 以降のロジック...
```
ユーザー: ありがとうございます。動作しました。
シャープレシオを改善するためにパラメータを最適化できますか?
Claude: はい、optuna を使ったパラメータ最適化コードを生成します...
Cursorでの開発ポイント
| 機能 | 活用方法 |
|---|---|
| Tab補完 | 戦略コードのボイラープレートを自動補完 |
| Cmd+K | 選択したコードブロックの改善を依頼 |
| Chat | エラーメッセージを貼り付けてデバッグ |
| @codebase | プロジェクト全体を参照した修正提案 |
生成コードの品質検証
LLM生成コードは品質検証が必須です。
静的分析チェック
import ast
import subprocess
from dataclasses import dataclass
@dataclass
class CodeQualityReport:
"""コード品質レポート"""
syntax_valid: bool
type_check_passed: bool
lint_issues: list[str]
security_issues: list[str]
def validate_generated_code(code: str) -> CodeQualityReport:
"""生成コードの品質検証"""
# 1. 構文チェック
try:
ast.parse(code)
syntax_valid = True
except SyntaxError as e:
syntax_valid = False
return CodeQualityReport(
syntax_valid=False,
type_check_passed=False,
lint_issues=[str(e)],
security_issues=[]
)
# 2. 一時ファイルに書き出してlint実行
with open("temp_strategy.py", "w") as f:
f.write(code)
# ruffでlintチェック
result = subprocess.run(
["ruff", "check", "temp_strategy.py"],
capture_output=True,
text=True
)
lint_issues = result.stdout.split("\n") if result.stdout else []
# 3. セキュリティチェック(危険な関数の使用検出)
dangerous_patterns = ["eval(", "exec(", "subprocess.call(", "__import__"]
security_issues = [
p for p in dangerous_patterns if p in code
]
return CodeQualityReport(
syntax_valid=syntax_valid,
type_check_passed=len(lint_issues) == 0,
lint_issues=lint_issues,
security_issues=security_issues
)
バックテスト結果の妥当性検証
def validate_backtest_results(
stats: dict,
price_data: pd.Series
) -> tuple[bool, list[str]]:
"""バックテスト結果の妥当性チェック"""
warnings = []
# 1. 非現実的なリターンの検出
if stats.get("sharpe_ratio", 0) > 3.0:
warnings.append(
f"シャープレシオが異常に高い: {stats['sharpe_ratio']:.2f}。"
"過学習の可能性があります。"
)
# 2. 取引回数のチェック
trade_count = stats.get("total_trades", 0)
if trade_count < 30:
warnings.append(
f"取引回数が少ない: {trade_count}回。"
"統計的有意性が低い可能性があります。"
)
# 3. 期間に対するパフォーマンスの妥当性
annual_return = stats.get("annual_return", 0)
if annual_return > 100:
warnings.append(
f"年率リターンが異常: {annual_return:.1f}%。"
"ルックアヘッドバイアスの可能性を確認してください。"
)
# 4. 最大ドローダウンのチェック
max_dd = abs(stats.get("max_drawdown", 0))
if max_dd > 50:
warnings.append(
f"最大ドローダウンが大きい: {max_dd:.1f}%。"
"リスク管理の見直しを推奨します。"
)
is_valid = len(warnings) == 0
return is_valid, warnings
過学習検出の自動化
from sklearn.model_selection import TimeSeriesSplit
def detect_overfitting(
strategy_func,
price_data: pd.DataFrame,
n_splits: int = 5
) -> dict:
"""時系列交差検証で過学習を検出"""
tscv = TimeSeriesSplit(n_splits=n_splits)
train_sharpes = []
test_sharpes = []
for train_idx, test_idx in tscv.split(price_data):
train_data = price_data.iloc[train_idx]
test_data = price_data.iloc[test_idx]
# 訓練期間でバックテスト
train_stats = strategy_func(train_data)
train_sharpes.append(train_stats["sharpe_ratio"])
# テスト期間でバックテスト
test_stats = strategy_func(test_data)
test_sharpes.append(test_stats["sharpe_ratio"])
avg_train_sharpe = np.mean(train_sharpes)
avg_test_sharpe = np.mean(test_sharpes)
# 過学習度の計算(訓練とテストの乖離)
overfit_ratio = (avg_train_sharpe - avg_test_sharpe) / avg_train_sharpe
return {
"avg_train_sharpe": avg_train_sharpe,
"avg_test_sharpe": avg_test_sharpe,
"overfit_ratio": overfit_ratio,
"is_overfitting": overfit_ratio > 0.3, # 30%以上の乖離で過学習判定
"recommendation": (
"過学習の兆候あり。パラメータ数を減らすか、戦略を簡素化してください。"
if overfit_ratio > 0.3 else "過学習の兆候なし。"
)
}
過学習検出の基準値
| 指標 | 健全 | 要注意 | 過学習の疑い |
|---|---|---|---|
| 訓練/テスト シャープ比 | < 1.2 | 1.2-1.5 | > 1.5 |
| パラメータ数 / 取引回数 | < 0.01 | 0.01-0.05 | > 0.05 |
| In-sample/Out-sample乖離 | < 20% | 20-30% | > 30% |
実運用までのワークフロー
graph TD
A[戦略アイデア] --> B[LLMでコード生成]
B --> C[静的分析・lint]
C --> D[単体テスト]
D --> E[バックテスト]
E --> F{シャープ比 > 1.5?}
F -->|Yes| G[過学習検証]
F -->|No| B
G --> H{過学習なし?}
H -->|Yes| I[ペーパートレード]
H -->|No| B
I --> J[1ヶ月検証]
J --> K{実績一致?}
K -->|Yes| L[小ロット実運用]
K -->|No| B
L --> M[段階的スケール]
各フェーズのチェックリスト
| フェーズ | チェック項目 | 基準 |
|---|---|---|
| コード生成 | 構文エラーなし | 100% |
| 静的分析 | セキュリティ警告なし | 0件 |
| バックテスト | シャープレシオ | > 1.5 |
| バックテスト | 最大ドローダウン | < 20% |
| バックテスト | 取引回数 | > 50回 |
| 過学習検証 | 訓練/テスト乖離 | < 30% |
| ペーパートレード | 実績との乖離 | < 20% |
リスクと注意事項
LLMコード生成のリスク
| リスク | 影響 | 対策 |
|---|---|---|
| 誤ったロジック生成 | 意図しない取引実行 | コードレビュー必須 |
| ルックアヘッドバイアス | バックテスト結果の過大評価 | 時系列検証の実施 |
| セキュリティ脆弱性 | システム侵害 | 静的分析ツールで検出 |
| ハルシネーション | 存在しないAPIの呼び出し | 実行前テスト |
| モデルドリフト | 生成品質の変化 | バージョン固定 |
推奨されるベストプラクティス
- 段階的検証: コード生成 → 静的分析 → バックテスト → ペーパートレード → 小ロット実運用
- 人間によるレビュー: 特にリスク管理ロジックは必ず人間が確認
- バージョン管理: 生成に使用したLLMモデルとプロンプトを記録
- 継続的モニタリング: 実運用後もパフォーマンスを監視し、乖離があれば停止
まとめ
LLMによるコード生成は、取引戦略開発のスピードを劇的に向上させます。従来数週間かかっていたプロトタイピングが数時間で完了し、より多くの戦略アイデアを検証できるようになります。
ただし、生成されたコードは必ず検証が必要です。過学習リスク、セキュリティ脆弱性、ハルシネーションへの対策を怠ると、実運用で大きな損失につながる可能性があります。
推奨導入ステップ:
- まず簡単な戦略(移動平均クロス等)でワークフローを習得
- 静的分析・過学習検証のパイプラインを整備
- ペーパートレードで実際の市場と比較
- 小ロットから実運用を開始し、段階的にスケール
免責事項: 本記事は情報提供を目的としており、特定の投資行動を推奨するものではありません。投資判断はご自身の責任で行ってください。LLMが生成したコードは必ず検証し、十分なテストを経てから実運用してください。