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>
This commit is contained in:
2026-06-07 15:59:05 +08:00
co-authored by Claude Opus 4.7
commit 271a9343a5
293 changed files with 59598 additions and 0 deletions
+175
View File
@@ -0,0 +1,175 @@
"""
Sprint 4 验证脚本 — ML 模型训练与回测。
用法:
python cli/demo_ml.py
python cli/demo_ml.py --ts_code 600519.SH
python cli/demo_ml.py --ts_code 000001.SZ --lookahead 10
"""
import sys, os, argparse
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
import numpy as np
from data.data_manager import DataManager
from factors.engine import FactorEngine
from factors.registry import get_factor
from models.features import FeatureEngine
from models.lightgbm.model import LightGBMModel
from models.catboost.model import CatBoostModel
from models.backtest_integration import MLStrategy, MLBenchmark
from backtest.vectorbt.engine import VectorBTEngine
def main():
p = argparse.ArgumentParser(description="Sprint 4 — ML 模型训练与回测验证")
p.add_argument("--ts_code", default="000001.SZ", help="测试股票代码")
p.add_argument("--lookahead", type=int, default=5, help="预测未来 N 日(默认: 5")
args = p.parse_args()
print("=" * 60)
print("Sprint 4 — ML 模型训练与回测验证")
print("=" * 60)
print("\n[1/6] 准备数据...")
dm = DataManager(); dm.init_db()
engine_fe = FactorEngine(dm)
price_df = dm.get_daily(args.ts_code).set_index("trade_date").sort_index()
tech_names = ["momentum_5", "momentum_10", "momentum_20", "momentum_60", "rsi_7", "rsi_14", "macd",
"vol_ratio_5", "vol_ratio_20", "vol_chg_5", "boll", "boll_width", "atr_14", "atr_ratio_14",
"ma_cross_5_20", "ma_cross_10_60", "ma_dev_20", "ma_dev_60", "volatility_20", "volatility_60",
"down_vol_20", "turnover_5", "turnover_chg_5", "amplitude_5", "amplitude_20"]
factor_df = engine_fe.compute(args.ts_code, [get_factor(n) for n in tech_names])
print(" 日线: {} 条, 因子: {}".format(len(price_df), factor_df.shape[1]))
print("\n[2/6] 特征工程 (lookahead={}, regression)...".format(args.lookahead))
fe = FeatureEngine(lookahead=args.lookahead, label_type="regression")
X, y = fe.build(factor_df, price_df, fit=True)
print(" 特征矩阵: {} x {}".format(X.shape[0], X.shape[1]))
print(" 标签: mean={:.2f}% std={:.2f}% min={:.1f}% max={:.1f}%".format(y.mean(), y.std(), y.min(), y.max()))
print("\n[3/6] 训练集/测试集划分 (前70%训练)...")
n = len(X); split = int(n * 0.7)
X_train, X_test = X.iloc[:split], X.iloc[split:]
y_train, y_test = y.iloc[:split], y.iloc[split:]
print(" 训练集: {} 行 ({} ~ {})".format(len(X_train), X_train.index[0], X_train.index[-1]))
print(" 测试集: {} 行 ({} ~ {})".format(len(X_test), X_test.index[0], X_test.index[-1]))
print("\n[4/6] LightGBM 训练...")
lgb_model = LightGBMModel(eval_ratio=0.0, early_stopping=100)
lgb_model.fit(X_train, y_train)
ic_lgb = lgb_model.predict(X_test).corr(y_test)
print(" 测试集 IC: {:.4f} trees: {}".format(ic_lgb, lgb_model.n_estimators_used))
imp = lgb_model.get_feature_importance()
print(" Top 5 特征:")
for _, row in imp.head(5).iterrows():
print(" {:25s} {:5.1f}%".format(row["feature"], row["importance_pct"]))
# 解读
abs_ic = abs(ic_lgb)
if abs_ic < 0.03:
print(" > 解读: IC 接近 0,单股票预测信号极弱(正常现象)。多股票截面预测效果更好。")
elif abs_ic < 0.08:
print(" > 解读: IC {:.3f} 有微弱预测能力,可用于因子组合。".format(ic_lgb))
else:
print(" > 解读: IC {:.3f} 有显著预测能力,特征工程有效。".format(ic_lgb))
top_feat = imp.iloc[0]
print(" > 最重要特征 '{}' 占比 {:.1f}%,说明该类因子对短期收益影响最大。".format(top_feat["feature"], top_feat["importance_pct"]))
print("\n[5/6] CatBoost 训练...")
cb_model = CatBoostModel(eval_ratio=0.0, early_stopping=100)
cb_model.fit(X_train, y_train)
ic_cb = cb_model.predict(X_test).corr(y_test)
print(" 测试集 IC: {:.4f} trees: {}".format(ic_cb, cb_model.n_estimators_used))
if abs(ic_cb) < 0.03:
print(" > 解读: CatBoost IC 同样接近 0,两者结论一致:单股票短期收益很难预测。")
print("\n[6/6] ML 策略回测对比...")
test_price = price_df.loc[X_test.index]
test_factor = factor_df.loc[X_test.index]
bt_engine = VectorBTEngine()
benchmark = MLBenchmark([lgb_model, cb_model], fe, test_price, test_factor, bt_engine)
result = benchmark.run()
print(result.round(2).to_string())
print(" > 解读: 回测结果反映 ML 策略在测试集上的实盘表现。")
print(" > 正收益+高夏普=模型有效;负收益=需更多特征或换截面预测。")
print(" > 单股票 ML 策略通常不如多因子规则策略稳定,这是正常现象。")
try:
from reports.storage import save_report
# 组装完整报告
report_lines = []
report_lines.append("# ML 模型训练报告 — {}".format(args.ts_code))
report_lines.append("")
report_lines.append("## 数据概况")
report_lines.append("- 日线: {} 条 ({} ~ {})".format(len(price_df), price_df.index[0], price_df.index[-1]))
report_lines.append("- 因子: {}".format(factor_df.shape[1]))
report_lines.append("- 特征矩阵: {} × {}".format(X.shape[0], X.shape[1]))
report_lines.append("- 标签 (未来{}日收益): mean={:.2f}% std={:.2f}%".format(args.lookahead, y.mean(), y.std()))
report_lines.append("- 训练集: {} 行 | 测试集: {}".format(len(X_train), len(X_test)))
report_lines.append("")
report_lines.append("## LightGBM")
report_lines.append("- 测试集 IC: {:.4f} | 树数: {}".format(ic_lgb, lgb_model.n_estimators_used))
report_lines.append("- 解读: {}".format(
"IC 接近 0,单股票预测信号极弱(正常现象)" if abs(ic_lgb) < 0.03
else "IC {:.3f} 有微弱预测能力".format(ic_lgb) if abs(ic_lgb) < 0.08
else "IC {:.3f} 有显著预测能力".format(ic_lgb)))
report_lines.append("")
report_lines.append("### 特征重要性 (Top 10)")
report_lines.append("| 特征 | 重要性 |")
report_lines.append("|------|--------|")
for _, row in imp.head(10).iterrows():
report_lines.append("| {} | {:.1f}% |".format(row["feature"], row["importance_pct"]))
report_lines.append("")
report_lines.append("## CatBoost")
report_lines.append("- 测试集 IC: {:.4f} | 树数: {}".format(ic_cb, cb_model.n_estimators_used))
report_lines.append("- 解读: {}".format(
"IC 接近 0,两者结论一致:单股票短期收益很难预测" if abs(ic_cb) < 0.03
else "IC {:.3f}".format(ic_cb)))
cb_imp = cb_model.get_feature_importance()
report_lines.append("")
report_lines.append("### 特征重要性 (Top 10)")
report_lines.append("| 特征 | 重要性 |")
report_lines.append("|------|--------|")
for _, row in cb_imp.head(10).iterrows():
report_lines.append("| {} | {:.1f}% |".format(row["feature"], row["importance_pct"]))
report_lines.append("")
report_lines.append("## 回测对比")
report_lines.append("")
# 将 DataFrame 转为 MD 管道表格
if result is not None and not result.empty:
cols = result.columns.tolist()
report_lines.append("| model | " + " | ".join(cols) + " |")
report_lines.append("|" + "|".join(["------"] * (len(cols) + 1)) + "|")
for idx, row in result.iterrows():
vals = []
for c in cols:
v = row[c]
vals.append("{:.2f}".format(v) if isinstance(v, (int, float)) and not np.isnan(v) else str(v) if not (isinstance(v, float) and np.isnan(v)) else "-")
report_lines.append("| " + str(idx) + " | " + " | ".join(vals) + " |")
report_lines.append("")
report_lines.append("> 解读: 正收益+高夏普=模型有效;负收益=需更多特征或换截面预测。单股票 ML 策略通常不如多因子规则策略稳定。")
else:
report_lines.append("无回测数据")
report_lines.append("")
report_lines.append("> 解读: 正收益+高夏普=模型有效;负收益=需更多特征或换截面预测。单股票 ML 策略通常不如多因子规则策略稳定,这是正常现象。")
save_report("\n".join(report_lines), "ML 模型训练报告",
subject_type="stock", subject_code=args.ts_code)
print("\n 报告已存入 DB")
except Exception as e:
print("\n [WARN] 报告入库失败: {}".format(e))
print("\n" + "=" * 60)
print("Sprint 4 验证完成")
print("=" * 60)
if __name__ == "__main__":
main()