Skip to content

Commit c9d29f9

Browse files
committed
Merge branch 'master' into feat_mcp
2 parents 2772926 + a483c39 commit c9d29f9

21 files changed

+244
-76
lines changed

CHANGELOG.md

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,25 @@
1+
## 3.4.4 (2025-07-31)
2+
### New
3+
选股器支持分页 cursor_id 参数
4+
5+
## 3.4.3 (2025-07-22)
6+
### Fix
7+
下单返回 orders 属性处理
8+
9+
## 3.4.2 (2025-07-18)
10+
### New
11+
订单工具函数支持 time_in_force 参数
12+
QuoteClient 期货合约接口增加字段 合约规模 product_worth,交割方式 delivery_mode,合约类型 product_type
13+
QuoteClient 期货实时行情接口增加字段 `open_interest_change`
14+
15+
### Breaking
16+
订单/成交记录支持 page token;
17+
18+
## 3.4.1 (2025-06-26)
19+
### New
20+
- `QuoteClient.get_option_timeline` 期权分时接口
21+
- Contract 添加属性 `support_fractional_share`
22+
123
## 3.4.0 (2025-06-17)
224
### New
325
- `QuoteClient.get_bars` 增加 `date` 参数,用于查询历史分钟K线

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.0'
7+
__VERSION__ = '3.4.4'

tigeropen/common/consts/__init__.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ class Market(Enum):
3131

3232
@unique
3333
class TradingSession(Enum):
34+
All = 'All'
3435
PreMarket = 'PreMarket' # 盘前
3536
Regular = 'Regular' # 盘中
3637
AfterHours = 'AfterHours' # 盘后
@@ -43,6 +44,9 @@ class TradingSessionType(Enum):
4344
OVERNIGHT ='OVERNIGHT' # 夜盘
4445
RTH = 'RTH' # 盘中
4546
FULL ='FULL' # 全时段
47+
HK_AUC = 'HK_AUC'
48+
HK_CTS = 'HK_CTS'
49+
HK_AUC_CTS ='HK_AUC_CTS'
4650

4751

4852
@unique

tigeropen/common/consts/service_types.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@
8686
OPTION_TRADE_TICK = "option_trade_tick"
8787
OPTION_DEPTH = "option_depth"
8888
ALL_HK_OPTION_SYMBOLS = "all_hk_option_symbols"
89+
OPTION_TIMELINE = "option_timeline"
8990

9091

9192
# 期货行情

tigeropen/common/util/common_utils.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99
import delorean
1010
import pytz
1111

12+
from tigeropen.common.consts import Market
13+
1214
eastern = pytz.timezone('US/Eastern')
1315
china = pytz.timezone('Asia/Shanghai')
1416
hongkong = pytz.timezone('Asia/Hong_Kong')
@@ -43,3 +45,9 @@ def date_str_to_timestamp(dt, timezone):
4345
pass
4446
return dt
4547

48+
def get_tz_by_market(market: Market):
49+
if Market.HK == market:
50+
return hongkong
51+
elif Market.CN == market:
52+
return china
53+
return eastern

tigeropen/common/util/order_utils.py

Lines changed: 19 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
from tigeropen.common.consts import OrderStatus, OrderType
1010

1111

12-
def market_order(account, contract, action, quantity):
12+
def market_order(account, contract, action, quantity, time_in_force='DAY'):
1313
"""
1414
市价单
1515
:param account:
@@ -18,10 +18,10 @@ def market_order(account, contract, action, quantity):
1818
:param quantity:
1919
:return:
2020
"""
21-
return Order(account, contract, action, 'MKT', quantity)
21+
return Order(account, contract, action, 'MKT', quantity, time_in_force=time_in_force)
2222

2323

24-
def market_order_by_amount(account, contract, action, amount):
24+
def market_order_by_amount(account, contract, action, amount, time_in_force='DAY'):
2525
"""
2626
按金额的市价单(用于基金)
2727
:param account:
@@ -30,10 +30,10 @@ def market_order_by_amount(account, contract, action, amount):
3030
:param amount:
3131
:return:
3232
"""
33-
return Order(account, contract, action, 'MKT', total_cash_amount=amount)
33+
return Order(account, contract, action, 'MKT', total_cash_amount=amount, time_in_force=time_in_force)
3434

3535

36-
def limit_order(account, contract, action, quantity, limit_price):
36+
def limit_order(account, contract, action, quantity, limit_price, time_in_force='DAY'):
3737
"""
3838
限价单
3939
:param account:
@@ -43,10 +43,10 @@ def limit_order(account, contract, action, quantity, limit_price):
4343
:param limit_price: 限价的价格
4444
:return:
4545
"""
46-
return Order(account, contract, action, 'LMT', quantity, limit_price=limit_price)
46+
return Order(account, contract, action, 'LMT', quantity, limit_price=limit_price, time_in_force=time_in_force)
4747

4848

49-
def stop_order(account, contract, action, quantity, aux_price):
49+
def stop_order(account, contract, action, quantity, aux_price, time_in_force='DAY'):
5050
"""
5151
止损单
5252
:param account:
@@ -56,10 +56,10 @@ def stop_order(account, contract, action, quantity, aux_price):
5656
:param aux_price: 触发止损单的价格
5757
:return:
5858
"""
59-
return Order(account, contract, action, 'STP', quantity, aux_price=aux_price)
59+
return Order(account, contract, action, 'STP', quantity, aux_price=aux_price, time_in_force=time_in_force)
6060

6161

62-
def stop_limit_order(account, contract, action, quantity, limit_price, aux_price):
62+
def stop_limit_order(account, contract, action, quantity, limit_price, aux_price, time_in_force='DAY'):
6363
"""
6464
限价止损单
6565
:param account:
@@ -70,10 +70,11 @@ def stop_limit_order(account, contract, action, quantity, limit_price, aux_price
7070
:param aux_price: 触发止损单的价格
7171
:return:
7272
"""
73-
return Order(account, contract, action, 'STP_LMT', quantity, limit_price=limit_price, aux_price=aux_price)
73+
return Order(account, contract, action, 'STP_LMT', quantity, limit_price=limit_price, aux_price=aux_price,
74+
time_in_force=time_in_force)
7475

7576

76-
def trail_order(account, contract, action, quantity, trailing_percent=None, aux_price=None):
77+
def trail_order(account, contract, action, quantity, trailing_percent=None, aux_price=None, time_in_force='DAY'):
7778
"""
7879
移动止损单
7980
:param account:
@@ -84,7 +85,8 @@ def trail_order(account, contract, action, quantity, trailing_percent=None, aux_
8485
:param aux_price: 价差 aux_price 和 trailing_percent 两者互斥
8586
:return:
8687
"""
87-
return Order(account, contract, action, 'TRAIL', quantity, trailing_percent=trailing_percent, aux_price=aux_price)
88+
return Order(account, contract, action, 'TRAIL', quantity, trailing_percent=trailing_percent,
89+
aux_price=aux_price, time_in_force=time_in_force)
8890

8991

9092
def auction_limit_order(account, contract, action, quantity, limit_price, time_in_force='DAY'):
@@ -135,7 +137,7 @@ def order_leg(leg_type, price=None, time_in_force='DAY', outside_rth=None, limit
135137
quantity=quantity)
136138

137139

138-
def limit_order_with_legs(account, contract, action, quantity, limit_price, order_legs=None):
140+
def limit_order_with_legs(account, contract, action, quantity, limit_price, order_legs=None, time_in_force='DAY'):
139141
"""
140142
限价单 + 附加订单(仅环球账户支持)
141143
:param account:
@@ -148,7 +150,8 @@ def limit_order_with_legs(account, contract, action, quantity, limit_price, orde
148150
"""
149151
if order_legs and len(order_legs) > 2:
150152
raise Exception('2 order legs at most')
151-
return Order(account, contract, action, 'LMT', quantity, limit_price=limit_price, order_legs=order_legs)
153+
return Order(account, contract, action, 'LMT', quantity, limit_price=limit_price, order_legs=order_legs,
154+
time_in_force=time_in_force)
152155

153156

154157
def algo_order_params(start_time=None, end_time=None, no_take_liq=None, allow_past_end_time=None,
@@ -166,7 +169,7 @@ def algo_order_params(start_time=None, end_time=None, no_take_liq=None, allow_pa
166169
allow_past_end_time=allow_past_end_time, participation_rate=participation_rate)
167170

168171

169-
def algo_order(account, contract, action, quantity, strategy, algo_params=None, limit_price=None):
172+
def algo_order(account, contract, action, quantity, strategy, algo_params=None, limit_price=None, time_in_force='DAY'):
170173
"""
171174
算法订单
172175
:param account:
@@ -179,7 +182,7 @@ def algo_order(account, contract, action, quantity, strategy, algo_params=None,
179182
:return:
180183
"""
181184
return Order(account, contract, action, order_type=strategy, quantity=quantity, algo_params=algo_params,
182-
limit_price=limit_price, outside_rth=False)
185+
limit_price=limit_price, outside_rth=False, time_in_force=time_in_force)
183186

184187

185188
def contract_leg(symbol=None, sec_type=None, expiry=None, strike=None, put_call=None, action=None,

tigeropen/quote/domain/filter.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -154,7 +154,7 @@ def __repr__(self):
154154

155155

156156
class ScannerResult:
157-
def __init__(self, page, page_size, total_page, total_count, items):
157+
def __init__(self, page, page_size, total_page, total_count, items, cursor_id=None):
158158
"""
159159
{'page': 0, 'total_page': 668, 'total_count': 6678, 'page_size': 10,
160160
'items': [
@@ -175,6 +175,7 @@ def __init__(self, page, page_size, total_page, total_count, items):
175175
self.total_page = total_page
176176
self.total_count = total_count
177177
self.page_size = page_size
178+
self.cursor_id = cursor_id
178179
self.items = list()
179180
self.symbols = list()
180181
self._build_items(items)

tigeropen/quote/quote_client.py

Lines changed: 33 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
FUTURE_CONTRACTS, MARKET_SCANNER, \
2020
STOCK_BROKER, CAPITAL_FLOW, CAPITAL_DISTRIBUTION, WARRANT_REAL_TIME_QUOTE, WARRANT_FILTER, MARKET_SCANNER_TAGS, \
2121
KLINE_QUOTA, FUND_ALL_SYMBOLS, FUND_CONTRACTS, FUND_QUOTE, FUND_HISTORY_QUOTE, FINANCIAL_CURRENCY, \
22-
FINANCIAL_EXCHANGE_RATE, ALL_HK_OPTION_SYMBOLS, OPTION_DEPTH, BROKER_HOLD
22+
FINANCIAL_EXCHANGE_RATE, ALL_HK_OPTION_SYMBOLS, OPTION_DEPTH, BROKER_HOLD, OPTION_TIMELINE
2323
from tigeropen.common.consts.service_types import MARKET_STATE, ALL_SYMBOLS, ALL_SYMBOL_NAMES, BRIEF, \
2424
TIMELINE, KLINE, TRADE_TICK, OPTION_EXPIRATION, OPTION_CHAIN, FUTURE_EXCHANGE, OPTION_BRIEF, \
2525
OPTION_KLINE, OPTION_TRADE_TICK, FUTURE_KLINE, FUTURE_TICK, FUTURE_CONTRACT_BY_EXCHANGE_CODE, \
@@ -68,6 +68,7 @@
6868
from tigeropen.quote.response.option_quote_bar_response import OptionQuoteBarResponse
6969
from tigeropen.quote.response.option_quote_ticks_response import OptionTradeTickResponse
7070
from tigeropen.quote.response.option_symbols_response import OptionSymbolsResponse
71+
from tigeropen.quote.response.option_timeline_response import OptionTimelineResponse
7172
from tigeropen.quote.response.quote_bar_response import QuoteBarResponse
7273
from tigeropen.quote.response.quote_brief_response import QuoteBriefResponse
7374
from tigeropen.quote.response.quote_delay_briefs_response import DelayBriefsResponse
@@ -937,6 +938,34 @@ def get_option_depth(self, identifiers, market: Market = Market.US, timezone=Non
937938
else:
938939
raise ApiException(response.code, response.message)
939940

941+
def get_option_timeline(self, identifiers, market: Market, begin_time=None, timezone=None):
942+
params = OptionContractsParams()
943+
contracts = []
944+
for identifier in identifiers:
945+
symbol, expiry, put_call, strike = extract_option_info(identifier)
946+
if symbol is None or expiry is None or put_call is None or strike is None:
947+
continue
948+
param = SingleOptionQuoteParams()
949+
param.symbol = symbol
950+
param.expiry = date_str_to_timestamp(expiry, self._parse_timezone(timezone, market))
951+
param.put_call = put_call
952+
param.strike = strike
953+
param.begin_time = date_str_to_timestamp(begin_time, self._parse_timezone(timezone, market))
954+
contracts.append(param)
955+
params.option_query = contracts
956+
if market:
957+
params.market = get_enum_value(market)
958+
request = OpenApiRequest(OPTION_TIMELINE, biz_model=params)
959+
response_content = self.__fetch_data(request)
960+
if response_content:
961+
response = OptionTimelineResponse()
962+
response.parse_response_content(response_content)
963+
if response.is_success():
964+
return response.result
965+
else:
966+
raise ApiException(response.code, response.message)
967+
968+
940969
def get_future_exchanges(self, sec_type=SecurityType.FUT, lang=None):
941970
"""
942971
获取期货交易所列表
@@ -1266,7 +1295,7 @@ def get_future_brief(self, identifiers):
12661295
response = FutureBriefsResponse()
12671296
response.parse_response_content(response_content)
12681297
if response.is_success():
1269-
return response.briefs
1298+
return response.result
12701299
else:
12711300
raise ApiException(response.code, response.message)
12721301

@@ -1557,7 +1586,7 @@ def get_stock_industry(self, symbol, market=Market.US):
15571586
else:
15581587
raise ApiException(response.code, response.message)
15591588

1560-
def market_scanner(self, market=Market.US, filters=None, sort_field_data=None, page=0, page_size=100):
1589+
def market_scanner(self, market=Market.US, filters=None, sort_field_data=None, page=0, page_size=100, cursor_id=None):
15611590
"""
15621591
screen stocks
15631592
:param market: tigeropen.common.consts.Market
@@ -1588,6 +1617,7 @@ def market_scanner(self, market=Market.US, filters=None, sort_field_data=None, p
15881617
params.sort_field_data = sort_field_data
15891618
params.page = page
15901619
params.page_size = page_size
1620+
params.cursor_id = cursor_id
15911621
request = OpenApiRequest(MARKET_SCANNER, biz_model=params)
15921622
response_content = self.__fetch_data(request)
15931623
if response_content:

tigeropen/quote/request/model.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -956,6 +956,7 @@ def __init__(self):
956956
self._page = None
957957
self._page_size = None
958958
self._multi_tags_fields = None
959+
self._cursor_id = None
959960

960961
@property
961962
def market(self):
@@ -1029,6 +1030,14 @@ def multi_tags_fields(self):
10291030
def multi_tags_fields(self, value):
10301031
self._multi_tags_fields = value
10311032

1033+
@property
1034+
def cursor_id(self):
1035+
return self._cursor_id
1036+
1037+
@cursor_id.setter
1038+
def cursor_id(self, value):
1039+
self._cursor_id = value
1040+
10321041
def to_openapi_dict(self):
10331042
"""
10341043
example
@@ -1064,6 +1073,8 @@ def to_openapi_dict(self):
10641073
params['page'] = self.page
10651074
if self.page_size is not None:
10661075
params['page_size'] = self.page_size
1076+
if self.cursor_id is not None:
1077+
params['cursor_id'] = self.cursor_id
10671078
if self.multi_tags_fields:
10681079
params['multi_tag_field_list'] = [f.field_request_name for f in self.multi_tags_fields]
10691080
return params

tigeropen/quote/response/future_briefs_response.py

Lines changed: 14 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -7,45 +7,30 @@
77
import pandas as pd
88

99
from tigeropen.common.response import TigerResponse
10+
from tigeropen.common.util.string_utils import camel_to_underline
1011

11-
COLUMNS = ['identifier', 'ask_price', 'ask_size', 'bid_price', 'bid_size', 'pre_close', 'latest_price', 'latest_size',
12-
'latest_time', 'volume', 'open_interest', 'open', 'high', 'low', 'limit_up', 'limit_down', 'settlement',
13-
'settle_date']
14-
BRIEF_FIELD_MAPPINGS = {'askPrice': 'ask_price', 'askSize': 'ask_size', 'bidPrice': 'bid_price', 'bidSize': 'bid_size',
15-
'latestPrice': 'latest_price', 'openInterest': 'open_interest', 'preClose': 'pre_close',
16-
'right': 'put_call', 'latestTime': 'latest_time', 'latestSize': 'latest_size',
17-
'limitUp': 'limit_up', 'limitDown': 'limit_down', 'contractCode': 'identifier',
18-
'settleDate': 'settle_date'}
12+
BRIEF_FIELD_MAPPINGS = {'right': 'put_call', 'contractCode': 'identifier'}
1913

2014

2115
class FutureBriefsResponse(TigerResponse):
2216
def __init__(self):
2317
super(FutureBriefsResponse, self).__init__()
24-
self.briefs = None
18+
self.result = None
2519
self._is_success = None
2620

2721
def parse_response_content(self, response_content):
2822
response = super(FutureBriefsResponse, self).parse_response_content(response_content)
2923
if 'is_success' in response:
3024
self._is_success = response['is_success']
3125

32-
brief_data = []
33-
if self.data and isinstance(self.data, list):
34-
for item in self.data:
35-
item_values = {}
36-
for key, value in item.items():
37-
if value is None:
38-
continue
39-
tag = BRIEF_FIELD_MAPPINGS[key] if key in BRIEF_FIELD_MAPPINGS else key
40-
item_values[tag] = value
41-
brief_data.append([item_values.get(tag) for tag in COLUMNS])
42-
elif isinstance(self.data, dict):
43-
item_values = {}
44-
for key, value in self.data.items():
45-
if value is None:
46-
continue
47-
tag = BRIEF_FIELD_MAPPINGS[key] if key in BRIEF_FIELD_MAPPINGS else key
48-
item_values[tag] = value
49-
brief_data.append([item_values.get(tag) for tag in COLUMNS])
50-
51-
self.briefs = pd.DataFrame(brief_data, columns=COLUMNS)
26+
if self.data:
27+
if isinstance(self.data, list):
28+
fields_map = {origin: camel_to_underline(origin) for origin in self.data[0].keys()}
29+
df = pd.DataFrame(self.data)
30+
else:
31+
fields_map = {origin: camel_to_underline(origin) for origin in self.data.keys()}
32+
df = pd.DataFrame([self.data])
33+
fields_map.update(BRIEF_FIELD_MAPPINGS)
34+
self.result = df.rename(columns=fields_map)
35+
else:
36+
self.result = pd.DataFrame()

0 commit comments

Comments
 (0)