Files
stock/stock-html/run_backtest_top3.py
freedakgmail 9c7d7abdd4 Initial commit
2026-07-17 18:49:35 +08:00

239 lines
10 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/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()