Skip to content

Commit c4e3121

Browse files
committed
Merge branch 'dev' into 'master'
Dev-master See merge request server/openapi/openapi-python-sdk!109
2 parents 2c4a562 + 8b41a71 commit c4e3121

File tree

13 files changed

+500
-49
lines changed

13 files changed

+500
-49
lines changed

CHANGELOG.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,11 @@
1+
## 2.1.0 (2022-05-07)
2+
### New
3+
- 动态获取服务域名;更改默认域名
4+
- 新增期权计算工具(examples.option_helpers.helpers)
5+
- 新增根据期货代码获取期货合约接口 `QuoteClient.get_future_contract`
6+
- 新增根据正股查衍生合约接口 `TradeClient.get_derivative_contracts`
7+
8+
19
## 2.0.9 (2022-04-18)
210
### New
311
- 新增历史分时接口 `QuoteClient.get_timeline_history`

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.0.9'
7+
__VERSION__ = '2.1.0'

tigeropen/common/consts/service_types.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
"""
3030
CONTRACT = "contract"
3131
CONTRACTS = "contracts"
32+
QUOTE_CONTRACT = "quote_contract"
3233

3334
"""
3435
行情

tigeropen/common/util/web_utils.py

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -53,16 +53,26 @@ def get_http_connection(url, query_string, timeout):
5353

5454

5555
def do_post(url, query_string=None, headers=None, params=None, timeout=15, charset=None):
56+
return do_request('POST', url=url, query_string=query_string, headers=headers, params=params, timeout=timeout,
57+
charset=charset)
58+
59+
60+
def do_get(url, query_string=None, headers=None, params=None, timeout=15, charset=None):
61+
return do_request('GET', url=url, query_string=query_string, headers=headers, params=params, timeout=timeout,
62+
charset=charset)
63+
64+
65+
def do_request(method, url, query_string=None, headers=None, params=None, timeout=15, charset=None):
5666
url, connection = get_http_connection(url, query_string, timeout)
5767

5868
try:
5969
connection.connect()
6070
except Exception as e:
61-
raise RequestException('[' + THREAD_LOCAL.uuid + ']post connect failed. ' + str(e))
71+
raise RequestException('[' + THREAD_LOCAL.uuid + ']' + method + ' connect failed. ' + str(e))
6272
try:
63-
connection.request("POST", url, body=json.dumps(params), headers=headers)
73+
connection.request(method, url, body=json.dumps(params), headers=headers)
6474
except Exception as e:
65-
raise RequestException('[' + THREAD_LOCAL.uuid + ']post request failed. ' + str(e))
75+
raise RequestException('[' + THREAD_LOCAL.uuid + ']' + method + ' request failed. ' + str(e))
6676
response = connection.getresponse()
6777
result = response.read()
6878

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
# -*- coding: utf-8 -*-
2+
#
3+
# @Date : 2022/4/15
4+
# @Author : sukai
Lines changed: 295 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,295 @@
1+
# -*- coding: utf-8 -*-
2+
# Helpers for calculating the Greeks of options
3+
# @Date : 2022/4/15
4+
# @Author : sukai
5+
import argparse
6+
from typing import List
7+
8+
try:
9+
import QuantLib as ql
10+
except ImportError:
11+
pass
12+
13+
14+
class FDDividendOptionHelper(ql.DividendVanillaOption):
15+
def __init__(self,
16+
engine_class,
17+
option_type: ql.Option,
18+
underlying: float,
19+
strike: float,
20+
risk_free_rate: float,
21+
dividend_rate: float,
22+
volatility: float,
23+
reference_date: ql.Date,
24+
exercise: ql.Exercise,
25+
dates: List[ql.Date] = None,
26+
dividends: List[float] = None,
27+
calendar: ql.Calendar = ql.NullCalendar(),
28+
day_counter: ql.DayCounter = ql.Actual365Fixed()
29+
):
30+
self.dates = dates if dates is not None else list()
31+
self.dividends = dividends if dividends is not None else list()
32+
super(FDDividendOptionHelper, self).__init__(ql.PlainVanillaPayoff(option_type, strike), exercise, self.dates,
33+
self.dividends)
34+
35+
self.option_type = option_type
36+
self.underlying = underlying
37+
self.strike = strike
38+
self.risk_free_rate = risk_free_rate
39+
self.dividend_rate = dividend_rate
40+
self.volatility = volatility
41+
self.reference_date = reference_date
42+
self.exercise = exercise
43+
self.calendar = calendar
44+
self.day_counter = day_counter
45+
self.engine_class = engine_class
46+
self.bsm_process: ql.GeneralizedBlackScholesProcess
47+
self.underlying_quote = ql.SimpleQuote(underlying)
48+
self.risk_free_rate_quote = ql.SimpleQuote(risk_free_rate)
49+
self.dividend_rate_quote = ql.SimpleQuote(dividend_rate)
50+
self.volatility_quote = ql.SimpleQuote(volatility)
51+
52+
self.setPricingEngine(self.get_engine(reference_date))
53+
54+
def get_engine(self, date: ql.Date) -> ql.PricingEngine:
55+
spot_handle = ql.QuoteHandle(self.underlying_quote)
56+
risk_free_rate_handle = ql.QuoteHandle(self.risk_free_rate_quote)
57+
volatility_handle = ql.QuoteHandle(self.volatility_quote)
58+
59+
r_ts = ql.YieldTermStructureHandle(ql.FlatForward(date, risk_free_rate_handle, self.day_counter))
60+
vol_ts = ql.BlackVolTermStructureHandle(ql.BlackConstantVol(date, self.calendar, volatility_handle,
61+
self.day_counter))
62+
63+
if self.dividend_rate is not None:
64+
dividend_handle = ql.QuoteHandle(self.dividend_rate_quote)
65+
d_ts = ql.YieldTermStructureHandle(ql.FlatForward(date, dividend_handle, self.day_counter))
66+
self.bsm_process = ql.BlackScholesMertonProcess(spot_handle,
67+
d_ts,
68+
r_ts,
69+
vol_ts)
70+
else:
71+
self.bsm_process = ql.BlackScholesProcess(spot_handle,
72+
r_ts,
73+
vol_ts)
74+
75+
return self.engine_class(self.bsm_process)
76+
77+
def theta(self) -> float:
78+
yesterday = self.reference_date - ql.Period(1, ql.Days)
79+
tomorrow = self.reference_date + ql.Period(1, ql.Days)
80+
if self.exercise.lastDate() == tomorrow:
81+
dt = self.day_counter.yearFraction(yesterday, self.reference_date)
82+
else:
83+
dt = self.day_counter.yearFraction(yesterday, tomorrow)
84+
self.setPricingEngine(self.get_engine(tomorrow))
85+
value_p = self.NPV()
86+
self.setPricingEngine(self.get_engine(yesterday))
87+
value_m = self.NPV()
88+
89+
theta = (value_p - value_m) / dt
90+
self.setPricingEngine(self.get_engine(self.reference_date))
91+
return theta / ql.Daily
92+
93+
def vega(self) -> float:
94+
return self.numeric_first_order(self.volatility_quote)
95+
96+
def rho(self) -> float:
97+
return self.numeric_first_order(self.risk_free_rate_quote)
98+
99+
def implied_volatility(self, price: float, accuracy: float = 1.0e-4, max_evaluations: int = 100,
100+
min_vol: float = 1.0e-7, max_vol: float = 4.0, try_times: int = 4) -> float:
101+
"""
102+
103+
:param price: expected option NPV
104+
:param accuracy:
105+
:param max_evaluations:
106+
:param min_vol:
107+
:param max_vol:
108+
:param try_times:
109+
:return:
110+
"""
111+
try:
112+
return self.impliedVolatility(price, self.bsm_process, accuracy, max_evaluations, min_vol, max_vol)
113+
except RuntimeError as e:
114+
if try_times == 0:
115+
return 0
116+
elif 'root not bracketed' in str(e):
117+
return self.implied_volatility(price, accuracy, max_evaluations, min_vol, max_vol * 2, try_times - 1)
118+
else:
119+
raise e
120+
121+
def update_implied_volatility(self, implied_volatility: float):
122+
self.volatility_quote.setValue(implied_volatility)
123+
124+
def numeric_first_order(self, quote: ql.SimpleQuote) -> float:
125+
"""
126+
reference: https://github.com/lballabio/QuantLib/issues/779
127+
https://github.com/frgomes/jquantlib/blob/master/jquantlib-helpers/src/main/java/org/jquantlib/helpers/FDDividendOptionHelper.java
128+
:param self:
129+
:param quote:
130+
:return:
131+
"""
132+
sigma0 = quote.value()
133+
h = sigma0 * 1E-4
134+
quote.setValue(sigma0 - h)
135+
p_minus = self.NPV()
136+
quote.setValue(sigma0 + h)
137+
p_plus = self.NPV()
138+
quote.setValue(sigma0)
139+
return (p_plus - p_minus) / (2 * h) / 100
140+
141+
142+
class FDAmericanDividendOptionHelper(FDDividendOptionHelper):
143+
"""American option"""
144+
145+
def __init__(self,
146+
option_type: ql.Option,
147+
underlying: float,
148+
strike: float,
149+
risk_free_rate: float,
150+
dividend_rate: float,
151+
volatility: float,
152+
settlement_date: ql.Date,
153+
expiration_date: ql.Date,
154+
dates: List[ql.Date] = None,
155+
dividends: List[float] = None,
156+
calendar: ql.Calendar = ql.NullCalendar(),
157+
day_counter: ql.DayCounter = ql.Actual365Fixed(),
158+
engine_class: ql.PricingEngine.__class__ = ql.FdBlackScholesVanillaEngine
159+
):
160+
"""
161+
162+
:param option_type: ql.Option.Call or ql.Option.Put
163+
:param underlying:
164+
:param strike:
165+
:param risk_free_rate:
166+
:param dividend_rate:
167+
:param volatility:
168+
:param settlement_date:
169+
:param expiration_date:
170+
:param dates:
171+
:param dividends:
172+
:param calendar:
173+
:param day_counter:
174+
:param engine_class:
175+
176+
>>> helper = FDAmericanDividendOptionHelper(ql.Option.Call, 985, 990, 0.017, 0, 0.6153, ql.Date(14, 4, 2022), ql.Date(22, 4, 2022))
177+
>>> print(f'value:{helper.NPV()}')
178+
>>> print(f'delta:{helper.delta()}')
179+
>>> print(f'gamma:{helper.gamma()}')
180+
>>> print(f'theta:{helper.theta()}')
181+
>>> print(f'vega:{helper.vega()}')
182+
>>> print(f'rho:{helper.rho()}')
183+
"""
184+
super(FDAmericanDividendOptionHelper, self).__init__(engine_class=engine_class,
185+
option_type=option_type, underlying=underlying,
186+
strike=strike, risk_free_rate=risk_free_rate,
187+
dividend_rate=dividend_rate, volatility=volatility,
188+
reference_date=settlement_date,
189+
exercise=ql.AmericanExercise(settlement_date,
190+
expiration_date),
191+
dates=dates, dividends=dividends, calendar=calendar,
192+
day_counter=day_counter)
193+
194+
195+
class FDEuropeanDividendOptionHelper(FDDividendOptionHelper):
196+
"""European option"""
197+
198+
def __init__(self,
199+
option_type: ql.Option,
200+
underlying: float,
201+
strike: float,
202+
risk_free_rate: float,
203+
dividend_rate: float,
204+
volatility: float,
205+
settlement_date: ql.Date,
206+
expiration_date: ql.Date,
207+
dates: List[ql.Date] = None,
208+
dividends: List[float] = None,
209+
calendar: ql.Calendar = ql.NullCalendar(),
210+
day_counter: ql.DayCounter = ql.Actual365Fixed(),
211+
engine_class: ql.PricingEngine.__class__ = ql.FdBlackScholesVanillaEngine
212+
):
213+
super(FDEuropeanDividendOptionHelper, self).__init__(engine_class=engine_class,
214+
option_type=option_type, underlying=underlying,
215+
strike=strike, risk_free_rate=risk_free_rate,
216+
dividend_rate=dividend_rate, volatility=volatility,
217+
reference_date=settlement_date,
218+
exercise=ql.EuropeanExercise(expiration_date),
219+
dates=dates, dividends=dividends, calendar=calendar,
220+
day_counter=day_counter)
221+
222+
223+
if __name__ == '__main__':
224+
parser = argparse.ArgumentParser(description='Helpers for calculating the Greeks of options')
225+
226+
parser.add_argument('-t', '--type', type=str,
227+
help='Option type, CALL/PUT (C/P)', choices=('C', 'P', 'CALL', 'PUT'))
228+
parser.add_argument('-u', '--underlying', type=float,
229+
help='The price of the underlying asset')
230+
parser.add_argument('-p', '--strike', type=float,
231+
help='The strike price at expiration')
232+
parser.add_argument('-d', '--dividend', type=float, nargs='?', default=0,
233+
help='The dividend rate')
234+
parser.add_argument('-r', '--rfrate', type=float, nargs='?', default=0.01,
235+
help='The risk free rate')
236+
parser.add_argument('-v', '--volatility', type=float, nargs='?', default=0,
237+
help='The implied volatility')
238+
parser.add_argument('-s', '--settlement', type=str,
239+
help='The settlement date, like "2022-04-22"')
240+
parser.add_argument('-e', '--expiration', type=str,
241+
help='The expiration date, like "2022-04-29"')
242+
parser.add_argument('-n', '--npv', type=float, nargs='?',
243+
help='The expected NPV of option')
244+
parser.add_argument('-a', '--ask', type=float, nargs='?',
245+
help='The ask price of option')
246+
parser.add_argument('-b', '--bid', type=float, nargs='?',
247+
help='The bid price of option')
248+
parser.add_argument('-o', '--european', action='store_true',
249+
help='Is european option')
250+
251+
args = parser.parse_args()
252+
253+
if args.type.upper() in ('C', 'CALL'):
254+
ql_option_type = ql.Option.Call
255+
elif args.type.upper() in ('P', 'PUT'):
256+
ql_option_type = ql.Option.Put
257+
else:
258+
parser.error('Wrong option type')
259+
if args.volatility is None and args.npv is None and args.ask is None and args.bid is None:
260+
parser.error('Must specify the volatility or option npv(or option\'s ask, bid)')
261+
262+
print(args.__dict__)
263+
settlement_date = ql.DateParser.parseFormatted(str(args.settlement).replace('/', '-'), '%Y-%m-%d')
264+
expiration_date = ql.DateParser.parseFormatted(str(args.expiration).replace('/', '-'), '%Y-%m-%d')
265+
266+
if args.european:
267+
helper_class = FDEuropeanDividendOptionHelper
268+
else:
269+
helper_class = FDAmericanDividendOptionHelper
270+
271+
helper = helper_class(option_type=ql_option_type,
272+
underlying=args.underlying,
273+
strike=args.strike,
274+
risk_free_rate=args.rfrate,
275+
dividend_rate=args.dividend,
276+
volatility=args.volatility,
277+
settlement_date=settlement_date,
278+
expiration_date=expiration_date)
279+
280+
if args.volatility is None or args.volatility == 0:
281+
expected_npv = None
282+
npv_msg = ''
283+
if args.ask and args.bid:
284+
expected_npv = (args.ask + args.bid) / 2
285+
npv_msg = f'with ask {args.ask}, bid {args.bid}, expected_npv {expected_npv}'
286+
else:
287+
expected_npv = args.npv
288+
npv_msg = f'with expected npv: {expected_npv}'
289+
290+
volatility = helper.implied_volatility(expected_npv)
291+
helper.update_implied_volatility(volatility)
292+
print(f'The implied volatility {npv_msg} is: {volatility}')
293+
294+
print(f'npv: {helper.NPV()}, delta:{helper.delta()}, gamma:{helper.gamma()}, theta:{helper.theta()}, '
295+
f'vega:{helper.vega()}, rho:{helper.rho()}')

tigeropen/examples/quote_client_demo.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,12 +99,15 @@ def get_future_quote():
9999
print(ticks)
100100
contracts = openapi_client.get_future_contracts('CME')
101101
print(contracts)
102+
contract = openapi_client.get_future_contract('VIX2206')
103+
print(contract)
102104
trading_times = openapi_client.get_future_trading_times('CN1901', trading_date=1545049282852)
103105
print(trading_times)
104106
briefs = openapi_client.get_future_brief(['ES1906', 'CN1901'])
105107
print(briefs)
106108

107109

110+
108111
def get_fundamental():
109112
"""获取基础数据"""
110113

tigeropen/examples/trade_client_demo.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,16 @@
2828
client_config = get_client_config()
2929

3030

31+
def get_contract_apis():
32+
openapi_client = TradeClient(client_config, logger=logger)
33+
contract = openapi_client.get_contracts('AAPL')[0]
34+
print(contract)
35+
contract = openapi_client.get_contract('AAPL', SecurityType.STK, currency=Currency.USD)
36+
print(contract)
37+
# get derivative contracts of stock. include OPT, WAR, IOPT
38+
contracts = openapi_client.get_derivative_contracts('00700', SecurityType.WAR, '20220929')
39+
print(contracts)
40+
3141
def get_account_apis():
3242
openapi_client = TradeClient(client_config, logger=logger)
3343
openapi_client.get_managed_accounts()

0 commit comments

Comments
 (0)