Merge pull request #1558 from 1122455801/huobi_gateway01
[Add]Huobi gateway
This commit is contained in:
commit
f98c356156
4
vnpy/gateway/huobi/__init__.py
Normal file
4
vnpy/gateway/huobi/__init__.py
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
from .huobi_gateway import HuobiGateway
|
||||||
|
|
||||||
|
|
||||||
|
|
838
vnpy/gateway/huobi/huobi_gateway.py
Normal file
838
vnpy/gateway/huobi/huobi_gateway.py
Normal file
@ -0,0 +1,838 @@
|
|||||||
|
# encoding: UTF-8
|
||||||
|
|
||||||
|
"""
|
||||||
|
火币交易接口
|
||||||
|
"""
|
||||||
|
|
||||||
|
import hashlib
|
||||||
|
import hmac
|
||||||
|
from copy import copy
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
from vnpy.api.rest import Request, RestClient
|
||||||
|
from vnpy.api.websocket import WebsocketClient
|
||||||
|
from vnpy.trader.constant import (
|
||||||
|
Direction,
|
||||||
|
Exchange,
|
||||||
|
Product,
|
||||||
|
Status,
|
||||||
|
)
|
||||||
|
from vnpy.trader.gateway import BaseGateway
|
||||||
|
from vnpy.trader.object import (
|
||||||
|
TickData,
|
||||||
|
OrderData,
|
||||||
|
TradeData,
|
||||||
|
AccountData,
|
||||||
|
ContractData,
|
||||||
|
OrderRequest,
|
||||||
|
CancelRequest,
|
||||||
|
SubscribeRequest,
|
||||||
|
LogData,
|
||||||
|
)
|
||||||
|
|
||||||
|
import re
|
||||||
|
import urllib
|
||||||
|
import base64
|
||||||
|
import json
|
||||||
|
import zlib
|
||||||
|
from vnpy.event.engine import Event, EVENT_TIMER
|
||||||
|
from vnpy.trader.event import EVENT_LOG
|
||||||
|
|
||||||
|
|
||||||
|
REST_HOST = "https://api.huobi.pro"
|
||||||
|
WEBSOCKET_MARKET_HOST = "wss://api.huobi.pro/ws" # 行情
|
||||||
|
WEBSOCKET_TRADE_HOST = "wss://api.huobi.pro/ws/v1" # 资金和委托
|
||||||
|
|
||||||
|
STATUS_HUOBI2VT = {
|
||||||
|
"submitted": Status.SUBMITTING,
|
||||||
|
"partial-filled": Status.PARTTRADED,
|
||||||
|
"filled": Status.ALLTRADED,
|
||||||
|
"cancelling": Status.CANCELLED,
|
||||||
|
"partial-canceled": Status.CANCELLED,
|
||||||
|
"canceled": Status.CANCELLED,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class HuobiGateway(BaseGateway):
|
||||||
|
"""
|
||||||
|
VN Trader Gateway for Huobi connection.
|
||||||
|
"""
|
||||||
|
|
||||||
|
default_setting = {
|
||||||
|
"ID": "",
|
||||||
|
"Secret": "",
|
||||||
|
"Symbols": "",
|
||||||
|
}
|
||||||
|
|
||||||
|
def __init__(self, event_engine):
|
||||||
|
"""Constructor"""
|
||||||
|
super(HuobiGateway, self).__init__(event_engine, "HUOBI")
|
||||||
|
|
||||||
|
self.local_id = 10000
|
||||||
|
|
||||||
|
self.accountDict = {}
|
||||||
|
self.orderDict = {}
|
||||||
|
self.localOrderDict = {}
|
||||||
|
self.orderLocalDict = {}
|
||||||
|
|
||||||
|
self.qry_enabled = False
|
||||||
|
|
||||||
|
self.rest_api = HuobiRestApi(self)
|
||||||
|
self.trade_ws_api = HuobiTradeWebsocketApi(self)
|
||||||
|
self.market_ws_api = HuobiMarketWebsocketApi(self)
|
||||||
|
|
||||||
|
def connect(self, setting: dict):
|
||||||
|
""""""
|
||||||
|
key = setting["ID"]
|
||||||
|
secret = setting["Secret"]
|
||||||
|
symbols = setting["Symbols"]
|
||||||
|
|
||||||
|
self.rest_api.connect(symbols, secret, key)
|
||||||
|
self.trade_ws_api.connect(symbols, secret, key)
|
||||||
|
self.market_ws_api.connect(symbols, secret, key)
|
||||||
|
# websocket will push all account status on connected, including asset, position and orders.
|
||||||
|
|
||||||
|
def subscribe(self, req: SubscribeRequest):
|
||||||
|
""""""
|
||||||
|
self.ws_api.subscribe(req)
|
||||||
|
|
||||||
|
def send_order(self, req: OrderRequest):
|
||||||
|
""""""
|
||||||
|
return self.rest_api.send_order(req)
|
||||||
|
|
||||||
|
def cancel_order(self, req: CancelRequest):
|
||||||
|
""""""
|
||||||
|
self.rest_api.cancel_order(req)
|
||||||
|
|
||||||
|
def query_account(self):
|
||||||
|
""""""
|
||||||
|
self.rest_api.query_account()
|
||||||
|
|
||||||
|
def query_position(self):
|
||||||
|
""""""
|
||||||
|
pass
|
||||||
|
|
||||||
|
def close(self):
|
||||||
|
""""""
|
||||||
|
self.rest_api.stop()
|
||||||
|
self.trade_ws_api.stop()
|
||||||
|
self.market_ws_api.stop()
|
||||||
|
|
||||||
|
def init_query(self):
|
||||||
|
"""初始化连续查询"""
|
||||||
|
if self.qry_enabled:
|
||||||
|
# 需要循环的查询函数列表
|
||||||
|
self.qry_functionList = [self.qry_info]
|
||||||
|
|
||||||
|
self.qry_count = 0 # 查询触发倒计时
|
||||||
|
self.qry_trigger = 1 # 查询触发点
|
||||||
|
self.qry_next_function = 0 # 上次运行的查询函数索引
|
||||||
|
|
||||||
|
self.start_query()
|
||||||
|
|
||||||
|
def query(self, event):
|
||||||
|
"""注册到事件处理引擎上的查询函数"""
|
||||||
|
self.qry_count += 1
|
||||||
|
|
||||||
|
if self.qry_count > self.qry_trigger:
|
||||||
|
# 清空倒计时
|
||||||
|
self.qry_count = 0
|
||||||
|
|
||||||
|
# 执行查询函数
|
||||||
|
function = self.qry_functionList[self.qry_next_function]
|
||||||
|
function()
|
||||||
|
|
||||||
|
# 计算下次查询函数的索引,如果超过了列表长度,则重新设为0
|
||||||
|
self.qry_next_function += 1
|
||||||
|
if self.qry_next_function == len(self.qry_functionList):
|
||||||
|
self.qry_next_function = 0
|
||||||
|
|
||||||
|
def start_query(self):
|
||||||
|
"""启动连续查询"""
|
||||||
|
self.event_engine.register(EVENT_TIMER, self.query)
|
||||||
|
|
||||||
|
def set_qry_enabled(self, qry_enabled):
|
||||||
|
"""设置是否要启动循环查询"""
|
||||||
|
self.qry_enabled = qry_enabled
|
||||||
|
|
||||||
|
def write_log(self, msg):
|
||||||
|
""""""
|
||||||
|
log = LogData()
|
||||||
|
log.log_content = msg
|
||||||
|
log.gateway_name = self.gateway_name
|
||||||
|
|
||||||
|
event = Event(EVENT_LOG)
|
||||||
|
event.dict_["data"] = log
|
||||||
|
self.event_engine.put(event)
|
||||||
|
|
||||||
|
|
||||||
|
class HuobiRestApi(RestClient):
|
||||||
|
"""
|
||||||
|
HUOBI REST API
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, gateway: BaseGateway):
|
||||||
|
""""""
|
||||||
|
super(HuobiRestApi, self).__init__()
|
||||||
|
|
||||||
|
self.gateway = gateway
|
||||||
|
self.gateway_name = gateway.gateway_name
|
||||||
|
|
||||||
|
self.symbols = []
|
||||||
|
self.key = ""
|
||||||
|
self.secret = ""
|
||||||
|
self.sign_host = ""
|
||||||
|
|
||||||
|
self.account_id = ""
|
||||||
|
self.cancelReqDict = {}
|
||||||
|
self.orderBufDict = {}
|
||||||
|
|
||||||
|
self.accountDict = gateway.accountDict
|
||||||
|
self.orderDict = gateway.orderDict
|
||||||
|
self.orderLocalDict = gateway.orderLocalDict
|
||||||
|
self.localOrderDict = gateway.localOrderDict
|
||||||
|
|
||||||
|
self.account_id = ""
|
||||||
|
self.cancelReqDict = {}
|
||||||
|
self.orderBufDict = {}
|
||||||
|
|
||||||
|
def sign(self, request):
|
||||||
|
"""
|
||||||
|
Generate HUOBI signature.
|
||||||
|
"""
|
||||||
|
|
||||||
|
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"
|
||||||
|
}
|
||||||
|
params_with_signature = create_signature(self.key, request.method, self.sign_host, request.path, self.secret, request.params)
|
||||||
|
request.params = params_with_signature
|
||||||
|
|
||||||
|
if request.method == "POST":
|
||||||
|
request.headers["Content-Type"] = "application/json"
|
||||||
|
|
||||||
|
if request.data:
|
||||||
|
request.data = json.dumps(request.data)
|
||||||
|
return request
|
||||||
|
|
||||||
|
def connect(
|
||||||
|
self,
|
||||||
|
key: str,
|
||||||
|
secret: str,
|
||||||
|
symbols,
|
||||||
|
session_number=3,
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
Initialize connection to REST server.
|
||||||
|
"""
|
||||||
|
self.key = key
|
||||||
|
self.secret = secret
|
||||||
|
self.symbols = symbols
|
||||||
|
|
||||||
|
host, path = _split_url(REST_HOST)
|
||||||
|
self.init(REST_HOST)
|
||||||
|
|
||||||
|
self.sign_host = host
|
||||||
|
self.start(session_number)
|
||||||
|
|
||||||
|
self.gateway.write_log("REST API启动成功")
|
||||||
|
|
||||||
|
def query_account(self):
|
||||||
|
""""""
|
||||||
|
self.add_request(
|
||||||
|
method="GET",
|
||||||
|
path="/v1/account/accounts",
|
||||||
|
callable=self.on_query_account
|
||||||
|
)
|
||||||
|
|
||||||
|
def query_account_balance(self):
|
||||||
|
""""""
|
||||||
|
# path = "/v1/account/accounts/%s/balance" %self.account_id
|
||||||
|
path = f"/v1/account/accounts/{self.account_id}/balance"
|
||||||
|
self.add_request(
|
||||||
|
method="GET",
|
||||||
|
path=path,
|
||||||
|
callable=self.on_query_account_balance
|
||||||
|
)
|
||||||
|
|
||||||
|
def query_order(self):
|
||||||
|
""""""
|
||||||
|
path = "/v1/order/orders"
|
||||||
|
|
||||||
|
today_date = datetime.now().strftime("%Y-%m-%d")
|
||||||
|
states_active = "submitted, partial-filled"
|
||||||
|
|
||||||
|
for symbol in self.symbols:
|
||||||
|
params = {
|
||||||
|
"symbol": symbol,
|
||||||
|
"states": states_active,
|
||||||
|
"end_date": today_date
|
||||||
|
}
|
||||||
|
self.add_request(
|
||||||
|
method="GET",
|
||||||
|
path=path,
|
||||||
|
callable=self.on_query_order,
|
||||||
|
params=params
|
||||||
|
)
|
||||||
|
|
||||||
|
def query_contract(self):
|
||||||
|
""""""
|
||||||
|
self.add_request(
|
||||||
|
method="GET",
|
||||||
|
path="/v1/common/symbols",
|
||||||
|
callable=self.on_query_contract
|
||||||
|
)
|
||||||
|
|
||||||
|
def send_order(self, req: OrderRequest):
|
||||||
|
""""""
|
||||||
|
self.gateway.local_id += 1
|
||||||
|
|
||||||
|
local_id = str(self.gateway.local_id)
|
||||||
|
|
||||||
|
if req.direction == Direction.LONG:
|
||||||
|
type_ = "buy-limit"
|
||||||
|
else:
|
||||||
|
type_ = "sell-limit"
|
||||||
|
|
||||||
|
data = {
|
||||||
|
"account-id": self.account_id,
|
||||||
|
"amount": str(req.volume),
|
||||||
|
"symbol": req.symbol,
|
||||||
|
"type": type_,
|
||||||
|
"price": str(req.price),
|
||||||
|
"source": "api"
|
||||||
|
}
|
||||||
|
path = "/v1/order/orders/place"
|
||||||
|
|
||||||
|
self.add_request(
|
||||||
|
method="POST",
|
||||||
|
path=path,
|
||||||
|
callable=self.on_send_order,
|
||||||
|
data=data,
|
||||||
|
extra=local_id,
|
||||||
|
)
|
||||||
|
|
||||||
|
order = OrderData(
|
||||||
|
symbol=req.symbol,
|
||||||
|
exchange=Exchange.HUOBI,
|
||||||
|
orderid=local_id,
|
||||||
|
direction=req.direction,
|
||||||
|
price=req.price,
|
||||||
|
volume=req.volume,
|
||||||
|
time=datetime.now(),
|
||||||
|
gateway_name=self.gateway_name,
|
||||||
|
)
|
||||||
|
|
||||||
|
self.orderBufDict[local_id] = order
|
||||||
|
|
||||||
|
self.gateway.on_order(order)
|
||||||
|
return order.vt_orderid
|
||||||
|
|
||||||
|
def cancel_order(self, req: CancelRequest):
|
||||||
|
""""""
|
||||||
|
local_id = req.orderid
|
||||||
|
order_id = self.localOrderDict.get(local_id, None)
|
||||||
|
|
||||||
|
if order_id:
|
||||||
|
path = f"/v1/order/orders/{order_id}/submitcancel"
|
||||||
|
self.add_request(
|
||||||
|
method="POST",
|
||||||
|
path=path,
|
||||||
|
callable=self.on_cancel_order,
|
||||||
|
)
|
||||||
|
|
||||||
|
if local_id in self.cancelReqDict:
|
||||||
|
del self.cancelReqDict[local_id]
|
||||||
|
else:
|
||||||
|
self.cancelReqDict[local_id] = req
|
||||||
|
|
||||||
|
def on_query_account(self, data, request): # type: (dict, Request)->None
|
||||||
|
""""""
|
||||||
|
for d in data["data"]:
|
||||||
|
if str(d["type"]) == "spot":
|
||||||
|
self.account_id = str(d["id"])
|
||||||
|
self.gateway.write_log(f"账户代码{self.account_id}查询成功")
|
||||||
|
|
||||||
|
self.query_account_balance()
|
||||||
|
|
||||||
|
def on_query_account_balance(self, data, request): # type: (dict, Request)->None
|
||||||
|
""""""
|
||||||
|
status = data.get("status", None)
|
||||||
|
if status == "error":
|
||||||
|
msg = "错误代码:%s, 错误信息:%s" % (data["err-code"], data["err-msg"])
|
||||||
|
self.gateway.write_log(msg)
|
||||||
|
return
|
||||||
|
|
||||||
|
self.gateway.write_log(u"资金信息查询成功")
|
||||||
|
|
||||||
|
for d in data["data"]["list"]:
|
||||||
|
currency = d["currency"]
|
||||||
|
account = self.accountDict.get(currency, None)
|
||||||
|
|
||||||
|
if not account:
|
||||||
|
account = AccountData(
|
||||||
|
account_id=d["currency"],
|
||||||
|
gateway_name=self.gateway_name,
|
||||||
|
available=float(d["balance"]) if d["type"] == "trade" else 0.0,
|
||||||
|
margin=float(d["balance"]) if d["type"] == "frozen" else 0.0,
|
||||||
|
balance=account.margin + account.available,
|
||||||
|
)
|
||||||
|
self.accountDict[currency] = account
|
||||||
|
|
||||||
|
for account in self.accountDict.values():
|
||||||
|
self.gateway.on_account(account)
|
||||||
|
|
||||||
|
self.query_order()
|
||||||
|
|
||||||
|
def on_query_order(self, data, request): # type: (dict, Request)->None
|
||||||
|
""""""
|
||||||
|
status = data.get("status", None)
|
||||||
|
if status == "error":
|
||||||
|
msg = "错误代码:%s, 错误信息:%s" % (data["err-code"], data["err-msg"])
|
||||||
|
self.gateway.write_log(msg)
|
||||||
|
return
|
||||||
|
|
||||||
|
symbol = request.params["symbol"]
|
||||||
|
self.gateway.write_log(f"{symbol}委托信息查询成功")
|
||||||
|
|
||||||
|
data["data"].reverse()
|
||||||
|
for d in data["data"]:
|
||||||
|
order_id = str(d["id"])
|
||||||
|
self.gateway.local_id += 1
|
||||||
|
local_id = str(self.gateway.local_id)
|
||||||
|
|
||||||
|
self.orderLocalDict[order_id] = local_id
|
||||||
|
self.localOrderDict[local_id] = order_id
|
||||||
|
|
||||||
|
if "buy" in d["type"]:
|
||||||
|
direction = Direction.LONG
|
||||||
|
else:
|
||||||
|
direction = Direction.SHORT
|
||||||
|
|
||||||
|
if d["canceled-at"]:
|
||||||
|
time = datetime.fromtimestamp(d["canceled-at"] / 1000).strftime("%H:%M:%S")
|
||||||
|
else:
|
||||||
|
time = datetime.fromtimestamp(d["created-at"] / 1000).strftime("%H:%M:%S")
|
||||||
|
|
||||||
|
order = OrderData(
|
||||||
|
orderid=local_id,
|
||||||
|
symbol=d["symbol"],
|
||||||
|
exchange=Exchange.HUOBI,
|
||||||
|
price=float(d["price"]),
|
||||||
|
volume=float(d["amount"]),
|
||||||
|
direction=direction,
|
||||||
|
traded=float(d["field-amount"]),
|
||||||
|
status=STATUS_HUOBI2VT.get(d["state"], None),
|
||||||
|
time=time,
|
||||||
|
gateway_name=self.gateway_name,
|
||||||
|
|
||||||
|
)
|
||||||
|
|
||||||
|
self.orderDict[order_id] = order
|
||||||
|
self.gateway.on_order(order)
|
||||||
|
|
||||||
|
def on_query_contract(self, data, request): # type: (dict, Request)->None
|
||||||
|
""""""
|
||||||
|
status = data.get("status", None)
|
||||||
|
if status == "error":
|
||||||
|
msg = "错误代码:%s, 错误信息:%s" % (data["err-code"], data["err-msg"])
|
||||||
|
self.gateway.write_log(msg)
|
||||||
|
return
|
||||||
|
|
||||||
|
self.gateway.write_log("合约信息查询成功")
|
||||||
|
|
||||||
|
for d in data["data"]:
|
||||||
|
contract = ContractData(
|
||||||
|
symbol=d["base-currency"] + d["quote-currency"],
|
||||||
|
exchange=Exchange.HUOBI,
|
||||||
|
name="/".join([d["base-currency"].upper(), d["quote-currency"].upper()]),
|
||||||
|
pricetick=1 / pow(10, d["price-precision"]),
|
||||||
|
size=1 / pow(10, d["amount-precision"]),
|
||||||
|
product=Product.SPOT,
|
||||||
|
gateway_name=self.gateway_name,
|
||||||
|
)
|
||||||
|
self.gateway.on_contract(contract)
|
||||||
|
|
||||||
|
self.query_account()
|
||||||
|
|
||||||
|
def on_send_order(self, data, request): # type: (dict, Request)->None
|
||||||
|
""""""
|
||||||
|
local_id = request.extra
|
||||||
|
order = self.orderBufDict[local_id]
|
||||||
|
|
||||||
|
status = data.get("status", None)
|
||||||
|
|
||||||
|
if status == "error":
|
||||||
|
msg = "错误代码:%s, 错误信息:%s" % (data["err-code"], data["err-msg"])
|
||||||
|
self.gateway.write_log(msg)
|
||||||
|
|
||||||
|
order.status = Status.REJECTED
|
||||||
|
self.gateway.on_order(order)
|
||||||
|
return
|
||||||
|
|
||||||
|
order_id = str(data["data"])
|
||||||
|
|
||||||
|
self.localOrderDict[local_id] = order_id
|
||||||
|
self.orderDict[order_id] = order
|
||||||
|
|
||||||
|
req = self.cancelReqDict.get(local_id, None)
|
||||||
|
if req:
|
||||||
|
self.cancel_order(req)
|
||||||
|
|
||||||
|
def on_cancel_order(self, data, request): # type: (dict, Request)->None
|
||||||
|
""""""
|
||||||
|
status = data.get("status", None)
|
||||||
|
if status == "error":
|
||||||
|
msg = "错误代码:%s, 错误信息:%s" % (data["err-code"], data["err-msg"])
|
||||||
|
self.gateway.write_log(msg)
|
||||||
|
return
|
||||||
|
|
||||||
|
self.gateway.write_log(f"委托撤单成功:{data}")
|
||||||
|
|
||||||
|
|
||||||
|
class HuobiWebsocketApiBase(WebsocketClient):
|
||||||
|
""""""
|
||||||
|
|
||||||
|
def __init__(self, gateway):
|
||||||
|
""""""
|
||||||
|
super(HuobiWebsocketApiBase, self).__init__()
|
||||||
|
|
||||||
|
self.gateway = gateway
|
||||||
|
self.gateway_name = gateway.gateway_name
|
||||||
|
|
||||||
|
self.key = ""
|
||||||
|
self.secret = ""
|
||||||
|
self.sign_host = ""
|
||||||
|
self.path = ""
|
||||||
|
|
||||||
|
def connect(self, key: str, secret: str, url: str):
|
||||||
|
""""""
|
||||||
|
self.key = key
|
||||||
|
self.secret = secret
|
||||||
|
host, path = _split_url(url)
|
||||||
|
|
||||||
|
self.init(url)
|
||||||
|
self.sign_host = host
|
||||||
|
self.path = path
|
||||||
|
self.start()
|
||||||
|
|
||||||
|
def login(self):
|
||||||
|
""""""
|
||||||
|
params = {"op": "auth", }
|
||||||
|
params.update(create_signature(self.key, "GET", self.sign_host, self.path, self.secret))
|
||||||
|
return self.send_packet(params)
|
||||||
|
|
||||||
|
def on_login(self, packet):
|
||||||
|
""""""
|
||||||
|
pass
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def unpack_data(data):
|
||||||
|
""""""
|
||||||
|
return json.loads(zlib.decompress(data, 31))
|
||||||
|
|
||||||
|
def on_packet(self, packet):
|
||||||
|
""""""
|
||||||
|
if "ping" in packet:
|
||||||
|
self.send_packet({"pong": packet["ping"]})
|
||||||
|
return
|
||||||
|
|
||||||
|
if "err-msg" in packet:
|
||||||
|
return self.on_error_msg(packet)
|
||||||
|
|
||||||
|
if "op" in packet and packet["op"] == "auth":
|
||||||
|
return self.on_login()
|
||||||
|
|
||||||
|
self.on_data(packet)
|
||||||
|
|
||||||
|
def on_data(self, packet):
|
||||||
|
""""""
|
||||||
|
print("data : {}".format(packet))
|
||||||
|
|
||||||
|
def on_error_msg(self, packet):
|
||||||
|
""""""
|
||||||
|
msg = packet["err-msg"]
|
||||||
|
if msg == "invalid pong":
|
||||||
|
return
|
||||||
|
|
||||||
|
self.gateway.write_log(packet["err-msg"])
|
||||||
|
|
||||||
|
|
||||||
|
class HuobiTradeWebsocketApi(HuobiWebsocketApiBase):
|
||||||
|
""""""
|
||||||
|
def __init__(self, gateway):
|
||||||
|
""""""
|
||||||
|
super(HuobiTradeWebsocketApi, self).__init__(gateway)
|
||||||
|
|
||||||
|
self.req_id = 10000
|
||||||
|
|
||||||
|
self.accountDict = gateway.accountDict
|
||||||
|
self.orderDict = gateway.orderDict
|
||||||
|
self.orderLocalDict = gateway.orderLocalDict
|
||||||
|
self.localOrderDict = gateway.localOrderDict
|
||||||
|
|
||||||
|
def connect(self, symbols, key, secret):
|
||||||
|
""""""
|
||||||
|
self.symbols = symbols
|
||||||
|
super(HuobiTradeWebsocketApi, self).connect(key, secret, WEBSOCKET_TRADE_HOST)
|
||||||
|
|
||||||
|
def subscribe_topic(self):
|
||||||
|
""""""
|
||||||
|
# 订阅资金变动
|
||||||
|
self.req_id += 1
|
||||||
|
req = {
|
||||||
|
"op": "sub",
|
||||||
|
"cid": str(self.req_id),
|
||||||
|
"topic": "accounts",
|
||||||
|
}
|
||||||
|
self.send_packet(req)
|
||||||
|
|
||||||
|
# 订阅委托变动
|
||||||
|
for symbol in self.symbols:
|
||||||
|
self.req_id += 1
|
||||||
|
req = {
|
||||||
|
"op": "sub",
|
||||||
|
"cid": str(self.req_id),
|
||||||
|
"topic": f"orders.{symbol}"
|
||||||
|
}
|
||||||
|
self.send_packet(req)
|
||||||
|
|
||||||
|
def on_connected(self):
|
||||||
|
""""""
|
||||||
|
self.login()
|
||||||
|
|
||||||
|
def on_login(self):
|
||||||
|
""""""
|
||||||
|
self.gateway.write_log("交易Websocket服务器登录成功")
|
||||||
|
|
||||||
|
self.subscribe_topic()
|
||||||
|
|
||||||
|
def on_data(self, packet): # type: (dict)->None
|
||||||
|
""""""
|
||||||
|
op = packet.get("op", None)
|
||||||
|
if op != "notify":
|
||||||
|
return
|
||||||
|
|
||||||
|
topic = packet["topic"]
|
||||||
|
if topic == "accounts":
|
||||||
|
self.on_account(packet["data"])
|
||||||
|
elif "orders" in topic:
|
||||||
|
self.on_order(packet["data"])
|
||||||
|
|
||||||
|
def on_account(self, data):
|
||||||
|
""""""
|
||||||
|
for d in data["list"]:
|
||||||
|
account = self.accountDict.get(d["currency"], None)
|
||||||
|
if not account:
|
||||||
|
continue
|
||||||
|
|
||||||
|
if d["type"] == "trade":
|
||||||
|
account.available = float(d["balance"])
|
||||||
|
elif d["type"] == "frozen":
|
||||||
|
account.margin = float(d["balance"])
|
||||||
|
|
||||||
|
account.balance = account.margin + account.available
|
||||||
|
self.gateway.on_account(account)
|
||||||
|
|
||||||
|
def on_order(self, data: list):
|
||||||
|
""""""
|
||||||
|
order_id = str(data["order-id"])
|
||||||
|
order = self.orderDict.get(order_id, None)
|
||||||
|
|
||||||
|
if not order:
|
||||||
|
local_id = self._new_order_id()
|
||||||
|
local_id = str(local_id)
|
||||||
|
|
||||||
|
self.orderLocalDict[order_id] = local_id
|
||||||
|
self.localOrderDict[local_id] = order_id
|
||||||
|
|
||||||
|
if "buy" in data["order-type"]:
|
||||||
|
direction = Direction.LONG
|
||||||
|
else:
|
||||||
|
direction = Direction.SHORT
|
||||||
|
|
||||||
|
order = OrderData(
|
||||||
|
orderid=local_id,
|
||||||
|
symbol=data["symbol"],
|
||||||
|
exchange=Exchange.HUOBI,
|
||||||
|
price=float(data["order-price"]),
|
||||||
|
volume=float(data["order-amount"]),
|
||||||
|
direction=direction,
|
||||||
|
status=STATUS_HUOBI2VT.get(data["order-state"], None),
|
||||||
|
time=datetime.fromtimestamp(data["created-at"] / 1000).strftime("%H:%M:%S"),
|
||||||
|
gateway_name=self.gateway_name,
|
||||||
|
)
|
||||||
|
order.traded += float(data['filled-amount'])
|
||||||
|
self.orderDict[order_id] = order
|
||||||
|
self.gateway.onOrder(order)
|
||||||
|
|
||||||
|
if float(data["filled-amount"]):
|
||||||
|
trade = TradeData(
|
||||||
|
orderid=order.orderid,
|
||||||
|
tradeid=str(data["seq-id"]),
|
||||||
|
symbol=data["symbol"],
|
||||||
|
exchange=Exchange.HUOBI,
|
||||||
|
direction=order.direction,
|
||||||
|
price=float(data["price"]),
|
||||||
|
volume=float(data["filled-amount"]),
|
||||||
|
time=datetime.now().strftime("%H:%M:%S"),
|
||||||
|
gateway_name=self.gateway_name,
|
||||||
|
)
|
||||||
|
self.gateway.onTrade(trade)
|
||||||
|
|
||||||
|
|
||||||
|
class HuobiMarketWebsocketApi(HuobiWebsocketApiBase):
|
||||||
|
""""""
|
||||||
|
def __init__(self, gateway):
|
||||||
|
""""""
|
||||||
|
super(HuobiMarketWebsocketApi, self).__init__(gateway)
|
||||||
|
|
||||||
|
self.req_id = 10000
|
||||||
|
self.tickDict = {}
|
||||||
|
|
||||||
|
def connect(self, symbols, key, secret):
|
||||||
|
""""""
|
||||||
|
self.symbols = symbols
|
||||||
|
super(HuobiMarketWebsocketApi, self).connect(key, secret, WEBSOCKET_MARKET_HOST)
|
||||||
|
|
||||||
|
def on_connected(self):
|
||||||
|
""""""
|
||||||
|
self.subscribe_topic()
|
||||||
|
|
||||||
|
def subscribe_topic(self): # type:()->None
|
||||||
|
""""""
|
||||||
|
for symbol in self.symbols:
|
||||||
|
# 创建Tick对象
|
||||||
|
tick = TickData(
|
||||||
|
symbol=symbol,
|
||||||
|
exchange=Exchange.HUOBI,
|
||||||
|
gateway_name=self.gateway_name,
|
||||||
|
)
|
||||||
|
|
||||||
|
self.tickDict[symbol] = tick
|
||||||
|
|
||||||
|
# 订阅深度和成交
|
||||||
|
self.req_id += 1
|
||||||
|
req = {
|
||||||
|
"sub": "market.{symbol}.depth.step0",
|
||||||
|
"id": str(self.req_id)
|
||||||
|
}
|
||||||
|
self.send_packet(req)
|
||||||
|
|
||||||
|
self.req_id += 1
|
||||||
|
req = {
|
||||||
|
"sub": "market.{symbol}.detail",
|
||||||
|
"id": str(self.req_id)
|
||||||
|
}
|
||||||
|
self.send_packet(req)
|
||||||
|
|
||||||
|
def on_data(self, packet): # type: (dict)->None
|
||||||
|
""""""
|
||||||
|
if "ch" in packet:
|
||||||
|
if "depth.step" in packet["ch"]:
|
||||||
|
self.on_market_depth(packet)
|
||||||
|
elif "detail" in packet["ch"]:
|
||||||
|
self.on_market_detail(packet)
|
||||||
|
elif "err-code" in packet:
|
||||||
|
self.gateway.write_log("错误代码:%s, 错误信息:%s" % (data["err-code"], data["err-msg"]))
|
||||||
|
|
||||||
|
def on_market_depth(self, data):
|
||||||
|
"""行情深度推送 """
|
||||||
|
symbol = data["ch"].split(".")[1]
|
||||||
|
|
||||||
|
tick = self.tickDict.get(symbol, None)
|
||||||
|
if not tick:
|
||||||
|
return
|
||||||
|
|
||||||
|
tick.datetime = datetime.fromtimestamp(data["ts"] / 1000)
|
||||||
|
tick.date = tick.datetime.strftime("%Y%m%d")
|
||||||
|
tick.time = tick.datetime.strftime("%H:%M:%S.%f")
|
||||||
|
|
||||||
|
bids = data["tick"]["bids"]
|
||||||
|
for n in range(5):
|
||||||
|
l = bids[n]
|
||||||
|
tick.__setattr__("bid_price_" + str(n + 1), float(l[0]))
|
||||||
|
tick.__setattr__("bid_volume_" + str(n + 1), float(l[1]))
|
||||||
|
|
||||||
|
asks = data["tick"]["asks"]
|
||||||
|
for n in range(5):
|
||||||
|
l = asks[n]
|
||||||
|
tick.__setattr__("ask_price_" + str(n + 1), float(l[0]))
|
||||||
|
tick.__setattr__("ask_volume_" + str(n + 1), float(l[1]))
|
||||||
|
|
||||||
|
if tick.last_price:
|
||||||
|
newtick = copy(tick)
|
||||||
|
self.gateway.on_tick(newtick)
|
||||||
|
|
||||||
|
def on_market_detail(self, data):
|
||||||
|
"""市场细节推送"""
|
||||||
|
symbol = data["ch"].split(".")[1]
|
||||||
|
|
||||||
|
tick = self.tickDict.get(symbol, None)
|
||||||
|
if not tick:
|
||||||
|
return
|
||||||
|
|
||||||
|
tick.datetime = datetime.fromtimestamp(data["ts"] / 1000)
|
||||||
|
tick.date = tick.datetime.strftime("%Y%m%d")
|
||||||
|
tick.time = tick.datetime.strftime("%H:%M:%S.%f")
|
||||||
|
|
||||||
|
t = data["tick"]
|
||||||
|
tick.open_price = float(t["open"])
|
||||||
|
tick.high_price = float(t["high"])
|
||||||
|
tick.low_price = float(t["low"])
|
||||||
|
tick.last_price = float(t["close"])
|
||||||
|
tick.volume = float(t["vol"])
|
||||||
|
tick.pre_close = float(tick.open_price)
|
||||||
|
|
||||||
|
if tick.bid_price_1:
|
||||||
|
newtick = copy(tick)
|
||||||
|
self.gateway.on_tick(newtick)
|
||||||
|
|
||||||
|
|
||||||
|
def print_dict(d):
|
||||||
|
""""""
|
||||||
|
print("-" * 30)
|
||||||
|
l = d.keys()
|
||||||
|
l.sort()
|
||||||
|
for k in l:
|
||||||
|
print(type(k), k, d[k])
|
||||||
|
|
||||||
|
|
||||||
|
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 create_signature(api_key, method, host, path, secret_key, get_params=None):
|
||||||
|
"""
|
||||||
|
创建签名
|
||||||
|
:param get_params: dict 使用GET方法时附带的额外参数(urlparams)
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
sortedParams = [
|
||||||
|
("AccessKeyId", api_key),
|
||||||
|
("SignatureMethod", "HmacSHA256"),
|
||||||
|
("SignatureVersion", "2"),
|
||||||
|
("Timestamp", datetime.utcnow().strftime("%Y-%m-%dT%H:%M:%S"))
|
||||||
|
]
|
||||||
|
if get_params:
|
||||||
|
sortedParams.extend(get_params.items())
|
||||||
|
sortedParams = list(sorted(sortedParams))
|
||||||
|
encodeParams = urllib.urlencode(sortedParams)
|
||||||
|
|
||||||
|
payload = [method, host, path, encodeParams]
|
||||||
|
payload = "\n".join(payload)
|
||||||
|
payload = payload.encode(encoding="UTF8")
|
||||||
|
|
||||||
|
secret_key = secret_key.encode(encoding="UTF8")
|
||||||
|
|
||||||
|
digest = hmac.new(secret_key, payload, digestmod=hashlib.sha256).digest()
|
||||||
|
signature = base64.b64encode(digest)
|
||||||
|
|
||||||
|
params = dict(sortedParams)
|
||||||
|
params["Signature"] = signature
|
||||||
|
return params
|
@ -100,6 +100,7 @@ class Exchange(Enum):
|
|||||||
# CryptoCurrency
|
# CryptoCurrency
|
||||||
BITMEX = "BITMEX"
|
BITMEX = "BITMEX"
|
||||||
OKEX = "OKEX"
|
OKEX = "OKEX"
|
||||||
|
HUOBI = "HUOBI"
|
||||||
|
|
||||||
|
|
||||||
class Currency(Enum):
|
class Currency(Enum):
|
||||||
|
Loading…
Reference in New Issue
Block a user