Python API Usage Examples
These are the basic usage examples of the Otyca Python API.
1import pandas as pd
2import numpy as np
3import datetime as dt
4import dateutil.parser
5import logging
6from bidict import bidict
7import atexit
8from parse import parse
9
10from otyca_cpp import otyca_cpp
11
12def setup_logging(logger, level=logging.INFO):
13 """
14 Sets up a logging callback to bridge C++ logging (from `otyca_cpp`) into Python's logging system.
15
16 This function maps C++ log levels defined in `otyca_cpp.LogLevel` to corresponding Python `logging` levels.
17 It sets a logging callback so that log messages from the C++ side are passed to the given Python logger.
18
19 It also ensures that the logging callback is cleared automatically at program exit.
20
21 Args:
22 logger (logging.Logger): The Python logger instance to use for logging messages.
23 level (int): The minimum Python logging level to capture (e.g., logging.INFO, logging.DEBUG).
24 """
25 level_map = bidict()
26 level_map[otyca_cpp.LogLevel.Debug] = logging.DEBUG
27 level_map[otyca_cpp.LogLevel.Info] = logging.INFO
28 level_map[otyca_cpp.LogLevel.Warning] = logging.WARNING
29 level_map[otyca_cpp.LogLevel.Error] = logging.ERROR
30
31 def python_logging_callback(level, message):
32 # Use the mapped level and log the message
33 python_level = level_map.get(level, logging.INFO) # Default to INFO if level not found
34 logger.log(python_level, message)
35
36 otyca_cpp.Logger.set_log_callback(python_logging_callback)
37 otyca_cpp.Logger.set_log_level(level_map.inverse[level])
38
39 def clear_logging_callback():
40 otyca_cpp.Logger.set_log_callback(None)
41
42 atexit.register(clear_logging_callback)
43
44def try_float(value, default=np.nan):
45 try:
46 return float(value)
47 except:
48 return default
49
50logger = logging.getLogger(__name__)
51
52setup_logging(logger, logging.ERROR)
53
54quotedata_file = open('spy_quotedata.csv', "r")
55lines = quotedata_file.read().splitlines()
56
57# create UnderlyingDataset
58uds = otyca_cpp.UnderlyingDataset()
59
60underlying = uds.create_underlying()
61
62r = parse('"Date: {valuation_time}",Bid: {bid},Ask: {ask},Size: {bid_size}*{ask_size},{volume}', lines[2])
63uds.set_valuation_time(dateutil.parser.parse(r['valuation_time']))
64underlying.set_bid_ask(float(r['bid']), int(r['bid_size']), float(r['ask']), int(r['ask_size']))
65
66# overwrite if needed
67# uds.set_option_style(otyca_cpp.OptionStyle.AMERICAN)
68# uds.set_option_settlement_lag(1)
69# uds.set_underlying_settlement_lag(1)
70# uds.set_exercise_settlement_lag(1)
71
72# now create options
73for option_line in lines[4:]:
74 option_line = option_line.split(',')
75
76 expiration = dt.datetime.strptime(option_line[0], '%a %b %d %Y').replace(hour=16)
77 strike = float(option_line[11])
78
79 bid = try_float(option_line[4])
80 ask = try_float(option_line[5])
81 opt = uds.create_option(expiration, strike, otyca_cpp.OptionType.CALL)
82 opt.set_bid_ask(bid, 1, ask, 1) # the example data doesn't have sizes
83
84 bid = try_float(option_line[15])
85 ask = try_float(option_line[16])
86 opt = uds.create_option(expiration, strike, otyca_cpp.OptionType.PUT)
87 opt.set_bid_ask(bid, 1, ask, 1) # the example data doesn't have sizes
88
89# add projected dividend, assuming annualized yield of 1.2%, fade from pure cash to pure proportional in 1.5 years
90uds.add_projected_dividend(dt.date(2025, 3, 21), 1.6932, 0, 0)
91uds.add_projected_dividend(dt.date(2025, 6, 20), 1.4110, 0.0005, 0)
92uds.add_projected_dividend(dt.date(2025, 9, 19), 1.1288, 0.0010, 0)
93uds.add_projected_dividend(dt.date(2025, 12, 19), 0.8466, 0.0015, 0)
94uds.add_projected_dividend(dt.date(2026, 3, 20), 0.5644, 0.0020, 0)
95uds.add_projected_dividend(dt.date(2026, 6, 19), 0.2822, 0.0025, 0)
96uds.add_projected_dividend(dt.date(2026, 9, 18), 0, 0.003, 0)
97uds.add_projected_dividend(dt.date(2026, 12, 18), 0, 0.003, 0)
98uds.add_projected_dividend(dt.date(2027, 3, 19), 0, 0.003, 0)
99uds.add_projected_dividend(dt.date(2027, 6, 18), 0, 0.003, 0)
100uds.add_projected_dividend(dt.date(2027, 9, 17), 0, 0.003, 0)
101uds.add_projected_dividend(dt.date(2027, 12, 17), 0, 0.003, 0)
102
103# now create yield curve (risk free discount curve)
104yc = otyca_cpp.PieceWiseYieldCurve()
105yc.add(0.219, otyca_cpp.Estimate(0.04544, 0.005))
106yc.add(0.526, otyca_cpp.Estimate(0.04424, 0.004))
107yc.add(1.025, otyca_cpp.Estimate(0.04289, 0.003))
108yc.add(2.770, otyca_cpp.Estimate(0.04109, 0.003))
109uds.set_yield_curve(yc)
110
111# now create borrow curve, assuming 0 borrow with 5% uncertainty
112bc = otyca_cpp.PieceWiseYieldCurve()
113bc.add(1, otyca_cpp.Estimate(0, 0.05))
114bc.add(2, otyca_cpp.Estimate(0, 0.05))
115uds.set_borrow_curve(bc)
116
117# overwrite if needed
118# tc = otyca_cpp.RealizedVarianceTenorCalculator()
119# tc.set_variance_weights(0.63, 0.09, 0.28)
120# uds.set_variance_tenor_calculator(tc)
121
122
123# add a stochastic jump diffusion model which consists of Heston diffusion (LCIR) and Merton (MT) jump.
124# add upto 3 Bimodal Gaussian jumps and calibrate the process using all options with tenor in the range of
125# (0, 2] years
126uds.add_composite_stochastic_process_config(otyca_cpp.CompositeStochasticProcessConfig("LCIR(MT)",3, 0, 2))
127
128# add a stochastic jump diffusion model which consists of Heston diffusion (LCIR) and double exponential (KOU)
129# jump. add upto 1 Bimodal Gaussian jump and calibrate the process using all options with tenor in the range of
130# (0, 0.5] years
131uds.add_composite_stochastic_process_config(otyca_cpp.CompositeStochasticProcessConfig("LCIR(KOU)", 1, 0, 0.5))
132
133# after all the inputs are setup, call setup()
134uds.setup()
135
136# now call various getters to retrieve calculated results
137
138# get the 3 month and 1 year atm volatility evaluated by each calibrated stochastic jump diffusion model
139for p in uds.get_composite_stochastic_processes():
140 print(f'{p.name()} 3 month atm vol: {p.volatility(0.25, 0)}')
141 print(f'{p.name()} 1 year atm vol: {p.volatility(1, 0)}')
142
143# get implied event schedule
144print(uds.get_implied_event_schedule())
145
146# get dividend schedule
147print(uds.get_dividend_schedule())
148
149# get expiration by expiration values
150print("forward_value,forward_uncertainty,implied_interest_value,implied_interest_uncertainty,"
151 "implied_borrow_value,implied_borrow_uncertainty,implied_forward_value,implied_forward_uncertainty,"
152 "variance_term_structure_model_value,fair_atm_volatility,atm_volatility_value,"
153 "fair_volatility_skew,vix_style_volatility,atm_volatility_uncertainty")
154for option_expiration in uds.get_option_expirations():
155 print(f"{option_expiration.get_forward_value()},"
156 f"{option_expiration.get_forward_uncertainty()},"
157 f"{option_expiration.get_implied_interest_value()},"
158 f"{option_expiration.get_implied_interest_uncertainty()},"
159 f"{option_expiration.get_implied_borrow_value()},"
160 f"{option_expiration.get_implied_borrow_uncertainty()},"
161 f"{option_expiration.get_implied_forward_value()},"
162 f"{option_expiration.get_implied_forward_uncertainty()},"
163 f"{option_expiration.get_variance_term_structure_model_value()},"
164 f"{option_expiration.get_fair_atm_volatility()},"
165 f"{option_expiration.get_atm_volatility_value()},"
166 f"{option_expiration.get_fair_volatility_skew()},"
167 f"{option_expiration.get_vix_style_volatility()},"
168 f"{option_expiration.get_atm_volatility_uncertainty()}")
169
170# get strike by strike values
171print("expiration,strike,call_bid_volatility,call_ask_volatility,put_bid_volatility,put_ask_volatility,"
172 "strike_volatility_value,strike_volatility_uncertainty,fair_volatility,")
173for option_expiration in uds.get_option_expirations():
174 for option_strike in option_expiration.get_option_strikes():
175 call = option_strike.get_call()
176 put = option_strike.get_put()
177 print(
178 f"{option_expiration.get_expiration().date()},"
179 f"{option_strike.get_strike()}",
180 f"{call.get_bid_volatility()},"
181 f"{call.get_bid_volatility()},"
182 f"{put.get_bid_volatility()},"
183 f"{put.get_bid_volatility()},"
184 f"{option_strike.get_strike_volatility_value()},"
185 f"{option_strike.get_fair_volatility()},"
186 f"{option_strike.get_strike_volatility_uncertainty()},")
187
188# dataframe style
189ticker = 'SPY'
190data = uds.get_implied_interest_borrow_dataframe()
191implied_interest_borrow_df = pd.DataFrame(data=np.array(data[2]).transpose(), columns=data[1],
192 index=pd.MultiIndex.from_tuples([(ticker, m) for m in data[0]],
193 names=['tkr', 'exp']))
194print(implied_interest_borrow_df.head())
195
196data = uds.get_expiration_volatility_model_dataframe()
197exp_vol_model_df = pd.DataFrame(data=np.array(data[2]).transpose(), columns=data[1],
198 index=pd.MultiIndex.from_tuples([(ticker, m) for m in data[0]], names=['tkr', 'exp']))
199print(exp_vol_model_df.head())
200
201data = uds.get_option_expiration_dataframe()
202expiration_df = pd.DataFrame(data=np.array(data[2]).transpose(),
203 index=pd.MultiIndex.from_tuples([(ticker, m) for m in data[0]], names=['tkr', 'exp']),
204 columns=data[1])
205print(expiration_df.head())
206
207data = uds.get_option_pricing_dataframe()
208option_pricing_df = pd.DataFrame(data=np.array(data[2]).transpose(),
209 index=pd.Index([f'{ticker}{o}' for o in data[0]], name='opt_tkr'),
210 columns=data[1])
211print(option_pricing_df.head())
212
213data = uds.get_option_bid_ask_implied_volatility_dataframe()
214option_bid_ask_df = pd.DataFrame(data=np.array(data[2]).transpose(),
215 index=pd.Index([f'{ticker}{o}' for o in data[0]], name='opt_tkr'),
216 columns=data[1])
217print(option_bid_ask_df.head())
218
219for option_expiration in uds.get_option_expirations():
220 data = option_expiration.get_option_strike_dataframe()
221 option_strike_df = pd.DataFrame(data=np.array(data[2]).transpose(),
222 index=pd.Index(data[0], name='k'),
223 columns=data[1])
224 option_strike_df['exp'] = option_expiration.get_expiration()
225 print(option_strike_df.head())
Preview of spy_quotedata.csv
SPDR S&P 500 ETF Trust,Last: 564.4,Change: 3.38
"Date: March 19, 2025 at 10:57 AM EDT",Bid: 564.39,Ask: 564.4,Size: 1*1,"Volume: 6,805,714"
Expiration Date,Calls,Last Sale,Net,Bid,Ask,Volume,IV,Delta,Gamma,Open Interest,Strike,Puts,Last Sale,Net,Bid,Ask,Volume,IV,Delta,Gamma,Open Interest
Wed Mar 19 2025,SPY250319C00400000,161.33,0.795,163.15,165,4,5.5771,0.9997,0,132,400.00,SPY250319P00400000,0.01,0,0,0.01,0,4.003,-0.0003,0,752
Wed Mar 19 2025,SPY250319C00405000,153.27,0,158.16,160.9,0,5.322,0.9997,0,8,405.00,SPY250319P00405000,0.01,0.005,0,0.01,2,3.8669,-0.0003,0,5
Wed Mar 19 2025,SPY250319C00410000,147.83,0,153.18,155.86,0,5.8506,0.9997,0,8,410.00,SPY250319P00410000,0.01,0,0,0.01,0,3.7323,-0.0003,0,39
Wed Mar 19 2025,SPY250319C00415000,0,0,148.17,150.81,0,5.9167,0.9997,0,0,415.00,SPY250319P00415000,0.01,0,0,0.01,0,3.5992,-0.0003,0,7
Wed Mar 19 2025,SPY250319C00420000,139.34,0,143.2,146.12,0,5.0644,0.9996,0,4,420.00,SPY250319P00420000,0.02,0,0,0.01,0,3.4674,-0.0004,0,13225
Wed Mar 19 2025,SPY250319C00425000,0,0,138.17,140.93,0,0,0.9996,0,0,425.00,SPY250319P00425000,0.01,0,0,0.01,0,3.3368,-0.0004,0,2256