Skip to content

Commit d72c4eb

Browse files
committed
Merge branch 'fix_timeline_history' into 'master'
fix timeline history See merge request server/openapi/openapi-python-sdk!256
2 parents 29a882c + 2e996d5 commit d72c4eb

File tree

10 files changed

+134
-58
lines changed

10 files changed

+134
-58
lines changed

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,9 @@
1+
## 3.4.7 (2025-09-23)
2+
### Fix
3+
- `QuoteClient.get_timeline_history` 解析盘前盘后分时数据问题
4+
### Breaking
5+
`QuoteClient.get_timeline` 返回的 DataFrame 中原来的 `tradingSession` 列改为 `trade_session`, 值改为 `TradingSession` 枚举值字符串
6+
17
## 3.4.6 (2025-08-28)
28
### New
39
- `TradeClient.get_positions` 持仓合约增加字段 `name`, `underlying_contract_name`

README.md

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ python setup.py install
2424
- 接入前需要在[开放平台](https://quant.itiger.com/#openapi)登记开发者信息
2525
- 详情查看[接入说明](https://quant.itiger.com/openapi/zh/python/overview/introduction.html)
2626

27-
###### 注: 本SDK当前支持 Python3.4 及以上版本
27+
###### 注: 本SDK当前支持 Python3.8 及以上版本
2828

2929
---
3030

@@ -47,14 +47,10 @@ from tigeropen.trade.trade_client import TradeClient
4747
4848
def get_client_config():
4949
"""
50-
:return:
50+
Get client config.
5151
"""
52-
is_sandbox = False
53-
client_config = TigerOpenClientConfig(sandbox_debug=is_sandbox)
54-
client_config.private_key = read_private_key('your private key file path')
55-
client_config.tiger_id = 'your tiger id'
56-
client_config.account = 'your account'
57-
client_config.language = Language.en_US
52+
# First, get properties config file from the developer's website, then save it to local disk, such as /Users/demo/props/
53+
client_config = TigerOpenClientConfig(props_path='/Users/demo/props/')
5854
return client_config
5955
6056
def get_account_info():

setup.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,6 @@
3131
'Programming Language :: Python',
3232
'Operating System :: Microsoft :: Windows',
3333
'Operating System :: Unix',
34-
'Programming Language :: Python :: 3.6',
35-
'Programming Language :: Python :: 3.7',
3634
'Programming Language :: Python :: 3.8',
3735
'Programming Language :: Python :: 3.9',
3836
'Programming Language :: Python :: 3.10',

tests/test_quote_client.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -369,15 +369,15 @@ def test_get_timeline(self):
369369
self.assertIn('avg_price', result.columns)
370370
self.assertIn('pre_close', result.columns)
371371
self.assertIn('volume', result.columns)
372-
self.assertIn('trading_session', result.columns)
372+
self.assertIn('trade_session', result.columns)
373373
first_row = result.iloc[0]
374374
self.assertEqual(first_row['symbol'], 'AAPL')
375375
self.assertEqual(first_row['time'], 1754919000000)
376376
self.assertAlmostEqual(first_row['price'], 226.75, places=2)
377377
self.assertAlmostEqual(first_row['avg_price'], 227.75438, places=5)
378378
self.assertEqual(first_row['pre_close'], 229.09)
379379
self.assertEqual(first_row['volume'], 1656620)
380-
self.assertEqual(first_row['trading_session'], 'regular')
380+
self.assertEqual(first_row['trade_session'], 'Regular')
381381
else:
382382
result = self.client.get_timeline(symbols=['AAPL'],
383383
# trade_session=TradingSession.OverNight
@@ -417,7 +417,7 @@ def test_get_timeline_history(self):
417417
else:
418418
result = self.client.get_timeline_history(symbols=['AAPL'],
419419
date="2025-08-21",
420-
# trade_session=TradingSession.OverNight
420+
trade_session=TradingSession.OverNight
421421
)
422422
logger.debug(f"Timeline History (real):\n {result}")
423423

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__ = '3.4.6'
7+
__VERSION__ = '3.4.7'

tigeropen/examples/ai/mcp_server/tigermcp/server.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -734,7 +734,7 @@ def place_order(action: str = Field(..., description="Order action. BUY/SELL", p
734734
@staticmethod
735735
@server.tool(description="Cancel Order")
736736
@ApiHelper.handle_result
737-
def cancel_order(id: str = Field(..., description='Order.id, like 38000878710423552')) -> Any:
737+
def cancel_order(id: str = Field(..., description='Order.id, a multi digit number')) -> Any:
738738
"""
739739
Cancel order
740740
撤销订单
@@ -786,7 +786,7 @@ def get_orders(symbol: Optional[str] = None,
786786
@staticmethod
787787
@server.tool(description="Get detailed information for a specified order.")
788788
@ApiHelper.handle_result
789-
def get_order(id: str = Field(..., description='Order.id, a multi digit number, like 38000878710423552')) -> Any:
789+
def get_order(id: str = Field(..., description='Order.id, a multi digit number')) -> Any:
790790
"""
791791
Get order
792792
获取订单详情

tigeropen/quote/quote_client.py

Lines changed: 16 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@
8686
from tigeropen.quote.response.quote_grab_permission_response import QuoteGrabPermissionResponse
8787
from tigeropen.quote.response.quote_overnight_response import QuoteOvernightResponse
8888
from tigeropen.quote.response.quote_ticks_response import TradeTickResponse
89+
from tigeropen.quote.response.quote_timeline_history_response import QuoteTimelineHistoryResponse
8990
from tigeropen.quote.response.quote_timeline_response import QuoteTimelineResponse
9091
from tigeropen.quote.response.stock_briefs_response import StockBriefsResponse
9192
from tigeropen.quote.response.stock_broker_response import StockBrokerResponse
@@ -517,12 +518,15 @@ def get_timeline(self,
517518
- avg_price: volume weighted avg price up to now 加权均价
518519
- pre_close: previous close 昨日收盘价
519520
- volume: volume of the minute 该分钟成交量
520-
- trading_session: trading session 交易时段
521+
- trade_session: trading session 交易时段
521522
522523
:return example:
523-
symbol time price avg_price pre_close volume trading_session
524-
0 AAPL 1754919000000 226.7500 227.75438 229.09 1656620 regular
525-
1 AAPL 1754919060000 226.6000 227.51157 229.09 426781 regular
524+
symbol pre_close trade_session time volume price avg_price
525+
0 AAPL 245.5 PreMarket 1758528000000 13203 245.35000 245.70515
526+
1 AAPL 245.5 PreMarket 1758528060000 2568 245.50000 245.66475
527+
2 AAPL 245.5 PreMarket 1758528120000 16896 245.20000 245.47733
528+
3 AAPL 245.5 PreMarket 1758528180000 8056 245.11000 245.43240
529+
4 AAPL 245.5 PreMarket 1758528240000 2378 245.16000 245.41733
526530
"""
527531
params = MultipleQuoteParams()
528532
params.symbols = self._format_to_list(symbols)
@@ -542,7 +546,7 @@ def get_timeline(self,
542546
response = QuoteTimelineResponse()
543547
response.parse_response_content(response_content)
544548
if response.is_success():
545-
return response.timelines
549+
return response.result
546550
else:
547551
raise ApiException(response.code, response.message)
548552

@@ -566,12 +570,12 @@ def get_timeline_history(
566570
- avg_price: volume weighted avg price up to now 加权均价
567571
568572
Example:
569-
symbol time volume price avg_price
570-
0 AAPL 1698845400000 654691 171.1000 171.04840
571-
1 AAPL 1698845460000 175598 170.5950 171.01788
572-
2 AAPL 1698845520000 186093 170.5350 170.95750
573-
3 AAPL 1698845580000 145550 170.2719 170.90828
574-
4 AAPL 1698845640000 221063 170.4100 170.82759
573+
symbol trade_session time volume price avg_price
574+
0 NVDA Regular 1744378200000 2734029 109.03000 108.544205
575+
1 NVDA Regular 1744378260000 1174433 108.70000 108.564995
576+
2 NVDA Regular 1744378320000 1209217 108.09000 108.557440
577+
3 NVDA Regular 1744378380000 1087583 108.42500 108.543790
578+
4 NVDA Regular 1744378440000 958574 108.43500 108.532360
575579
"""
576580
params = MultipleQuoteParams()
577581
params.symbols = self._format_to_list(symbols)
@@ -582,7 +586,7 @@ def get_timeline_history(
582586
request = OpenApiRequest(HISTORY_TIMELINE, biz_model=params)
583587
response_content = self.__fetch_data(request)
584588
if response_content:
585-
response = QuoteDataframeResponse()
589+
response = QuoteTimelineHistoryResponse()
586590
response.parse_response_content(response_content)
587591
if response.is_success():
588592
return response.result
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
2+
import pandas as pd
3+
4+
from tigeropen.common.consts import TradingSession
5+
from tigeropen.common.response import TigerResponse
6+
from tigeropen.common.util.string_utils import camel_to_underline_obj, camel_to_underline
7+
8+
FIELD_PREMARKET = 'preMarket'
9+
FIELD_ITEMS = 'items'
10+
FIELD_AFTERHOURS = 'afterHours'
11+
FIELD_OVERNIGHT = 'overnight'
12+
COLUMN_TRADE_SESSION = 'trade_session'
13+
14+
class QuoteTimelineHistoryResponse(TigerResponse):
15+
def __init__(self):
16+
super(QuoteTimelineHistoryResponse, self).__init__()
17+
self.result = None
18+
self._is_success = None
19+
20+
def parse_response_content(self, response_content):
21+
response = super(QuoteTimelineHistoryResponse, self).parse_response_content(response_content)
22+
if 'is_success' in response:
23+
self._is_success = response['is_success']
24+
25+
if self.data and isinstance(self.data, list):
26+
timeline_items = []
27+
for symbol_item in self.data:
28+
symbol = symbol_item.get('symbol')
29+
if FIELD_PREMARKET in symbol_item: # 盘前
30+
pre_markets = symbol_item[FIELD_PREMARKET]
31+
if pre_markets:
32+
for item in pre_markets:
33+
item_values = self.parse_timeline(item, symbol, TradingSession.PreMarket.value)
34+
timeline_items.append(camel_to_underline_obj(item_values))
35+
if FIELD_ITEMS in symbol_item:
36+
regulars = symbol_item[FIELD_ITEMS]
37+
if regulars:
38+
for item in regulars:
39+
item_values = self.parse_timeline(item, symbol, TradingSession.Regular.value)
40+
timeline_items.append(camel_to_underline_obj(item_values))
41+
if FIELD_AFTERHOURS in symbol_item: # 盘后
42+
after_hours = symbol_item[FIELD_AFTERHOURS]
43+
if after_hours:
44+
for item in after_hours:
45+
item_values = self.parse_timeline(item, symbol, TradingSession.AfterHours.value)
46+
timeline_items.append(camel_to_underline_obj(item_values))
47+
if FIELD_OVERNIGHT in symbol_item: # 夜盘
48+
overnights = symbol_item[FIELD_OVERNIGHT]
49+
if overnights:
50+
for item in overnights:
51+
item_values = self.parse_timeline(item, symbol, TradingSession.OverNight.value)
52+
timeline_items.append(camel_to_underline_obj(item_values))
53+
54+
self.result = pd.DataFrame(timeline_items)
55+
56+
@staticmethod
57+
def parse_timeline(item, symbol, trading_session):
58+
item_values = {'symbol': symbol, COLUMN_TRADE_SESSION: trading_session}
59+
for key, value in item.items():
60+
if value is None:
61+
continue
62+
tag = camel_to_underline(key)
63+
item_values[tag] = value
64+
65+
return item_values

tigeropen/quote/response/quote_timeline_response.py

Lines changed: 33 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -7,16 +7,23 @@
77

88
import pandas as pd
99

10+
from tigeropen.common.consts import TradingSession
1011
from tigeropen.common.response import TigerResponse
12+
from tigeropen.common.util.string_utils import camel_to_underline
1113

12-
COLUMNS = ['symbol', 'time', 'price', 'avg_price', 'pre_close', 'volume', 'trading_session']
13-
TIMELINE_FIELD_MAPPINGS = {'avgPrice': 'avg_price'}
14+
FIELD_PREMARKET = 'preMarket'
15+
FIELD_ITEMS = 'items'
16+
FIELD_INTRADAY = 'intraday'
17+
FIELD_AFTERHOURS = 'afterHours'
18+
FIELD_OVERNIGHT = 'overnight'
19+
COLUMN_TRADE_SESSION = 'trade_session'
20+
COLUMN_PRE_CLOSE = 'pre_close'
1421

1522

1623
class QuoteTimelineResponse(TigerResponse):
1724
def __init__(self):
1825
super(QuoteTimelineResponse, self).__init__()
19-
self.timelines = None
26+
self.result = None
2027
self._is_success = None
2128

2229
def parse_response_content(self, response_content):
@@ -29,40 +36,46 @@ def parse_response_content(self, response_content):
2936
for symbol_item in self.data:
3037
symbol = symbol_item.get('symbol')
3138
pre_close = symbol_item.get('preClose')
32-
if 'preMarket' in symbol_item: # 盘前
33-
pre_markets = symbol_item['preMarket'].get('items')
39+
if FIELD_PREMARKET in symbol_item: # 盘前
40+
pre_markets = symbol_item[FIELD_PREMARKET].get('items')
3441
if pre_markets:
3542
for item in pre_markets:
36-
item_values = self.parse_timeline(item, symbol, pre_close, 'pre_market')
37-
timeline_items.append([item_values.get(tag) for tag in COLUMNS])
43+
item_values = self.parse_timeline(item, symbol, pre_close, TradingSession.PreMarket.value)
44+
timeline_items.append(item_values)
3845

39-
if 'intraday' in symbol_item: # 盘中
40-
regulars = symbol_item['intraday'].get('items')
41-
elif 'items' in symbol_item:
42-
regulars = symbol_item['items'][0].get('items')
46+
if FIELD_INTRADAY in symbol_item: # 盘中
47+
regulars = symbol_item[FIELD_INTRADAY].get('items')
48+
elif FIELD_ITEMS in symbol_item:
49+
regulars = symbol_item[FIELD_ITEMS][0].get('items')
4350
else:
4451
regulars = None
4552
if regulars:
4653
for item in regulars:
47-
item_values = self.parse_timeline(item, symbol, pre_close, 'regular')
48-
timeline_items.append([item_values.get(tag) for tag in COLUMNS])
54+
item_values = self.parse_timeline(item, symbol, pre_close, TradingSession.Regular.value)
55+
timeline_items.append(item_values)
4956

50-
if 'afterHours' in symbol_item: # 盘后
51-
after_hours = symbol_item['afterHours'].get('items')
57+
if FIELD_AFTERHOURS in symbol_item: # 盘后
58+
after_hours = symbol_item[FIELD_AFTERHOURS].get('items')
5259
if after_hours:
5360
for item in after_hours:
54-
item_values = self.parse_timeline(item, symbol, pre_close, 'after_hours')
55-
timeline_items.append([item_values.get(tag) for tag in COLUMNS])
61+
item_values = self.parse_timeline(item, symbol, pre_close, TradingSession.AfterHours.value)
62+
timeline_items.append(item_values)
63+
if FIELD_OVERNIGHT in symbol_item: # 夜盘
64+
overnights = symbol_item[FIELD_OVERNIGHT].get('items')
65+
if overnights:
66+
for item in overnights:
67+
item_values = self.parse_timeline(item, symbol, pre_close, TradingSession.OverNight.value)
68+
timeline_items.append(item_values)
5669

57-
self.timelines = pd.DataFrame(timeline_items, columns=COLUMNS)
70+
self.result = pd.DataFrame(timeline_items)
5871

5972
@staticmethod
6073
def parse_timeline(item, symbol, pre_close, trading_session):
61-
item_values = {'symbol': symbol, 'pre_close': pre_close, 'trading_session': trading_session}
74+
item_values = {'symbol': symbol, COLUMN_PRE_CLOSE: pre_close, COLUMN_TRADE_SESSION: trading_session}
6275
for key, value in item.items():
6376
if value is None:
6477
continue
65-
tag = TIMELINE_FIELD_MAPPINGS[key] if key in TIMELINE_FIELD_MAPPINGS else key
78+
tag = camel_to_underline(key)
6679
item_values[tag] = value
6780

6881
return item_values

tigeropen/tiger_open_config.py

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
TIGEROPEN_ACCOUNT = ENV_PREFIX + 'ACCOUNT'
2828
TIGEROPEN_SECRET_KEY = ENV_PREFIX + 'SECRET_KEY'
2929
TIGEROPEN_PRIVATE_KEY = ENV_PREFIX + 'PRIVATE_KEY'
30+
TIGEROPEN_LICENSE = ENV_PREFIX + 'LICENSE'
3031
TIGEROPEN_PROPS_PATH = ENV_PREFIX + 'PROPS_PATH'
3132

3233
DEFAULT_DOMAIN = 'openapi.tigerfintech.com'
@@ -321,20 +322,14 @@ def _get_props_path(self, filename):
321322
return None
322323

323324
def _load_env_vars(self):
324-
"""
325-
从环境变量加载配置
326-
"""
327-
# 读取 props_path
328325
if os.environ.get(TIGEROPEN_PROPS_PATH):
329326
self.props_path = os.environ.get(TIGEROPEN_PROPS_PATH)
330327

331-
# 读取核心配置
332328
if not self.tiger_id and os.environ.get(TIGEROPEN_TIGER_ID):
333329
self.tiger_id = os.environ.get(TIGEROPEN_TIGER_ID)
334330

335331
if not self.private_key and os.environ.get(TIGEROPEN_PRIVATE_KEY):
336332
private_key_content = os.environ.get(TIGEROPEN_PRIVATE_KEY)
337-
# 检查是否是文件路径
338333
if os.path.exists(private_key_content):
339334
self.private_key = read_private_key(private_key_content)
340335
else:
@@ -346,11 +341,10 @@ def _load_env_vars(self):
346341
if not self.secret_key and os.environ.get(TIGEROPEN_SECRET_KEY):
347342
self.secret_key = os.environ.get(TIGEROPEN_SECRET_KEY)
348343

344+
if not self.license and os.environ.get(TIGEROPEN_LICENSE):
345+
self.license = os.environ.get(TIGEROPEN_LICENSE)
346+
349347
def _load_props(self):
350-
"""
351-
从配置文件读取配置
352-
"""
353-
# 从配置文件读取剩余配置或未在环境变量中指定的配置
354348
full_path = self._get_props_path(DEFAULT_PROPS_FILE)
355349
if full_path and os.path.exists(full_path):
356350
try:

0 commit comments

Comments
 (0)