Skip to content

Commit e611996

Browse files
committed
Merge branch 'feature_position_param' into 'dev'
Feature position param See merge request server/openapi/openapi-python-sdk!121
2 parents 89c9934 + 9cfc172 commit e611996

File tree

8 files changed

+195
-4
lines changed

8 files changed

+195
-4
lines changed

tests/test_utils.py

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
# -*- coding: utf-8 -*-
2+
#
3+
# @Date : 2022/7/14
4+
# @Author : sukai
5+
import unittest
6+
7+
from tigeropen.common.util.price_util import PriceUtil
8+
9+
10+
class TestUtils(unittest.TestCase):
11+
def test_price_util(self):
12+
delta = 1e-6
13+
tick_sizes = [{'begin': '0', 'end': '1', 'type': 'CLOSED', 'tick_size': 0.0001},
14+
{'begin': '1', 'end': 'Infinity', 'type': 'OPEN', 'tick_size': 0.01}]
15+
self.assertFalse(PriceUtil.match_tick_size(None, None))
16+
self.assertTrue(PriceUtil.match_tick_size(2.33, tick_sizes))
17+
self.assertTrue(PriceUtil.match_tick_size(2.3, tick_sizes))
18+
self.assertFalse(PriceUtil.match_tick_size(1.334, tick_sizes))
19+
self.assertTrue(PriceUtil.match_tick_size(0.5, tick_sizes))
20+
self.assertFalse(PriceUtil.match_tick_size(0.22223, tick_sizes))
21+
self.assertAlmostEqual(PriceUtil.fix_price_by_tick_size(2.334, tick_sizes, True), 2.34, delta=delta)
22+
self.assertAlmostEqual(PriceUtil.fix_price_by_tick_size(2.334, tick_sizes, False), 2.33, delta=delta)
23+
self.assertAlmostEqual(PriceUtil.fix_price_by_tick_size(2.3345, None), 2.3345, delta=delta)
24+
25+
tick_sizes = [{'begin': '0', 'end': '1', 'type': 'CLOSED', 'tick_size': 0.0005},
26+
{'begin': '1', 'end': '100', 'type': 'OPEN_CLOSED', 'tick_size': 0.05},
27+
{'begin': '100', 'end': '1000', 'type': 'OPEN_CLOSED', 'tick_size': 1.0},
28+
{'begin': '1000', 'end': '10000', 'type': 'OPEN_CLOSED', 'tick_size': 2.0},
29+
{'begin': '10000', 'end': 'Infinity', 'type': 'OPEN', 'tick_size': 5.0}]
30+
self.assertTrue(PriceUtil.match_tick_size(0.0005, tick_sizes))
31+
self.assertTrue(PriceUtil.match_tick_size(1.15, tick_sizes))
32+
self.assertFalse(PriceUtil.match_tick_size(0.0008, tick_sizes))
33+
self.assertFalse(PriceUtil.match_tick_size(1.11, tick_sizes))
34+
self.assertTrue(PriceUtil.match_tick_size(300, tick_sizes))
35+
self.assertFalse(PriceUtil.match_tick_size(300.5, tick_sizes))
36+
self.assertFalse(PriceUtil.match_tick_size(5001, tick_sizes))
37+
self.assertAlmostEqual(PriceUtil.fix_price_by_tick_size(0.0021, tick_sizes), 0.002, delta=delta)
38+
self.assertAlmostEqual(PriceUtil.fix_price_by_tick_size(0.0021, tick_sizes, True), 0.0025, delta=delta)
39+
self.assertAlmostEqual(PriceUtil.fix_price_by_tick_size(3.027, tick_sizes), 3.0, delta=delta)
40+
self.assertAlmostEqual(PriceUtil.fix_price_by_tick_size(3.027, tick_sizes, True), 3.05, delta=delta)
41+
self.assertAlmostEqual(PriceUtil.fix_price_by_tick_size(200.5, tick_sizes), 200, delta=delta)
42+
self.assertAlmostEqual(PriceUtil.fix_price_by_tick_size(2001, tick_sizes, True), 2002, delta=delta)
43+
self.assertAlmostEqual(PriceUtil.fix_price_by_tick_size(20001, tick_sizes, True), 20005, delta=delta)

tigeropen/common/consts/__init__.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,7 +148,15 @@ class IndustryLevel(Enum):
148148
GSUBIND = 'GSUBIND'
149149

150150

151+
@unique
152+
class TickSizeType(Enum):
153+
CLOSED = 'CLOSED'
154+
OPEN_CLOSED = 'OPEN_CLOSED'
155+
OPEN = 'OPEN'
156+
157+
151158
@unique
152159
class OrderSortBy(Enum):
153160
LATEST_CREATED = 'LATEST_CREATED'
154161
LATEST_STATUS_UPDATED = 'LATEST_STATUS_UPDATED'
162+
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
# -*- coding: utf-8 -*-
2+
#
3+
# @Date : 2022/7/13
4+
# @Author : sukai
5+
from decimal import Decimal, ROUND_DOWN
6+
7+
import math
8+
9+
from tigeropen.common.consts import TickSizeType
10+
11+
12+
class PriceUtil:
13+
INFINITY = 'Infinity'
14+
RELATIVE_TOLERANCE = 1e-6
15+
16+
@staticmethod
17+
def match_tick_size(price, tick_sizes):
18+
"""
19+
check if the price matches the tick sizes
20+
:param price: user input price
21+
:param tick_sizes: tick sizes list like:
22+
[{'begin': '0', 'end': '1', 'type': 'CLOSED', 'tick_size': 0.0001},
23+
{'begin': '1', 'end': 'Infinity', 'type': 'OPEN', 'tick_size': 0.01}]
24+
:return:
25+
"""
26+
if not price or not tick_sizes:
27+
return False
28+
fixed_price = PriceUtil.fix_price_by_tick_size(price=price, tick_sizes=tick_sizes)
29+
return math.isclose(price, fixed_price, rel_tol=PriceUtil.RELATIVE_TOLERANCE)
30+
31+
@staticmethod
32+
def fix_price_by_tick_size(price, tick_sizes, is_up=False):
33+
"""
34+
fix the user input price by tick sizes of the contract
35+
:param price:
36+
:param tick_sizes:
37+
:param is_up:
38+
:return:
39+
"""
40+
if not price:
41+
return None
42+
tick_size_item = PriceUtil._find_tick_size_item(price=price, tick_sizes=tick_sizes)
43+
if not tick_size_item:
44+
return price
45+
min_tick = tick_size_item.get('tick_size')
46+
begin = tick_size_item.get('begin')
47+
return PriceUtil._round_with_tick(price=price, begin=begin, min_tick=min_tick, is_up=is_up)
48+
49+
@staticmethod
50+
def _find_tick_size_item(price, tick_sizes):
51+
"""
52+
:param price:
53+
:param tick_sizes: tick size infos, example:
54+
[{'begin': '0', 'end': '1', 'type': 'CLOSED', 'tick_size': 0.0001},
55+
{'begin': '1', 'end': 'Infinity', 'type': 'OPEN', 'tick_size': 0.01}]
56+
:return: dict
57+
"""
58+
if not price or not tick_sizes:
59+
return None
60+
for item in tick_sizes:
61+
type_ = item.get('type')
62+
begin = float(item.get('begin'))
63+
end = float('inf') if PriceUtil.INFINITY == item.get('end') else float(item.get('end'))
64+
65+
if TickSizeType.OPEN.value == type_:
66+
if begin < price < end:
67+
return item
68+
elif TickSizeType.OPEN_CLOSED.value == type_:
69+
if begin < price <= end:
70+
return item
71+
elif TickSizeType.CLOSED.value == type_:
72+
if begin <= price <= end:
73+
return item
74+
return None
75+
76+
@staticmethod
77+
def _round_with_tick(price, begin, min_tick, is_up):
78+
p = Decimal(str(price))
79+
t = Decimal(str(min_tick))
80+
base = Decimal(str(begin))
81+
multiple = (p - base) / t
82+
if multiple <= 0:
83+
return price
84+
if is_up:
85+
multiple += Decimal(1)
86+
87+
return float(multiple.quantize(0, ROUND_DOWN) * t + base)

tigeropen/examples/trade_client_demo.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import logging
88
import traceback
99

10+
from tigeropen.common.util.price_util import PriceUtil
1011
from tigeropen.trade.domain.order import OrderStatus
1112
from tigeropen.trade.request.model import AccountsParams
1213
from tigeropen.tiger_open_client import TigerOpenClient
@@ -122,6 +123,13 @@ def trade_apis():
122123
order_legs = openapi_client.get_open_orders(account, parent_id=main_order.order_id)
123124
print(order_legs)
124125

126+
# adjust price by contract tick sizes
127+
contract = openapi_client.get_contract('UVXY')
128+
price = 10.125
129+
if not PriceUtil.match_tick_size(price, contract.tick_sizes):
130+
price = PriceUtil.fix_price_by_tick_size(price, contract.tick_sizes)
131+
132+
125133

126134
def algo_order_demo():
127135
account = client_config.account

tigeropen/trade/domain/contract.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ def __init__(self, symbol=None, currency=None, contract_id=None, sec_type=None,
1515
long_maintenance_margin=None, contract_month=None, identifier=None, primary_exchange=None,
1616
market=None, min_tick=None, trading_class=None, status=None, continuous=None, trade=None,
1717
marginable=None, close_only=None,
18-
last_trading_date=None, first_notice_date=None, last_bidding_close_time=None):
18+
last_trading_date=None, first_notice_date=None, last_bidding_close_time=None, tick_sizes=None):
1919
self.contract_id = contract_id
2020
self.symbol = symbol
2121
self.currency = get_enum_value(currency)
@@ -54,6 +54,8 @@ def __init__(self, symbol=None, currency=None, contract_id=None, sec_type=None,
5454
self.market = market
5555
# 最小报价单位
5656
self.min_tick = min_tick
57+
# tick size info list
58+
self.tick_sizes = tick_sizes
5759
# 合约的交易级别名称
5860
self.trading_class = trading_class
5961
# 状态

tigeropen/trade/request/model.py

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,9 @@ def __init__(self):
117117
self._currency = None
118118
self._market = None
119119
self._sub_accounts = None
120+
self._expiry = None
121+
self._strike = None
122+
self._right = None
120123

121124
@property
122125
def account(self):
@@ -174,6 +177,30 @@ def sub_accounts(self):
174177
def sub_accounts(self, value):
175178
self._sub_accounts = value
176179

180+
@property
181+
def expiry(self):
182+
return self._expiry
183+
184+
@expiry.setter
185+
def expiry(self, value):
186+
self._expiry = value
187+
188+
@property
189+
def strike(self):
190+
return self._strike
191+
192+
@strike.setter
193+
def strike(self, value):
194+
self._strike = value
195+
196+
@property
197+
def right(self):
198+
return self._right
199+
200+
@right.setter
201+
def right(self, value):
202+
self._right = value
203+
177204
def to_openapi_dict(self):
178205
params = dict()
179206
if self.account:
@@ -197,6 +224,15 @@ def to_openapi_dict(self):
197224
if self.sub_accounts:
198225
params['sub_accounts'] = self.sub_accounts
199226

227+
if self.expiry:
228+
params['expiry'] = self.expiry
229+
230+
if self.strike:
231+
params['strike'] = self.strike
232+
233+
if self.right:
234+
params['right'] = self.right
235+
200236
return params
201237

202238

tigeropen/trade/response/contracts_response.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
import json
88

99
from tigeropen.common.response import TigerResponse
10-
from tigeropen.common.util.string_utils import camel_to_underline
10+
from tigeropen.common.util.string_utils import camel_to_underline, camel_to_underline_obj
1111
from tigeropen.trade.domain.contract import Contract
1212

1313
CONTRACT_FIELD_MAPPINGS = {'conid': 'contract_id', 'right': 'put_call', 'tradeable': 'trade'}
@@ -31,6 +31,8 @@ def parse_response_content(self, response_content):
3131
contract_fields = {}
3232
for key, value in item.items():
3333
tag = CONTRACT_FIELD_MAPPINGS[key] if key in CONTRACT_FIELD_MAPPINGS else camel_to_underline(key)
34+
if isinstance(value, (list, dict)):
35+
value = camel_to_underline_obj(value)
3436
contract_fields[tag] = value
3537
contract = Contract()
3638
for k, v in contract_fields.items():

tigeropen/trade/trade_client.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -176,7 +176,7 @@ def get_derivative_contracts(self, symbol, sec_type, expiry, lang=None):
176176
return None
177177

178178
def get_positions(self, account=None, sec_type=SecurityType.STK, currency=Currency.ALL, market=Market.ALL,
179-
symbol=None, sub_accounts=None):
179+
symbol=None, sub_accounts=None, expiry=None, strike=None, put_call=None):
180180
"""
181181
获取持仓数据
182182
:param account:
@@ -203,7 +203,12 @@ def get_positions(self, account=None, sec_type=SecurityType.STK, currency=Curren
203203
params.currency = get_enum_value(currency)
204204
params.market = get_enum_value(market)
205205
params.symbol = symbol
206-
206+
if expiry:
207+
params.expiry = expiry
208+
if strike:
209+
params.strike = strike
210+
if put_call:
211+
params.right = put_call
207212
request = OpenApiRequest(POSITIONS, biz_model=params)
208213
response_content = self.__fetch_data(request)
209214
if response_content:

0 commit comments

Comments
 (0)