Skip to content

Commit d362674

Browse files
committed
push tick; subscribe quote query callback
1 parent 1f9df93 commit d362674

File tree

12 files changed

+241
-20
lines changed

12 files changed

+241
-20
lines changed

CHANGELOG.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,13 @@
1+
## 2.1.3 (2022-07-01)
2+
### New
3+
- 长连接新增逐笔订阅: `PushClient.subscribe_tick`, 退订 `PushClient.unsubscribe_tick`
4+
- 新增已订阅查询回调方法 `PushClient.query_subscribed_callback` 取代旧有的 `Pushclient.subscribed_symbols`
5+
- `QuoteClient.get_trade_ticks` 新增 `trade_session` 参数,可指定该参数查询盘前盘后数据
6+
7+
### Modify
8+
- `Pushclient.subscribed_symbols` 标记为废弃
9+
10+
111
## 2.1.2 (2022-06-14)
212
### Modify
313
- 升级 stomp.py 版本, 将之前的 4.1.24 升级到 8.0.1

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
name='tigeropen',
1717
version=__VERSION__,
1818
description='TigerBrokers Open API',
19-
packages=find_packages(exclude=[]),
19+
packages=find_packages(exclude=["tests"]),
2020
author='TigerBrokers',
2121
author_email='[email protected]',
2222
license='Apache License v2',

tigeropen/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,4 @@
44
55
@author: gaoan
66
"""
7-
__VERSION__ = '2.1.2'
7+
__VERSION__ = '2.1.3'

tigeropen/common/consts/push_destinations.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
QUOTE_DEPTH = 'quotedepth'
55
QUOTE_FUTURE = 'future'
66
QUOTE_OPTION = 'option'
7+
TRADE_TICK = 'tradetick'
78

89
TRADE_ASSET = 'trade/asset'
910
TRADE_POSITION = 'trade/position'

tigeropen/common/consts/push_subscriptions.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
SUBSCRIPTION_QUOTE_DEPTH = 'QuoteDepth'
55
SUBSCRIPTION_QUOTE_FUTURE = 'Future'
66
SUBSCRIPTION_QUOTE_OPTION = 'Option'
7+
SUBSCRIPTION_TRADE_TICK = 'TradeTick'
78

89
SUBSCRIPTION_TRADE_ASSET = 'Asset'
910
SUBSCRIPTION_TRADE_POSITION = 'Position'

tigeropen/common/consts/service_types.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@
5050
QUOTE_DEPTH = "quote_depth" # level2 深度行情
5151
GRAB_QUOTE_PERMISSION = "grab_quote_permission" # 抢占行情
5252
GET_QUOTE_PERMISSION = "get_quote_permission"
53+
TRADING_CALENDAR = "trading_calendar"
5354

5455
# 期权行情
5556
OPTION_EXPIRATION = "option_expiration"

tigeropen/examples/push_client_demo.py

Lines changed: 47 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,29 @@
66
"""
77
import time
88
# from tigeropen.common.consts import QuoteKeyType
9+
import pandas as pd
10+
911
from tigeropen.push.push_client import PushClient
1012
from tigeropen.examples.client_config import get_client_config
1113

1214

15+
def query_subscribed_callback(data):
16+
"""
17+
callback of PushClient.query_subscribed_quote
18+
:param data:
19+
example:
20+
{'subscribed_symbols': ['QQQ'], 'limit': 1200, 'used': 1, 'symbol_focus_keys': {'qqq': ['open', 'prev_close', 'low', 'volume', 'latest_price', 'close', 'high']},
21+
'subscribed_quote_depth_symbols': ['NVDA'], 'quote_depth_limit': 20, 'quote_depth_used': 1,
22+
'subscribed_trade_tick_symbols': ['QQQ', 'AMD', '00700'], 'trade_tick_limit': 1200, 'trade_tick_used': 3
23+
}
24+
:return:
25+
"""
26+
print(data)
27+
28+
1329
def on_query_subscribed_quote(symbols, focus_keys, limit, used):
1430
"""
31+
deprecated. Use query_subscribed_callback instead.
1532
查询已订阅symbol回调
1633
:param symbols: 订阅合约的列表
1734
:param focus_keys: 每个合约订阅的 key 列表
@@ -58,6 +75,28 @@ def on_quote_changed(symbol, items, hour_trading):
5875
print(symbol, items, hour_trading)
5976

6077

78+
def on_tick_changed(symbol, items):
79+
"""
80+
81+
:param symbol:
82+
:param items:
83+
items example:
84+
[{'tick_type': '*', 'price': 293.87, 'volume': 102, 'part_code': 'NSDQ',
85+
'part_code_name': 'NASDAQ Stock Market, LLC (NASDAQ)', 'cond': 'US_FORM_T', 'time': 1656405615779,
86+
'server_timestamp': 1656405573461, 'type': 'TradeTick', 'quote_level': 'usStockQuote', 'sn': 342,
87+
'timestamp': 1656405617385},
88+
{'tick_type': '*', 'price': 293.87, 'volume': 102, 'part_code': 'NSDQ',
89+
'part_code_name': 'NASDAQ Stock Market, LLC (NASDAQ)', 'cond': 'US_FORM_T', 'time': 1656405616573,
90+
'server_timestamp': 1656405573461,
91+
'type': 'TradeTick', 'quote_level': 'usStockQuote', 'sn': 343, 'timestamp': 1656405617385}]
92+
:return:
93+
"""
94+
print(symbol, items)
95+
# convert to DataFrame
96+
# frame = pd.DataFrame(items)
97+
# print(frame)
98+
99+
61100
def on_order_changed(account, items):
62101
"""
63102
@@ -152,8 +191,12 @@ def disconnect_callback():
152191

153192
# 行情变动回调
154193
push_client.quote_changed = on_quote_changed
194+
# 逐笔数据回调
195+
push_client.tick_changed = on_tick_changed
155196
# 已订阅 symbol 查询回调
156-
push_client.subscribed_symbols = on_query_subscribed_quote
197+
push_client.query_subscribed_callback = query_subscribed_callback
198+
# 已订阅 symbol 查询回调(已废弃)
199+
# push_client.subscribed_symbols = on_query_subscribed_quote
157200
# 订单变动回调
158201
push_client.order_changed = on_order_changed
159202
# 资产变动回调
@@ -182,6 +225,9 @@ def disconnect_callback():
182225
# 订阅深度行情
183226
push_client.subscribe_depth_quote(['AMD', 'BABA'])
184227

228+
# 订阅逐笔数据
229+
push_client.subscribe_tick(['AMD', 'QQQ'])
230+
185231
# 订阅资产变动
186232
push_client.subscribe_asset()
187233
# 订阅订单变动

tigeropen/examples/quote_client_demo.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,10 @@ def get_quote():
6262
delay_brief = openapi_client.get_stock_delay_briefs(['AAPL', 'GOOG'])
6363
print(delay_brief)
6464

65+
# 获取市场交易日历
66+
calendar = openapi_client.get_trading_calendar(Market.US, begin_date='2022-07-01', end_date='2022-09-02')
67+
print(calendar)
68+
6569

6670
def test_gat_bars_by_page():
6771
bars = openapi_client.get_bars_by_page(['AAPL'], period=BarPeriod.DAY,

tigeropen/push/push_client.py

Lines changed: 89 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@
77
import json
88
import logging
99
import sys
10-
import os
1110
from collections import defaultdict
11+
from itertools import accumulate, zip_longest
1212

1313
import stomp
1414
from stomp.exception import ConnectFailedException
@@ -17,17 +17,19 @@
1717
from tigeropen.common.consts import OrderStatus
1818
from tigeropen.common.consts.params import P_SDK_VERSION, P_SDK_VERSION_PREFIX
1919
from 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
2121
from 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
2424
from tigeropen.common.consts.push_types import RequestType, ResponseType
2525
from tigeropen.common.consts.quote_keys import QuoteChangeKey, QuoteKeyType
2626
from tigeropen.common.exceptions import ApiException
27-
from tigeropen.common.util.string_utils import camel_to_underline
2827
from tigeropen.common.util.common_utils import get_enum_value
2928
from tigeropen.common.util.order_utils import get_order_status
3029
from 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
3133
from tigeropen.push import _patch_ssl
3234

3335
HOUR_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

Comments
 (0)