Commit Graph
48 Commits
Author SHA1 Message Date
jackandCursor 1ee4c3d4e1 记 §3.6:scan=200 补测,意外地落在对的位置
scan 从未在 1m 加当前出场结构下被单独验过——step29 的网格里它和
pullback_win/tol 联动、跑在 15m 上用旧出场参数,分离不出单独效应。文档里
原先只有一行表格、没有依据。

澄清两个口径:scan 只约束找突破,入场由 pullback_win 单独限制且不受
scan_end 约束,实际触达 230 根;中枢跨度再长也不占窗口,因为 available_ts
取 bis[-1] 落在右边缘(1m 上跨度中位 135~169 根、31~40% 超 200 根)。

实测效应是砍掉约 30% 信号,但分层很陡:200-600 那桶 33 笔 PF 1.01、净均
0.21bp 等于零,>600 那桶 15 笔 PF 2.34 和保留的那批一样好。所以 200 正好
切掉了无效的中间桶,放宽到 600 只会稀释。>600 样本太少不足以动参数。

样本只有本地 SOL/ETH,ETH 多数过不了 ATR 门控,故 118 笔以 SOL 为主,
ADA/DOGE/XRP 本地无数据。结论是"支持保留现状",不是"200 最优"。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-29 00:16:07 +08:00
jackandCursor 881cc5e77d 补上「一个币一个仓位、账户专用」这两个没落在代码里的前提
三处都源于同一个隐含假设从未被写成代码。

① 同币并发会让 pnl_day 翻倍。blocks() 只查幂等键与三条计数闸,没有按币的
占用检查。同币开两笔时交易所净成一个仓位,于是 watch() 按 pair 判出场会让
两个 key 同时进 gone,_match_hist 给它们返回同一条历史记录,realized() 被
调两次。而 pnl_day 正是 MAX_DAY_LOSS 读的数:亏损翻倍提前停机,盈利翻倍让
闸变迟钝。sweep() 也会在第一笔截止时平掉合并后的整个仓位。

合并后的行为(一个止损、两个不同价位的止盈、超时一锅端)不是任何一版回测
建模的东西,所以在 on_signal 里跳过第二个信号,是最接近安全的近似。期望并发
0.18 笔,损失极小。另外给 _match_hist 加 positionId 独占认领,让这个不变量
在记账处本地成立,而不是依赖两百行外的检查——花钱的路径值得两道。

② positions() 返回账户全部仓位,三个调用点都没过滤。账户上任何第三方仓位
都会被 reconcile 在重启时市价平掉,而 watch() 会因该 symbol 一直在场而永不
结算,MAX_OPEN 名额泄漏、pnl_day 不再更新。加 PAIRS 过滤与 my_positions()。
残留局限记在注释里:同币上的第三方仓位仍分不出来,账户仍应专用。

③ oid_of 把非字母数字换成下划线,理由是 : 和 + 未必被接受,但调用方又拼了
"-tp" 把 - 加回去,自相矛盾。真被拒时止盈单会全部挂不上,而那条路径只告警
不停机,收益结构静默退化成「只有止损 + 超时」,且空跑验不到(dry 返回假
成功)。改用 _tp,并把字符集约束写进 oid_of 的文档。

实测:同币第二个信号被挡、别币放行、独占认领不重复计账、PAIRS 排除
PEPEUSDT、全部 clientOid 只含字母数字下划线。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-28 18:58:47 +08:00
jackandCursor 5ffbadb0b2 setup_symbols 不再无条件报"已设好",失败要说出来
那行"已设 N 个币为逐仓 10x"原先无条件打印,20 次调用全失败也照样这么说。
日志里写假话比不写更糟。而且这段只在真跑模式执行,空跑从没碰过它,第一次
运行就是上实盘的那一刻。

杠杆设失败是有经济后果的:仓位大小由名义额算、与杠杆无关,但保证金要求会
变。若交易所侧实际是 1x,每笔需 100 USDT 保证金,第 2、3 笔必然失败,且
可能只成一条腿、静默变成半仓——收益结构从「50% 在 3 ATR + 50% 在 8 ATR」
变成别的东西。

现在按失败项数如实报告,并推一条 Telegram。不硬性阻止启动:真正的失败会在
下单时暴露,但日志必须诚实。

实测全部成功/一项失败/全部失败三种情形。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-28 18:41:37 +08:00
jackandCursor 668ebe1e44 探针把 40014 判为最小权限的正常表现,并翻译 parentId/权限/白名单
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-28 18:36:10 +08:00
jackandCursor f62b4f62a5 加只读探针定位资金所在账户:子账户/现货/币本位/USDC 本位一次看清
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-28 18:29:23 +08:00
jackandCursor c513455998 余额不足时算出能开几笔,并点明半仓风险:两条腿分别下单,第二条失败会静默改掉收益结构
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-28 18:25:35 +08:00
jackandCursor 7435cef61f 空跑打全余额字段:逐仓下管用的是 isolatedMaxAvailable,只看 available 会误判
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-28 18:23:57 +08:00
jackandCursor 9562b8c598 空跑补一次带签名请求,否则密钥与白名单根本没被验到
dryrun 第 5 步声称"会真实验到密钥与白名单",但 start() 在空跑下于密钥检查
之前就 return,全程只发了 contracts() —— 那是公开端点,不验签。于是空跑会
"通过"却什么都没测到。这种假保证比不测更糟:等第一个真信号来时才暴露,而
信号那时正在过期,没有从容排查的余地。

现在空跑返回前发一次 account()(只读),失败即退出并给出码表(40018 白名单
/ 40037 key 不存在 / 40001,40009 secret,passphrase / 40099 权限)。顺带报
可用余额,并在不足 MAX_OPEN 笔并发所需保证金时告警。

两处 SystemExit 会绕过 run() 的收尾,退出前不关 aiohttp 会话,日志尾部一串
Unclosed client session 会把真正的报错顶出视野。都补上了 close()。

BITGET_PASSPHRASE 现在也接受 BITGET_API_PASSPHRASE:另两项都带 API_,只有
它不带,很容易顺手写错,而后果是"像是填了"但签名一直失败。我自己就踩了。

启动时打一行 Telegram 是否启用。配错 token 的表现原本是完全静默。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-28 18:17:53 +08:00
jackandCursor d797adc77e dryrun: timeout 套进 sudo 内层,否则 SIGTERM 未必传到 ssh
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-28 18:09:29 +08:00
jackandCursor 040810d48d known_hosts 校验命令补 sudo:.ssh 是 700 chan,ubuntu 读不了
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-28 18:02:54 +08:00
jackandCursor d884de2c36 dryrun 的 ssh 探测适配强制命令,并分清指纹与认证两种失败
采集机那侧的 authorized_keys 用了强制命令,把这把 key 锁成只能跑 tail,
拿不到 shell。副作用是任何请求都变成 tail -F 而它永不返回,原先
`ssh <host> 'echo ok'` 的探测会永久挂住——ConnectTimeout 只管建连,不管
命令时长。改成照 ship_signals 的真实用法读流:tail -c +0 先吐出整个文件,
数行数就等于对端总线条数,之后由 timeout 收掉。

失败诊断原先一律说"装 key",但 Host key verification failed 是 known_hosts
空的,装 key 修不了,会把人引到错方向。现在分三类:指纹未确认、认证被拒、
其他,各给对应做法。指纹那条明确不建议 StrictHostKeyChecking=no——这条链路
上跑的是下单信号。

install.sh 的第 2 步补上 known_hosts:原先只说装 key,而服务跑起来会撞同一
个墙(BatchMode=yes 不允许交互确认)。

实测:强制命令下故意请求错路径,仍拿到真总线内容且 stderr 干净。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-28 18:01:01 +08:00
jackandCursor b6ed5c8b50 on_signal 校验字段,畸形记录只丢一条而不是停掉交易
on_signal 直接取 r["emit_ms"],总线上一条字段不全的记录就会抛 KeyError
打死 poll 任务,进而整个执行器停止交易。一行坏数据换全面停摆,代价不对等。
搬运侧已挡半行,但挡不住字段级的不全。

现在缺 key/sym/emit_ms/direction/entry_px/atr_pct 任一项即丢弃该条,记日志
并推一条 Telegram(这类事应当可见,否则只是少做几笔,统计上看不出来)。

实测:两条字段不全 + 一条非法 JSON + 一条正常,前三条分别被丢弃/被
read_all 吞掉,正常那条照常下单,进程存活。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-28 17:57:24 +08:00
jackandCursor 999ad924ad install.sh 报出模板新增的配置项,并给 --sync-env 追加
live.env 装着密钥,所以安装时刻意不覆盖。但这样一来,往 live.env.example
里加配置,已有部署会永远拿不到、且毫无提示——静默漂移,等到某个开关根本
没生效才发现。这次加 TG_TOKEN 就撞上了。

现在重复安装会 diff 两边的变量名:报模板多出的项(提示跑 --sync-env),
也报配置里已被模板删掉的项(可能已废弃)。

--sync-env 独立成一个模式而不是塞进安装流程:追加要改一个装着密钥的文件,
这种事应当由人显式发起。只追加缺的项、连它上面的注释一起,不动已有任何
一行,先备份。实测原有内容逐行未变且重复跑幂等。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-28 17:45:48 +08:00
jackandCursor 15f4aa088e 加 Telegram 通知,并修好一道死掉的闸
接 Telegram 时查出 Guard.realized() 定义了但全仓库没有调用点——pnl_day
恒为 0,MAX_DAY_LOSS 完全不生效。三条硬约束里最重要的一条是死的。

根因是止损与止盈都挂在交易所侧成交,本进程收不到通知,而 sweep 只在 48
分钟到点才查持仓。连带第二个后果:execs 条目不清,MAX_OPEN 把已出场的
仓位继续算在场,新信号被白挡到截止时刻。

补 watch() 循环(10s):持仓消失即判出场,去 history-position 取
netProfit(= pnl + 资金费 + 开平手续费)记回闸并释放名额。盈亏取交易所的
数而不自己按标记价估——估会漏掉费用且方向总偏乐观。历史未落库时留到下轮,
不会漏记。字段名按文档与官方 TS 类型的差异同时兼容 ctime/cTime。

Telegram(live/tg.py,stdlib + aiohttp):推开仓、平仓带已实现盈亏、被硬
约束挡住、报错、对账平仓、跨日结算、启动与停机。不推信号过期跳过(常态,
搬运重连会重放旧信号)与心跳,否则真事会被淹掉。启动那条兼作通道自检。
研究侧 tg_notify.send 改为复用生产的传输层,方向与 signal_bus 一致。

status.sh 增加一条判读:开过仓但 pnl 仍为 0 就是 watch() 出了问题。

实测:8 类消息渲染、_match_hist 的过早/方向不符/币不符/驼峰字段/取最近
五种情形、100 USDT 下 SOL 与 ADA 的端到端空跑(两腿等量,50/50 精确)。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-28 17:30:11 +08:00
jackandCursor 3e3d579e35 文档与实现对齐:停机是 SIGTERM 且会平仓,补充崩溃/主动停机的差异
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-28 17:13:56 +08:00
jackandCursor 1642b1bbfc 补上停机处理:SIGTERM 撤挂单平仓再退,并关掉 aiohttp 会话
README 里原先写「执行器收到 SIGINT 会先平掉在场仓位再退出」,但代码里
完全没有信号处理——asyncio.run 外面连 except KeyboardInterrupt 都没有。
断言了一个不存在的行为,现在把它实现。

为什么停机要平仓而崩溃不用:崩溃后 systemd/docker 几秒内重启,reconcile
接着清掉遗留仓位,空窗期有交易所侧止损兜着。而主动停机后没人重启,仓位会
一直挂到止损或止盈,48 分钟超时腿丢了,跑的就不是回测那个出场结构。

必须显式挂 SIGTERM:docker stop 与 systemd stop 默认发的都是它,而 Python
对 SIGTERM 不抛 KeyboardInterrupt,不挂就是直接消失、没有任何清理。挂上后
systemd 单元里 KillSignal=SIGINT 那个绕法也去掉了。

主循环因异常退出时同样走停机流程,不把仓位留给已经没人管的进程。
顺带修掉 "Unclosed client session"(bitget_rest 本有 close() 但没人调,
Restart=always 下会漏 socket)。

实测:发 SIGTERM 后走完停机流程、无泄漏警告。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-28 17:12:41 +08:00
jackandCursor 335d891478 生产与研究分家:实盘执行器独立成 live/ 子树,加 AWS 部署
实盘要跑在 AWS(API key 绑了 IP 白名单),而信号在新加坡那台算。借这次
把生产从研究侧摘出来,四条具体代价里第一条已经咬过:

1. live_state.json 原先落在 research/out/,而那里 shadow_hb 会自动 rename
   归档、研究脚本会写、人也手工清过。那文件装的是 MAX_DAY_LOSS 累计与已
   处理信号键,被清掉不报错,只是两道闸静默失效。改到 LIVE_HOME。
2. 采集器十币清空 300~560ms 直接叠在信号到达执行器的延迟上。
3. 研究侧探针 OOM 过一次(14.9GB),当时若有仓位在场会连坐执行器。
4. 为读两个常量 import 研究侧 step43,把 numpy/pandas/pyarrow 拖进实盘
   进程。抽出 stdlib-only 的 live/exit_params.py,install.sh 加断言挡回归。

新增 live/ship_signals.py:AWS 侧 ssh tail 拉总线,每次重连从文件头重放
+ 按幂等键去重,断线期间的信号自愈;旧信号由 staleness 闸挡掉不补做。
带时钟倒流检测——两机时钟不同步会让那道闸静默放宽。

部署件:systemd 两单元(搬运挂了执行器仍管在场仓位的超时平仓)、
install.sh、dryrun.sh(验密钥/白名单/时钟/ssh/取整)、status.sh、README。

验证:live_exec 重构后端到端空跑,SOL 多头与 ADA 空头的止损/两级止盈/
数量取整逐项核对正确,isolated + post_only + reduceOnly 都在;搬运的去重、
重启不重复追加、脏数据跳过、断线重连重放均已测。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-28 17:03:03 +08:00
jackandCursor af029bc26e 更正上一提交:争抢是 1.5x 不是 3.3x,并发现按币绑 worker 可省三分之一
上一提交的 3.3x 基线是废的:离线每次喂同一窗口,append_bar 一根没追
(实测 6 次调用 stream 2001→2001 零增长),测的只是信号链地板 30ms,
拿它比在场 inner_ms 又是一次口径不对齐——和刚撤回的 22/86 同一类错。

对齐后:在场每次平均追 2.81 根(2 worker 各存一份缓存、各自漏掉对方
处理过的根),离线同口径 67.3ms,在场 100ms → 争抢 1.49x。
顺带修正「重建占 21.5%」:真重建只有 0.31%,此前把换 worker 的小幅
回退误判成重建。

新杠杆:symbol 固定到同一 worker,每次只追 1 根,离线 67.3 → 45.0ms,
在场约省 33ms/币(inner_ms 三分之一)。心跳判定改指这一刀。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-28 16:23:02 +08:00
jackandCursor cef894376c 定位 inner_ms 的 3.3 倍:是争抢不是算法档,撤回 22/86 拆分
服务端跑 probe_inner.py 与本机对表:分档占比一致(TF_DF 两腿 68~74%,
信号链 16~21ms),此前报的「chan 构建 22ms / 信号链 86ms」是无效减法
——孤立环境的 1m append 减在场十币的 inner_ms,还漏了 5m 腿。

真实构成:inner_ms 100ms = 约 30ms 计算 × 3.3 倍争抢。逐个排除
_rebuild(1.6ms)、币种差异、周期重建(21.5%/1.5x)、批内次序(相关-0.083)
后,用外生的到达密集度确认(67→117ms),并以注入合成负载复现
(2 个满载进程 2.68x,3 个 2.86x,在场实测 2.24~3.34x)。

心跳判定不再推荐「继续压算法」,改为加核或减币。

第一版用 t_signal-inner_ms 反推并发区间得相关 0.533,是循环构造,
已废弃并在 §5.72 记下这个坑。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-28 16:19:25 +08:00
jack be442783a1 Merge branch 'chan' of ssh://git.jackyu66.com:2222/jack/chan into chan 2026-08-28 16:07:28 +08:00
jackandCursor f11c6507b0 实盘执行改走 Bitget v2 REST,止损挂到服务端
丢掉 Hummingbot 的 PositionExecutor。它的连接器只暴露 LIMIT / LIMIT_MAKER /
MARKET,没有触发单,于是 control_stop_loss() 只能本地盯价、触发时才发市价单
——**进程一死仓位就是裸的**。而交易所本身支持 place-order 带
presetStopLossPrice,下单时就把止损挂到服务端。绕过连接器不是图省事,是为了
消掉一整类故障。顺带 TripleBarrierConfig 只有单级止盈,装不下两级,自己写更短。

## 三条出场腿各自挂在哪

    止损   交易所侧(presetStopLossPrice,随入场单一起到)→ 进程死了仍在
    止盈   交易所侧(post_only reduce-only 限价)        → 进程死了仍在
    超时   本进程,48 分钟到点市价平

所以进程死掉只会让持仓超过 48 根,不会变成裸仓,退化是良性的。

## 止盈不能用 presetStopSurplusPrice

它触发后按市价执行,而成本模型里止盈是 maker——那 60% 的出场不吃滑点、按
maker 费率计(LEG_IS_TAKER)。用 preset 会让这部分变成 taker,预算就不成立。
所以止盈单独挂 post_only + reduceOnly 限价单。止损反过来必须市价:stop-limit
在急跌里可能不成交,损失远大于省下的费。

## clientOid 是交易所级幂等,但要小心两个坑

信号键形如 SOL:1787904388411:+1,`:` 和 `+` 未必被接受,带过去直接拒单——而
拒单发生在入场腿上,等于这笔信号静默漏掉。

清洗时不能简单把非字母数字换成下划线:那样 `+1` 和 `-1` 都变成 `_1`,同一根上
的多空信号得到相同 oid,第二笔被当重复拒掉。方向显式编码为 L/S。

用 clientOid 而非只靠本地去重,是因为「已发出但没收到回复」这种情况本地判不了,
重试就会开两次仓。

## 空跑要走完 open_position

第一版在 on_signal 里 `if dry: return`,结果数量取整、价位对齐 tick、请求体
构造全都没被验到。现在空跑走完整条路径,不下真单由 Bitget(dry=True) 负责。

实测两笔:SOL 多头 entry 106.706 / ATR 11bp → 止损 106.471、止盈 107.058 与
107.645;BTC 空头 → 止损在上方 79747.5、止盈在下方。半仓精确一半(SOL 2.3+2.3、
BTC 0.0031+0.0031)。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-28 16:07:28 +08:00
jackandCursor 360e4e1c41 自动化实盘执行器:信号总线 + 两个半仓分解 + 四条硬约束
## 架构:影子发信号,实盘执行,两个进程

不让实盘自己算信号。前两个理由是资源与隔离(2 核上再来一份十币计算会把清空
从 247ms 推到 500ms+;实盘崩溃不该影响正在采的数据集),第三个最要紧:
**实盘交易的必须是影子测量的那一个信号**。各算一份会让两边悄悄分叉,之后就
没法把实盘实际成交和影子测的滑点曲线对照——而那个对照是整件事的目的。

总线用 append-only JSONL + fsync:崩溃安全,且「影子在跑、实盘没在跑」会表现
为信号在攒着,而不是静默丢弃。

## 出场结构精确分解成两个半仓

TripleBarrierConfig 只有单级止盈,装不下 3ATR 减半 + 8ATR 目标。但已核实
exit_model.py:151 的 runner_stops 是从**入场价**算的(ret = (entry-low)/a),
且 RUNNER_STOP == SL == 2.0,所以两半共用同一个不动的止损,可精确分解为:

    半仓 A  市价入场 · TP 3ATR · SL 2ATR · 48min
    半仓 B  市价入场 · TP 8ATR · SL 2ATR · 48min

止损先到则两半都在 -2ATR 出场;3ATR 先到则 A 出场、B 继续且止损仍在 2ATR。
与回测逐情形一致。assert_decomposable() 在启动时挡住 RUNNER_STOP != SL 的
改动,否则实盘会跑另一个收益结构且不报错。

## 硬约束是这个文件的重点

一笔止损只亏约 1 USDT,所以「亏损可控」对单笔成立。但三类故障的代价**不随
仓位缩小**,必须显式封住:失控下单(MAX_OPEN=3 / MAX_DAY=15)、亏损累积
(MAX_DAY_LOSS=20)、裸仓(重启对账)。状态落盘且原子替换——不落盘的话反复
重启就等于反复重置日上限,而失控下单恰好常伴随反复重启。

已验:幂等去重、并发上限、日开仓上限、日亏损上限、跨日归零且保留 done 键、
重启后计数不清零、状态文件损坏时不抛异常。

## 重启对账选择平掉而非接管

崩溃重启后交易所可能还有仓位,而 executor 全没了,那些仓位没有任何止损在盯。
接管需要重建入场价/ATR/剩余半仓状态/已过根数,任一项猜错就让出场结构变成
另一个东西;平掉的代价只是一笔小额亏损,且行为确定。

## 已知风险:止损在机器人侧

Bitget 连接器只支持 LIMIT / LIMIT_MAKER / MARKET,无触发单。
PositionExecutor.control_stop_loss() 是本地盯价、触发时才发市价单,所以进程
一死仓位就是裸的。10x 下强平需逆向 10%(约 100 个 ATR),48 分钟内极不可能,
单次代价仍封在保证金内。下一步用 Bitget 服务端 TP/SL 计划单做兜底。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-28 15:52:03 +08:00
jackandCursor f716ddd149 推送带杠杆与保证金,名义额默认抬到 500
杠杆只影响占用保证金,**不改**名义敞口、手续费、滑点、盈亏绝对值——费与滑点
都按名义额收。所以「用杠杆所以可以很小额」这个方向要说清:杠杆省的是本金
占用,不是成本。

它真正的用处是让「抬名义额」几乎免费,而抬名义额正好压掉步长取整。实测
取整到步长偶数倍后最差币的名义偏差:100U → 6.7%(SOL)、500U → 1.8%、
1000U → 0.6%。所以默认名义改 500、杠杆 10x,每笔占 50 USDT 保证金。

止损在 2 ATR ≈ 0.2%,而 10x 的强平约需逆向 10% = 100 个 ATR,差 50 倍。
这个止损紧到杠杆几乎不引入强平风险,所以这里没有「杠杆换风险」的取舍。
仍建议逐仓,让每笔最大损失被保证金封住。

推送里加上占用保证金与「触止损亏多少 USDT / 占保证金百分之几」,手工执行时
不必自己换算。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-28 15:44:39 +08:00
jackandCursor c57adc8cb0 推送按合约规则取整,减半腿精确一半
100 USDT 的仓位在 Bitget 上不是最小下单量的问题(minTradeUSDT=5、
minTradeNum 极小,10U 都能下),是**步长取整**的问题:

  SOL  步长 0.1 币 ≈ 10.7U → 50U 腿只能取 0.4 币 = 42.7U,偏 14.6%
  LINK 步长 1 币 ≈ 11.7U   → 4 币 = 46.8U,偏 6.4%
  BTC  步长 0.0001 ≈ 8.0U  → 0.0006 = 47.8U,偏 4.4%

减半腿变成全仓的 43% 而非 50%,剩下 57% 暴露在 8ATR 目标上,而回测的收益
结构假设 50/50。小额下这不影响机制验证,但会让 P&L 读不出回测那个结构。

解法是入场量取到**步长的偶数倍**,这样一半天然落在步长上。实测十个币减半腿
全部精确 50%,名义额落在 93.6~106.7 USDT——小额实盘无所谓。

同时把价位对齐到 tick(priceEndStep × 10^-pricePlace),否则限价单会被拒。
规则拉一次缓存;拉不到就退化为不取整并打日志,不阻断推送。

费率一事已核实无需改动:预算假设的挂牌 taker 0.040% / maker 0.016% 正是
Bitget VIP2 的官方档(返 50% 后 2.0 / 0.8bp)。合约接口返的 6bp/2bp 是 VIP0
基础档,不适用。8bp 门控阈值不变。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-28 15:42:17 +08:00
jackandCursor 97457b0518 过滤网信号推 Telegram,供手工小额实盘
自动执行链一行都还没写(下单/持仓状态/跨重启持久化/对账/熔断),而过全部
滤网的信号只有约 5.3 笔/天——低到人手能接。先手工跑一批,就能在写自动化
**之前**拿到真实费率档、真实成交价、真实出场行为,让执行链的每个假设都有
实测对照,而不是写完再发现出场模型不对。

推送内容按手工执行需要给全:参考成交价(回测口径的次根开盘)、按 2/3/8 ATR
换算的绝对价位、下单数量、该币滑点预算,以及一句「偏离超过预算就不值得做」。
剩余半仓止损保持 2ATR 不移成本,这是回测参数,移了就不是同一个收益结构。

时效是这条路最大的风险,所以起点取 kline_ts 而不是信号产生时刻——参考价就是
在 kline_ts 那一刻存在的,从信号时刻起算会漏掉数据延迟加计算那 0.5~1.5s,而
那段不可压缩。超过 TG_STALE_S 直接标记已失效,不让人自己判断:宁可漏做,不
要在偏离预算之外入场。

三条防线:lag 退化时不推(与「停开新仓」同一条规则,不能只在自动化里执行);
按 (币, K线时刻, 方向) 去重,避免补根或池重建重放导致开两次仓;无预算的币
(如 TRX)不推。推送任何失败只打日志,不连坐采集——已验假 token 下降级为
HTTP 401 日志而非抛异常。

凭据走 tg.env(已 gitignore),给了 tg.env.example 说明怎么拿 token 和 chat id。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-28 15:35:17 +08:00
jackandCursor 0745e73b4f 判定要知道增量开没开,否则会推荐已经在做的事
上线 9 小时后实测踩到:清空 326ms 触发「币数 > 核数」那一支,于是继续输出
「唯一出路是走增量」——而增量已经生效。同一个分支在增量前后给同一个答案,
等于没有判定。

现在分开:增量未开就指向开增量;已开则指出单币 102ms 里 chan 构建只剩约
22ms,其余是 build_htf_zones / find_fast_bsp3 / attach_htf_context,要继续
压得改这三个或减币。顺带把「宽裕」阈值从 300 提到 400ms——清空 326ms 在
数据到达中位 347~558ms 面前不是瓶颈。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-28 15:24:31 +08:00
jackandCursor 545fcc7c77 补验多 worker 交错下的一次追加多根
原对拍每根都只追加 1 根,覆盖不到多 worker 交错的实际路径:
ProcessPoolExecutor 不保证同一个币落到同一个 worker,所以每个 worker 隔 nw
根才再见到这个币,一次要补 nw 根。「补 nw 根等价于连续追加 nw 次」是推理,
没实测过。

--interleave N 用 N 份独立缓存轮流接同一个币。BTC/SOL 各 300 根、2 份缓存,
逐字段零分歧,各缓存末窗 2299 根。

顺带记清亲和性的性质:缓存是 worker 进程内的 dict、键含 symbol,不存在
「worker A 的状态被 B 读到」或「拿到别的币的状态」。亲和性影响的是内存
(每个 worker 最终缓存全部币)与补根次数,不影响正确性。实测内存
597→598MiB,代价在噪声里。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-28 06:24:51 +08:00
jackandCursor 34a8f37b39 HANDOFF:更正 cal_trend 依赖那句,补 §5.6 增量落地
原先写「cal_trend 不能跳过——bi.py:221 读 klc.trend,笔的计算依赖它」。这条
不成立:221 行在 cal_trend 自己的循环里,读的是它自身的序列状态,不是
cal_bi_list 的依赖。1,800 根对拍定论——增量追加的 klc 其 trend 恒为 UNKNOWN,
与批量构建(trend 有值)逐字段相同。这处偏差是读代码读出来的,对拍一次就
定论了,同类判断优先用对拍。

另标注「append_bar 32ms 对 800ms 有 25 倍余量」的口径问题:单币构建不是信号
总延迟,多币要串行清空,且 800ms 哨兵测的是数据到达、不与计算共享预算。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-28 06:19:00 +08:00
jackandCursor 0a0fd2f682 影子信号走增量:清空十币 560ms → 247ms
compute() 每根新建 TF_DF 换成按 (symbol, timeframe) 缓存的流式对象。worker
进程被复用,所以缓存跨根存活;2 worker 轮流拿 10 币,每个 worker 最终缓存
全部 20 条流,实测内存开销落在噪声里(597→598MiB)。

## 前提先验,否则整个改动建立在沙子上

init_stream/append_bar **没有 trim**,dataframe 靠 pd.concat 无界增长。所以
增量必然让窗口每根 +1,只能周期性重建拉回,两次重建之间窗口是 [W, W+500]
而非恒定 W。于是必须先证明 compute() 输出对窗口长度不敏感——否则增量等于
静默换掉一批信号,不报错不崩。

verify_window_sens.py:三币 75 个信号窗口,+200/+500/+1000 三档全部逐字段
一致。step39 说的是「命中率在 2000 根饱和」,饱和不等于不变,这是两回事。

## 对拍

verify_incr_parity.py:三币 1,800 根、21 个命中、各跨 1 次重建边界,逐字段
零分歧。不能引用 HANDOFF §5.5——那验的是 bsp_list 那条链的整体哈希,而这里
是 find_fast_bsp3 那条链,且流式对象跨根复用,状态污染只会让信号悄悄换一批。

对拍顺带定论一件读代码定不了的事:cal_bi_list **不依赖** klc.trend。
init_stream/append_bar 从不调 cal_trend(它只在 get_klc_list 里),所以追加
出来的 klc 其 trend 恒为 UNKNOWN,而批量构建的有值;两者结果逐字段相同。
HANDOFF §5.5 那句「bi.py:221 读 klc.trend,笔的计算依赖它」不成立——221 行
在 cal_trend 自己的循环里,读的是它自身的序列状态。

## 重建不走 init_stream

init_stream 是逐行 dataframe.iloc[idx],正是引擎提速刚修掉的反模式:2001 根
要 238.5ms,而批量 lean 只 74.3ms,慢 3.2 倍。第一版用它重建,10 个币启动时
各来一次,清空反而涨到 1686ms。改用 TF_DF(df, lean=True) 重建,append_bar
靠 _ensure_stream_state 就能接上。

## 实测

append_bar 21.8ms vs 批量 lean 重建 77.7ms = 3.56x,与研究侧测的 3.7x 一致。
拆解:add_indicators 全表 7.5ms(34%,为加一根重算 2001 行)+ cal_bi_list
整表重扫 11.1ms(51%)+ concat 1.4ms。这两项都在引擎侧,值得反馈。

十币 / 2 核:清空 560→247ms,排队 92→10ms,纯计算 219→108ms。判定从
「加 worker 无用,唯一出路是增量」变成「宽裕,无需优化」。

注意 inner 108ms 里 chan 构建只占约 22ms,其余是 build_htf_zones /
find_fast_bsp3 / attach_htf_context。**瓶颈已不在 chan 构建**,再压增量收益
有限。

stream_bars 落到 latency CSV:恒等于 2001 说明缺口判定在每根都回退重建、
增量静默失效,这一点从耗时上看不出是哪一环。实测窗口稳定长大。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-28 06:17:47 +08:00
jack c8a0062707 Merge branch 'chan' of ssh://git.jackyu66.com:2222/jack/chan into chan 2026-08-28 05:19:15 +08:00
jackandCursor f5f456c523 计算判定改看「清空全部币」,并挡掉空窗口
判定原先比逐根的 queue_ms 与 inner_ms,结构上错了两处,十币下直接指反:

判据错。要紧的是一个收盘时刻清空所有币要多久,不是单币的 q 或 i。币同一秒
收盘,币数超 worker 数时后面的币串行等待,这笔代价不出现在任何单根的 q 或 i
里。现在按 kline_ts 聚合取各币最大 compute_ms,落到 clear_hist。

出路错。「排队为主 → 加核」只在还有空闲核时成立。worker 已等于核数时加
worker 不增吞吐,只把等待从 queue 挪到 inner。十币实测正是如此:inner 被
争抢从 144 抬到 192ms 反超 queue 135ms,于是判定落到「量级已低、无需优化」
——而此时最后一个币已在 1376ms。现在币数超核数就直接指向增量路径。

顺带修一个瞬时故障:WS 重连瞬间 feed 的 deque 可能为空,空窗口放行会让
worker 抛「DataFrame for 1m is empty」,白占一个计算槽(币数超核数时会推迟
后面所有币),而报错文本还会让人以为是缺历史数据。加 MIN_BARS 守卫。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-28 05:19:14 +08:00
jack 2b9a837a58 Merge branch 'chan' of ssh://git.jackyu66.com:2222/jack/chan into chan 2026-08-28 04:50:16 +08:00
jackandCursor ef2a85dd8c 币池可配,实测十币的排队代价;标出漂移的 tick 地板
加 --syms(start.sh 传 SYMS),默认仍是三币。TRX 不进十币池:实盘口径 208 天
只有 5 笔,ATR 门控几乎全刷掉。

十币实测(对比三币):

  queue_ms        2 → 169ms(P90 519ms,每刻最大排队中位 533ms)
  inner_ms      144 → 196ms(CPU 争抢也拖慢了纯计算)
  lag_signal_ms 621 → 958ms;同一收盘时刻最后算完的币中位 1376ms

排队从可忽略变成主项,与「10 币 × 196ms ÷ 2 核 ≈ 640ms 突发」吻合。这也是
之前「加核没用」那个结论唯一会翻转的场景:CPU 占用率只有约 3%,问题纯粹是
所有币同一秒收盘的突发,加核压的是并行度而非单币耗时。

但 800ms 哨兵不受影响——它喂的是 t_data − kline_ts(数据腿),十币下逐币
154~532ms 全在线内。加币不碰那道闸。

另外发现一个会被静默误读的东西:ADA/AVAX/DOGE/LINK/LTC 的漂移中位精确等于
半个 tick 且在四个延迟点上完全相同。那不是漂移,是中价的最小变动量——ADA 半
tick 就有 2.34bp。拿这个数去比预算会误判某币不可做。shadow_report 加了
tick_floor() 标注;方向上安全(真实漂移只会更小,这些是上界)。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-28 04:50:09 +08:00
jack 7c13781f90 Merge branch 'chan' of ssh://git.jackyu66.com:2222/jack/chan into chan 2026-08-28 04:35:17 +08:00
jackandCursor d13a0f85bc 固化窗口召回率:2000 根窗口不丢信号(90/90)
全量历史找出的最近 30 笔信号,在 2000 根窗口里逐笔重算,BTC/ETH/SOL 各
30/30 全部复现且过全部滤网。这是窗口左边界效应的一半答案:窗口不丢信号。

另一半没答,且对实盘更危险——窗口会不会多造出全量历史没有的信号(会多开
仓)。那要反向扫描:遍历窗口找命中再回全量核对。lean 之后单窗 130ms,抽样
2 万个窗口约 43 分钟,已经可做,docstring 里记了。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-28 04:35:12 +08:00
jack 99ae072cc6 Merge branch 'chan' of ssh://git.jackyu66.com:2222/jack/chan into chan 2026-08-28 04:24:54 +08:00
jackandCursor d37bfcb26b 心跳的优化建议改看绝对量级
lean + 新引擎后纯计算约 128ms,仍固定提示「需改增量计算」是误导:尾部已由
数据腿主导(lag_data P90 1482ms vs compute P90 237ms),压计算换不到东西。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-28 04:24:48 +08:00
jack 4a4f8727fd Merge branch 'chan' of ssh://git.jackyu66.com:2222/jack/chan into chan 2026-08-28 04:24:22 +08:00
jackandCursor 6c843639d5 影子路径开 lean 模式,实测完整链路 1410 → 683ms
拉到新引擎后在服务器侧直接实测,没有沿用 3.4 倍那个换算——那是在旧代码上量的。

先做等价性验证。不能直接引用 step46 的对拍结论:它固化的是 bsp_list 那条链的
哈希,而影子路径走 find_fast_bsp3 + build_htf_zones + htf_fx_timeline +
attach_htf_context,两条链读的东西不一样。所以新建 verify_lean_parity.py 在
这条路径上逐根对拍 compute() 的每个返回字段。

其中一个坑:随机取窗口测不到信号分支。信号密度约 1/2000 根,头 12 个窗口命中
0 个,「一致」只覆盖了早退路径。改成一半窗口对齐到已知信号根,命中率才上来。
最终 180 窗口 / 两模式各 90 命中 / 零分歧。

生产实测(inner_ms,容器内同口径):

  compute_ms      646 → 132ms   4.89x
  inner_ms        612 → 128ms   4.80x
  lag_signal_ms  1410 → 683ms   完整链路,落回 800ms 线内

比 3.4 倍更好,因为是引擎 ~3.5x 叠 lean ~1.35x。

两点判读上的订正:

- lag_data_ms 那 576→490ms 是噪声,不要记在引擎账上。均值 721±36 vs
  740±127,重叠;而且引擎本来就影响不到交易所与网络那一段。
- 仍有 44% 的根超 800ms,但尾部现在完全由数据腿主导(lag_data P90 1482ms
  vs compute P90 237ms)。计算既不是瓶颈也不是尾部主因了,继续压计算换不到
  尾部改善。800ms 那道闸取的是最近 30 根的中位数,683ms 已满足。

start.sh 加 SHADOW_LEAN(默认 1),设 0 可退回 full 复量两模式差异。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-28 04:24:08 +08:00
jackandCursor 3b14bba247 离线验证信号时刻流动性不更差,免掉攒 30 笔信号的 17 天等待
三币过完三滤网只有 1.72 笔/天,攒 30 笔要 17 天。但滑点本来每根都在记,信号
时刻的测量只多回答一个问题:信号那一刻的流动性是否比普通根差。这个问题可以
用 210 天历史离线回答。

关键是必须做匹配对照。信号按 ATR ≥ 8bp 门控,信号根天然比平均根波动大,直接
和全体根比一定会「发现」一个我们自己施加的差异。所以对照组按「同时段 × 同
ATR 十分位」抽取,并排除距信号 48 根内的根(持仓期不独立)。自检 ATR 比值
0.976~1.004,匹配成立。

结果三币一致,方向与担心的相反:信号根成交额是对照的 2.1~2.6 倍、Amihud 非
流动性只有 0.47~0.60 倍、Roll 有效价差三个币都不显著。信号跟在突破后面,
突破自带成交量。所以全体根测出的冲击与价差偏保守而非偏乐观。

唯一真实差异是根内波幅宽 25~28%,但那是波动而非流动性,对应漂移而非冲击,
且可直接当缩放系数:1 秒延迟点漂移上调后仍只占预算 1.4%/3.0%/4.5%。

判读按流动性与波动分两组。初版把 range_bp 当成流动性红旗,会得出「信号时刻
更差、必须等样本」的相反结论——它是波动度量,且 ATR 已匹配。
检验用置换而非 t:成交额跨几个数量级,右尾太重。scipy 未安装,也不宜在采集
运行中动环境,所以用 numpy 自己实现。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-28 03:52:23 +08:00
jackandCursor 75fbf4167b 修掉 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>
2026-08-28 03:23:07 +08:00
jackandCursor 54792fe015 把 compute_ms 拆成排队与纯计算,据此否掉换机器这个方向
compute_ms 一直是「提交进程池到拿到结果」的墙钟时间,排队和纯计算混在一个
数里,所以「加核有没有用」只能靠猜——这也是原先打算在 AWS 开第二台比 CPU
的依据。

worker 内部自己计时,连同父进程传入的提交时刻一起回传,拆出 queue_ms 与
inner_ms。实测中位 3ms / 695ms:2 个 worker 跑 3 个币并不排队,因为三个币的
收盘消息错峰到达。瓶颈全在单线程,加核压不到。

顺带把 README 里的内存数据从臆测的 1.5GB 改成实测 410MiB,并注明跨站点比
CPU 收益有限。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-28 03:02:06 +08:00
jackandCursor 95f2b614a2 延迟瓶颈实测为本机 CPU 而非机房位置,跨站对比主指标改为 compute_ms
用户指出本机选新加坡是因为 Bitget 机房在新加坡。查证下来 ws.bitget.com 解析
到的是 CloudFront(dxotqhr62n6z4.cloudfront.net),落在新加坡 AS16509
(Amazon),即连的是 AWS 的 CDN 边缘而非 Bitget 自有机房。

腾讯云 ap-singapore(AS132203)到该边缘实测:ICMP 往返 2.1ms、TCP 握手
3.3ms、TLS 完成 9.0ms、首字节 87.8ms。首字节减 TLS 那约 79ms 是 CloudFront
回源开销,与我们的位置无关。

延迟构成(33 根样本):数据到达中位 506ms、信号计算中位 646ms、合计 1315ms。
随机房位置变化的只有那 2ms 往返,占总延迟 0.15%。而 646ms 的信号计算是每根
在 2000 根 1m 加 800 根 5m 上重建缠论结构,本机 2 核、2 个计算进程,三币同时
收盘时第三个还要排队——这才是有改善空间的一项。

因此:
- 新增 deploy/netprobe.sh,把网络那一段单独量出来并入运行元数据。用到达
  延迟去比两个机房等于用公斤秤称克,必须把可变的那段拿出来单独看。
- compare_sites.py 主指标改为 compute_ms,lag_data_ms 降为自检项(两站应当
  接近;若差很多,先怀疑时钟而非网络)。元数据表加 nproc/cpu_model/往返。
- README 改写:第二台机器该测 CPU 规格而非地理位置,WORKERS 按核数减一给,
  币数多于 worker 数时排队时间直接计入 compute_ms。

另修正站点标签:本机是腾讯云而非 Hetzner,sg-hetzner → sg-tencent,标错的
33 行数据已清掉重采。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-28 02:51:51 +08:00
jackandCursor 1661bbbac9 gitignore: 运行元数据属采集产物,不入库
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-28 02:40:57 +08:00
jackandCursor 631d97e493 加跨地部署脚本与站点对比,数据全表加 site 列
目的是在第二台机器(AWS)上跑一套完全相同的采集,看不同地理位置的数据差异。
滑点里最大的一项是延迟漂移,而延迟含网络传输,所以机房选址是可优化参数。

数据侧两处必需改动:

- 全部输出加 site 列(三张 CSV 加 gzip 里的盘口与成交流)。没有这一列,两台
  机器的数据合起来就分不清来源。为免四处 writerow 漏加一处产生静默空值,
  改在 _SiteWriter 里统一注入。
- start.sh 在时钟未同步或偏移超 10ms 时**拒绝启动**。所有延迟数字都是
  「本地时钟 − 交易所 K 线收盘」,时钟偏 50ms 就全部同向偏 50ms,且不报错,
  只会让跨地对比得出一个干净且完全错误的结论。

deploy/ 下四个文件:setup.sh(docker + chrony + 拉镜像)、start.sh(校验时钟、
写运行元数据、起容器)、status.sh(健康速查)、README。运行元数据记 git commit、
镜像摘要、时钟偏移——两地数据对不上时,这三项任一不同都足以解释差异。

compare_sites.py 做配对对比:只取各站都有的 K 线(不取交集可能在比不同时段,
而延迟对市场活跃度敏感),并报配对差的符号占比而非两个中位数相减。已用注入
120ms 的合成数据验证能精确还原。另有一条自检:同一固定延迟点上两站漂移应当
相同——漂移是市场性质,若也差很多则先查时钟与时段对齐。

status.sh 里按列名取字段下标而非写死数字:加 site 列时字段整体右移过一次,
写死 $8 会静默变成读 lag_signal_ms 而非 lag_data_ms。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-28 02:39:47 +08:00
jackandCursor 01c4a4ab4d 主口径仓位定为 10 万 USDT,并量出限价单前方的排队量
用户 2026-08-28 确认资金规模不会更大,故仓位档改为 25k/50k/100k/200k
(上下留档是为了读出局部斜率,单点看不出再大一倍会怎样)。该规模下两条
约束都不绑定:冲击占预算 0.1~11.1%,成交量效应使预算降幅不足 1%。

新增 queue_ahead:排队是唯一还没建模的成本项,exit_fill 假定我们能吃到该
价位的全部对手方成交量。10 万仓位相对最优档为 BTC 0.2 倍、ETH 0.7 倍、
SOL 69.7 倍。SOL 畸高是 tick 更细所致(同样的量摊到约 10 倍价位上),
对它应看 5bp 档(0.17 倍),但仍是三币中排队压力最大者。

同时在 shadow_depth 模块头标注:composite_fill 只算首次触及那一根的可成交
量,系统性偏悲观,不作为成交率结论——真实成交率见 lib/exit_fill.py。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-28 02:39:47 +08:00
jackandCursor 28075173af 出场模型改为按成交量结算止盈限价单,并出预算对仓位规模的曲线
exit_model.walk_exits 给止盈记的毛收益是 target*a/entry,即假定限价单全额
成交在目标价。新增 lib/exit_fill.py:两张挂单常驻(半仓 3ATR、半仓 8ATR),
每根按该根在限价之上的可成交量逐步吃进,未成交部分继续持有,止损触发时
市价平掉剩余。可成交量 = 形状函数 f(k) × 该根主动买成交额,f 由影子成交流
实测(近似线性,即区间内均匀分布,故结论对形状假设不敏感)。

结果:预算对仓位规模远比预期稳健。到 100 万名义额,BTC 10.97→10.69、
ETH 15.34→15.20、SOL 17.21→15.83bp。原因是挂单常驻多根而非只在首次触及
那一根成交,且价格决定性穿过限价时整根成交量都可用。

首版实现有个静默 bug 值得记:avail_above 里有个 `hi <= 0` 的守卫,而空头
用「价格取负」处理,负价格空间里 hi 恒为负——所有空头挂单的可成交量一律
判 0,空头全被拖到 48 根超时收盘。下跌段里那比 3ATR 目标赚得多,于是预算
反而偏高 0.76bp,表现为「一个看似合理的模型差异」。已改为显式方向参数,
并加 assert_converges:仓位趋近 0 时必须逐笔收敛到 walk_exits,不符即抛错。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-28 02:39:47 +08:00
jackandCursor 181bca303f 影子测量改用框架吃单原语,并补齐容量与 maker 成交率两项测算
吃单查询换成 Hummingbot 的 OrderBook.get_vwap_for_volume:手写的 walk_book
返回的是按计价币吃单的加权均价,但框架的 get_price_for_quote_volume 返回
边际价、get_vwap_for_volume 收基础币量,两者语义不同。改为按基础币下单
(真实委托与 PositionExecutor.amount 均是基础币计价),深度不足由
query_volume/result_volume 判定,框架此时返回 nan 而非一个看似正常的
部分成交均价。

落盘完整盘口(双边 50 档)。此前只记三个固定名义额的成交价,这批数据的
寿命就等于那几个档位的寿命;存完整深度后任意资金量级的冲击都能离线重算。
仓位档同时从 1k/5k/20k 提到十万量级,此前低估真实仓位约两个数量级。

订阅成交流,按根按价位聚合。买卖分开存——多头在目标位挂卖出靠主动买盘
成交,混在一起会把成交率高估约一倍。BTC 每根总成交额中位与 210 天历史
的 volume×close 差 0.3%,可确认采集完整。

新增两项测算:
- 冲击不是绑定约束。32 万仓位单边冲击 0.19~2.39bp,对 8.58~20.64bp 的
  预算只占 1.6~14.2%,冲击反推的资金上限 100~500 万。
- maker 成交率才是。止盈位被首次触及时,限价在该根价格区间中的位置
  中位 k=0.28(63.9 万次触及,三币一致);合并每根成交额后,32 万仓位
  的全额成交率仅 30.1%/15.6%/1.5%。要 80% 全额成交,仓位须 ≤ 4.7 万
  /1.4 万/0.26 万——比冲击反推的上限低 40~370 倍。

回测把这些止盈按「全额成交在目标价」计,故预算所依据的收益流本身需重估。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-28 02:39:47 +08:00