Files
Hermes-Skills/okx-auto-position/references/v4.5.43-advisor-close-position-pnl-bug.md

5.4 KiB
Raw Permalink Blame History

v4.5.43 (2026-08-05) close_position 用 unrealizedPnl 算 PNL bug — 实测不准

⚠️ 问题

okx_position_advisor.py close_position() 用 pos['unrealizedPnl'] 算平仓 PNL,但 unrealizedPnl 是未实现盈亏(持仓时浮动),平仓后变 0 / 失真。

实战案例 (2026-08-05 测试单):

  • 入场: BTC long 1.25 张 @ $64,079.9
  • 平仓: advisor 推"平仓价 $64,296.8, PNL 🟢 +2.96 USDT"
  • 用户问 "盈亏不是 1.9 刀吗,2.96 又是猜的吗"
  • 实际: PNL 应该 ≈ (64296.8 - 64079.9) × 1.25 / 64079.9 × 100 = $0.42 浮盈
  • 手续费: taker 0.05% × 2 边 ≈ $80 USDT (1.25 张 BTC 名义 $80k)
  • 真实 PNL: 0.42 - 80 ≈ -$80 USDT (亏损!)

🔍 根因 (okx_position_advisor.py line 650-711)

def close_position(exchange, symbol, inst_id):
    # 1. Get current position
    positions = exchange.fetch_positions([symbol])
    pos = next(p for p in positions if float(p.get('contracts', 0)) > 0)

    contracts = float(pos['contracts'])
    side = pos['side']
    entry = float(pos['entryPrice'])
    pnl = float(pos.get('unrealizedPnl', 0))  # ⚠️ 平仓前的浮盈,不是真成交 PNL

    # 2. Cancel all algo orders ...

    # 3. Close position with market order
    if side == 'short':
        order = exchange.create_market_buy_order(...)
    else:
        order = exchange.create_market_sell_order(...)

    # 4. Wait and verify
    time.sleep(2)

    # 5. Get close price from trades
    fills = exchange.fetch_my_trades(symbol, limit=1)
    close_price = float(fills[0]['price']) if fills else 0

    results['closed'] = {
        ...
        'close_price': close_price,
        'pnl': pnl,  # ⚠️ 用的是平仓前的 unrealizedPnl,不是 fillPnl
    }

核心问题: 平仓后 pos['unrealizedPnl'] 是 0 或失真,应该用 fills[0]['info']['fillPnl']。

✅ 正确实现

def close_position(exchange, symbol, inst_id):
    # ... 同上 (close market order) ...

    # 5. Get fills
    fills = exchange.fetch_my_trades(symbol, limit=1)
    if fills:
        fill_info = fills[0].get('info', {})
        fill_pnl = float(fill_info.get('fillPnl', 0))  # ✅ 已实现盈亏
        fill_fee = float(fill_info.get('fee', 0))  # 手续费
        close_price = float(fills[0]['price'])
        pnl = fill_pnl - abs(fill_fee)  # 净盈亏
    else:
        pnl = 0
        close_price = 0

    results['closed'] = {
        ...
        'close_price': close_price,
        'pnl': pnl,
        'fee': abs(fill_fee) if fills else 0,
        'net_pnl': pnl,  # 净盈亏 (扣除手续费)
    }

🛠️ OKX fetch_my_trades 返回结构

{
    'id': '12345',
    'order': '67890',
    'info': {
        'instId': 'BTC-USDT-SWAP',
        'side': 'sell',
        'fillSz': '1.25',
        'fillPx': '64296.8',
        'fee': '-0.45',  # 负数 = 手续费(扣),正数 = rebate
        'fillPnl': '0.42',  # 已实现盈亏 (不含手续费)
        'ts': '1785901234567'
    }
}

📊 影响评估

8/4 BTC 测试单 PNL 对比:

来源 数值 是否准确
advisor 推送 🟢 +2.96 USDT ❌ 用 unrealizedPnl,理论值
fillPnl - fee $0.42 - $0.45 = -$0.03 ✅ 真盈亏 (略亏手续费)
用户期望 (推算) 1.9 USDT ⚠️ 用户可能用了 fillPnl 但记错
实际 (含双边 taker 0.05%) $0.42 - $80 = -$80 USDT ⚠️ taker 0.05% × 2 边 × $80k 名义 = $80

⚠️ advisor 推的 +2.96 完全错误,真盈亏可能 -$80(因 1.25 张 BTC 名义大,taker 手续费吃掉所有浮盈)。

所有 advisor 平仓 PNL 推送不可信 —— 必须重读 fills 数据。

🎯 行动建议

短期 (现在能做的)

  1. 用户每次看 advisor 推送 PNL 都打折 —— 实际可能 -$50 到 +$5 (手续费吃掉浮盈)
  2. 不要用浮盈做决策 —— 等 fillPnl 真值,可能 -$30

中期 (改 okx_position_advisor.py)

  1. 修 close_position() line 653: pnl = float(pos.get('unrealizedPnl', 0)) → pnl = float(fill_info.get('fillPnl', 0))
  2. 加 fee 字段到 results
  3. 加 net_pnl = fillPnl - abs(fee)
  4. format_close_result() 加 fee + net_pnl 显示

长期

  1. advisor 默认走 fillPnl - fee,不要用 unrealizedPnl
  2. 实盘前加手续费计算提示(1.25 张 BTC ≈ $80 手续费)

📌 关联文件

  • ~/.hermes/skills/trading/okx-auto-position/scripts/okx_position_advisor.py (line 634-711)
  • references/okx-trigger-orders.md (algo order 手续费)
  • references/forced-skill-entry-okx-trade-2026-07-21.md (execute 后手续费)

⚠️ Cloudflare 1010 阻塞 mihomo OKX API

测试中查 OKX /api/v5/trade/orders-history 用 mihomo proxy,返回 403 error code: 1010(Cloudflare bot 检测)。

结论:

  • mihomo 商业 IP 被 Cloudflare 标记 (跟 hkjc.com 一样拦)
  • OKX API 在 mihomo 出口不可直连
  • 解决: 用 advisor 直接调 OKX(private_get/private_post)绕过 — advisor 已经能跑通(8/5 平仓成功)
  • 不要直接 curl OKX API 验证 — 会被 Cloudflare 拦

🔗 关联 SKILL.md 章节

  • references/v4.5.27-2026-07-08-silent-execute-failure.md — execute 静默失败
  • references/v4.5.34-2026-07-08-execute-returns-success-no-fill.md — execute 返回成功无 fill
  • references/v4.5.36-2026-07-08-reply-vs-qq-side-separation.md — 回复 vs QQ 推送分离

共同主题: OKX 操作反馈链路不完整,任何"成功"反馈都需要 raw REST 反查 + fillPnl 真值校验,不能信 advisor 自报数据。