Merge pull request #1296 from nanoric/beta_new_huobi
[Add] 基于新的RestClient和WebSocketClient重写了huobiGateway的底层
This commit is contained in:
commit
dbd967bb6d
0
beta/__init__.py
Normal file
0
beta/__init__.py
Normal file
0
beta/gateway/__init__.py
Normal file
0
beta/gateway/__init__.py
Normal file
0
beta/gateway/huobiGateway/__init__.py
Normal file
0
beta/gateway/huobiGateway/__init__.py
Normal file
302
beta/gateway/huobiGateway/huobiGateway.py
Normal file
302
beta/gateway/huobiGateway/huobiGateway.py
Normal file
@ -0,0 +1,302 @@
|
||||
# encoding: UTF-8
|
||||
|
||||
'''
|
||||
'''
|
||||
|
||||
from __future__ import print_function
|
||||
|
||||
import base64
|
||||
import hashlib
|
||||
import hmac
|
||||
import json
|
||||
import re
|
||||
import urllib
|
||||
import zlib
|
||||
|
||||
from vnpy.api.rest import Request, RestClient
|
||||
from vnpy.api.websocket import WebsocketClient
|
||||
from vnpy.trader.vtGateway import *
|
||||
|
||||
REST_HOST = 'https://api.huobipro.com'
|
||||
WEBSOCKET_MARKET_HOST = 'wss://api.huobi.pro/ws' # Global站行情
|
||||
WEBSOCKET_ASSETS_HOST = 'wss://api.huobi.pro/ws/v1' # 资产和订单
|
||||
WEBSOCKET_CONTRACT_HOST = 'wss://www.hbdm.com/ws' # 合约站行情
|
||||
|
||||
|
||||
#----------------------------------------------------------------------
|
||||
def _split_url(url):
|
||||
"""
|
||||
将url拆分为host和path
|
||||
:return: host, path
|
||||
"""
|
||||
m = re.match('\w+://([^/]*)(.*)', url)
|
||||
if m:
|
||||
return m.group(1), m.group(2)
|
||||
|
||||
|
||||
#----------------------------------------------------------------------
|
||||
def createSignature(apiKey, method, host, path, secretKey):
|
||||
"""创建签名"""
|
||||
sortedParams = (
|
||||
("AccessKeyId", apiKey),
|
||||
("SignatureMethod", 'HmacSHA256'),
|
||||
("SignatureVersion", "2"),
|
||||
("Timestamp", datetime.utcnow().strftime('%Y-%m-%dT%H:%M:%S'))
|
||||
)
|
||||
encodeParams = urllib.urlencode(sortedParams)
|
||||
|
||||
payload = [method, host, path, encodeParams]
|
||||
payload = '\n'.join(payload)
|
||||
payload = payload.encode(encoding='UTF8')
|
||||
|
||||
secretKey = secretKey.encode(encoding='UTF8')
|
||||
|
||||
digest = hmac.new(secretKey, payload, digestmod=hashlib.sha256).digest()
|
||||
|
||||
signature = base64.b64encode(digest)
|
||||
params = dict(sortedParams)
|
||||
params["Signature"] = signature
|
||||
return params
|
||||
|
||||
|
||||
########################################################################
|
||||
class HuobiRestApi(RestClient):
|
||||
|
||||
def __init__(self, gateway): # type: (VtGateway)->HuobiRestApi
|
||||
super(HuobiRestApi, self).__init__()
|
||||
self.gateway = gateway
|
||||
self.gatewayName = gateway.gatewayName
|
||||
|
||||
self.apiKey = ""
|
||||
self.apiSecret = ""
|
||||
self.signHost = ""
|
||||
|
||||
#----------------------------------------------------------------------
|
||||
def sign(self, request):
|
||||
request.headers = {
|
||||
"User-Agent":
|
||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/39.0.2171.71 Safari/537.36"}
|
||||
additionalParams = createSignature(self.apiKey,
|
||||
request.method,
|
||||
self.signHost,
|
||||
request.path,
|
||||
self.apiSecret)
|
||||
if not request.params:
|
||||
request.params = additionalParams
|
||||
else:
|
||||
request.params.update(additionalParams)
|
||||
if request.method == "POST":
|
||||
request.headers['Content-Type'] = 'application/json'
|
||||
return request
|
||||
|
||||
#----------------------------------------------------------------------
|
||||
def connect(self, apiKey, apiSecret, sessionCount=3):
|
||||
"""连接服务器"""
|
||||
self.apiKey = apiKey
|
||||
self.apiSecret = apiSecret
|
||||
|
||||
host, path = _split_url(REST_HOST)
|
||||
self.init(REST_HOST)
|
||||
self.signHost = host
|
||||
self.start(sessionCount)
|
||||
|
||||
#----------------------------------------------------------------------
|
||||
def qeuryAccount(self):
|
||||
self.addRequest('GET', '/v1/account/accounts', self.onAccount)
|
||||
|
||||
#----------------------------------------------------------------------
|
||||
def onAccount(self, data, request): # type: (dict, Request)->None
|
||||
pass
|
||||
|
||||
#----------------------------------------------------------------------
|
||||
def cancelWithdraw(self, id):
|
||||
self.addRequest('POST',
|
||||
"/v1/dw/withdraw-virtual/" + str(id) + "/cancel",
|
||||
self.onWithdrawCanceled
|
||||
)
|
||||
|
||||
#----------------------------------------------------------------------
|
||||
def onWithdrawCanceled(self, data, request): # type: (dict, Request)->None
|
||||
pass
|
||||
|
||||
|
||||
########################################################################
|
||||
class HuobiWebsocketApiBase(WebsocketClient):
|
||||
|
||||
#----------------------------------------------------------------------
|
||||
def __init__(self, gateway):
|
||||
"""Constructor"""
|
||||
super(HuobiWebsocketApiBase, self).__init__()
|
||||
|
||||
self.gateway = gateway
|
||||
self.gatewayName = gateway.gatewayName
|
||||
|
||||
self.apiKey = ''
|
||||
self.apiSecret = ''
|
||||
self.signHost = ''
|
||||
self.path = ''
|
||||
|
||||
#----------------------------------------------------------------------
|
||||
def connect(self, apiKey, apiSecret, url):
|
||||
""""""
|
||||
self.apiKey = apiKey
|
||||
self.apiSecret = apiSecret
|
||||
|
||||
host, path = _split_url(url)
|
||||
|
||||
self.init(url)
|
||||
self.signHost = host
|
||||
self.path = path
|
||||
self.start()
|
||||
|
||||
#----------------------------------------------------------------------
|
||||
def login(self):
|
||||
params = {
|
||||
'op': 'auth',
|
||||
}
|
||||
params.update(
|
||||
createSignature(self.apiKey,
|
||||
'GET',
|
||||
self.signHost,
|
||||
self.path,
|
||||
self.apiSecret)
|
||||
)
|
||||
return self.sendPacket(params)
|
||||
|
||||
#----------------------------------------------------------------------
|
||||
def onLogin(self, packet):
|
||||
pass
|
||||
|
||||
#----------------------------------------------------------------------
|
||||
@staticmethod
|
||||
def unpackData(data):
|
||||
return json.loads(zlib.decompress(data, 31))
|
||||
|
||||
#----------------------------------------------------------------------
|
||||
def onPacket(self, packet):
|
||||
"""
|
||||
这里我新增了一个onHuobiPacket的函数,也可以让子类重写这个函数,然后调用super.onPacket
|
||||
"""
|
||||
if 'ping' in packet:
|
||||
self.sendPacket({'pong': packet['ping']})
|
||||
return
|
||||
|
||||
# todo: use another error handing method
|
||||
if 'err-msg' in packet:
|
||||
return self.onHuobiErrorPacket(packet)
|
||||
|
||||
if "op" in packet and packet["op"] == "auth":
|
||||
return self.onLogin(packet)
|
||||
|
||||
self.onHuobiPacket(packet)
|
||||
|
||||
#----------------------------------------------------------------------
|
||||
def onHuobiPacket(self, packet): # type: (dict)->None
|
||||
pass
|
||||
|
||||
#----------------------------------------------------------------------
|
||||
def onHuobiErrorPacket(self, packet): # type: (dict)->None
|
||||
print("error : {}".format(packet))
|
||||
|
||||
|
||||
########################################################################
|
||||
class HuobiAssetsWebsocketApi(HuobiWebsocketApiBase):
|
||||
|
||||
def connect(self, apiKey, apiSecret, host=WEBSOCKET_ASSETS_HOST):
|
||||
"""
|
||||
这里我使用重写connect,添加了默认参数。这样写感觉~~不太好~~,不过目前想到的比较好的方式就是这样了
|
||||
虽然在Python中可以直接把这个connect()写成不接收host和path的形式,但是PyCharm会提示重载错误,所以不接收host和path似乎不太好?
|
||||
|
||||
我觉得最好的写法应该是这个函数不接收host和path。同时为了让PyCharm不提示重载错误(减少歧义),应该给
|
||||
HuobiWebsocketApiBase.connect起另外一个名字。
|
||||
"""
|
||||
return super(HuobiAssetsWebsocketApi, self). \
|
||||
connect(apiKey, apiSecret, host)
|
||||
|
||||
#----------------------------------------------------------------------
|
||||
def onConnected(self):
|
||||
self.login()
|
||||
|
||||
#----------------------------------------------------------------------
|
||||
def subscribeAccount(self):
|
||||
"""
|
||||
:param symbol: str ethbtc, ltcbtc, etcbtc, bchbtc
|
||||
:param period: str 1min, 5min, 15min, 30min, 60min, 1day, 1mon, 1week, 1year
|
||||
"""
|
||||
self.sendPacket({
|
||||
"op": "sub",
|
||||
"cid": "any thing you want",
|
||||
"topic": "accounts"
|
||||
})
|
||||
|
||||
#----------------------------------------------------------------------
|
||||
def onHuobiPacket(self, packet): # type: (dict)->None
|
||||
if 'op' in packet:
|
||||
if packet['op'] == 'sub':
|
||||
timestamp = packet['ts']
|
||||
topic = packet['topic']
|
||||
"""
|
||||
"data": {
|
||||
"event": "order.match|order.place|order.refund|order.cancel|order.fee-refund|margin.transfer|margin.loan|margin.interest|margin.repay|other",
|
||||
"list": [
|
||||
{
|
||||
"account-id": 419013,
|
||||
"currency": "usdt",
|
||||
"type": "trade",
|
||||
"balance": "500009195917.4362872650"
|
||||
},
|
||||
{
|
||||
"account-id": 419013,
|
||||
"currency": "btc",
|
||||
"type": "frozen",
|
||||
"balance": "9786.6783000000"
|
||||
}
|
||||
]
|
||||
}
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
########################################################################
|
||||
class HuobiMarketWebsocketApi(HuobiWebsocketApiBase):
|
||||
|
||||
#----------------------------------------------------------------------
|
||||
def connect(self, apiKey, apiSecret, host=WEBSOCKET_MARKET_HOST):
|
||||
"""
|
||||
这里我使用重写connect,添加了默认参数。这样写感觉~~不太好~~,不过目前想到的比较好的方式就是这样了
|
||||
虽然在Python中可以直接把这个connect()写成不接收host和path的形式,但是PyCharm会提示重载错误,所以不接收host和path似乎不太好?
|
||||
|
||||
我觉得最好的写法应该是这个函数不接收host和path。同时为了让PyCharm不提示重载错误(减少歧义),应该给
|
||||
HuobiWebsocketApiBase.connect起另外一个名字。
|
||||
"""
|
||||
return super(HuobiMarketWebsocketApi, self). \
|
||||
connect(apiKey, apiSecret, host)
|
||||
|
||||
#----------------------------------------------------------------------
|
||||
def subscribeKLine(self, symbol, period): # type:(str, str)->None
|
||||
"""
|
||||
:param symbol: str ethbtc, ltcbtc, etcbtc, bchbtc
|
||||
:param period: str 1min, 5min, 15min, 30min, 60min, 1day, 1mon, 1week, 1year
|
||||
:return:
|
||||
"""
|
||||
self.sendPacket({
|
||||
"sub": "market." + symbol + ".kline." + period,
|
||||
"id": "any thing you want"
|
||||
})
|
||||
|
||||
#----------------------------------------------------------------------
|
||||
def onHuobiPacket(self, packet): # type: (dict)->None
|
||||
# code for test purpose only
|
||||
if 'ch' in packet:
|
||||
if packet['ch'] == 'market.btcusdt.kline.1min':
|
||||
timestamp = packet['ts']
|
||||
data = packet['tick']
|
||||
id = data['id']
|
||||
amount = data['amount']
|
||||
count = data['count']
|
||||
open = data['open']
|
||||
close = data['close']
|
||||
low = data['low']
|
||||
high = data['high']
|
||||
vol = data['vol']
|
||||
pass
|
Loading…
Reference in New Issue
Block a user