修掉 gzip 追加会毁掉整个文件的数据丢失,并分开买卖两侧的成交分布曲线
两件事,都是「静默出错」那一类。 一、BookLog/TapeLog 追加到同一个 .gz,进程被 SIGKILL 时当前成员停在 deflate 块中间,下一轮追加的新成员接在垃圾字节之后。顺序解压在损坏点抛 invalid block type,该点之后全部读不出来——包括后续每轮写进去的。而读侧的异常处理 把这个当成「正常的尾部截断」静默跳过,于是只读出 21 行还不报错。 原 docstring 里写的「只丢最后一个缓冲块,不会毁掉整个文件」是错的,已证伪。 写侧改成每轮运行一个文件;读侧按 gzip 成员边界扫描、坏成员单独跳过并出声 报告,同时把同前缀的多轮文件一并读入。旧损坏文件因此多恢复出 31/21 条 (tape)与 132/95 条(books)。 二、tape_shape 只统计主动买、只自区间顶部累积,这条曲线只适用于多头止盈。 exit_fill 两侧共用它,等于把空头的可成交量按多头分布高估。实测二者不对称: 主动买在顶部 20% 内已占 40%,主动卖在底部 20% 内只有 18%。分成 SHAPE_F 与 SHAPE_F_SHORT,avail_at 按方向查各自曲线。 两条曲线只有 45 根成交流样本,所以补了 --sensitivity:把空头可成交量砍一半, 10 万仓位下 BTC/ETH 预算完全不动、SOL 动 0.07bp。结论不依赖这 45 根样本。 顺带撤掉 step43 docstring 里已作废的「成交率 30%/16%/1.5%」。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -45,20 +45,37 @@ from lib.exit_model import FEE_MAKER, FEE_TAKER, SLIP
|
||||
|
||||
# 成交流实测的区间内成交分布形状(BTC/ETH/SOL 均值,见 shadow_depth.tape_shape)
|
||||
SHAPE_K = np.linspace(0.0, 1.0, 21)
|
||||
# 多头止盈挂卖出,靠**主动买**打上来 —— 自区间顶部向下累积
|
||||
SHAPE_F = np.array([0.044, 0.088, 0.110, 0.179, 0.204, 0.240, 0.282, 0.316,
|
||||
0.390, 0.430, 0.465, 0.537, 0.583, 0.617, 0.662, 0.714,
|
||||
0.761, 0.804, 0.857, 0.904, 1.000])
|
||||
# 空头止盈挂买回,靠**主动卖**打下来 —— 自区间底部向上累积。
|
||||
# 两侧并不对称:主动买集中在区间顶部(k=0.2 处已 40%),主动卖在底部只有
|
||||
# 18%,BTC/ETH 平均偏差 0.21。原先两侧共用 SHAPE_F,等于把空头的可成交量
|
||||
# 按多头的分布高估。
|
||||
# ⚠ 这条曲线只有 45 根样本(每币 14~16),够说明「不对称」这个方向,不够
|
||||
# 定具体数值——一周数据到手要重新导出。用它而非 SHAPE_F 的理由是方向正确
|
||||
# 优于数值精确:结论对形状不敏感(见 step43_fill_aware_budget 的敏感性检查),
|
||||
# 但用错方向是系统性偏乐观。
|
||||
SHAPE_F_SHORT = np.array([0.031, 0.076, 0.121, 0.193, 0.230, 0.265, 0.297,
|
||||
0.356, 0.382, 0.420, 0.453, 0.485, 0.523, 0.552,
|
||||
0.586, 0.629, 0.681, 0.754, 0.813, 0.871, 1.000])
|
||||
TAKER_SHARE = 0.5
|
||||
|
||||
|
||||
def avail_at(price: float, hi: float, lo: float, vol_notional: float,
|
||||
is_long: bool, kgrid=SHAPE_K, f=SHAPE_F) -> float:
|
||||
is_long: bool, kgrid=SHAPE_K, f=None,
|
||||
f_short=None) -> float:
|
||||
"""该根里能打到限价 `price` 的对手方成交额。
|
||||
|
||||
多头在 `price` 挂卖出,靠价格 ≥ price 的主动买成交;空头挂买回,靠
|
||||
价格 ≤ price 的主动卖成交。方向用显式参数而非「把价格取负」——取负会
|
||||
让所有价格变成负数,任何对价格正负的假设都会静默失效。
|
||||
|
||||
两个方向查各自的成交分布曲线(实测二者不对称,见 SHAPE_F_SHORT)。
|
||||
"""
|
||||
f = SHAPE_F if f is None else f
|
||||
f_short = SHAPE_F_SHORT if f_short is None else f_short
|
||||
if vol_notional <= 0 or not (np.isfinite(hi) and np.isfinite(lo)):
|
||||
return 0.0
|
||||
if hi <= lo:
|
||||
@@ -77,16 +94,20 @@ def avail_at(price: float, hi: float, lo: float, vol_notional: float,
|
||||
if price >= hi:
|
||||
return vol_notional # 整根都在限价之下
|
||||
k = (price - lo) / (hi - lo)
|
||||
return float(np.interp(k, kgrid, f)) * vol_notional
|
||||
return float(np.interp(k, kgrid, f if is_long else f_short)) * vol_notional
|
||||
|
||||
|
||||
def walk_filled(cdf: pd.DataFrame, sig: pd.DataFrame, notional: float,
|
||||
sl: float = 2.0, scale_at: float = 3.0, runner: float = 8.0,
|
||||
runner_stop: float = 2.0, maxb: int = 48,
|
||||
taker_share: float = TAKER_SHARE) -> pd.DataFrame:
|
||||
taker_share: float = TAKER_SHARE,
|
||||
f=None, f_short=None) -> pd.DataFrame:
|
||||
"""前推每笔信号,返回按成交量结算的出场权重与毛收益。
|
||||
|
||||
每行的 `w_*` 是各出场去向占**全仓名义额**的比例,四者相加为 1。
|
||||
|
||||
`f` / `f_short` 是两个方向的区间内成交分布曲线,留出接口是为了能做敏感性
|
||||
检查——这两条曲线目前只有 45 根样本,必须能验证结论对它们不敏感。
|
||||
"""
|
||||
high = cdf["high"].to_numpy(float)
|
||||
low = cdf["low"].to_numpy(float)
|
||||
@@ -130,7 +151,7 @@ def walk_filled(cdf: pd.DataFrame, sig: pd.DataFrame, notional: float,
|
||||
v = vol[j] * close[j] * taker_share
|
||||
is_long = d == 1
|
||||
if rem_scale > 0:
|
||||
got = avail_at(p_scale, hi, lo, v, is_long)
|
||||
got = avail_at(p_scale, hi, lo, v, is_long, SHAPE_K, f, f_short)
|
||||
fill = min(rem_scale, got / notional) if notional > 0 else \
|
||||
rem_scale
|
||||
if fill > 0:
|
||||
@@ -138,7 +159,7 @@ def walk_filled(cdf: pd.DataFrame, sig: pd.DataFrame, notional: float,
|
||||
w_scale += fill
|
||||
scaled_any = True
|
||||
if rem_run > 0:
|
||||
got = avail_at(p_run, hi, lo, v, is_long)
|
||||
got = avail_at(p_run, hi, lo, v, is_long, SHAPE_K, f, f_short)
|
||||
fill = min(rem_run, got / notional) if notional > 0 else \
|
||||
rem_run
|
||||
if fill > 0:
|
||||
|
||||
Reference in New Issue
Block a user