Files
myquant/finance/models/backtest_integration.py
T
simonandClaude Opus 4.7 271a9343a5 Initial commit: cc-cursor 全链路量化研究平台
7 Sprints 全部完成:
  Sprint 0: 基础设施 (DataManager + MariaDB)
  Sprint 1: 因子引擎 (34因子/12分类)
  Sprint 2: VectorBT 回测 (5策略+截面)
  Sprint 3: Optuna 优化 (+Walk-Forward)
  Sprint 4: ML 模型 (LightGBM+CatBoost)
  Sprint 5: Qwen 情绪因子 (三源新闻+日期对齐)
  Sprint 6: Agent 系统 (4Agent+日报.md/.html)

生产加固 (15项): Tushare双源fallback, SSH自动恢复, pool_pre_ping,
  save_daily先删后插, load_dotenv绝对路径, 日报5d/20d修复,
  RiskAgent改上证指数, 昨日对比+数据截止, mac_report utf8mb4,
  CLAUDE-*.md 9条已知Bug, demo全参数化, djapi数据源归一化,
  indexDatas API修正

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-07 15:59:05 +08:00

125 lines
4.1 KiB
Python

"""
ML 模型回测集成。
MLStrategy: 将 ML 预测值作为交易信号接入回测引擎。
MLBenchmark: 多模型基准对比。
"""
import numpy as np
import pandas as pd
from backtest.base import BaseStrategy
from backtest.vectorbt.engine import VectorBTEngine
from models.base import BaseModel
from models.features import FeatureEngine
class MLStrategy(BaseStrategy):
"""
ML 预测 → 交易信号。
用模型预测未来 N 日收益,按预测值分位数生成信号:
- 预测值 > buy_quantile → 买入
- 预测值 < sell_quantile → 平仓
参数:
model: 已训练的 BaseModel
feature_engine: 已 fit 的 FeatureEngine
buy_quantile: 买入分位阈值(0.7 = 预测值最高的30%买入)
sell_quantile: 卖出分位阈值(0.3 = 预测值最低的30%平仓)
rebalance_freq: 调仓间隔(交易日)
"""
category = "ml"
def __init__(
self,
model: BaseModel,
feature_engine: FeatureEngine,
buy_quantile: float = 0.7,
sell_quantile: float = 0.3,
rebalance_freq: int = 5,
):
self.model = model
self.feature_engine = feature_engine
self.buy_quantile = buy_quantile
self.sell_quantile = sell_quantile
self.rebalance_freq = rebalance_freq
self.name = f"ml_{model.name}"
def generate_signals(self, factor_df: pd.DataFrame) -> pd.Series:
X, _ = self.feature_engine.build(factor_df, factor_df, fit=False)
if X.empty:
return pd.Series(-1, index=factor_df.index)
preds = self.model.predict(X)
# 用预测值本身的分布作为阈值(相对排序,避免模型偏差影响)
buy_threshold = preds.quantile(self.buy_quantile)
sell_threshold = preds.quantile(self.sell_quantile)
signals = pd.Series(-1, index=factor_df.index)
common = signals.index.intersection(preds.index)
buy_mask = preds.loc[common] > buy_threshold
sell_mask = preds.loc[common] < sell_threshold
signals.loc[buy_mask[buy_mask].index] = 1
signals.loc[sell_mask[sell_mask].index] = 0
signals = self._filter_rebalance(signals)
return signals
def _filter_rebalance(self, signals: pd.Series) -> pd.Series:
"""每隔 rebalance_freq 个交易日保留第一个非持有信号。"""
result = signals.copy()
last_active = -self.rebalance_freq - 1
for i in range(len(result)):
sig = result.iloc[i]
if sig in (0, 1):
if i - last_active >= self.rebalance_freq:
last_active = i
else:
result.iloc[i] = -1
return result
class MLBenchmark:
"""ML 模型基准对比测试。"""
def __init__(
self,
models: list[BaseModel],
feature_engine: FeatureEngine,
price_df: pd.DataFrame,
factor_df: pd.DataFrame,
bt_engine: VectorBTEngine | None = None,
):
self.models = models
self.feature_engine = feature_engine
self.price_df = price_df
self.factor_df = factor_df
self.bt_engine = bt_engine or VectorBTEngine()
def run(self) -> pd.DataFrame:
"""对比各模型的预测质量和回测表现。"""
rows = []
for model in self.models:
strategy = MLStrategy(model, self.feature_engine)
report = self.bt_engine.run(strategy, self.price_df, self.factor_df)
# OOS 预测 vs 真实值
X, y_true = self.feature_engine.build(self.factor_df, self.price_df, fit=True)
y_pred = model.predict(X)
ic = y_pred.corr(y_true) if len(y_pred) > 0 else 0
rows.append({
"model": model.name,
"ic": round(ic, 4),
"total_return": report.total_return,
"cagr": report.cagr,
"max_dd": report.max_drawdown,
"sharpe": report.sharpe_ratio,
"win_rate": report.win_rate,
"trades": report.total_trades,
})
return pd.DataFrame(rows).set_index("model")