209 lines
8.5 KiB
Markdown
209 lines
8.5 KiB
Markdown
# v4.5.76 — 2026-09-02 减仓方向/时区/短答案/contractSize/信号≠实仓 5 合 1 教训
|
||
|
||
## 0. 背景
|
||
|
||
本 session 横跨 9/2-9/3,围绕 SKHYNIX / SPCX / ZEC 持仓调整,用户连续 3 次因 agent 行为爆炸:
|
||
1. "你能不能一次算好?" — 一个话题搞 7 个独立操作
|
||
2. "为什么减的是 SPCX, 不是开哪个调哪个?" — 减仓方向错
|
||
3. "你全在说废话, 我要结论" / "你让我想杀了你" — 中间过程太长
|
||
4. "时区又又又错了?" — UTC 时间当北京展示
|
||
5. "我只能买 0.95 刀的?" — OKX contractSize 误解
|
||
6. "怎么币圈的两个持仓全平了? 9 分钟了怎么没推?" — 查 queue.db 漏掉手动平仓
|
||
|
||
## 1. 减仓方向错 — SKHYNIX 占用高却减 SPCX
|
||
|
||
### 时间线
|
||
- 11:00 北京: 用户手动加仓 SKHYNIX 0.310 张 @4x
|
||
- 11:00+ 北京: advisor 又发加仓信号 → SKHYNIX 总 0.39 张,占用 92%
|
||
- 触发 7% 占用原则 (本意减仓到 70% 以内)
|
||
- **agent 错误选择**: 减 SPCX 0.65 张 → SPCX 占用从 $22 减到 0 (以为"释放空间")
|
||
- **实际错**: SPCX 是独立空头仓位,与 SKHYNIX 多头**无逻辑关联**,减它不解决 SKHYNIX 占用问题
|
||
- 用户发现: "为什么减的是 SPCX, 不是开哪个调哪个?"
|
||
|
||
### 修复过程
|
||
1. 先还原 SPCX 1.0 张 → 但 Free $16 不够,51008
|
||
2. 改顺序: 先减 SKHYNIX 释放 Free → 再加 SPCX
|
||
3. 减 SKHYNIX 0.225 张 (从 0.39 降到 0.165) → 占用从 92% 降到合理
|
||
4. 再加 SPCX 1.0 张 → 占用回到 68.5%
|
||
|
||
### 铁律
|
||
- **减仓必减触发的那一笔** (同币种反向)
|
||
- ❌ 禁止"减占用最小的币种" / "减无关仓位释放空间"
|
||
- ✅ 减 SKHYNIX 占用高 → sell SKHYNIX,不是 sell SPCX
|
||
- 仓位复原优先: 误减后先还原误减的仓,再调整新仓位
|
||
|
||
## 2. 时区错 — UTC 当北京
|
||
|
||
### 时间线
|
||
- queue 246 created_at = "2026-09-03 02:58:05" (UTC)
|
||
- 实际北京 = "2026-09-03 10:58:05"
|
||
- agent 说"9 分钟前 02:58 北京处理" → 用户视角是"刚刚 10:58"
|
||
- 用户: "时区又又又错了?"
|
||
|
||
### 同 session 还有一次
|
||
- 查 order history 时也踩过 (UTC 时间戳直接说)
|
||
|
||
### 铁律
|
||
- SQL/SQLite `created_at` / `timestamp` → 默认 UTC
|
||
- OKX API 返 `cTime` / `uTime` (毫秒) → UTC
|
||
- 推送和最终回复 → **永远 +8h 转北京**
|
||
- agent 内部思考 → 可混用,但**最终输出必须明确时区**
|
||
|
||
### 代码
|
||
```python
|
||
from datetime import datetime, timezone, timedelta
|
||
beijing = timezone(timedelta(hours=8))
|
||
ts_bj = datetime.fromtimestamp(ts_ms/1000, tz=beijing)
|
||
# 输出: "9-03 10:58 北京"
|
||
```
|
||
|
||
## 3. 短答案铁律再强化
|
||
|
||
### 用户原话
|
||
- "你能不能一次算好?" (一个话题搞 7 个独立操作)
|
||
- "我让你想杀了你"
|
||
- "你全在说废话, 我不要看你中间状态"
|
||
- "我要结论"
|
||
|
||
### 踩坑模式 (本次再发生)
|
||
用户问"为什么没推?" → agent:
|
||
1. 查 queue.db → 给过程
|
||
2. 查 order history → 给过程
|
||
3. 查 advisor cron → 给过程
|
||
4. 查 59108 → 给过程
|
||
5. 查时区 → 给过程
|
||
6. 最后才说"OKX 持仓=0,信号已处理"
|
||
|
||
**用户已经在第 2 步就爆炸**
|
||
|
||
### 正确模式
|
||
```
|
||
结论: [1-2 句话直接答]
|
||
证据: [1 行数字 / log / 命令输出]
|
||
详细: [可展开,但默认折叠]
|
||
```
|
||
|
||
### 触发短答案的场景
|
||
- "为什么 X 没发生" → 直接给原因 + 证据
|
||
- "拉一下" → 直接给数据,不问"拉什么"
|
||
- "减少一点" → 直接给具体数字建议
|
||
- "现在怎么样" → 直接给账户快照
|
||
|
||
## 4. contractSize ≠ 1 张 = 1 个币
|
||
|
||
### 用户原话
|
||
"我只能买 0.95 刀的? — 算法没问题吗?"
|
||
|
||
### 真相
|
||
- ZEC 永续合约: 1 张 = 0.01 ZEC
|
||
- 名义 = 0.01 × $956 = $9.56
|
||
- @10x 杠杆 → 保证金 = $0.96
|
||
- 用户以为"买 1 张"是买 1 ZEC → 实际买了 0.01 ZEC
|
||
|
||
### OKX 各币种 contractSize (常见)
|
||
- BTC-USDT-SWAP: 0.01 (1 张 = 0.01 BTC ≈ $1000)
|
||
- ETH-USDT-SWAP: 0.1 (1 张 = 0.1 ETH ≈ $400)
|
||
- SOL-USDT-SWAP: 1 (1 张 = 1 SOL ≈ $200)
|
||
- ZEC-USDT-SWAP: 0.01
|
||
- SKHYNIX-USDT-SWAP: ? (本次持仓看到 0.01 类似)
|
||
- SPCX-USDT-SWAP: ? (本次持仓看到 0.01 类似)
|
||
|
||
### 修复
|
||
- 下单前: `ex.load_markets()` + `ex.market(symbol)['contractSize']`
|
||
- 推送时: 展示**名义 $** 而不是张数
|
||
- "建议买 X 张" → 用户视角会误以为买 X 个币
|
||
- agent 应自动换算: "买 X 张 = 0.Y 币 = 名义 $Z"
|
||
|
||
### 涉及代码位置
|
||
```python
|
||
m = ex.market('ZEC/USDT:USDT')
|
||
contract_size = m['contractSize'] # 0.01
|
||
ct_val = contract_size * 1 # 单张 = 0.01 ZEC
|
||
margin_per_contract = ct_val * price / leverage
|
||
```
|
||
|
||
## 5. 信号队列 ≠ 真实持仓
|
||
|
||
### 用户原话
|
||
"怎么币圈的两个持仓全平了? 9 分钟了怎么没推?"
|
||
|
||
### 真相
|
||
- queue.db 最后一条是 SPCX 平仓 (10:58 北京) → 已 done
|
||
- 但 SKHYNIX 在 11:01 北京也被平了 — 用户**手动**在 OKX app 平的
|
||
- queue.db **没记录**用户手动操作
|
||
- agent 推断错: "SPCX 平了,SKHYNIX 应该还在" → 实际 SKHYNIX 也没了
|
||
|
||
### 铁律
|
||
- 用户问"X 持仓还在吗" → **`fetch_positions()` 拉 OKX 真实持仓**
|
||
- queue.db 只是入站信号,**不是持仓实况**
|
||
- 用户手动 / 强平 / SL 触发 / 自动止盈 → **都不在 queue**
|
||
- 持仓状态以 **OKX 为准**,不要拿 queue 最后一条推断
|
||
|
||
### 实战代码
|
||
```python
|
||
# 第一件事: 查真实持仓
|
||
positions = ex.fetch_positions()
|
||
for p in positions:
|
||
if abs(p['contracts']) > 0:
|
||
print(f"{p['symbol']} {p['side']}: {p['contracts']}张")
|
||
|
||
# 第二件事: 查 queue.db (辅助)
|
||
# 只看"信号源是什么",不推断持仓
|
||
```
|
||
|
||
## 6. 59108 降杠杆拒绝 + 用户已平仓
|
||
|
||
### 实战
|
||
- 11:07 北京: 收到 ZEC 加仓信号 → advisor 尝试 set_leverage 2x → 59108
|
||
- 但用户**已平完 ZEC** → 没有持仓 → OKX 报"杠杆太低/账户保证金不足" (cross_margin 不足新仓位)
|
||
|
||
### 铁律
|
||
- 收到加仓信号 → 先 `fetch_positions()` 看是否有持仓
|
||
- **没有此币种持仓** → silent skip, 标记 done, 推送"账户无此持仓,可能用户已手动平仓"
|
||
- 无持仓 + 想 2x → 直接 `create_order`,**不必先 set_leverage** (除非明确要改)
|
||
- 59108 多种原因: 杠杆太低 / cross_margin 不足 / 持仓已平仓位剩
|
||
|
||
## 7. 反模式 (本次再强化)
|
||
|
||
- ❌ "让我先查 X / 然后查 Y" 一长串前置 → 用户已经爆炸
|
||
- ❌ UTC 时间直接展示 → "时区又又又错了?"
|
||
- ❌ 减仓方向错 (SKHYNIX 占用高 → 减 SPCX) → "为什么减的是 SPCX"
|
||
- ❌ "1 张 = 1 个币"假设 → "我只能买 0.95 刀的?"
|
||
- ❌ 查 queue.db 就以为知道持仓 → "为什么全平了"漏掉用户手动
|
||
- ❌ 59108 盲目重试 set_leverage → 应该 silent skip 或直接 create_order
|
||
- ❌ 一个话题搞 7 个独立操作散乱 → 用户: "你能不能一次算好?"
|
||
|
||
## 8. 关联文件
|
||
|
||
- `~/.hermes/scripts/parse_order_errors.py` (sCode 翻译)
|
||
- `~/.hermes/scripts/order-errors-monitor.sh` (改调 parse_order_errors.py)
|
||
- `~/.hermes/skills/trading/okx-auto-position/scripts/okx_position_advisor.py` (SAFETY_FACTOR 1.07)
|
||
- `~/.hermes/trading/order_errors.log` (本次 59108 + 51008 共 4 条)
|
||
- `~/.hermes/trading/signal_queue.db` (queue 246 SPCX 平仓, queue 251 ZEC 加仓)
|
||
- `~/.hermes/memories/MEMORY.md` (新合并条目: 减仓方向 + 时区 + 短答案)
|
||
- `~/.hermes/skills/okx-trading-failover/SKILL.md` (新建: 自救公式 + sCode 翻译)
|
||
|
||
## 9. 完整时间线
|
||
|
||
| 北京时间 | 事件 | agent 行为 | 用户反应 |
|
||
|---|---|---|---|
|
||
| 10:00-11:30 | SKHYNIX 加仓 + 减仓 + SPCX 减仓 | 减错方向 | "为什么减的是 SPCX?" |
|
||
| 11:11 | advisor 加仓 SPCX 失败 51008 | 推错误原因 | OK (新推送格式工作) |
|
||
| 11:30 | advisor 推加仓 → 用户手动平仓 | queue 显示 done 但持仓已变 | "怎么两个全平了?" |
|
||
| 11:50 | agent 查了一堆中间状态 | 长篇过程 | "你让我想杀了你" |
|
||
| 11:51 | agent 说"02:58 北京" | UTC 当北京 | "时区又又又错了?" |
|
||
| 11:55 | ZEC 加仓 14 张 @ 2x 失败 59108 | advisor 报杠杆错误 | "我刚平仓了, 你重下" |
|
||
| 12:00 | 手动买 1 张 ZEC @ 10x | 显示占用 $0.96 | "我只能买 0.95 刀的?" |
|
||
| 12:10 | 用户说"调到 50% 占用" | 给选项列表 | "之前能买 139 张, 现在只能买 100 张?" |
|
||
| 12:20 | 挂单 945 买 99 张 | 执行 | OK |
|
||
|
||
## 10. 结论
|
||
|
||
本 session 教训密度极高: 5 个独立硬伤 + 1 个工作流问题。已全部 patch 到 v4.5.76。下次开 session 应该:
|
||
1. **结论先行** (绝不展示中间过程除非用户问)
|
||
2. **所有时间戳 +8h 转北京再展示**
|
||
3. **减仓必减同币种** (同方向反向操作)
|
||
4. **OKX 持仓状态以 fetch_positions() 为准**, queue.db 只是信号源
|
||
5. **contractSize 不是 1, 推送必须展示名义 $ 而非张数**
|
||
6. **59108 不一定是杠杆问题, 先查持仓再判断**
|