This commit is contained in:
2026-09-19 19:45:43 +08:00
parent 7183cb45f8
commit 8131b158b4
60 changed files with 7669 additions and 909 deletions

View File

@@ -0,0 +1,53 @@
"""加标的 vs 加仓位:同一引擎、按比例缩放资金,隔离"标的数量"的影响。
注意:把 universe 缩成 N 只、资金缩到 N/3 只能近似"同时持有更多标的"
但它同时缩短了白名单,所以结论按"资金可部署机会数"来读,而不是精确预测。
用法: py -3.14 -B analysis/etf/universe.py
"""
import sys
from pathlib import Path
HERE = Path(__file__).resolve().parent
sys.path.insert(0, str(HERE))
from backtest import ( # noqa: E402
MIN_CASH_RATIO, START_CASH, SYMBOLS, SYMBOL_PARAMS, EtfDefaults, EtfSymbolConfig,
analyze, fetch_daily, precondition, simulate,
)
BASE = EtfDefaults()
FULL = {code: fetch_daily(code) for code in SYMBOLS}
# (白名单, 起始资金, 说明)
CASES = []
for size in (1, 2, 3):
codes = SYMBOLS[:size]
CASES.append((f"白名单 {size} 只({'+'.join(c[:6] for c in codes)}),资金按 {size}/3 缩放",
codes, START_CASH * size / 3))
# 同样 20 万资金,但只放 1 只 / 2 只 → 观察"钱多而标的少"是否浪费
CASES.append(("白名单 1 只,但仍是 20 万资金", SYMBOLS[:1], START_CASH))
CASES.append(("白名单 2 只,但仍是 20 万资金", SYMBOLS[:2], START_CASH))
CASES.append(("白名单 3 只20 万(基准)", SYMBOLS[:3], START_CASH))
# 每档放大到 5000 股,资金同步放大到 100 万 → 检验"标的不变、仓位变大"的天花板
CASES.append(("3 只 + buy_shares=5000资金 100 万", SYMBOLS[:3], 1_000_000.0))
CASES.append(("3 只 + buy_shares=10000资金 200 万", SYMBOLS[:3], 2_000_000.0))
print(f"{'场景':52} {'资金':>10} {'权益变动':>10} {'收益率':>7} {'回撤':>6} "
f"{'平均占用':>8} {'峰值占用':>8} {'ROI/占用':>8} {'底仓':>4} {'补仓':>4}")
for label, codes, cash in CASES:
data = {c: FULL[c] for c in codes}
shares = 5000 if "5000" in label else 10000 if "10000" in label else SYMBOL_PARAMS[codes[0]]["buy_shares"]
params = {c: {**SYMBOL_PARAMS[c], "buy_shares": shares, "max_shares": shares * 10} for c in codes}
pre = {c: precondition(data[c], EtfSymbolConfig(**params[c]), BASE) for c in codes}
result = simulate(data, symbol_params=params, precomputed=pre, start_cash=cash)
stats = analyze(result)
delta = stats["final_equity"] - cash
util = stats["avg_deployed"] / cash * 100
max_util = stats["max_deployed"] / cash * 100
roi = delta / stats["avg_deployed"] * 100 if stats["avg_deployed"] else 0.0
print(f"{label:52} {cash:10.0f} {delta:10.2f} {stats['return_pct']:6.2f}% "
f"{stats['max_dd_pct']:5.2f}% {util:7.2f}% {max_util:7.2f}% {roi:7.2f}% "
f"{sum(1 for f in result['fills'] if f.kind == 'base'):4} "
f"{sum(1 for f in result['fills'] if f.kind == 'add'):4}")