"""
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()
