Files
big-qmt/labs/analysis/etf/decide_list.py
2026-09-21 12:38:13 +08:00

92 lines
3.8 KiB
Python
Raw 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.
"""名单决策依据:
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")