update README

This commit is contained in:
shiyao.zhu 2023-02-16 15:00:24 +08:00
commit 617081b8a9
6 changed files with 44 additions and 673 deletions

3
.gitignore vendored
View File

@ -29,4 +29,5 @@ build/
### VS Code ### ### VS Code ###
.vscode/ .vscode/
setup.py setup.py
id_rsa*

156
README.md
View File

@ -1,143 +1,66 @@
[TOC] ### Overview
This is an unofficial Python wrapper for the [OKX exchange v5 API](https://www.okx.com/okx-api)
### 如何使用? If you came here looking to purchase cryptocurrencies from the OKX exchange, please go [here](https://www.okx.com/).
`python版本>=3.9 #### Source code
https://github.com/okxapi/python-okx
#### OKX API Telegram
https://t.me/OKXAPI
#### API trading tutorials
- Spot trading: https://www.okx.com/learn/spot-trading-with-jupyter-notebook
- Derivative trading: https://www.okx.com/learn/derivatives-trading-with-jupyter-notebook
`WebSocketAPIautobahn.twisted>=22.10.0` Make sure you update often and check the [Changelog](https://www.okx.com/docs-v5/log_en/) for new features and bug fixes.
#### 第一步下载SDK安装相关所需库 ### Features
- Implementation of all Rest API endpoints.
- Private and Public Websocket implementation
- Testnet support
- Websocket handling with reconnection and multiplexed connections
1.1 下载`python SDK` ### Quick start
#### Prerequisites
* 将SDK目录`Clone`或者`Download`到本地,选择使用`okx-python-sdk-api-v5`即可
1.2 安装所需库
```python
pip install requests
pip install autobahn\[twisted\]
pip install pyOpenSSL
```
#### 第二步:配置个人信息
2.1 如果还未有API可[点击](https://www.okx.com/account/users/myApi)前往官网进行申请
```python
api_key = ""
secret_key = ""
passphrase = ""
```
#### 第三步:调用接口
* RestAPI
* 运行`example.py`
* 解开相应方法的注释传参调用各接口即可
* WebSocketAPI
* 参考Test文件夹下`WsPrivate`和`WsPublic`文件示例;
* 根据`公共频道`/`私有频道`选择对应`url`(如果是私有频道需要设置登陆信息),传入相应参数即可。
```python
# WebSocket公共频道
url = "wss://ws.okx.com:8443/ws/v5/public"
# WebSocket私有频道
url = "wss://ws.okx.com:8443/ws/v5/private"
```
```python
# 公共频道 不需要登录行情持仓总量K线标记价格深度资金费率等
参考 WsPublicTest.py
# 私有频道 需要登录(账户,持仓,订单等)
参考 WsPrivateTest.py
```
附言:
* 如果对API尚不了解建议参考`OKX`官方[API文档](https://www.okx.com/docs-v5/zh/)
* 使用RestAPI的用户可以通过参考Test文件夹下的示例设置正确的参数即可
* 使用WebSocketAPI的用户可以通过参考Test文件夹下的`WsPublicTest.py`和`WsPrivateTest.py`,设置正确的参数即可
* 若使用`WebSocketAPI`遇到问题建议参考相关链接
* `asyncio`、`websockets`文档/`github`
https://docs.python.org/3/library/asyncio-dev.html
https://websockets.readthedocs.io/en/stable/intro.html
https://github.com/aaugustin/websockets
* 关于`code=1006`
https://github.com/Rapptz/discord.py/issues/1996
https://github.com/aaugustin/websockets/issues/587
### How to use ?
`python version>=3.9` `python version>=3.9`
`WebSocketAPI autobahn.twisted>=22.10.0` `WebSocketAPI websockets package advise version 6.0`
#### Step 1: Download the SDK and install the necessary libraries #### Step 1: register an account on OKX and apply for an API key
- Register for an account: https://www.okx.com/account/register
- Apply for an API key: https://www.okx.com/account/users/myApi
1.1 Download python SDK #### Step 2: install python-okx
- `Clone` or `Download` the SDK directory to your local directory,choose to use `okx-python-sdk-api-v5`
1.2 Install the necessary libraries
```python ```python
pip install requests pip install python-okx
pip install autobahn\[twisted\]
pip install pyOpenSSL
``` ```
#### Step 2: Configure Personal Information #### Step 3: Run examples
2.1 If you have no API[Click here](https://www.okx.com/account/users/myApi) to the official websit to apply for the API
2.2 Fill out all necessary informatiuon in `example.pyRestAPI` and `websocket_example.pyWebSocketAPI`
- Fill in API credentials in the corresponding examples
```python ```python
api_key = "" api_key = ""
secret_key = "" secret_key = ""
passphrase = "" passphrase = ""
``` ```
#### Step 3: Call API
- RestAPI - RestAPI
- Run `example.py` - For spot trading: run example/get_started_en.ipynb
- Uncomment the corresponding method and then pass the arguments and call the interfaces - For derivative trading: run example/trade_derivatives_en.ipynb
- Tweak the value of the parameter `flag` (live trading: 0, demo trading: 1
) to switch between live and demo trading environment
- WebSocketAPI - WebSocketAPI
- Open `websocket_example.py` - Run test/WsPrivateTest.py for private websocket channels
- According to the `public channel`/`private channel`, select the corresponding `url`, the corresponding start method, and pass in the corresponding parameters - Run test/WsPublicTest.py for public websocket channels
- Use different URLs for different environment
- Live trading URLs: https://www.okx.com/docs-v5/en/#overview-production-trading-services
- Demo trading URLs: https://www.okx.com/docs-v5/en/#overview-demo-trading-services
```python Note
# WebSocket public channel
url = "wss://ws.okx.com:8443/ws/v5/public?brokerId=9999"
# WebSocket private channel - To learn more about OKX API, visit official [OKX API documentation](https://www.okx.com/docs-v5/en/)
url = "wss://ws.okx.com:8443/ws/v5/private?brokerId=9999"
```
P.S. - If you face any questions when using `WebSocketAPI`,you can consult the following links
- If you know little about API, advise consulting the offical [API document](https://www.okx.com/docs-v5/en/)
- User with RestAPI can configure parameter `flag` in `example.py` in to choose to access to real trading or demo trading
- User with WebSocketAPI can ucomment the corresponding `url` to choose to access to real trading or demo trading
- Rest API support request by http2, you can refer to http2_example
- If you face any questions when using `WebSocketAPI`,you can consult related link
- `asyncio`、`websockets` document/`github` - `asyncio`、`websockets` document/`github`
@ -153,6 +76,3 @@ P.S.
https://github.com/Rapptz/discord.py/issues/1996 https://github.com/Rapptz/discord.py/issues/1996
https://github.com/aaugustin/websockets/issues/587 https://github.com/aaugustin/websockets/issues/587
``` ```

View File

@ -1,31 +0,0 @@
import json
import time
import okx.Account as Account
async def http2_request(request, parameters):
while 1:
begin = time.time()
if type(parameters) is list:
result = request(*parameters)
else:
result = request(**parameters)
end = time.time()
cost = end - begin
print(f'request_cost:{cost}\nresponse_body:{json.dumps(result)}')
api_key = ""
secret_key = ""
passphrase = ""
# flag是实盘与模拟盘的切换参数 flag is the key parameter which can help you to change between demo and real trading.
# flag = '1' # 模拟盘 demo trading
flag = '0' # 实盘 real tradiang
if __name__ == '__main__':
# account api
accountAPI = Account.AccountAPI(api_key, secret_key, passphrase, False, flag)
accountAPI.get_account_config()
accountAPI.get_greeks('BTC')

View File

@ -2,6 +2,7 @@ import time
from okx.websocket.WsPrivate import WsPrivate from okx.websocket.WsPrivate import WsPrivate
def privateCallback(message): def privateCallback(message):
print("WsPrivate subscribe callback:", message) print("WsPrivate subscribe callback:", message)

View File

@ -1,12 +1,14 @@
import time import time
from okx.websocket.WsPublic import WsPublic from okx.websocket.WsPublic import WsPublic
def publicCallback(message): def publicCallback(message):
print("publicCallback", message) print("publicCallback", message)
if __name__ == '__main__': if __name__ == '__main__':
url = "wss://wspri.coinall.ltd:8443/ws/v5/public?brokerId=9999" url = "wss://ws.okx.com:8443/ws/v5/public"
ws = WsPublic(url=url) ws = WsPublic(url=url)
ws.start() ws.start()
args = [] args = []

View File

@ -1,522 +0,0 @@
import asyncio
import base64
import datetime
import hmac
import json
import time
import zlib
import requests
import websockets
def get_timestamp():
now = datetime.datetime.now()
t = now.isoformat("T", "milliseconds")
return t + "Z"
def get_server_time():
url = "https://www.okx.com/api/v5/public/time"
response = requests.get(url)
if response.status_code == 200:
return response.json()['data'][0]['ts']
else:
return ""
def get_local_timestamp():
return int(time.time())
def login_params(timestamp, api_key, passphrase, secret_key):
message = timestamp + 'GET' + '/users/self/verify'
mac = hmac.new(bytes(secret_key, encoding='utf8'), bytes(message, encoding='utf-8'), digestmod='sha256')
d = mac.digest()
sign = base64.b64encode(d)
login_param = {"op": "login", "args": [{"apiKey": api_key,
"passphrase": passphrase,
"timestamp": timestamp,
"sign": sign.decode("utf-8")}]}
login_str = json.dumps(login_param)
return login_str
def partial(res):
data_obj = res['data'][0]
bids = data_obj['bids']
asks = data_obj['asks']
instrument_id = res['arg']['instId']
# print('全量数据bids为' + str(bids))
# print('档数为:' + str(len(bids)))
# print('全量数据asks为' + str(asks))
# print('档数为:' + str(len(asks)))
return bids, asks, instrument_id
def update_bids(res, bids_p):
# 获取增量bids数据
bids_u = res['data'][0]['bids']
# print('增量数据bids为' + str(bids_u))
# print('档数为:' + str(len(bids_u)))
# bids合并
for i in bids_u:
bid_price = i[0]
for j in bids_p:
if bid_price == j[0]:
if i[1] == '0':
bids_p.remove(j)
break
else:
del j[1]
j.insert(1, i[1])
break
else:
if i[1] != "0":
bids_p.append(i)
else:
bids_p.sort(key=lambda price: sort_num(price[0]), reverse=True)
# print('合并后的bids为' + str(bids_p) + ',档数为:' + str(len(bids_p)))
return bids_p
def update_asks(res, asks_p):
# 获取增量asks数据
asks_u = res['data'][0]['asks']
# print('增量数据asks为' + str(asks_u))
# print('档数为:' + str(len(asks_u)))
# asks合并
for i in asks_u:
ask_price = i[0]
for j in asks_p:
if ask_price == j[0]:
if i[1] == '0':
asks_p.remove(j)
break
else:
del j[1]
j.insert(1, i[1])
break
else:
if i[1] != "0":
asks_p.append(i)
else:
asks_p.sort(key=lambda price: sort_num(price[0]))
# print('合并后的asks为' + str(asks_p) + ',档数为:' + str(len(asks_p)))
return asks_p
def sort_num(n):
if n.isdigit():
return int(n)
else:
return float(n)
def check(bids, asks):
# 获取bid档str
bids_l = []
bid_l = []
count_bid = 1
while count_bid <= 25:
if count_bid > len(bids):
break
bids_l.append(bids[count_bid - 1])
count_bid += 1
for j in bids_l:
str_bid = ':'.join(j[0: 2])
bid_l.append(str_bid)
# 获取ask档str
asks_l = []
ask_l = []
count_ask = 1
while count_ask <= 25:
if count_ask > len(asks):
break
asks_l.append(asks[count_ask - 1])
count_ask += 1
for k in asks_l:
str_ask = ':'.join(k[0: 2])
ask_l.append(str_ask)
# 拼接str
num = ''
if len(bid_l) == len(ask_l):
for m in range(len(bid_l)):
num += bid_l[m] + ':' + ask_l[m] + ':'
elif len(bid_l) > len(ask_l):
# bid档比ask档多
for n in range(len(ask_l)):
num += bid_l[n] + ':' + ask_l[n] + ':'
for l in range(len(ask_l), len(bid_l)):
num += bid_l[l] + ':'
elif len(bid_l) < len(ask_l):
# ask档比bid档多
for n in range(len(bid_l)):
num += bid_l[n] + ':' + ask_l[n] + ':'
for l in range(len(bid_l), len(ask_l)):
num += ask_l[l] + ':'
new_num = num[:-1]
int_checksum = zlib.crc32(new_num.encode())
fina = change(int_checksum)
return fina
def change(num_old):
num = pow(2, 31) - 1
if num_old > num:
out = num_old - num * 2 - 2
else:
out = num_old
return out
# subscribe channels un_need login
async def subscribe_without_login(url, channels):
l = []
while True:
try:
async with websockets.connect(url) as ws:
sub_param = {"op": "subscribe", "args": channels}
sub_str = json.dumps(sub_param)
await ws.send(sub_str)
print(f"send: {sub_str}")
while True:
try:
res = await asyncio.wait_for(ws.recv(), timeout=25)
except (asyncio.TimeoutError, websockets.exceptions.ConnectionClosed) as e:
try:
await ws.send('ping')
res = await ws.recv()
print(res)
continue
except Exception as e:
print("连接关闭,正在重连……")
break
print(get_timestamp() + res)
res = eval(res)
if 'event' in res:
continue
for i in res['arg']:
if 'books' in res['arg'][i] and 'books5' not in res['arg'][i]:
# 订阅频道是深度频道
if res['action'] == 'snapshot':
for m in l:
if res['arg']['instId'] == m['instrument_id']:
l.remove(m)
# 获取首次全量深度数据
bids_p, asks_p, instrument_id = partial(res)
d = {}
d['instrument_id'] = instrument_id
d['bids_p'] = bids_p
d['asks_p'] = asks_p
l.append(d)
# 校验checksum
checksum = res['data'][0]['checksum']
# print('推送数据的checksum为' + str(checksum))
check_num = check(bids_p, asks_p)
# print('校验后的checksum为' + str(check_num))
if check_num == checksum:
print("校验结果为True")
else:
print("校验结果为False正在重新订阅……")
# 取消订阅
await unsubscribe_without_login(url, channels)
# 发送订阅
async with websockets.connect(url) as ws:
sub_param = {"op": "subscribe", "args": channels}
sub_str = json.dumps(sub_param)
await ws.send(sub_str)
print(f"send: {sub_str}")
elif res['action'] == 'update':
for j in l:
if res['arg']['instId'] == j['instrument_id']:
# 获取全量数据
bids_p = j['bids_p']
asks_p = j['asks_p']
# 获取合并后数据
bids_p = update_bids(res, bids_p)
asks_p = update_asks(res, asks_p)
# 校验checksum
checksum = res['data'][0]['checksum']
# print('推送数据的checksum为' + str(checksum))
check_num = check(bids_p, asks_p)
# print('校验后的checksum为' + str(check_num))
if check_num == checksum:
print("校验结果为True")
else:
print("校验结果为False正在重新订阅……")
# 取消订阅
await unsubscribe_without_login(url, channels)
# 发送订阅
async with websockets.connect(url) as ws:
sub_param = {"op": "subscribe", "args": channels}
sub_str = json.dumps(sub_param)
await ws.send(sub_str)
print(f"send: {sub_str}")
except Exception as e:
print(e)
print("连接断开,正在重连……")
continue
# subscribe channels need login
async def subscribe(url, api_key, passphrase, secret_key, channels):
while True:
try:
async with websockets.connect(url) as ws:
# login
timestamp = str(get_local_timestamp())
login_str = login_params(timestamp, api_key, passphrase, secret_key)
await ws.send(login_str)
# print(f"send: {login_str}")
res = await ws.recv()
print(res)
# subscribe
sub_param = {"op": "subscribe", "args": channels}
sub_str = json.dumps(sub_param)
await ws.send(sub_str)
print(f"send: {sub_str}")
while True:
try:
res = await asyncio.wait_for(ws.recv(), timeout=25)
except (asyncio.TimeoutError, websockets.exceptions.ConnectionClosed) as e:
try:
await ws.send('ping')
res = await ws.recv()
print(res)
continue
except Exception as e:
print("连接关闭,正在重连……")
break
print(get_timestamp() + res)
except Exception as e:
print("连接断开,正在重连……")
continue
# trade
async def trade(url, api_key, passphrase, secret_key, trade_param):
while True:
try:
async with websockets.connect(url) as ws:
# login
timestamp = str(get_local_timestamp())
login_str = login_params(timestamp, api_key, passphrase, secret_key)
await ws.send(login_str)
# print(f"send: {login_str}")
res = await ws.recv()
print(res)
# trade
sub_str = json.dumps(trade_param)
await ws.send(sub_str)
print(f"send: {sub_str}")
while True:
try:
res = await asyncio.wait_for(ws.recv(), timeout=25)
except (asyncio.TimeoutError, websockets.exceptions.ConnectionClosed) as e:
try:
await ws.send('ping')
res = await ws.recv()
print(res)
continue
except Exception as e:
print("连接关闭,正在重连……")
break
print(get_timestamp() + res)
except Exception as e:
print("连接断开,正在重连……")
continue
# unsubscribe channels
async def unsubscribe(url, api_key, passphrase, secret_key, channels):
async with websockets.connect(url) as ws:
# login
timestamp = str(get_local_timestamp())
login_str = login_params(timestamp, api_key, passphrase, secret_key)
await ws.send(login_str)
# print(f"send: {login_str}")
res = await ws.recv()
print(f"recv: {res}")
# unsubscribe
sub_param = {"op": "unsubscribe", "args": channels}
sub_str = json.dumps(sub_param)
await ws.send(sub_str)
print(f"send: {sub_str}")
res = await ws.recv()
print(f"recv: {res}")
# unsubscribe channels
async def unsubscribe_without_login(url, channels):
async with websockets.connect(url) as ws:
# unsubscribe
sub_param = {"op": "unsubscribe", "args": channels}
sub_str = json.dumps(sub_param)
await ws.send(sub_str)
print(f"send: {sub_str}")
res = await ws.recv()
print(f"recv: {res}")
api_key = ""
secret_key = ""
passphrase = ""
# WebSocket公共频道 public channels
# 实盘 real trading
# url = "wss://ws.okx.com:8443/ws/v5/public"
# 模拟盘 demo trading
# url = "wss://wspap.okx.com:8443/ws/v5/public"
# WebSocket私有频道 private channels
# 实盘 real trading
# url = "wss://ws.okx.com:8443/ws/v5/private"
# 模拟盘 demo trading
# url = "wss://wspap.okx.com:8443/ws/v5/private"
'''
公共频道 public channel
:param channel: 频道名
:param instType: 产品类型
:param instId: 产品ID
:param uly: 合约标的指数
'''
# 产品频道 Instruments Channel
# channels = [{"channel": "instruments", "instType": "FUTURES"}]
# 行情频道 tickers channel
# channels = [{"channel": "tickers", "instId": "BTC-USDT"}, {"channel": "tickers", "instId": "ETH-USDT"}]
# 持仓总量频道 Open interest Channel
# channels = [{"channel": "open-interest", "instId": "BTC-USD-210326"}]
# K线频道 Candlesticks Channel
# channels = [{"channel": "candle1m", "instId": "BTC-USD-210326"}]
# 交易频道 Trades Channel
# channels = [{"channel": "trades", "instId": "BTC-USD-201225"}]
# 预估交割/行权价格频道 Estimated delivery/exercise Price Channel
# channels = [{"channel": "estimated-price", "instType": "FUTURES", "uly": "BTC-USD"}]
# 标记价格频道 Mark Price Channel
# channels = [{"channel": "mark-price", "instId": "BTC-USDT-210326"}]
# 标记价格K线频道 Mark Price Candlesticks Channel
# channels = [{"channel": "mark-price-candle1D", "instId": "BTC-USD-201225"}]
# 限价频道 Price Limit Channel
# channels = [{"channel": "price-limit", "instId": "BTC-USD-201225"}]
# 深度频道 Order Book Channel
# channels = [{"channel": "books", "instId": "BTC-USD-SWAP"}]
# 期权定价频道 OPTION Summary Channel
# channels = [{"channel": "opt-summary", "uly": "BTC-USD"}]
# 资金费率频道 Funding Rate Channel
# channels = [{"channel": "funding-rate", "instId": "BTC-USD-SWAP"}]
# 指数K线频道 Index Candlesticks Channel
# channels = [{"channel": "index-candle1m", "instId": "BTC-USDT"}]
# 指数行情频道 Index Tickers Channel
# channels = [{"channel": "index-tickers", "instId": "BTC-USDT"}]
# status频道 Status Channel
# channels = [{"channel": "status"}]
# 公共大宗交易频道 Public block trading channel
# channels = [{"channel": "public-struc-block-trades"}]
# 大宗交易行情频道 Block trading market channel
# channels = [{"channel": "block-tickers", "instId":"BTC-USDT-SWAP"}]
'''
私有频道 private channel
:param channel: 频道名
:param ccy: 币种
:param instType: 产品类型
:param uly: 合约标的指数
:param instId: 产品ID
'''
# 账户频道 Account Channel
# channels = [{"channel": "account", "ccy": "BTC"}]
# 持仓频道 Positions Channel
# channels = [{"channel": "positions", "instType": "FUTURES", "uly": "BTC-USDT", "instId": "BTC-USDT-210326"}]
# 余额和持仓频道 Balance and Position Channel
# channels = [{"channel": "balance_and_position"}]
# 订单频道 Order Channel
# channels = [{"channel": "orders", "instType": "FUTURES", "uly": "BTC-USD", "instId": "BTC-USD-201225"}]
# 策略委托订单频道 Algo Orders Channel
# channels = [{"channel": "orders-algo", "instType": "FUTURES", "uly": "BTC-USD", "instId": "BTC-USD-201225"}]
# 高级策略委托订单频道 Cancel Advance Algos
# channels = [{"channel": "algo-advance", "instType": "SPOT","instId": "BTC-USD-201225","algoId":"12345678"}]
# 爆仓风险预警推送频道
# channels = [{"channel": "liquidation-warning", "instType": "SWAP","instType": "","uly":"","instId":""}]
# 账户greeks频道
# channels = [{"channel": "account-greeks", "ccy": "BTC"}]
# 询价频道 Inquiry channel
# channels = [{"channel": "rfqs"}]
# 报价频道 Quote channel
# channels = [{"channel": "quotes"}]
# 大宗交易频道 Block trading channel
# channels = [{"channel": "struc-block-trades"}]
# 现货网格策略委托订单频道 Consignment order channel of spot grid strategy
# channels = [{"channel": "grid-orders-spot", "instType": "ANY"}]
# 合约网格策略委托订单频道 Spot grid policy delegated order channel contract grid policy delegated order channel
# channels = [{"channel": "grid-orders-contract", "instType": "ANY"}]
# 合约网格持仓频道 Contract grid position channel
# channels = [{"channel": "grid-positions", "algoId": ""}]
# 网格策略子订单频道 Grid policy suborder channel
# channels = [{"channel": "grid-sub-orders", "algoId": ""}]
'''
交易 trade
'''
# 下单 Place Order
# trade_param = {"id": "1512", "op": "order", "args": [{"side": "buy", "instId": "BTC-USDT", "tdMode": "isolated", "ordType": "limit", "px": "19777", "sz": "1"}]}
# 批量下单 Place Multiple Orders
# trade_param = {"id": "1512", "op": "batch-orders", "args": [
# {"side": "buy", "instId": "BTC-USDT", "tdMode": "isolated", "ordType": "limit", "px": "19666", "sz": "1"},
# {"side": "buy", "instId": "BTC-USDT", "tdMode": "isolated", "ordType": "limit", "px": "19633", "sz": "1"}
# ]}
# 撤单 Cancel Order
# trade_param = {"id": "1512", "op": "cancel-order", "args": [{"instId": "BTC-USDT", "ordId": "259424589042823169"}]}
# 批量撤单 Cancel Multiple Orders
# trade_param = {"id": "1512", "op": "batch-cancel-orders", "args": [
# {"instId": "BTC-USDT", "ordId": ""},
# {"instId": "BTC-USDT", "ordId": ""}
# ]}
# 改单 Amend Order
# trade_param = {"id": "1512", "op": "amend-order", "args": [{"instId": "BTC-USDT", "ordId": "259432767558135808", "newSz": "2"}]}
# 批量改单 Amend Multiple Orders
# trade_param = {"id": "1512", "op": "batch-amend-orders", "args": [
# {"instId": "BTC-USDT", "ordId": "", "newSz": "2"},
# {"instId": "BTC-USDT", "ordId": "", "newSz": "3"}
# ]}
loop = asyncio.get_event_loop()
# 公共频道 不需要登录行情持仓总量K线标记价格深度资金费率等subscribe public channel
loop.run_until_complete(subscribe_without_login(url, channels))
# 私有频道 需要登录账户持仓订单等subscribe private channel
# loop.run_until_complete(subscribe(url, api_key, passphrase, secret_key, channels))
# 交易下单撤单改单等trade
# loop.run_until_complete(trade(url, api_key, passphrase, secret_key, trade_param))
loop.close()