optz
This commit is contained in:
120
labs/analysis/etf/drawdown.py
Normal file
120
labs/analysis/etf/drawdown.py
Normal file
@@ -0,0 +1,120 @@
|
||||
"""未了结仓位的浮动亏损轨迹 + "时间止损"反事实对照(只做分析,不改策略代码)。
|
||||
|
||||
用法: py -3.14 -B analysis/etf/drawdown.py
|
||||
"""
|
||||
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
HERE = Path(__file__).resolve().parent
|
||||
sys.path.insert(0, str(HERE))
|
||||
|
||||
# Windows 控制台默认 GBK,报告里用了「−」等字符,统一切到 UTF-8 输出。
|
||||
try:
|
||||
sys.stdout.reconfigure(encoding="utf-8", errors="replace")
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
from analysis import round_trips # noqa: E402
|
||||
from backtest import SYMBOLS, analyze, fetch_daily, simulate # noqa: E402
|
||||
|
||||
data = {code: fetch_daily(code) for code in SYMBOLS}
|
||||
result = simulate(data)
|
||||
stats = analyze(result)
|
||||
by_date = {code: {bar["date"]: bar for bar in data[code]} for code in data}
|
||||
|
||||
# 逐日还原:每个标的的"本轮累计净买入成本"与"当前持仓量"
|
||||
cost = {code: 0.0 for code in data}
|
||||
volume = {code: 0 for code in data}
|
||||
fills_by_day = {}
|
||||
for fill in result["fills"]:
|
||||
fills_by_day.setdefault(fill.day.strftime("%Y%m%d"), []).append(fill)
|
||||
track = {code: [] for code in data}
|
||||
for day, equity, market_value, cash in result["curve"]:
|
||||
stamp = day.strftime("%Y%m%d")
|
||||
for fill in fills_by_day.get(stamp, []):
|
||||
amount = fill.price * fill.volume
|
||||
if fill.side == "BUY":
|
||||
cost[fill.code] += amount
|
||||
volume[fill.code] += fill.volume
|
||||
else:
|
||||
# 卖出按比例冲减成本基数(与均价法一致)
|
||||
if volume[fill.code] > 0:
|
||||
ratio = fill.volume / volume[fill.code]
|
||||
cost[fill.code] *= max(0.0, 1 - ratio)
|
||||
volume[fill.code] -= fill.volume
|
||||
for code in data:
|
||||
bar = by_date[code].get(stamp)
|
||||
if bar is None:
|
||||
continue
|
||||
track[code].append((day, cost[code], bar["close"], volume[code]))
|
||||
|
||||
print("== 持仓期间的浮动盈亏(均价法:市值 − 本轮累计净买入成本)==")
|
||||
print(f"{'标的':11} {'持仓天数':>8} {'最长连续浮亏天数':>16} {'最深浮亏':>10} {'最深浮亏%':>10} {'期末浮亏':>10}")
|
||||
for code in data:
|
||||
rows = [(day, c, close, vol) for day, c, close, vol in track[code] if vol > 0 and c > 0]
|
||||
if not rows:
|
||||
print(f"{code:11} {0:8} {'-':>16} {'-':>10} {'-':>10} {'-':>10}")
|
||||
continue
|
||||
pnl = [(day, close * vol - c, (close * vol - c) / c * 100) for day, c, close, vol in rows]
|
||||
longest = cur = 0
|
||||
for _, amount, _ in pnl:
|
||||
cur = cur + 1 if amount < 0 else 0
|
||||
longest = max(longest, cur)
|
||||
worst_amt = min(p[1] for p in pnl)
|
||||
worst_pct = min(p[2] for p in pnl)
|
||||
print(f"{code:11} {len(rows):8} {longest:16} {worst_amt:10.0f} {worst_pct:9.2f}% {pnl[-1][1]:10.0f}")
|
||||
|
||||
print()
|
||||
print("== 持有天数分布(完整轮次)==")
|
||||
trips = round_trips(result["fills"])
|
||||
buckets = {"≤1天": 0, "2-3天": 0, "4-7天": 0, "8-15天": 0, ">15天": 0}
|
||||
for t in trips:
|
||||
d = t.days
|
||||
key = "≤1天" if d <= 1 else "2-3天" if d <= 3 else "4-7天" if d <= 7 else "8-15天" if d <= 15 else ">15天"
|
||||
buckets[key] += 1
|
||||
print(" ", buckets)
|
||||
|
||||
print()
|
||||
print("== 时间止损反事实:把浮亏且持有超过 N 天的仓位按当日收盘价平掉 ==")
|
||||
print(f"{'规则':22} {'权益变动':>10} {'收益率':>7} {'最大回撤':>8} {'占用ROI':>8} {'佣金':>8} {'强平次数':>8}")
|
||||
|
||||
|
||||
def with_time_stop(limit: int) -> tuple[float, int]:
|
||||
"""在没有时间止损的结果上,找出"浮亏且连续持有 > limit 天"的仓位并按其后的实际
|
||||
主出口价格之差估算影响(近似:直接扣掉该仓位到期末的浮亏差额)。"""
|
||||
stops = 0
|
||||
delta = 0.0
|
||||
for code in data:
|
||||
rows = [(day, c, close, vol) for day, c, close, vol in track[code] if vol > 0 and c > 0]
|
||||
if not rows:
|
||||
continue
|
||||
start = rows[0][0]
|
||||
for index, (day, c, close, vol) in enumerate(rows):
|
||||
held = (day - start).days
|
||||
if held > limit and close * vol - c < 0:
|
||||
# 反事实:在当天以收盘价平掉,之后不再持有该标的
|
||||
loss = close * vol - c
|
||||
final_close = rows[-1][2]
|
||||
avoid = (final_close - close) * vol
|
||||
delta += loss * 0 - avoid # 平仓后避免了后续价格变动
|
||||
stops += 1
|
||||
break
|
||||
return delta, stops
|
||||
|
||||
|
||||
for limit in (0, 5, 10, 20):
|
||||
if limit == 0:
|
||||
print(f"{'不止损(现配置)':22} {stats['final_equity'] - 500000:10.2f} {stats['return_pct']:6.2f}% "
|
||||
f"{stats['max_dd_pct']:7.2f}% "
|
||||
f"{(stats['final_equity'] - 500000) / stats['avg_deployed'] * 100:7.2f}% "
|
||||
f"{stats['fees']:8.2f} {0:8}")
|
||||
continue
|
||||
delta, stops = with_time_stop(limit)
|
||||
equity = stats["final_equity"] - 500000 + delta
|
||||
print(f"{'持有>' + str(limit) + '天且浮亏即平':22} {equity:10.2f} {equity / 500000 * 100:6.2f}% "
|
||||
f"{stats['max_dd_pct']:7.2f}% {equity / stats['avg_deployed'] * 100:7.2f}% "
|
||||
f"{stats['fees']:8.2f} {stops:8}")
|
||||
print()
|
||||
print("注:上表是「平仓后不再持有该标的」的粗略反事实,只用于判断时间止损的方向性收益,")
|
||||
print(" 不是精确回测(未重算后续轮次与资金复用)。")
|
||||
Reference in New Issue
Block a user