You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

282 lines
12 KiB

import threading
import websocket
import gzip
import ssl
import logging
import urllib.parse
from huobi.constant import *
from huobi.utils import *
from huobi.exception.huobi_api_exception import HuobiApiException
from huobi.connection.impl.private_def import ConnectionState
# Key: original_connection, Value: connection
websocket_connection_handler = dict()
def on_message(original_connection, message):
websocket_connection = websocket_connection_handler[original_connection]
websocket_connection.on_message(message)
return
def on_error(original_connection, error):
websocket_connection = websocket_connection_handler[original_connection]
websocket_connection.on_failure(error)
def on_close(original_connection):
websocket_connection = websocket_connection_handler[original_connection]
websocket_connection.on_close()
def on_open(original_connection):
websocket_connection = websocket_connection_handler[original_connection]
websocket_connection.on_open(original_connection)
connection_id = 0
def websocket_func(*args):
try:
websocket_manage = args[0]
websocket_manage.original_connection = websocket.WebSocketApp(websocket_manage.url,
on_message=on_message,
on_error=on_error,
on_close=on_close)
global websocket_connection_handler
websocket_connection_handler[websocket_manage.original_connection] = websocket_manage
websocket_manage.logger.info("[Sub][" + str(websocket_manage.id) + "] Connecting...")
websocket_manage.original_connection.on_open = on_open
websocket_manage.original_connection.run_forever(sslopt={"cert_reqs": ssl.CERT_NONE})
websocket_manage.logger.info("[Sub][" + str(websocket_manage.id) + "] Connection event loop down")
if websocket_manage.state == ConnectionState.CONNECTED:
websocket_manage.state = ConnectionState.IDLE
except Exception as ex:
print(ex)
class WebsocketManage:
def __init__(self, api_key, secret_key, uri, request):
self.__thread = None
self.__market_url = HUOBI_WEBSOCKET_URI_PRO + "/ws"
self.__trading_url = HUOBI_WEBSOCKET_URI_PRO + "/ws/" + request.api_version
self.__mbp_feed_url = HUOBI_WEBSOCKET_URI_PRO + "/feed"
self.__api_key = api_key
self.__secret_key = secret_key
self.request = request
self.reconnect_at = 0
self.original_connection = None
self.last_receive_time = 0
self.logger = logging.getLogger("huobi-client")
self.state = ConnectionState.IDLE
global connection_id
connection_id += 1
self.id = connection_id
host = urllib.parse.urlparse(uri).hostname
if host.find("api") == 0:
self.__market_url = "wss://" + host + "/ws"
self.__mbp_feed_url = "wss://" + host + "/feed"
self.__trading_url = "wss://" + host + "/ws/" + request.api_version
else:
self.__market_url = "wss://" + host + "/api/ws"
self.__mbp_feed_url = "wss://" + host + "/feed"
self.__trading_url = "wss://" + host + "/ws/" + request.api_version
if request.is_trading:
self.url = self.__trading_url
elif request.is_mbp_feed:
self.url = self.__mbp_feed_url
else:
self.url = self.__market_url
def close_and_wait_reconnect(self, delay_in_ms):
if self.original_connection is not None:
self.original_connection.close()
self.original_connection = None
self.state = ConnectionState.WAIT_RECONNECT
self.reconnect_at = + delay_in_ms
self.logger.warning("[Sub][%d] Lost connectiong for %d ms, will try reconnecting " % (self.id, self.reconnect_at))
def re_connect(self):
if get_current_timestamp() > self.reconnect_at:
self.logger.info("[Sub][%d] Reconnecting ... " % self.id)
self.connect()
def connect(self):
if self.state == ConnectionState.CONNECTED:
self.logger.info("[Sub][" + str(self.id) + "] Already connected")
else:
self.__thread = threading.Thread(target=websocket_func, args=[self])
self.__thread.start()
def send(self, data):
# print("Send Data : " + data)
self.original_connection.send(data)
def close(self):
self.original_connection.close()
del websocket_connection_handler[self.original_connection]
self.state = ConnectionState.CLOSED
self.logger.info("[Sub][" + str(self.id) + "] Closing normally")
def on_open(self, original_connection):
self.logger.info("[Sub][" + str(self.id) + "] Connected to server")
self.original_connection = original_connection
self.last_receive_time = get_current_timestamp()
self.state = ConnectionState.CONNECTED
if self.request.is_trading:
try:
if self.request.api_version == ApiVersion.VERSION_V1:
builder = UrlParamsBuilder()
create_signature(self.__api_key, self.__secret_key,
"GET", self.url, builder)
builder.put_url("op", "auth")
self.send(builder.build_url_to_json())
elif self.request.api_version == ApiVersion.VERSION_V2:
builder = UrlParamsBuilder()
create_signature_v2(self.__api_key, self.__secret_key,
"GET", self.url, builder)
self.send(builder.build_url_to_json())
else:
self.on_error("api version for create the signature fill failed")
except Exception as e:
self.on_error("Unexpected error when create the signature: " + str(e))
else:
if self.request.subscription_handler is not None:
self.request.subscription_handler(self)
return
def on_error(self, error_message):
if self.request.error_handler is not None:
exception = HuobiApiException(HuobiApiException.SUBSCRIPTION_ERROR, error_message)
self.request.error_handler(exception)
self.logger.error("[Sub][" + str(self.id) + "] " + str(error_message))
def on_failure(self, error):
self.on_error("Unexpected error: " + str(error))
self.close_on_error()
def on_message(self, message):
self.last_receive_time = get_current_timestamp()
if isinstance(message, (str)): # V2
# print("RX string : ", message)
dict_data = json.loads(message)
elif isinstance(message, (bytes)): # V1
# print("RX bytes: " + gzip.decompress(message).decode("utf-8"))
dict_data = json.loads(gzip.decompress(message).decode("utf-8"))
else:
print("RX unknow type : ", type(message))
return
status_outer = dict_data.get("status", "")
err_code_outer = dict_data.get("err-code", 0)
op_outer = dict_data.get("op", "")
action_outer = dict_data.get("action", "")
ch_outer = dict_data.get("ch", "")
rep_outer = dict_data.get("rep", "")
ping_market_outer = int(dict_data.get("ping", 0))
if status_outer and len(status_outer) and status_outer != "ok":
error_code = dict_data.get("err-code", "Unknown error")
error_msg = dict_data.get("err-msg", "Unknown error")
self.on_error(error_code + ": " + error_msg)
elif err_code_outer and int(err_code_outer) != 0:
error_code = dict_data.get("err-code", "Unknown error")
error_msg = dict_data.get("err-msg", "Unknown error")
self.on_error(error_code + ": " + error_msg)
elif op_outer and len(op_outer): # for V1
if op_outer == "notify":
self.__on_receive(dict_data)
elif op_outer == "ping":
#print("******** receive trade ping pong ********", dict_data)
ping_ts = dict_data.get("ts", 0)
self.__process_ping_on_trading_line(ping_ts)
elif op_outer == "auth":
if self.request.subscription_handler is not None:
self.request.subscription_handler(self)
elif op_outer == "req":
self.__on_receive(dict_data)
elif action_outer and len(action_outer): # for V2
if action_outer == "ping":
action_data = dict_data.get("data")
ping_ts = action_data.get("ts")
self.__process_ping_on_v2_trade(ping_ts)
elif action_outer == "sub":
action_code = dict_data.get("code", -1)
if action_code == 200:
logging.info("subscribe ACK received")
else:
logging.error("receive error data : " + message)
elif action_outer == "req": #
action_code = dict_data.get("code", -1)
if action_code == 200:
logging.info("signature ACK received")
if self.request.subscription_handler is not None:
self.request.subscription_handler(self)
else:
logging.error("receive error data : " + message)
elif action_outer == "push":
action_data = dict_data.get("data")
if action_data:
self.__on_receive(dict_data)
else:
logging.error("receive error push data : " + message)
elif ch_outer and len(ch_outer):
self.__on_receive(dict_data)
elif rep_outer and len(rep_outer):
self.__on_receive(dict_data)
elif ping_market_outer:
#print("******** receive market ping pong ********", dict_data)
ping_ts = ping_market_outer
self.__process_ping_on_market_line(ping_ts)
else:
#print("unknown data process, RX: ", gzip.decompress(message).decode("utf-8"))
pass
def __on_receive(self, dict_data):
res = None
try:
if self.request.json_parser is not None:
res = self.request.json_parser(dict_data)
except Exception as e:
self.on_error("Failed to parse server's response: " + str(e))
try:
if self.request.update_callback is not None:
self.request.update_callback(res)
except Exception as e:
self.on_error("Process error: " + str(e)
+ " You should capture the exception in your error handler")
# websocket request will close the connection after receive
if self.request.auto_close:
self.close()
def __process_ping_on_trading_line(self, ping_ts):
#print("### __process_ping_on_trading_line ###")
#self.send("{\"op\":\"pong\",\"ts\":" + str(get_current_timestamp()) + "}")
PrintBasic.print_basic(ping_ts, "response time")
self.send("{\"op\":\"pong\",\"ts\":" + str(ping_ts) + "}")
return
def __process_ping_on_market_line(self, ping_ts):
#print("### __process_ping_on_market_line ###")
#self.send("{\"pong\":" + str(get_current_timestamp()) + "}")
PrintBasic.print_basic(ping_ts, "response time")
self.send("{\"pong\":" + str(ping_ts) + "}")
return
def __process_ping_on_v2_trade(self, ping_ts):
# PrintDate.timestamp_to_date(ping_ts)
self.send("{\"action\": \"pong\",\"data\": {\"ts\": " + str(ping_ts) +"}}")
return
def close_on_error(self):
if self.original_connection is not None:
self.original_connection.close()
self.state = ConnectionState.CLOSED_ON_ERROR
self.logger.error("[Sub][" + str(self.id) + "] Connection is closing due to error")