1m 腿的滑点余量只有几个 bp,所以要测的必须是生产路径的滑点——换个运行时 测出来的数就不作数。框架因此从「滑点已知后再定」提前到测量阶段就定为 Hummingbot(Spot/Perp 连接器均 v2.0,Bitget 是 Foundation Partner)。 新增 research/live/。前置测量: - bench_compute.py 本机算力,1m 单币 0.318s、三币串行 1.38s - venue_parity.py Binance 与 Bitget 同根信号重合仅 14.6~42.6% - signal_sensitivity.py 0.25bp 扰动就换掉一半信号 - aggregate_robustness.py 但总体期望不降——脆的是信号身份,不是 alpha - bitget_baseline.py 因此改用 Bitget 原生基线定预算:余量 BTC -0.13bp、 ETH +4.02bp、SOL +2.92bp。BTC 本就为负,只作延迟测量的参照物 运行时选型: - parity_env.py 容器与本机信号逐一相同(下标、中枢数、checksum 全等), 容器内 0.26s/币反而更快。故 chanlun 直接挂载进容器,不必另起信号服务。 装进现有 .venv 那条路走不通:Hummingbot 要 numba>=0.61.2 与 aiohttp<3.14,与本机 Python 3.14 冲突 - latency_ccxt.py / latency_hummingbot.py / latency_compare.py 初测显示 Hummingbot 比 ccxt.pro 慢约 1030ms,90 根逐根配对里 80~97% 更慢 - probe_ws_action.py 否掉「丢弃 snapshot」的猜测:换根首条就是 update - probe_hb_vs_raw.py 与 latency_attribute.py 四路归因——容器网络 2~18ms、 Hummingbot 处理 -10~-30ms,1350~1480ms 全落在解析方式上 - probe_ws_payload.py 定位根因:Bitget 换根会推一条带两根的消息 [上一根, 新一根],而上游取 data["data"][0] 拿到的是上一根,新一根要等 下一条单元素消息 修复: - patched_candles.py 处理消息里的全部元素。不能简单改成 [-1]——那样上一根 的收盘价会永远停在换根前约 1 秒的那次推送上,而信号对 0.25bp 都敏感 - verify_patch.py 60 根配对验证:拿回 1060~1090ms,与原始 WS 只差 5~14ms 已贴理论下限,19 根已收盘 K 线 OHLCV 逐根未变。折算 ETH 省 0.54bp、 SOL 省 0.42bp。此 bug 值得向上游反馈 影子交易器: - shadow_hb.py 不下单,读连接器真实盘口按仓位吃单深度算成交价,与次根开盘价 (回测 entry_delay=1 的口径)相减,分解成延迟漂移、盘口价差、深度冲击。 盘口 10Hz 滚动缓冲 30 秒,把延迟变成自变量:每个信号记 0.5/1/2/5s 与实际 算完时刻各一个滑点值,本机算得慢也不影响能读出的曲线 - shadow_signal.py 信号计算隔离到子进程。0.26s 是纯 CPU 且 chanlun 受 GIL 限制,放进 asyncio 循环会把行情处理一起卡住 - shadow_report.py 首日延迟门槛与滑点曲线报表 不用 paper trade 测滑点:它的成交由 Hummingbot 自己的撮合模型模拟, 测出来是模型行为而非市场行为。 Co-authored-by: Cursor <cursoragent@cursor.com>
117 lines
4.0 KiB
Python
117 lines
4.0 KiB
Python
"""把 1030ms 归因到具体环节。
|
||
|
||
前面的测量已经排除两个可能:
|
||
· Bitget 换根首条推送就是 update,Hummingbot 没有因丢弃 snapshot 而等待
|
||
· 容器内同进程里,Hummingbot 的 feed 只比原始 WS 慢 2~6ms,处理开销可忽略
|
||
|
||
剩下的变量是「位置」和「客户端」。本脚本对同一批 K 线三方对齐:
|
||
|
||
host_ccxt 宿主机 ccxt.pro watch_ohlcv latency_ccxt.csv
|
||
host_raw 宿主机 原始 aiohttp WS probe_raw_host.csv
|
||
cont_raw 容器内 原始 aiohttp WS probe_hb_vs_raw.csv
|
||
cont_hb 容器内 Hummingbot candles feed probe_hb_vs_raw.csv
|
||
|
||
据此可分离两件事:
|
||
cont_raw − host_raw 容器网络的代价(同一份代码,只换位置)
|
||
host_ccxt − host_raw ccxt.pro 客户端的差异(同一位置,只换客户端)
|
||
|
||
.venv/bin/python research/live/latency_attribute.py
|
||
"""
|
||
from __future__ import annotations
|
||
|
||
from pathlib import Path
|
||
|
||
import numpy as np
|
||
import pandas as pd
|
||
|
||
OUT = Path(__file__).resolve().parents[1] / "out"
|
||
SYMS = ("BTC", "ETH", "SOL")
|
||
|
||
|
||
def load() -> pd.DataFrame | None:
|
||
parts = []
|
||
|
||
f = OUT / "latency_ccxt.csv"
|
||
if f.exists():
|
||
d = pd.read_csv(f)
|
||
if not d.empty:
|
||
parts.append(d[["kline_ts", "sym", "t_data_ms"]]
|
||
.rename(columns={"t_data_ms": "host_ccxt"}))
|
||
|
||
f = OUT / "probe_raw_host.csv"
|
||
if f.exists():
|
||
d = pd.read_csv(f)
|
||
if not d.empty:
|
||
parts.append(d[["kline_ts", "sym", "raw_ms"]]
|
||
.rename(columns={"raw_ms": "host_raw"}))
|
||
|
||
f = OUT / "probe_hb_vs_raw.csv"
|
||
if f.exists():
|
||
d = pd.read_csv(f)
|
||
if not d.empty:
|
||
parts.append(d[["kline_ts", "sym", "raw_ms", "hb_ms"]]
|
||
.rename(columns={"raw_ms": "cont_raw",
|
||
"hb_ms": "cont_hb"}))
|
||
|
||
if not parts:
|
||
return None
|
||
m = parts[0]
|
||
for p in parts[1:]:
|
||
m = m.merge(p, on=["kline_ts", "sym"], how="outer")
|
||
return m
|
||
|
||
|
||
def main() -> None:
|
||
m = load()
|
||
if m is None or m.empty:
|
||
print("四路数据均缺失")
|
||
return
|
||
|
||
cols = [c for c in ("host_ccxt", "host_raw", "cont_raw", "cont_hb")
|
||
if c in m.columns]
|
||
print(f"各路样本数(重叠前):")
|
||
for c in cols:
|
||
print(f" {c}: {int(m[c].notna().sum())}")
|
||
|
||
print("\n########## 各路 t_data − t_close(ms,中位)##########")
|
||
print(f"{'币':<5}" + "".join(f"{c:>11}" for c in cols))
|
||
for s in SYMS:
|
||
g = m[m["sym"] == s]
|
||
line = f"{s:<5}"
|
||
for c in cols:
|
||
v = (g[c] - g["kline_ts"]).dropna()
|
||
line += f"{np.median(v):>11.0f}" if len(v) else f"{'—':>11}"
|
||
print(line)
|
||
|
||
# 只在四路都有的 K 线上做差,避免不同子集的中位数互相错位
|
||
full = m.dropna(subset=cols)
|
||
print(f"\n########## 归因(仅四路齐全的 {len(full)} 根)##########")
|
||
if full.empty:
|
||
print(" 无四路齐全的 K 线;检查三个采集窗口是否重叠")
|
||
return
|
||
pairs = []
|
||
if "cont_raw" in cols and "host_raw" in cols:
|
||
pairs.append(("容器网络代价", "cont_raw", "host_raw"))
|
||
if "host_ccxt" in cols and "host_raw" in cols:
|
||
pairs.append(("ccxt.pro 客户端差异", "host_ccxt", "host_raw"))
|
||
if "cont_hb" in cols and "cont_raw" in cols:
|
||
pairs.append(("Hummingbot 处理开销", "cont_hb", "cont_raw"))
|
||
if "cont_hb" in cols and "host_ccxt" in cols:
|
||
pairs.append(("合计:容器 HB vs 宿主 ccxt", "cont_hb", "host_ccxt"))
|
||
|
||
print(f"{'环节':<26}" + "".join(f"{s:>9}" for s in SYMS))
|
||
for name, a, b in pairs:
|
||
line = f"{name:<26}"
|
||
for s in SYMS:
|
||
g = full[full["sym"] == s]
|
||
if g.empty:
|
||
line += f"{'—':>9}"
|
||
continue
|
||
line += f"{np.median(g[a] - g[b]):>9.0f}"
|
||
print(line)
|
||
print("\n(单位 ms,正数表示前者更慢)")
|
||
|
||
|
||
if __name__ == "__main__":
|
||
main()
|