Files
Hermes-Skills/okx-auto-position/references/okx-pnl-from-bills-api.md
T

3.3 KiB
Raw Blame History

OKX 已实现盈亏 = /account/bills balChg (2026-09-22)

核心结论

永远不要用 (exit - entry) * 张数 * ctVal 自己算 pnl —— OKX app 显示的真实数 = /api/v5/account/bills 的 balChg 累加。

实测对账:

币种 公式算 OKX app 用 bills 拿 差
BTC (平 2.58 张) 取决于入场价 -$46.51 -$46.14 0.37 (funding 几小时)
ZEC (平 157 张) +$10.79 假设 +$13.44 +$14.42 0.98 (funding 累计)

API: /api/v5/account/bills

参数:

GET /api/v5/account/bills
  instId: ZEC-USDT-SWAP      # 不传也返回全部 (按 instType 过滤)
  instType: SWAP             # 必填, 否则 50014
  type: 2                    # 2 = 交易/手续费类
  subType: 1 / 2             # 1=已实现盈亏账单, 2=单笔成交
  limit: 50
  begin: <ms-timestamp>      # 7天前

返回字段 (per item):

  • ts: 时间戳 (毫秒)
  • instId: 币种
  • type: 2 (永不变)
  • subType: 1 (已实现盈亏/手续费) 或 2 (单笔成交)
  • pnl: 单笔盈亏 (subType=2 有值, subType=1=0)
  • balChg: 真实数 (含手续费/funding) ← 这就是 OKX app 显示值
  • fillPx: 成交价 (subType=2 有)
  • fillSz: 成交张数 (subType=2 有)

算法 (唯一可信)

def _fetch_close_pnl(symbol_usdt):
    inst_id = f"{symbol_usdt}-USDT-SWAP"
    bills = _okx_raw_request('GET', '/api/v5/account/bills', {
        'instId': inst_id, 'instType': 'SWAP', 'type': '2',
        'limit': '50', 'begin': str(<7d-ago-ms>)
    })
    # 找最新一次"平仓"事件 = 绝对值最大的 type=2 账单
    # (过滤掉手续费 subType=1, 绝对值 < 5 的)
    latest_ts = None
    for b in bills['data']:
        if b['type'] != '2':
            continue
        st = b['subType']
        bal = float(b['balChg'])
        if st == '1' and abs(bal) < 5.0:  # 过滤手续费
            continue
        ts = int(b['ts'])
        if latest_ts is None or ts > latest_ts:
            latest_ts = ts
    # 累加同一时刻 ±30 秒所有 type=2 账单
    pnl_total = sum(
        float(b['balChg']) for b in bills['data']
        if b['type'] == '2' and abs(int(b['ts']) - latest_ts) <= 30000
    )
    return latest_ts, pnl_total

推送格式 (已集成 process_signal.py)

✅ ZEC 做多 🟩 10x 已市价全平 | 熬鹰
long 157张 @ 1247.50 → 平仓价 $1248.19
⏱ 持仓: 1天3小时
💰 实际盈亏: **+$14.42** (OKX 实测, 含手续费/funding)
💡 OKX 历史订单实数据

关键 bug 教训 (2026-09-22)

❌ 旧算法 (错的)

用 /api/v5/trade/orders-history 的 info.pnl 字段 — 只算单笔成交盈亏,不含手续费和 funding,与 OKX app 不符。

❌ 早期尝试 (错)

用 (exit - entry) * contracts * ctVal 自己算 — 不包含手续费和 funding,实际是大码折扣。

✅ 唯一对的

/api/v5/account/bills 的 balChg 字段 + 30秒窗口累加 = OKX app。

与 cron 30秒精度配合

check_upl_notify.py 也算 pnl —— 如果做推送,可以用同样的 bills 接口拿实时已实现+ fetch_positions 的 UPL 做总盈亏。

相关代码

  • process_signal.py: process_signal_close() close_position_raw() — 平仓函数
  • process_signal.py: _fetch_close_pnl_details() — bills 累加 (实际盈亏拿法)
  • check_upl_notify.py: should_notify() — UPL 跨 100 倍数推送