Files
Hermes-Skills/okx-auto-position/references/okx-raw-rest-signing-pitfall.md
T
mike 98efe09387 lottery-hk: v1.2.6 — 特码脚本改中文文件名 lottery_特码.py
User 2026-07-30 反馈:
- 脚本名 lottery_te_ma.py → lottery_特码.py (中文, 更直观)
- 同步 SKILL.md 引用 (description + scripts 段)
- version 1.2.5 → 1.2.6
2026-07-30 20:43:39 +08:00

4.1 KiB

name, description, version, type
name description version type
okx-raw-rest-signing-pitfall OKX v5 raw REST HMAC 签名: GET 必须 '? + sorted query', POST 必须 body 直接拼; 任何 'Invalid Sign' 几乎都是这里错 1.0.0 reference

OKX v5 Raw REST HMAC 签名坑 (2026-07-13 v4.5.3 实测)

签名规范 (按 ccxt/okx.py sign() 实现)

auth = timestamp + method.upper() + request_path   # request_path = '/api/v5/...'

if method == 'GET':
    if query:
        urlencoded_query = '?' + self.urlencode(query)  # ⚠️ 必须带 ? 前缀
        auth += urlencoded_query
elif method == 'POST':
    if isArray or query:
        body = self.json(query)
        auth += body

3 个常见错:

  1. GET 签名 = ts + 'GET' + path + json.dumps(params) (没有 ? 前缀, 用 json 而非 urlencode)
  2. query 没按字典序排序 (ccxt 默认按字典序, requests 的 urlencode 保持插入顺序)
  3. POST body 用了 urllib.parse.urlencode({...}) 而不是 json.dumps(...)

症状: {"code":"50111","msg":"Invalid Sign"}code=51000 "Parameter posSide error"

正确实现 (从 process_signal.py v4.5.3 直接抠)

import hmac, hashlib, base64, urllib.parse, re, os, requests, time
from datetime import datetime

# 1. 读 bashrc 凭证 (绕过 shell 展开)
creds = {}
with open(os.path.expanduser('~/.bashrc')) as f:
    for line in f:
        line = line.strip()
        if line.startswith('export OKX_'):
            k, v = line.replace('export ', '').split('=', 1)
            creds[k] = v.strip().strip('"').strip("'")
for k, v in creds.items():
    if '${' not in v: os.environ[k] = v
for k, v in creds.items():
    if '${' in v:
        os.environ[k] = re.sub(r'\$\{(\w+)\}', lambda m: os.environ.get(m.group(1), ''), v)

# 2. 签名
ts = datetime.utcnow().strftime('%Y-%m-%dT%H:%M:%S.') + f"{datetime.utcnow().microsecond // 1000:03d}Z"
body_str = json.dumps(body) if body else ''
auth = ts + method.upper() + path

if method.upper() == 'GET':
    if params:
        # 关键: 按字典序排序 + ? 前缀
        sorted_q = '&'.join(f"{k}={urllib.parse.quote_plus(str(v), safe='')}" for k, v in sorted(params.items()))
        auth += '?' + sorted_q
        query = '?' + sorted_q
    else:
        query = ''
else:  # POST
    auth += body_str
    query = ''

sig = base64.b64encode(hmac.new(os.environ['OKX_SECRET'].encode(), auth.encode(), hashlib.sha256).digest()).decode()

# 3. 请求
headers = {
    'OK-ACCESS-KEY': os.environ['OKX_API_KEY'],
    'OK-ACCESS-SIGN': sig,
    'OK-ACCESS-TIMESTAMP': ts,
    'OK-ACCESS-PASSPHRASE': os.environ['OKX_PASSPHRASE'],
    'Content-Type': 'application/json',
}
proxies = {'http': 'http://127.0.0.1:7890', 'https': 'http://127.0.0.1:7890'}
url = f'https://www.okx.com{path}{query}'

r = requests.get(url, headers=headers, proxies=proxies, timeout=15) if method.upper() == 'GET' \
    else requests.post(url, data=body_str, headers=headers, proxies=proxies, timeout=15)
return r.json()

实战验证 (2026-07-13)

步骤 错版 正版
GET /api/v5/account/positions?instId=SKHY-USDT-SWAP Invalid Sign code=0, data=[{pos: -2.89, side: short, ...}]
POST /api/v5/trade/order 平仓 51000 Parameter posSide error (posSide 没设) code=0, data=[{ordId: MOCK-12345}]

应用位置 (2026-07-30)

  • process_signal.py:close_position_raw() (v4.5.3 平仓信号自动跟平)
  • signal_inbox.py 不直接调 raw REST, 但通过 process_signal.py 触发

老的错版 (在 references/okx-rest-fallback.md)

# ❌ 这种写法会一直 Invalid Sign
ts = time.strftime('%Y-%m-%dT%H:%M:%S.000Z', time.gmtime())
msg = ts + 'GET' + path + (json.dumps(params) if params else '')

下次 session 看老 reference 复制会再次踩坑。如果改 okx-rest-fallback.md, 用上面"正确实现"替换那个函数。

给下次 session 的指令

任何 raw REST 调用 OKX 时:

  1. 复制上面的"正确实现", 不要复制 references/okx-rest-fallback.md 里的版本
  2. 跑通后再封装成函数, 别一边写一边 debug 签名
  3. 看到 Invalid Sign 第一反应 = 检查 ? 前缀和字典序