Commit Graph
16 Commits
Author SHA1 Message Date
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