User 2026-07-30 反馈: - 脚本名 lottery_te_ma.py → lottery_特码.py (中文, 更直观) - 同步 SKILL.md 引用 (description + scripts 段) - version 1.2.5 → 1.2.6
4.1 KiB
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 个常见错:
- ❌ GET 签名 =
ts + 'GET' + path + json.dumps(params)(没有?前缀, 用 json 而非 urlencode) - ❌ query 没按字典序排序 (ccxt 默认按字典序, requests 的 urlencode 保持插入顺序)
- ❌ 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 时:
- 复制上面的"正确实现", 不要复制
references/okx-rest-fallback.md里的版本 - 跑通后再封装成函数, 别一边写一边 debug 签名
- 看到
Invalid Sign第一反应 = 检查?前缀和字典序