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

114 lines
4.1 KiB
Markdown

---
name: okx-raw-rest-signing-pitfall
description: "OKX v5 raw REST HMAC 签名: GET 必须 '? + sorted query', POST 必须 body 直接拼; 任何 'Invalid Sign' 几乎都是这里错"
version: 1.0.0
type: reference
---
# OKX v5 Raw REST HMAC 签名坑 (2026-07-13 v4.5.3 实测)
## 签名规范 (按 ccxt/okx.py sign() 实现)
```python
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 直接抠)
```python
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)
```python
# ❌ 这种写法会一直 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` 第一反应 = 检查 `?` 前缀和字典序