比特币程序化交易入门(5):WebSocket API

比特币程序化交易入门(5):WebSocket API

与REST API最大的不同是,websocket API可以实现订阅消息,以账户信息为例,REST请求一次,获得一次账户信息,而websocket订阅以后,每当账户信息有变动,就会推送消息,无变化是则不推送。可以同时订阅深度,ticker等,okcoin的websocket也支持下单。

Python使用websocket协议需要websocket的包,官方范例已经提供了,直接可用。Okcoin官方Python Websocket API代码.

我刚开始接触websocket时,一头雾水,最大的问题是不知道怎么处理推送数据,也不知道数据如何用在自己的策略里。本文将回答这些问题。

需要注意的点:

1)有的推送数据是经过压缩的,有的没有。需要判断。

2)订阅第一条推送和后续不同,以订阅ticker为例,订阅成功后先推送{'success': True, 'channel': 'ok_sub_spotcny_btc_ticker'},表示订阅成功,后来才是ticker数据。这样订阅账户信息就比较尴尬,如果账户没有变化,实际上是得不到具体的账户信息的。

3)订阅多重消息,也是分别推送,具体是那个信息,需要根据channel中的信息的判断。

4)websocket协议也会中断。

5)个人认为websocket使用行情推送,和消息驱动的策略,我目前只用于行情推送,下单还是使用REST。

6)推送的数据可以用全局变量来传递。

代码注解:

下面为通过websocket订阅ticker行情,实现每3s输出一次最新成交价的代码。

import websocket
import zlib    #压缩相关的库
from recursive_json_loads import *   #参见前面文章,用于json解析
import threading
import hashlib
import time
api_key='your api_key '
secret_key ='your secret_key'
#解压函数
def inflate(data):
    decompress = zlib.decompressobj(-zlib.MAX_WBITS)
    inflated = decompress.decompress(data)
    inflated += decompress.flush()
    return inflated

#签名函数,订阅个人信息,买卖等都需要签名
def buildMySign(params,secretKey):
    sign = ''
    for key in sorted(params.keys()):
        sign += key + '=' + str(params[key]) +'&'
    return  hashlib.md5((sign+'secret_key='+secretKey).encode("utf-8")).hexdigest().upper()

#返回签名的信息
def wsGetAccount(channel,api_key,secret_key):
    params = {
      'api_key':api_key,
    }
    sign = buildMySign(params,secret_key)
    return "{'event':'addChannel','channel':'"+channel+"','parameters':{'api_key':'"+api_key+"','sign':'"+sign+"'}}"


#每当有消息推送时,就会触发,信息包含为message,注意在这里也可以使用ws.send()发送新的信息。
def on_message(ws, message):
    try:
        msg=recursive_json_loads(inflate(message).decode('utf-8'))[0]
    except Exception as e:
        msg=recursive_json_loads(message)[0]
    finally:
        #print(msg)    #判断是否需要解压,然后用json解析出来
        pass
    global depth       #推送的信息保存在全局变量中,这样在其他线程中就可以读取了
    global account
    global ticker
    try:
        if 'ok_sub_spotcny_userinfo' ==msg.channel:
            account=msg.data
        if 'ok_sub_spotcny_btc_depth_60' == msg.channel:
            depth=msg.data
        if 'ok_sub_spotcny_btc_ticker'== msg.channel:
            ticker=msg.data    #判断推送信息的类型
    except Exception as e:
        pass

#出现错误时执行
def on_error(ws, error):
    print(error)

#关闭连接时执行
def on_close(ws):
    print("### closed ###")

#开始连接时执行,需要订阅的消息和其它操作都要在这里完成
def on_open(ws):
    ws.send(wsGetAccount('ok_sub_spotcny_userinfo',api_key,secret_key))
    ws.send("{'event':'addChannel','channel':'ok_sub_spotcny_btc_depth_60'}")
    ws.send("{'event':'addChannel','channel':'ok_sub_spotcny_btc_ticker'}")

#创建websocket连接
def ws_main():
    websocket.enableTrace(True)
    host = "wss://real.okcoin.cn:10440/websocket/okcoinapi"
    ws = websocket.WebSocketApp(host,
                                on_message=on_message,
                                on_error=on_error,
                                on_close=on_close)
    ws.on_open = on_open
    ws.run_forever()    #开始运行


if __name__ == "__main__":
    account=0
    depth=0
    ticker=0
    threading.Thread(target=ws_main).start()
    while True:
        #这里是需要进行的任务,下单的策略可以安排在这里
        time.sleep(3)
        print(ticker.last)

更新OKEX的一个简单websocket链接范例(python2.7):

#pip install websocket_client
import websocket,json
import ssl
ssl._create_default_https_context = ssl._create_unverified_context

ws = websocket.create_connection("wss://real.okex.com:10441/websocket", timeout=15, sslopt= {"cert_reqs": ssl.CERT_NONE})
ws.settimeout(15)
msg = [{'event':'addChannel','channel':'ok_sub_spot_btc_usdt_ticker'},{'event':'addChannel','channel':'ok_sub_spot_btc_usdt_depth'}]
ws.send(json.dumps(msg))
while True:
    print ws.recv()

具体解析ws.recv()以及保持链接自行探索吧

编辑于 2018-06-12