"""Step 51:把 §3.31 的两个分层结论做成仓位因子,看组合层面到底有没有变好。 已知(都用开仓时可知的信息): step49 `有前序`(过去 5 分钟别的币先发过)→ 滑点余量 +6bp,6/6 时段同向 step50 信号根低量(vr60 小)→ 余量 +14bp,ATR 4/4 + 时段 7/7 同向 但「分层看着好」到「加权之后组合更好」不是一回事,中间有三个坎: 1. 两个结论是分开切出来的,相乘等于假设正交且效应可乘。没验过。 2. 加权必然抬高方差。均R 涨了但 R夏普 可能不涨,那就白做。 3. `有前序` 按定义就是「别的币已有仓位时」,给它加权 = 在敞口最集中的 时刻再加杠杆。§3.31 已经把峰值并发列为扩币的前置约束,这里直接顶上去。 所以本步的判据不是均R,是这四项一起看:**均R / R夏普 / 最大回撤 / 峰值加权并发**。 阈值刻意取整数(vr60 的 1、2、4),权重取整数比,**一个参数都不搜**。 观测到的四分位边界在不同时段并不一致(样本外 0.63/1.26/2.28/5.34、 发现期 0.62/1.48/3.11/7.97),用样本分位数等于在同一批数据上既发现又调参。 主检验是滑点敏感性:这两个因子的优势主要在**余量**而非毛R, 所以真正该看的是「假定执行成本越高,加权方案是否衰减得越慢」。 若只在 slip=0 处好、随成本上升优势消失,那这两个因子就不值得做进仓位。 """ from __future__ import annotations import argparse import sys import warnings from pathlib import Path import numpy as np import pandas as pd warnings.filterwarnings("ignore") HERE = Path(__file__).resolve().parent sys.path.insert(0, str(HERE)) sys.path.insert(0, str(HERE.parent)) pd.set_option("display.width", 340) SL = 2.0 GATE_BP = 8.0 IS_START = pd.Timestamp("2026-01-30", tz="Asia/Shanghai") SLIPS = (0.0, 5.0, 10.0, 15.0, 20.0) # bp / taker 腿 def w_vol(vr: np.ndarray) -> np.ndarray: """量比越低给越多。整数阈值 1/2/4,权重 6:5:3:2(均值约 1)。""" w = np.full(len(vr), 1.0) w[vr < 1.0] = 1.5 w[(vr >= 1.0) & (vr < 2.0)] = 1.25 w[(vr >= 2.0) & (vr < 4.0)] = 0.75 w[vr >= 4.0] = 0.5 w[~np.isfinite(vr)] = 1.0 # 前 60 根不足时不表态 return w def w_prev(prev: np.ndarray) -> np.ndarray: return np.where(prev >= 1, 1.4, 1.0) def w_cut(vr: np.ndarray) -> np.ndarray: """硬砍最高量那一档。用来看「不做」和「少做」差多少。""" w = np.ones(len(vr)) w[np.isfinite(vr) & (vr >= 4.0)] = 0.0 return w def peak_concurrent(d: pd.DataFrame, w: np.ndarray) -> float: """扫描线求峰值同时在场的加权敞口(单位:等权仓位数)。""" t0 = d.date.values.astype("datetime64[m]").astype(np.int64) t1 = t0 + d.hold.values ev = np.concatenate([np.stack([t0, w], 1), np.stack([t1, -w], 1)]) ev = ev[np.lexsort((ev[:, 1], ev[:, 0]))] return float(np.max(np.cumsum(ev[:, 1]))) def evaluate(d: pd.DataFrame, w: np.ndarray, slip_bp: float) -> dict: """slip_bp 只加在 taker 腿上——止盈是限价,不吃滑点。""" from lib.exit_model import fee_of, taker_notional tn = taker_notional(d.r.values, d.c.values) net = d.g.values - fee_of(d.r.values, d.c.values) - slip_bp * 1e-4 * tn R = net / (SL * d.atr_pct.values) m = w > 0 if m.sum() < 50: return {} ww = w[m] / w[m].mean() # 归一化:各方案投出去的平均资金相同 wR = ww * R[m] eq = np.cumsum(wR) dd = float(np.max(np.maximum.accumulate(eq) - eq)) return { "笔数": int(m.sum()), "均R": round(float(wR.mean()), 3), "R夏普": round(float(wR.mean() / wR.std()), 3), "总R": round(float(eq[-1]), 1), "最大回撤R": round(dd, 1), "总R/回撤": round(eq[-1] / dd, 2) if dd > 0 else np.inf, "余量bp": round(float((ww * net[m]).sum() / (ww * tn[m]).sum() * 1e4), 2), "峰值并发": round(peak_concurrent(d[m], ww), 2), } def run(d: pd.DataFrame, label: str) -> pd.DataFrame: vr, pv = d.vr60.values, d.prev.values schemes = { "等权(现状)": np.ones(len(d)), "仅量因子": w_vol(vr), "仅前序因子": w_prev(pv), "两因子相乘": w_vol(vr) * w_prev(pv), "硬砍高量档": w_cut(vr), } rows = [] for slip in SLIPS: for name, w in schemes.items(): r = evaluate(d, w, slip) if r: rows.append({"时段": label, "滑点bp": slip, "方案": name, **r}) return pd.DataFrame(rows) def main() -> None: ap = argparse.ArgumentParser() ap.add_argument("--src", default=str(HERE / "out" / "step50_volume.feather")) args = ap.parse_args() from step50_volume import prep d = pd.read_feather(args.src) d["date"] = pd.to_datetime(d["date"]) d = prep(d[(d.htf == 1.0) & d.lad & (d.atr_bp >= GATE_BP)].copy()) oos, ins = d[d.date < IS_START], d[d.date >= IS_START] print(f"实盘口径 {len(d)} 笔 | 样本外 {len(oos)} 发现期 {len(ins)}") for lab, part in (("样本外", oos), ("发现期", ins), ("全样本", d)): t = run(part, lab) print("\n" + "=" * 118) print(f"########## {lab} ##########") for slip in SLIPS: g = t[t.滑点bp == slip].drop(columns=["时段", "滑点bp"]) print(f"\n--- 假定滑点 {slip:.0f}bp / taker 腿 ---") print(g.to_string(index=False)) print("\n" + "=" * 118) print("########## 判据:优势是否随执行成本上升而扩大 ##########") t = run(d, "全样本") base = t[t.方案 == "等权(现状)"].set_index("滑点bp") rows = [] for name in t.方案.unique(): if name == "等权(现状)": continue g = t[t.方案 == name].set_index("滑点bp") rows.append({"方案": name, **{ f"{int(s)}bp处均R差": round(g.loc[s, "均R"] - base.loc[s, "均R"], 3) for s in SLIPS}}) print(pd.DataFrame(rows).to_string(index=False)) if __name__ == "__main__": main()