Files
Hermes-Skills/okx-auto-position/scripts/manual_order.py
T

134 lines
4.6 KiB
Python

#!/usr/bin/env python3
"""
manual_order.py - 用户手动 raw REST 下单 OKX 永续合约
用法:
python3 manual_order.py <INST_ID> <SIDE> <SZ> [leverage]
示例:
python3 manual_order.py MSTR-USDT-SWAP sell 0.01 5
python3 manual_order.py BTC-USDT-SWAP buy 0.1 5
python3 manual_order.py ETH-USDT-SWAP sell 1 5
参数:
INST_ID : OKX 合约 instId, 如 MSTR-USDT-SWAP
SIDE : buy / sell
SZ : 张数 (OKX 最小变动单位一般是 0.01 张)
leverage : 可选, 默认 5, 自动调杠杆
⚠ 部署位置: ~/.hermes/skills/trading/okx-auto-position/scripts/manual_order.py
调用方: process_signal 或用户手动 raw execute
反查: 调用后必须跑 check_account.py 验实际 contracts
"""
import json
import os
import sys
import time
import hmac
import hashlib
import base64
import requests
from pathlib import Path
def okx_get(path, t=15):
"""GET 请求 (用于查持仓/余额/调杠杆)"""
creds = open(Path.home() / '.bashrc').read()
k = s = pw = None
for l in creds.splitlines():
if l.startswith('export OKX_API_KEY='):
k = l.split('=', 1)[1].strip().strip('"')
elif l.startswith('export OKX_SECRET='):
s = l.split('=', 1)[1].strip().strip('"')
elif l.startswith('export OKX_PASSPHRASE='):
pw = l.split('=', 1)[1].strip().strip('"')
ts = time.strftime('%Y-%m-%dT%H:%M:%S.000Z', time.gmtime())
method = 'GET'
pre = ts + method + path
sig = base64.b64encode(hmac.new(s.encode(), pre.encode(), hashlib.sha256).digest()).decode()
h = {'OK-ACCESS-KEY': k, 'OK-ACCESS-SIGN': sig, 'OK-ACCESS-TIMESTAMP': ts,
'OK-ACCESS-PASSPHRASE': pw, 'Content-Type': 'application/json'}
r = requests.get('https://www.okx.com' + path, headers=h, timeout=t,
proxies={'http': 'http://127.0.0.1:7890', 'https': 'http://127.0.0.1:7890'})
return r.json()
def okx_post(path, body, t=15):
"""POST 请求 (用于下单/撤单/调杠杆)"""
creds = open(Path.home() / '.bashrc').read()
k = s = pw = None
for l in creds.splitlines():
if l.startswith('export OKX_API_KEY='):
k = l.split('=', 1)[1].strip().strip('"')
elif l.startswith('export OKX_SECRET='):
s = l.split('=', 1)[1].strip().strip('"')
elif l.startswith('export OKX_PASSPHRASE='):
pw = l.split('=', 1)[1].strip().strip('"')
ts = time.strftime('%Y-%m-%dT%H:%M:%S.000Z', time.gmtime())
method = 'POST' # ⚠ 必须是字符串, 别写 method=POST
body_str = json.dumps(body)
pre = ts + method + path + body_str
sig = base64.b64encode(hmac.new(s.encode(), pre.encode(), hashlib.sha256).digest()).decode()
h = {'OK-ACCESS-KEY': k, 'OK-ACCESS-SIGN': sig, 'OK-ACCESS-TIMESTAMP': ts,
'OK-ACCESS-PASSPHRASE': pw, 'Content-Type': 'application/json'}
r = requests.post('https://www.okx.com' + path, headers=h, data=body_str,
timeout=t, proxies={'http': 'http://127.0.0.1:7890',
'https': 'http://127.0.0.1:7890'})
return r.json()
def set_leverage(inst_id, lever, side='net'):
"""调杠杆 (调杠杆后下单才能用对应张数)"""
body = {'instId': inst_id, 'lever': str(lever), 'mgnMode': 'cross', 'posSide': side}
return okx_post('/api/v5/account/set-leverage', body)
def market_order(inst_id, side, sz, td_mode='cross', reduce_only=False):
"""市价单"""
body = {
'instId': inst_id,
'tdMode': td_mode,
'side': side,
'posSide': 'net',
'ordType': 'market',
'sz': str(sz),
}
if reduce_only:
body['reduceOnly'] = True
return okx_post('/api/v5/trade/order', body)
def main():
if len(sys.argv) < 4:
print(__doc__)
sys.exit(1)
inst_id = sys.argv[1]
side = sys.argv[2] # buy / sell
sz = sys.argv[3]
leverage = int(sys.argv[4]) if len(sys.argv) > 4 else 5
print(f'📊 准备下单:')
print(f' 币种: {inst_id}')
print(f' 方向: {side}')
print(f' 张数: {sz}')
print(f' 杠杆: {leverage}x')
# 1. 调杠杆
print(f'\n1️⃣ 调杠杆 {leverage}x...')
r = set_leverage(inst_id, leverage)
print(json.dumps(r, ensure_ascii=False, indent=2))
# 2. 市价单
print(f'\n2️⃣ 市价单 {side} {sz} 张...')
r = market_order(inst_id, side, sz)
print(json.dumps(r, ensure_ascii=False, indent=2))
# 3. 提示反查
print(f'\n⚠ execute 后必查:')
print(f' timeout 30 python3 ~/.hermes/skills/trading/okx-auto-position/scripts/check_account.py {inst_id}')
if __name__ == '__main__':
main()