This commit is contained in:
2026-09-21 12:38:13 +08:00
parent bcacbed427
commit ed6d4ee7a5
14 changed files with 2458 additions and 1073 deletions

View File

@@ -0,0 +1,91 @@
"""名单决策依据:
1) 逐标的对组合的净贡献(了结 + 未实现)
2) 规模/成交额(流动性)是否够网格用
3) 是否还有更贴题的品类 ETF如真正的"存储"
4) 剔除弱标的后的组合结果对照
用法: py -3.14 -B labs/analysis/etf/decide_list.py
"""
import sys
import urllib.request
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 SYMBOLS, SYMBOL_PARAMS, analyze, fetch_daily, simulate # noqa: E402
from screen_etf import code_of, fetch_all, size_yi, turnover_yi # noqa: E402
CASH = 600_000.0
data = {code: fetch_daily(code) for code in SYMBOLS}
# ---------- 1) 逐标的贡献 ----------
result = simulate(data, start_cash=CASH, fill_mode="touch")
print("== 逐标的净贡献(了结 + 未实现,按市价;账户 60 万)==")
print(f"{'代码':11} {'了结净额':>10} {'未了结股数':>9} {'未实现':>9} {'净额':>10} {'买入名义':>10}")
contrib = {}
for code in SYMBOLS:
book = result["books"][code]
fills = [f for f in result["fills"] if f.code == code]
closed = sum((f.price * f.volume if f.side == "SELL" else -f.price * f.volume) - f.fee
for f in fills)
cost = book.avg_cost * book.volume
close = data[code][-1]["close"]
unreal = close * book.volume - cost if book.volume else 0.0
contrib[code] = closed + unreal
print(f"{code:11} {closed:10,.0f} {book.volume:9} {unreal:9,.0f} {closed + unreal:10,.0f} "
f"{sum(f.price * f.volume for f in fills if f.side == 'BUY'):10,.0f}")
# ---------- 2) 规模/成交额 ----------
rows = fetch_all()
info = {code_of(r): r for r in rows}
print()
print("== 流动性与规模 ==")
print(f"{'代码':11} {'规模(亿)':>9} {'成交额(亿)':>10} {'名称':26} 评价")
for code in SYMBOLS:
row = info.get(code)
if row is None:
print(f"{code:11} {'?':>9} {'?':>10} 未在东财列表找到")
continue
size, turn = size_yi(row), turnover_yi(row)
if size < 10 or turn < 0.3:
verdict = "⚠️ 规模/成交偏小,网格成交与冲击成本风险高"
elif size < 30 or turn < 1.0:
verdict = "△ 中等,可接受但优先级靠后"
else:
verdict = "✓ 流动性充足"
print(f"{code:11} {size:9.2f} {turn:10.3f} {str(row.get('f14')):26} {verdict}")
# ---------- 3) 剔除弱标的后的对照 ----------
weak = [c for c in SYMBOLS
if c in info and (size_yi(info[c]) < 10 or turnover_yi(info[c]) < 0.3)]
if weak:
print()
print(f"== 剔除弱流动性标的 {weak} 后的组合 ==")
kept = {c: data[c] for c in SYMBOLS if c not in weak}
for label, universe in (("全部 16 只", data), (f"剔除 {len(weak)} 只 → {len(kept)}", kept)):
res = simulate(universe, start_cash=CASH, fill_mode="touch")
st = analyze(res)
print(f" {label:24} 权益变动 {st['final_equity'] - CASH:10,.2f} "
f"收益率 {st['return_pct']:6.2f}% 回撤 {st['max_dd_pct']:5.2f}% "
f"底仓 {sum(1 for f in res['fills'] if f.kind == 'base'):4} "
f"补仓 {sum(1 for f in res['fills'] if f.kind == 'add'):3} "
f"佣金 {st['fees']:8,.2f}")
else:
print()
print("== 没有规模/成交额低于阈值的标的 ==")
# ---------- 4) 更贴题的品类 ETF存储 ----------
print()
print("== 存储/内存相关 ETF 全量搜索 ==")
hits = [r for r in rows if any(k in str(r.get("f14") or "") for k in ("存储", "内存", "存储器"))]
hits.sort(key=lambda r: -size_yi(r))
for row in hits[:10]:
print(f" {code_of(row):11} {str(row.get('f14')):26} 规模={size_yi(row):8.2f}亿 "
f"成交额={turnover_yi(row):7.3f}亿")
print(f"{len(hits)} 只名称含存储/内存的 ETF")