修掉 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:
jack
2026-08-28 03:23:07 +08:00
co-authored by Cursor
parent 54792fe015
commit 75fbf4167b
4 changed files with 183 additions and 37 deletions
+25 -5
View File
@@ -96,6 +96,20 @@ def out_dir() -> Path:
return p if p.is_dir() else Path(__file__).resolve().parents[1] / "out"
RUN_ID = time.strftime("%Y%m%dT%H%M%S", time.gmtime())
def run_path(path: Path) -> Path:
"""把 `x.jsonl.gz` 变成本轮专属的 `x.<RUN_ID>.jsonl.gz`。
gzip 追加流在进程被杀后不可靠(见 BookLog 的说明),分文件是唯一能保证
历史数据不被后续运行连坐的办法。读侧 shadow_depth.read_jsonl_gz 会把
同前缀的所有文件一并读入,所以分文件对分析是透明的。
"""
return path.with_name(path.name.replace(".jsonl.gz",
f".{RUN_ID}.jsonl.gz"))
def _writer(path: Path, cols: list[str]):
"""追加模式打开;表头对不上就先把旧文件归档。
@@ -146,13 +160,16 @@ class BookLog:
一次采集回答所有资金量级的问题——包括容量上限那个必须现在就算、
不该等实盘暴露的数。
用 gzip 追加(多个 gzip 成员首尾相接仍可正常解压),进程被杀也只丢最后
一个缓冲块,不会毁掉整个文件。
**每轮运行单独一个文件**,不追加到同一个。追加看着更省事,实际很危险:
进程被 SIGKILL 时当前 gzip 成员停在 deflate 块中间,下一轮追加的新成员
接在这段垃圾字节之后,顺序解压会在损坏点抛 `invalid block type`,该点
之后的所有数据——包括后续每一轮写进去的——全都读不出来。已经因此丢过
一次。分文件后损坏最多只影响被杀那一轮的尾部。
"""
def __init__(self, path: Path) -> None:
self.path = path
self.fh = gzip.open(path, "at", encoding="utf-8")
self.path = run_path(path)
self.fh = gzip.open(self.path, "at", encoding="utf-8")
self.n = 0
def write(self, sym: str, kline_ts: int, label: str, delay_ms: int,
@@ -192,10 +209,13 @@ class TapeLog:
聚合到「根 × 价位」而不是逐笔:判据是「本根内有多少量在 ≥ 限价处成交」,
逐笔的时序对这个判据没有增量信息,而聚合能把体量压下两个数量级。
每轮运行单独一个文件,理由同 BookLog。
"""
def __init__(self, path: Path) -> None:
self.fh = gzip.open(path, "at", encoding="utf-8")
self.path = run_path(path)
self.fh = gzip.open(self.path, "at", encoding="utf-8")
# sym -> side('b'/'s') -> price -> 累计基础币量
self.acc: dict[str, dict[str, dict[float, float]]] = {}
self.n_trades = 0