#!/usr/bin/env python3 """ Top 3 最挣钱算法回测 v5(使用5分钟K线实时价格版) 对比三组数据: 1. 原始版(日线close/open) - 作为基准 2. 5分钟实时价格版 - 使用stock_kline_5min的10:00买入价、15:00卖出价 3. Mid价格版 - 使用(open+close)/2作为替代 Top 3 算法: 🏆 v4|触发≥2+止盈10+损8 → +53,100 (33.1%, 年化28.3%) 🥈 v3|止盈10+损8 → +49,500 (28.4%, 年化24.4%) 🥉 v4.2-K1|延迟2天+止盈10+损8 → +48,920 (28.1%, 年化24.1%) """ import sys import os import time from datetime import date, datetime sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) from backtest_recommend import ( get_db_conn, get_codes_with_data, run_backtest, START_DATE, MAX_POSITION_AMOUNT, MAX_CONCURRENT_POSITIONS, PRICE_MIN, PRICE_MAX, SELL_COOLDOWN_DAYS, MAX_BUYS_PER_DAY, ) def main(): import argparse parser = argparse.ArgumentParser(description='Top 3 算法回测') parser.add_argument('--start', type=str, default=None, help='回测起始日 YYYY-MM-DD(默认取扫描历史最早日期)') parser.add_argument('--end', type=str, default=None, help='回测结束日 YYYY-MM-DD(默认今天)') args = parser.parse_args() conn = get_db_conn() end = date.today() if args.end: end = datetime.strptime(args.end, '%Y-%m-%d').date() # 使用完整扫描数据期间(而非 START_DATE 的短期) with conn.cursor() as cur: cur.execute("SELECT min(scan_date) FROM stock_scan_history") first_scan = cur.fetchone()[0] if args.start: start = datetime.strptime(args.start, '%Y-%m-%d').date() print(f"📅 回测起始: {start}(用户指定)") else: start = first_scan if first_scan else START_DATE print(f"📅 回测起始: {start}(扫描历史最早日期)") # 检查5分钟K线数据覆盖 with conn.cursor() as cur: cur.execute(""" SELECT count(*), count(DISTINCT code), count(DISTINCT dt::date), min(dt::date), max(dt::date) FROM stock_kline_5min """) cnt, codes, days, min_d, max_d = cur.fetchone() print(f"📊 stock_kline_5min 数据: {cnt:,}条 | {codes}只股票 | {days}天 | {min_d}~{max_d}") cur.execute("SELECT count(DISTINCT scan_date) FROM stock_scan_history WHERE scan_date >= %s", (start,)) scan_days = cur.fetchone()[0] print(f"📊 stock_scan_history: {scan_days}天扫描数据") cur.execute("SELECT count(DISTINCT trade_date) FROM stock_kline_daily WHERE trade_date >= %s", (start,)) kline_days = cur.fetchone()[0] print(f"📊 stock_kline_daily: {kline_days}天K线数据") # Top 3 算法配置 top3_algos = [ ("🏆 v4|触发≥2+止盈10+损8", { "min_buy_triggered": 2, "take_profit_pct": 10, "stop_loss_pct": 8, }), ("🥈 v3|止盈10+损8", { "take_profit_pct": 10, "stop_loss_pct": 8, }), ("🥉 v4.2-K1|延迟2天+止盈10+损8", { "sell_confirm_days": 2, "take_profit_pct": 10, "stop_loss_pct": 8, }), ] # 3 种定价模式 price_modes = [ ("日线(原版)", False, '10:00', '15:00'), # 用daily open/close ("5分钟最优时点", True, '09:35', '13:40'), # v7: 最优时点 ("5分钟旧时点", True, '10:00', '15:00'), # v5: 旧默认时点 (对比用) ] print("\n" + "=" * 140) print(" Top 3 最挣钱算法 × 3种定价模式 对比回测 v7") print("=" * 140) print(f" 回测区间: {start} ~ {end}") print(f" 定价说明:") print(f" 日线(原版): 买入用开盘价, 卖出用收盘价") print(f" 5分钟最优时点: 买入用09:35实时价, 卖出用13:40实时价 (v7网格搜索最优)") print(f" 5分钟旧时点: 买入用10:00实时价, 卖出用15:00实时价 (v5旧默认)") print(f" 股价区间: {PRICE_MIN}~{PRICE_MAX} 元 | 每笔1000股 | 每日最多买{MAX_BUYS_PER_DAY}只") print("=" * 140) print() rows = [] total_scenarios = len(top3_algos) * len(price_modes) idx = 0 for algo_name, algo_params in top3_algos: for mode_name, use_5min, bt, st in price_modes: idx += 1 scenario_name = f"{algo_name} | {mode_name}" print(f"[{idx}/{total_scenarios}] {scenario_name}", flush=True) t0 = time.time() result = run_backtest( conn, start_date=start, end_date=end, use_5min_prices=use_5min, buy_time=bt, sell_time=st, verbose=False, **algo_params ) elapsed = time.time() - t0 if not result or not result.get('stats'): print(f" ❌ 无结果") rows.append((scenario_name, algo_name, mode_name, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)) continue s = result['stats'] row = ( scenario_name, algo_name, mode_name, s['profit'], # 3: 盈亏 s.get('capital_pct', 0), # 4: 真实收益率 s.get('capital_ann_pct', 0), # 5: 真实年化 s['win_rate'], # 6: 胜率 s.get('max_drawdown_pct', 0), # 7: 回撤% s['avg_hold_days'], # 8: 持仓天 s['profit_factor'], # 9: 盈亏比 s['trade_count'], # 10: 交易数 s.get('max_capital', 0), # 11: 最大占用 s.get('5min_hit', 0), # 12: 5min命中 s.get('5min_miss', 0), # 13: 5min缺失 s.get('5min_coverage', 0), # 14: 5min覆盖率 ) rows.append(row) mc = s.get('max_capital', 0) cp = s.get('capital_pct', 0) ca = s.get('capital_ann_pct', 0) cov = s.get('5min_coverage', 0) print(f" → ¥{s['profit']:>+10,.0f} 占用¥{mc:>8,.0f} " f"真实{cp:>+6.1f}% 年化{ca:>+6.1f}% " f"胜率{s['win_rate']:>5.1f}% " f"{'5min覆盖' + str(cov) + '%' if use_5min else '日线'} " f"({elapsed:.1f}s)", flush=True) conn.close() # ═══ 输出对比表 ═══ print("\n\n" + "=" * 160) print(" 📊 Top 3 算法 × 定价模式 完整对比表") print("=" * 160) header = (f"{'场景':<55} {'盈亏(元)':>10} {'占用资金':>10} {'真实收益':>8} {'真实年化':>8} " f"{'胜率':>6} {'回撤%':>7} {'持仓':>5} {'盈亏比':>6} {'交易':>5} {'5min':>6}") print(header) print("-" * 160) prev_algo = None for row in rows: name = row[0] algo = row[1] mode = row[2] profit, cp, ca = row[3], row[4], row[5] wr, dd = row[6], row[7] hold, pf, n, mc = row[8], row[9], row[10], row[11] cov = row[14] if prev_algo and prev_algo != algo: print("-" * 160) prev_algo = algo cov_str = f"{cov:.0f}%" if cov > 0 else "日线" print(f"{name:<55} {profit:>+10,.0f} {'¥'+str(int(mc)):>10} {cp:>+7.1f}% {ca:>+7.1f}% " f"{wr:>5.1f}% {dd:>6.1f}% {hold:>4.0f}天 {pf:>6.2f} {n:>5} {cov_str:>6}") print("=" * 160) # ═══ 算法级汇总 ═══ print("\n 📊 按算法汇总:") for algo_name, _ in top3_algos: algo_rows = [r for r in rows if r[1] == algo_name] if len(algo_rows) >= 2: baseline = algo_rows[0] # 日线版 realtime = algo_rows[1] # 5分钟版 diff_profit = realtime[3] - baseline[3] diff_ann = realtime[5] - baseline[5] diff_wr = realtime[6] - baseline[6] print(f"\n {algo_name}:") print(f" 日线(原版) : 盈亏 ¥{baseline[3]:>+10,.0f} 年化 {baseline[5]:>+6.1f}% 胜率 {baseline[6]:.1f}%") print(f" 5分钟实时 : 盈亏 ¥{realtime[3]:>+10,.0f} 年化 {realtime[5]:>+6.1f}% 胜率 {realtime[6]:.1f}% " f"(5min覆盖{realtime[14]:.0f}%)") icon = '📈' if diff_profit > 0 else ('📉' if diff_profit < 0 else '➖') print(f" {icon} 差异: 盈亏{diff_profit:>+,.0f} 年化{diff_ann:>+.1f}% 胜率{diff_wr:>+.1f}%") # ═══ 写入文件 ═══ out_dir = os.path.join(os.path.dirname(__file__), 'docs') os.makedirs(out_dir, exist_ok=True) out_path = os.path.join(out_dir, 'backtest_top3_5min.md') with open(out_path, 'w', encoding='utf-8') as f: f.write("# Top 3 算法 × 5分钟实时价格 回测对比\n\n") f.write(f"回测区间: {start} ~ {end}\n\n") f.write("## 定价模式\n\n") f.write("| 模式 | 买入价 | 卖出价 | 说明 |\n") f.write("|------|--------|--------|------|\n") f.write("| 日线(原版) | 当日开盘价 | 当日收盘价 | 原始基准 |\n") f.write("| 5分钟实时 | 10:00 5min收盘 | 15:00 5min收盘 | 有5min数据用5min, 无则用mid=(开盘+收盘)/2 |\n\n") f.write("## 对比结果\n\n") f.write("| 算法 | 定价 | 盈亏(元) | 占用资金 | 真实收益 | 真实年化 | 胜率 | 回撤% | 盈亏比 | 交易 | 5min覆盖 |\n") f.write("|------|------|---------|---------|---------|---------|------|-------|--------|------|----------|\n") for row in rows: name, algo, mode = row[0], row[1], row[2] profit, cp, ca = row[3], row[4], row[5] wr, dd = row[6], row[7] hold, pf, n, mc = row[8], row[9], row[10], row[11] cov = row[14] cov_str = f"{cov:.0f}%" if cov > 0 else "-" f.write(f"| {algo} | {mode} | {profit:+,.0f} | ¥{mc:,.0f} | {cp:+.1f}% | {ca:+.1f}% | " f"{wr:.1f}% | {dd:.1f}% | {pf:.2f} | {n} | {cov_str} |\n") f.write("\n") print(f"\n结果已写入 {out_path}") if __name__ == "__main__": main()