Spaces:
Running
Running
| # backend/walk_forward.py | |
| import argparse | |
| import sys | |
| import pandas as pd | |
| import numpy as np | |
| import os | |
| # 确保 backend 目录在 sys.path 中 | |
| sys.path.append(os.path.dirname(os.path.abspath(__file__))) | |
| from app.data_manager import fetch_and_prepare_data | |
| from app.patterns import analyze_patterns | |
| from app.simulator import run_backtest_sim | |
| def walk_forward_optimization(ticker, interval="1d", period="2y", train_size=120, test_size=40): | |
| """ | |
| 运行滚动优化回测 (Walk-forward rolling optimization) | |
| - 使用 train_size 根 K线作为训练集寻找最佳参数组合 | |
| - 在随后的 test_size 根 K线中作为样本外测试执行该参数组合 | |
| - 滚动前进,直到覆盖所有行情数据 | |
| """ | |
| print("=" * 80) | |
| print(f" QUANT.AI - WALK-FORWARD OPTIMIZER (滚动参数优化引擎)") | |
| print(f" 标的代码: {ticker} | 周期: {interval} | 区间: {period} | 训练集大小: {train_size} | 测试集大小: {test_size}") | |
| print("=" * 80) | |
| # 1. 抓取与清洗指标数据 | |
| try: | |
| df_raw = fetch_and_prepare_data(ticker, period=period, interval=interval) | |
| df = analyze_patterns(df_raw) | |
| except Exception as e: | |
| raise ValueError(f"数据加载或指标计算失败: {str(e)}") | |
| total_len = len(df) | |
| if total_len < (train_size + test_size): | |
| raise ValueError(f"错误:历史数据共 {total_len} 根 Bar,不足以支持当前 Train({train_size}) + Test({test_size}) 的滚动窗口分配!") | |
| print(f"行情数据准备就绪:共包含 {total_len} 根 K 线。") | |
| print("正在生成参数搜索网格...") | |
| # 2. 定义参数网格 | |
| param_grid = [] | |
| for mode in ["dynamic", "consensus"]: | |
| for atr_mult in [1.5, 2.0, 2.5]: | |
| for rsi_th in [60.0, 65.0, 70.0]: | |
| param_grid.append({ | |
| "strategy_mode": mode, | |
| "trailing_stop_atr_mult": atr_mult, | |
| "rsi_threshold_buy": rsi_th, | |
| "stop_loss_pct": 0.015, | |
| "profit_target_pct": 0.030 | |
| }) | |
| risk_params = { | |
| "slippage_rate": 0.0003, | |
| "commission_per_share": 0.005, | |
| "min_commission_per_order": 1.0, | |
| "position_sizing_mode": "atr", | |
| "risk_per_trade_pct": 0.01, | |
| "max_position_size_pct": 0.50 | |
| } | |
| # 3. 滚动窗口优化循环 | |
| start_idx = 0 | |
| oos_results = [] | |
| is_intraday = interval in ["1m", "5m", "15m", "30m", "1h"] | |
| print("\n[开始滚动优化]") | |
| window_count = 1 | |
| while start_idx + train_size + test_size <= total_len: | |
| train_df = df.iloc[start_idx : start_idx + train_size] | |
| test_df = df.iloc[start_idx + train_size : start_idx + train_size + test_size] | |
| train_start_date = train_df.index[0].strftime("%Y-%m-%d") | |
| train_end_date = train_df.index[-1].strftime("%Y-%m-%d") | |
| test_start_date = test_df.index[0].strftime("%Y-%m-%d") | |
| test_end_date = test_df.index[-1].strftime("%Y-%m-%d") | |
| # 寻找训练集上的最佳参数 | |
| best_score = -999999.0 | |
| best_params = None | |
| for params in param_grid: | |
| res = run_backtest_sim(train_df, ticker, params, risk_params, is_intraday=is_intraday) | |
| # 目标函数:净亏损惩罚下的净利润收益 (Sharpe-like Objective) | |
| score = res["net_pnl"] - (res["max_drawdown"] * 30000.0 * 2.0) | |
| if score > best_score: | |
| best_score = score | |
| best_params = params | |
| # 使用最佳参数在测试集上回测(样本外测试) | |
| test_res = run_backtest_sim(test_df, ticker, best_params, risk_params, is_intraday=is_intraday) | |
| print(f" 窗口 #{window_count} | 训练集: {train_start_date} ~ {train_end_date} | 测试集: {test_start_date} ~ {test_end_date}") | |
| print(f" -> 最佳参数: Mode={best_params['strategy_mode']}, ATR_Mult={best_params['trailing_stop_atr_mult']}, RSI={best_params['rsi_threshold_buy']}") | |
| print(f" -> 样本外表现: 盈亏 ${test_res['net_pnl']:+,.2f} | 最大回撤 {test_res['max_drawdown']*100:.2f}% | 交易对数 {test_res['round_trips']} | 胜率 {test_res['win_rate']}%") | |
| oos_results.append({ | |
| "window": window_count, | |
| "train_period": f"{train_start_date} ~ {train_end_date}", | |
| "test_period": f"{test_start_date} ~ {test_end_date}", | |
| "best_params": best_params, | |
| "net_pnl": test_res["net_pnl"], | |
| "max_drawdown": test_res["max_drawdown"], | |
| "round_trips": test_res["round_trips"], | |
| "win_rate": test_res["win_rate"], | |
| "commission": test_res["commission"] | |
| }) | |
| # 前进一个测试集步长 | |
| start_idx += test_size | |
| window_count += 1 | |
| # 4. 对照组:在整个数据集上运行静态默认参数 | |
| default_params = { | |
| "strategy_mode": "dynamic", | |
| "trailing_stop_atr_mult": 2.0, | |
| "rsi_threshold_buy": 65.0, | |
| "stop_loss_pct": 0.015, | |
| "profit_target_pct": 0.030 | |
| } | |
| static_res = run_backtest_sim(df, ticker, default_params, risk_params, is_intraday=is_intraday) | |
| # 5. 汇总 Walk-forward 表现 | |
| total_wf_pnl = sum(r["net_pnl"] for r in oos_results) | |
| total_wf_commission = sum(r["commission"] for r in oos_results) | |
| avg_wf_drawdown = float(np.mean([r["max_drawdown"] for r in oos_results])) if oos_results else 0.0 | |
| total_wf_trades = sum(r["round_trips"] for r in oos_results) | |
| print("\n" + "=" * 80) | |
| print(" WALK-FORWARD PERFORMANCE VS STATIC CONTROL") | |
| print("=" * 80) | |
| print(f" 【滚动参数优化 (Walk-Forward) 样本外表现】:") | |
| print(f" - 累计样本外净利润: $ {total_wf_pnl:+,.2f}") | |
| print(f" - 总交易笔数(对): {total_wf_trades} 笔") | |
| print(f" - 佣金支出总计: $ {total_wf_commission:,.2f}") | |
| print(f" - 平均滚动最大回撤: {avg_wf_drawdown*100:.2f}%") | |
| print("-" * 80) | |
| print(f" 【静态默认参数 (Static Default) 全样本对照组表现】:") | |
| print(f" - 累计净利润: $ {static_res['net_pnl']:+,.2f} ({static_res['pnl_pct']:+.2f}%)") | |
| print(f" - 总交易笔数(对): {static_res['round_trips']} 笔") | |
| print(f" - 佣金支出总计: $ {static_res['commission']:,.2f}") | |
| print(f" - 全历史最大回撤: {static_res['max_drawdown']*100:.2f}%") | |
| print("=" * 80) | |
| return { | |
| "ticker": ticker, | |
| "interval": interval, | |
| "period": period or "1y", | |
| "train_size": train_size, | |
| "test_size": test_size, | |
| "oos_results": oos_results, | |
| "static_control": { | |
| "net_pnl": static_res["net_pnl"], | |
| "pnl_pct": static_res["pnl_pct"], | |
| "round_trips": static_res["round_trips"], | |
| "commission": static_res["commission"], | |
| "max_drawdown": static_res["max_drawdown"] | |
| }, | |
| "summary": { | |
| "total_wf_pnl": total_wf_pnl, | |
| "total_wf_commission": total_wf_commission, | |
| "avg_wf_drawdown": avg_wf_drawdown, | |
| "total_wf_trades": total_wf_trades | |
| } | |
| } | |
| if __name__ == "__main__": | |
| parser = argparse.ArgumentParser(description="Walk-Forward Rolling Parameter Optimizer") | |
| parser.add_argument("--ticker", type=str, default="TSLA", help="测试股票代码 (默认: TSLA)") | |
| parser.add_argument("--interval", type=str, default="1d", help="K线周期 (默认: 1d)") | |
| parser.add_argument("--period", type=str, default="1y", help="总历史时间段 (默认: 1y)") | |
| parser.add_argument("--train", type=int, default=120, help="训练集 K线根数 (默认: 120)") | |
| parser.add_argument("--test", type=int, default=40, help="测试集 K线根数 (默认: 40)") | |
| args = parser.parse_args() | |
| try: | |
| walk_forward_optimization(args.ticker, args.interval, args.period, args.train, args.test) | |
| except Exception as e: | |
| print(e) | |
| sys.exit(1) | |