77import json
88import logging
99import sys
10- import os
1110from collections import defaultdict
11+ from itertools import accumulate , zip_longest
1212
1313import stomp
1414from stomp .exception import ConnectFailedException
1717from tigeropen .common .consts import OrderStatus
1818from tigeropen .common .consts .params import P_SDK_VERSION , P_SDK_VERSION_PREFIX
1919from tigeropen .common .consts .push_destinations import QUOTE , QUOTE_DEPTH , QUOTE_FUTURE , QUOTE_OPTION , TRADE_ASSET , \
20- TRADE_ORDER , TRADE_POSITION
20+ TRADE_ORDER , TRADE_POSITION , TRADE_TICK
2121from tigeropen .common .consts .push_subscriptions import SUBSCRIPTION_QUOTE , SUBSCRIPTION_QUOTE_DEPTH , \
2222 SUBSCRIPTION_QUOTE_OPTION , SUBSCRIPTION_QUOTE_FUTURE , SUBSCRIPTION_TRADE_ASSET , SUBSCRIPTION_TRADE_POSITION , \
23- SUBSCRIPTION_TRADE_ORDER
23+ SUBSCRIPTION_TRADE_ORDER , SUBSCRIPTION_TRADE_TICK
2424from tigeropen .common .consts .push_types import RequestType , ResponseType
2525from tigeropen .common .consts .quote_keys import QuoteChangeKey , QuoteKeyType
2626from tigeropen .common .exceptions import ApiException
27- from tigeropen .common .util .string_utils import camel_to_underline
2827from tigeropen .common .util .common_utils import get_enum_value
2928from tigeropen .common .util .order_utils import get_order_status
3029from tigeropen .common .util .signature_utils import sign_with_rsa
30+ from tigeropen .common .util .string_utils import camel_to_underline , camel_to_underline_obj
31+ from tigeropen .common .util .tick_util import get_part_code , get_part_code_name , get_trade_condition_map , \
32+ get_trade_condition
3133from tigeropen .push import _patch_ssl
3234
3335HOUR_TRADING_QUOTE_KEYS_MAPPINGS = {'hourTradingLatestPrice' : 'latest_price' , 'hourTradingPreClose' : 'pre_close' ,
@@ -88,7 +90,9 @@ def __init__(self, host, port, use_ssl=True, connection_timeout=120, heartbeats=
8890 self ._destination_counter_map = defaultdict (lambda : 0 )
8991
9092 self .subscribed_symbols = None
93+ self .query_subscribed_callback = None
9194 self .quote_changed = None
95+ self .tick_changed = None
9296 self .asset_changed = None
9397 self .position_changed = None
9498 self .order_changed = None
@@ -99,6 +103,7 @@ def __init__(self, host, port, use_ssl=True, connection_timeout=120, heartbeats=
99103 self .error_callback = None
100104 self ._connection_timeout = connection_timeout
101105 self ._heartbeats = heartbeats
106+ self .logger = logging .getLogger ('tiger_openapi' )
102107 _patch_ssl ()
103108
104109 def _connect (self ):
@@ -153,17 +158,29 @@ def on_message(self, frame):
153158 try :
154159 response_type = headers .get ('ret-type' )
155160 if response_type == str (ResponseType .GET_SUB_SYMBOLS_END .value ):
156- if self .subscribed_symbols :
161+ if self .subscribed_symbols or self . query_subscribed_callback :
157162 data = json .loads (body )
158- limit = data .get ('limit' )
159- symbols = data .get ('subscribedSymbols' )
160- used = data .get ('used' )
161- symbol_focus_keys = data .get ('symbolFocusKeys' )
163+ formatted_data = camel_to_underline_obj (data )
164+
165+ limit = formatted_data .get ('limit' )
166+ subscribed_symbols = formatted_data .get ('subscribed_symbols' )
167+ used = formatted_data .get ('used' )
168+ symbol_focus_keys = formatted_data .get ('symbol_focus_keys' )
162169 focus_keys = dict ()
163170 for sym , keys in symbol_focus_keys .items ():
164171 keys = set (QUOTE_KEYS_MAPPINGS .get (key , camel_to_underline (key )) for key in keys )
165172 focus_keys [sym ] = list (keys )
166- self .subscribed_symbols (symbols , focus_keys , limit , used )
173+ formatted_data ['symbol_focus_keys' ] = focus_keys
174+ formatted_data ['subscribed_quote_depth_symbols' ] = formatted_data .pop ('subscribed_ask_bid_symbols' )
175+ formatted_data ['quote_depth_limit' ] = formatted_data .pop ('ask_bid_limit' )
176+ formatted_data ['quote_depth_used' ] = formatted_data .pop ('ask_bid_used' )
177+ if self .subscribed_symbols :
178+ self .logger .warning ('PushClient.subscribed_symbols is deprecated, '
179+ 'use PushClient.query_subscribed_callback instead.' )
180+ self .subscribed_symbols (subscribed_symbols , focus_keys , limit , used )
181+ if self .query_subscribed_callback :
182+ self .query_subscribed_callback (formatted_data )
183+
167184 elif response_type == str (ResponseType .GET_QUOTE_CHANGE_END .value ):
168185 if self .quote_changed :
169186 data = json .loads (body )
@@ -204,6 +221,11 @@ def on_message(self, frame):
204221 items .append ((camel_to_underline (key ), value ))
205222 if items :
206223 self .quote_changed (symbol , items , hour_trading )
224+ elif response_type == str (ResponseType .GET_TRADING_TICK_END .value ):
225+ if self .tick_changed :
226+ symbol , items = self ._convert_tick (body )
227+ self .tick_changed (symbol , items )
228+
207229 elif response_type == str (ResponseType .SUBSCRIBE_ASSET .value ):
208230 if self .asset_changed :
209231 data = json .loads (body )
@@ -258,19 +280,19 @@ def on_message(self, frame):
258280 if self .error_callback :
259281 self .error_callback (body )
260282 except Exception as e :
261- logging .error (e , exc_info = True )
283+ self . logger .error (e , exc_info = True )
262284
263285 def on_error (self , frame ):
264286 body = json .loads (frame .body )
265287 if body .get ('code' ) == 4001 :
266- logging .error (body )
288+ self . logger .error (body )
267289 self .disconnect_callback = None
268290 raise ApiException (4001 , body .get ('message' ))
269291
270292 if self .error_callback :
271293 self .error_callback (frame )
272294 else :
273- logging .error (frame .body )
295+ self . logger .error (frame .body )
274296
275297 def _update_subscribe_id (self , destination ):
276298 self ._destination_counter_map [destination ] += 1
@@ -342,6 +364,15 @@ def subscribe_quote(self, symbols, quote_key_type=QuoteKeyType.TRADE, focus_keys
342364 return self ._handle_quote_subscribe (destination = QUOTE , subscription = SUBSCRIPTION_QUOTE , symbols = symbols ,
343365 extra_headers = extra_headers )
344366
367+ def subscribe_tick (self , symbols ):
368+ """
369+ subscribe trade tick
370+ :param symbols: symbol列表
371+ :return:
372+ """
373+ return self ._handle_quote_subscribe (destination = TRADE_TICK , subscription = SUBSCRIPTION_TRADE_TICK ,
374+ symbols = symbols )
375+
345376 def subscribe_depth_quote (self , symbols ):
346377 """
347378 订阅深度行情
@@ -383,6 +414,14 @@ def unsubscribe_quote(self, symbols=None, id=None):
383414 """
384415 self ._handle_quote_unsubscribe (destination = QUOTE , subscription = SUBSCRIPTION_QUOTE , sub_id = id , symbols = symbols )
385416
417+ def unsubscribe_tick (self , symbols = None , id = None ):
418+ """
419+ 退订行情更新
420+ :return:
421+ """
422+ self ._handle_quote_unsubscribe (destination = TRADE_TICK , subscription = SUBSCRIPTION_TRADE_TICK , sub_id = id ,
423+ symbols = symbols )
424+
386425 def unsubscribe_depth_quote (self , symbols = None , id = None ):
387426 """
388427 退订深度行情更新
@@ -440,3 +479,40 @@ def _generate_headers(self, extra_headers=None):
440479 headers .update (extra_headers )
441480 return headers
442481
482+ @staticmethod
483+ def _convert_tick (tick ):
484+ data = json .loads (tick )
485+ symbol = data .pop ('symbol' )
486+ data = camel_to_underline_obj (data )
487+ price_offset = 10 ** data .pop ('price_offset' )
488+ price_base = data .pop ('price_base' )
489+ # The latter time is equal to the sum of all previous values
490+ price_items = [('price' , (item + price_base ) / price_offset ) for item in data .pop ('prices' )]
491+ time_items = [('time' , item ) for item in accumulate (data .pop ('times' ))]
492+ volumes = [('volume' , item ) for item in data .pop ('volumes' )]
493+ tick_type_items = [('tick_type' , item ) for item in data .pop ('tick_type' )]
494+ part_codes = data .pop ('part_code' )
495+ if part_codes :
496+ part_code_items = [('part_code' , get_part_code (item )) for item in part_codes ]
497+ part_code_name_items = [('part_code_name' , get_part_code_name (item )) for item in part_codes ]
498+ else :
499+ part_code_items = [('part_code' , None ) for _ in range (len (time_items ))]
500+ part_code_name_items = [('part_code_name' , None ) for _ in range (len (time_items ))]
501+
502+ conds = data .pop ('cond' )
503+ cond_map = get_trade_condition_map (data .get ('quote_level' ))
504+ if conds :
505+ cond_items = [('cond' , get_trade_condition (item , cond_map )) for item in conds ]
506+ else :
507+ cond_items = [('cond' , None ) for _ in range (len (time_items ))]
508+ sn = data .pop ('sn' )
509+ sn_list = [('sn' , sn + i ) for i in range (len (time_items ))]
510+ tick_data = zip_longest (tick_type_items , price_items , volumes , part_code_items ,
511+ part_code_name_items , cond_items , time_items , sn_list )
512+ items = []
513+ for item in tick_data :
514+ item_dict = dict (item )
515+ item_dict .update (data )
516+ items .append (item_dict )
517+ return symbol , items
518+
0 commit comments