"""新白名单(16 只板块 ETF)的快速回测冒烟:只打印,不覆盖既有 results.json。 用法: py -3.14 -B labs/analysis/etf/smoke_sectors.py [起始资金] """ import sys from pathlib import Path HERE = Path(__file__).resolve().parent sys.path.insert(0, str(HERE)) try: sys.stdout.reconfigure(encoding="utf-8", errors="replace") except Exception: pass from backtest import REPO_DEFAULTS, START_CASH, SYMBOLS, SYMBOL_PARAMS, analyze, fetch_daily, simulate # noqa: E402 CASH = float(sys.argv[1]) if len(sys.argv) > 1 else 600_000.0 data = {code: fetch_daily(code) for code in SYMBOLS} print(f"标的 {len(SYMBOLS)} 只,账户 {CASH:,.0f} 元,参数取 py-client/etc/_etf.yaml") print(f"{'成交模型':10} {'权益变动':>11} {'收益率':>8} {'最大回撤':>9} {'平均占用':>9} " f"{'峰值占用':>9} {'底仓':>5} {'补仓':>5} {'主出口':>6} {'佣金':>8}") for mode in ("touch", "bounce", "close"): result = simulate(data, start_cash=CASH, fill_mode=mode) stats = analyze(result) fills = result["fills"] print(f"{mode:10} {stats['final_equity'] - CASH:11,.2f} {stats['return_pct']:7.2f}% " f"{stats['max_dd_pct']:8.2f}% {stats['avg_deployed'] / CASH * 100:8.2f}% " f"{stats['max_deployed'] / CASH * 100:8.2f}% " f"{sum(1 for f in fills if f.kind == 'base'):5} " f"{sum(1 for f in fills if f.kind == 'add'):5} " f"{sum(1 for f in fills if f.kind == 'exit'):6} {stats['fees']:8.2f}") # 逐标的参与度:新白名单里谁真的被交易到 print() print("逐标的成交(touch 模型)") result = simulate(data, start_cash=CASH, fill_mode="touch") for code in SYMBOLS: rows = [f for f in result["fills"] if f.code == code] buys = sum(f.price * f.volume for f in rows if f.side == "BUY") print(f" {code:11} 成交 {len(rows):3} 笔 买入名义 {buys:10,.0f} " f"底仓 {sum(1 for f in rows if f.kind == 'base')} 补仓 {sum(1 for f in rows if f.kind == 'add')}")