voly 0.0.226__py3-none-any.whl → 0.0.228__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- voly/client.py +33 -27
- voly/core/charts.py +23 -23
- voly/core/data.py +55 -83
- voly/core/fit.py +26 -26
- voly/core/interpolate.py +5 -5
- voly/formulas.py +49 -49
- {voly-0.0.226.dist-info → voly-0.0.228.dist-info}/METADATA +1 -1
- voly-0.0.228.dist-info/RECORD +20 -0
- voly-0.0.226.dist-info/RECORD +0 -20
- {voly-0.0.226.dist-info → voly-0.0.228.dist-info}/WHEEL +0 -0
- {voly-0.0.226.dist-info → voly-0.0.228.dist-info}/licenses/LICENSE +0 -0
- {voly-0.0.226.dist-info → voly-0.0.228.dist-info}/top_level.txt +0 -0
voly/client.py
CHANGED
@@ -79,7 +79,7 @@ class VolyClient:
|
|
79
79
|
|
80
80
|
try:
|
81
81
|
option_chain = loop.run_until_complete(
|
82
|
-
fetch_option_chain(exchange, currency
|
82
|
+
fetch_option_chain(exchange, currency)
|
83
83
|
)
|
84
84
|
return option_chain
|
85
85
|
except VolyError as e:
|
@@ -97,68 +97,68 @@ class VolyClient:
|
|
97
97
|
|
98
98
|
@staticmethod
|
99
99
|
def d1(s: float, K: float, r: float, o: float, t: float,
|
100
|
-
|
101
|
-
return d1(s, K, r, o, t,
|
100
|
+
flag: str = 'call') -> float:
|
101
|
+
return d1(s, K, r, o, t, flag)
|
102
102
|
|
103
103
|
@staticmethod
|
104
104
|
def d2(s: float, K: float, r: float, o: float, t: float,
|
105
|
-
|
106
|
-
return d2(s, K, r, o, t,
|
105
|
+
flag: str = 'call') -> float:
|
106
|
+
return d2(s, K, r, o, t, flag)
|
107
107
|
|
108
108
|
@staticmethod
|
109
109
|
def bs(s: float, K: float, r: float, o: float, t: float,
|
110
|
-
|
111
|
-
return bs(s, K, r, o, t,
|
110
|
+
flag: str = 'call') -> float:
|
111
|
+
return bs(s, K, r, o, t, flag)
|
112
112
|
|
113
113
|
@staticmethod
|
114
114
|
def delta(s: float, K: float, r: float, o: float, t: float,
|
115
|
-
|
116
|
-
return delta(s, K, r, o, t,
|
115
|
+
flag: str = 'call') -> float:
|
116
|
+
return delta(s, K, r, o, t, flag)
|
117
117
|
|
118
118
|
@staticmethod
|
119
119
|
def gamma(s: float, K: float, r: float, o: float, t: float,
|
120
|
-
|
121
|
-
return gamma(s, K, r, o, t,
|
120
|
+
flag: str = 'call') -> float:
|
121
|
+
return gamma(s, K, r, o, t, flag)
|
122
122
|
|
123
123
|
@staticmethod
|
124
124
|
def vega(s: float, K: float, r: float, o: float, t: float,
|
125
|
-
|
126
|
-
return vega(s, K, r, o, t,
|
125
|
+
flag: str = 'call') -> float:
|
126
|
+
return vega(s, K, r, o, t, flag)
|
127
127
|
|
128
128
|
@staticmethod
|
129
129
|
def theta(s: float, K: float, r: float, o: float, t: float,
|
130
|
-
|
131
|
-
return theta(s, K, r, o, t,
|
130
|
+
flag: str = 'call') -> float:
|
131
|
+
return theta(s, K, r, o, t, flag)
|
132
132
|
|
133
133
|
@staticmethod
|
134
134
|
def rho(s: float, K: float, r: float, o: float, t: float,
|
135
|
-
|
136
|
-
return rho(s, K, r, o, t,
|
135
|
+
flag: str = 'call') -> float:
|
136
|
+
return rho(s, K, r, o, t, flag)
|
137
137
|
|
138
138
|
@staticmethod
|
139
139
|
def vanna(s: float, K: float, r: float, o: float, t: float,
|
140
|
-
|
141
|
-
return vanna(s, K, r, o, t,
|
140
|
+
flag: str = 'call') -> float:
|
141
|
+
return vanna(s, K, r, o, t, flag)
|
142
142
|
|
143
143
|
@staticmethod
|
144
144
|
def volga(s: float, K: float, r: float, o: float, t: float,
|
145
|
-
|
146
|
-
return volga(s, K, r, o, t,
|
145
|
+
flag: str = 'call') -> float:
|
146
|
+
return volga(s, K, r, o, t, flag)
|
147
147
|
|
148
148
|
@staticmethod
|
149
149
|
def charm(s: float, K: float, r: float, o: float, t: float,
|
150
|
-
|
151
|
-
return charm(s, K, r, o, t,
|
150
|
+
flag: str = 'call') -> float:
|
151
|
+
return charm(s, K, r, o, t, flag)
|
152
152
|
|
153
153
|
@staticmethod
|
154
154
|
def greeks(s: float, K: float, r: float, o: float, t: float,
|
155
|
-
|
156
|
-
return greeks(s, K, r, o, t,
|
155
|
+
flag: str = 'call') -> Dict[str, float]:
|
156
|
+
return greeks(s, K, r, o, t, flag)
|
157
157
|
|
158
158
|
@staticmethod
|
159
159
|
def iv(option_price: float, s: float, K: float, r: float, t: float,
|
160
|
-
|
161
|
-
return iv(option_price, s, K, r, t,
|
160
|
+
flag: str = 'call') -> float:
|
161
|
+
return iv(option_price, s, K, r, t, flag)
|
162
162
|
|
163
163
|
# -------------------------------------------------------------------------
|
164
164
|
# Model Fitting
|
@@ -235,6 +235,12 @@ class VolyClient:
|
|
235
235
|
# Generate IV surface and domain
|
236
236
|
iv_surface, x_surface = get_iv_surface(fit_results, domain_params, return_domain)
|
237
237
|
|
238
|
+
if option_chain:
|
239
|
+
# Create missing domains
|
240
|
+
option_chain['log_moneyness'] = np.log(option_chain['strikes'] / option_chain['spot_price'].iloc[0])
|
241
|
+
option_chain['moneyness'] = np.exp(option_chain['log_moneyness'])
|
242
|
+
option_chain['returns'] = df['moneyness'] - 1
|
243
|
+
|
238
244
|
# Plot volatility smiles
|
239
245
|
plots['smiles'] = plot_all_smiles(
|
240
246
|
x_surface=x_surface,
|
voly/core/charts.py
CHANGED
@@ -62,9 +62,9 @@ def plot_volatility_smile(x_array: np.ndarray,
|
|
62
62
|
|
63
63
|
# Add market data if provided
|
64
64
|
if option_chain is not None and maturity is not None:
|
65
|
-
maturity_data = option_chain[option_chain['
|
65
|
+
maturity_data = option_chain[option_chain['maturity'] == maturity]
|
66
66
|
if return_domain == 'delta':
|
67
|
-
maturity_data = maturity_data[maturity_data['
|
67
|
+
maturity_data = maturity_data[maturity_data['flag'] == 'C']
|
68
68
|
|
69
69
|
if not maturity_data.empty:
|
70
70
|
# Add bid and ask IVs if available
|
@@ -149,10 +149,10 @@ def plot_raw_parameters(fit_results: pd.DataFrame) -> go.Figure:
|
|
149
149
|
)
|
150
150
|
|
151
151
|
# Get maturity names from index
|
152
|
-
|
152
|
+
maturities = fit_results.index
|
153
153
|
|
154
154
|
# Create hover text with maturity info
|
155
|
-
tick_labels = [f"{m}" for m in
|
155
|
+
tick_labels = [f"{m}" for m in maturities]
|
156
156
|
|
157
157
|
# Plot each parameter
|
158
158
|
for i, param in enumerate(param_names):
|
@@ -160,7 +160,7 @@ def plot_raw_parameters(fit_results: pd.DataFrame) -> go.Figure:
|
|
160
160
|
|
161
161
|
fig.add_trace(
|
162
162
|
go.Scatter(
|
163
|
-
x=list(range(len(
|
163
|
+
x=list(range(len(maturities))),
|
164
164
|
y=fit_results[param],
|
165
165
|
mode='lines+markers',
|
166
166
|
name=param,
|
@@ -174,8 +174,8 @@ def plot_raw_parameters(fit_results: pd.DataFrame) -> go.Figure:
|
|
174
174
|
|
175
175
|
# Set x-axis labels
|
176
176
|
fig.update_xaxes(
|
177
|
-
tickvals=list(range(len(
|
178
|
-
ticktext=
|
177
|
+
tickvals=list(range(len(maturities))),
|
178
|
+
ticktext=maturities,
|
179
179
|
tickangle=45,
|
180
180
|
row=row, col=col
|
181
181
|
)
|
@@ -212,10 +212,10 @@ def plot_jw_parameters(fit_results: pd.DataFrame) -> go.Figure:
|
|
212
212
|
)
|
213
213
|
|
214
214
|
# Get maturity names from index
|
215
|
-
|
215
|
+
maturities = fit_results.index
|
216
216
|
|
217
217
|
# Create hover text with maturity info
|
218
|
-
tick_labels = [f"{m}" for m in
|
218
|
+
tick_labels = [f"{m}" for m in maturities]
|
219
219
|
|
220
220
|
# Plot each parameter
|
221
221
|
for i, param in enumerate(param_names):
|
@@ -223,7 +223,7 @@ def plot_jw_parameters(fit_results: pd.DataFrame) -> go.Figure:
|
|
223
223
|
|
224
224
|
fig.add_trace(
|
225
225
|
go.Scatter(
|
226
|
-
x=list(range(len(
|
226
|
+
x=list(range(len(maturities))),
|
227
227
|
y=fit_results[param],
|
228
228
|
mode='lines+markers',
|
229
229
|
name=param,
|
@@ -237,8 +237,8 @@ def plot_jw_parameters(fit_results: pd.DataFrame) -> go.Figure:
|
|
237
237
|
|
238
238
|
# Set x-axis labels
|
239
239
|
fig.update_xaxes(
|
240
|
-
tickvals=list(range(len(
|
241
|
-
ticktext=
|
240
|
+
tickvals=list(range(len(maturities))),
|
241
|
+
ticktext=maturities,
|
242
242
|
tickangle=45,
|
243
243
|
row=row, col=col
|
244
244
|
)
|
@@ -279,11 +279,11 @@ def plot_fit_performance(fit_results: pd.DataFrame) -> go.Figure:
|
|
279
279
|
)
|
280
280
|
|
281
281
|
# Get maturity names from index and create x-axis indices
|
282
|
-
|
283
|
-
x_indices = list(range(len(
|
282
|
+
maturities = fit_results.index
|
283
|
+
x_indices = list(range(len(maturities)))
|
284
284
|
|
285
285
|
# Create hover labels
|
286
|
-
hover_labels = [f"{m}" for m in
|
286
|
+
hover_labels = [f"{m}" for m in maturities]
|
287
287
|
|
288
288
|
# Plot each metric
|
289
289
|
for metric, config in metrics.items():
|
@@ -309,7 +309,7 @@ def plot_fit_performance(fit_results: pd.DataFrame) -> go.Figure:
|
|
309
309
|
for col in range(1, 3):
|
310
310
|
fig.update_xaxes(
|
311
311
|
tickvals=x_indices,
|
312
|
-
ticktext=
|
312
|
+
ticktext=maturities,
|
313
313
|
tickangle=45,
|
314
314
|
row=row, col=col
|
315
315
|
)
|
@@ -352,27 +352,27 @@ def plot_3d_surface(x_surface: Dict[str, np.ndarray],
|
|
352
352
|
}
|
353
353
|
|
354
354
|
# Get maturity names and sort by YTM
|
355
|
-
|
355
|
+
maturities = list(iv_surface.keys())
|
356
356
|
if fit_results is not None:
|
357
|
-
maturity_values = [fit_results.loc[name, 't'] for name in
|
357
|
+
maturity_values = [fit_results.loc[name, 't'] for name in maturities]
|
358
358
|
# Sort by maturity
|
359
359
|
sorted_indices = np.argsort(maturity_values)
|
360
|
-
|
360
|
+
maturities = [maturities[i] for i in sorted_indices]
|
361
361
|
maturity_values = [maturity_values[i] for i in sorted_indices]
|
362
362
|
else:
|
363
|
-
maturity_values = list(range(len(
|
363
|
+
maturity_values = list(range(len(maturities)))
|
364
364
|
|
365
365
|
# Create a common x-grid for all maturities
|
366
366
|
# Use 100 points between the min and max x-values across all maturities
|
367
|
-
all_x = np.concatenate([x_surface[m] for m in
|
367
|
+
all_x = np.concatenate([x_surface[m] for m in maturities])
|
368
368
|
x_min, x_max = np.min(all_x), np.max(all_x)
|
369
369
|
x_grid = np.linspace(x_min, x_max, 400)
|
370
370
|
|
371
371
|
# Create a matrix for the surface
|
372
|
-
z_matrix = np.zeros((len(
|
372
|
+
z_matrix = np.zeros((len(maturities), len(x_grid)))
|
373
373
|
|
374
374
|
# Fill the matrix with interpolated IV values
|
375
|
-
for i, maturity in enumerate(
|
375
|
+
for i, maturity in enumerate(maturities):
|
376
376
|
x_values = x_surface[maturity]
|
377
377
|
iv_values = iv_surface[maturity] * 100 # Convert to percentage
|
378
378
|
|
voly/core/data.py
CHANGED
@@ -181,7 +181,7 @@ async def get_deribit_data(currency: str = "BTC") -> pd.DataFrame:
|
|
181
181
|
|
182
182
|
return pd.DataFrame(all_data)
|
183
183
|
|
184
|
-
|
184
|
+
'''
|
185
185
|
@catch_exception
|
186
186
|
def process_option_chain(df: pd.DataFrame, currency: str) -> pd.DataFrame:
|
187
187
|
"""
|
@@ -232,124 +232,100 @@ def process_option_chain(df: pd.DataFrame, currency: str) -> pd.DataFrame:
|
|
232
232
|
logger.info(f"Processing complete!")
|
233
233
|
|
234
234
|
return df
|
235
|
+
'''
|
235
236
|
|
236
237
|
|
237
238
|
@catch_exception
|
238
|
-
def
|
239
|
+
def process_option_chain(df: pd.DataFrame, currency: str) -> pd.DataFrame:
|
239
240
|
"""
|
240
|
-
Process
|
241
|
+
Process raw option chain data into a standardized format.
|
241
242
|
|
242
|
-
|
243
|
-
|
244
|
-
|
243
|
+
Parameters:
|
244
|
+
df (pd.DataFrame): Raw option chain data
|
245
|
+
currency (str): Currency code (e.g., 'BTC', 'ETH')
|
245
246
|
|
246
247
|
Returns:
|
247
|
-
|
248
|
+
pd.DataFrame: Processed option chain data
|
248
249
|
"""
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
250
|
+
logger.info(f"Processing data for {currency}...")
|
251
|
+
|
252
|
+
# Apply extraction to create new columns
|
253
|
+
df['spot_price'] = df['index_price']
|
254
|
+
df['instrument'] = df['instrument_name']
|
255
|
+
splits = df['instrument'].str.split('-')
|
256
|
+
df['maturity'] = splits.str[1]
|
257
|
+
if currency == 'XRP':
|
258
|
+
df['strikes'] = splits.str[2].str.replace('d', '.', regex=False).astype(float)
|
259
|
+
else:
|
260
|
+
df['strikes'] = splits.str[2].astype(float)
|
261
|
+
df['flag'] = splits.str[3]
|
262
|
+
|
263
|
+
# Create maturity date at 8:00 AM UTC
|
264
|
+
df['expiry'] = pd.to_datetime(df['maturity'].apply(
|
265
|
+
lambda x: int(dt.datetime.strptime(x, "%d%b%y")
|
266
|
+
.replace(hour=8, minute=0, second=0, tzinfo=dt.timezone.utc)
|
267
|
+
.timestamp() * 1000)), unit='ms')
|
266
268
|
|
269
|
+
# Calculate implied volatility (convert from percentage)
|
270
|
+
df['mark_iv'] = df['mark_iv'] / 100
|
271
|
+
df['bid_iv'] = df['bid_iv'].replace({0: np.nan}) / 100
|
272
|
+
df['ask_iv'] = df['ask_iv'].replace({0: np.nan}) / 100
|
273
|
+
|
274
|
+
df['bid_price'] = df['best_bid_price']
|
275
|
+
df['ask_price'] = df['best_ask_price']
|
276
|
+
df['bid_amount'] = df['best_bid_amount']
|
277
|
+
df['ask_amount'] = df['best_ask_amount']
|
278
|
+
|
279
|
+
for idx, row in df.iterrows():
|
267
280
|
# Process bid side
|
268
281
|
if 'bids' in row and isinstance(row['bids'], list) and len(row['bids']) > 0:
|
269
|
-
# Clean up the bid data
|
270
|
-
|
271
|
-
for bid in row['bids'][:max_depth]: # Limit to max_depth levels
|
282
|
+
clean_bids = [] # Clean up the bid data
|
283
|
+
for bid in row['bids']:
|
272
284
|
if len(bid) >= 3:
|
273
285
|
# Extract price and quantity, removing 'new' if present
|
274
286
|
price = float(bid[1]) if bid[0] == 'new' else float(bid[0])
|
275
287
|
qty = float(bid[2]) if bid[0] == 'new' else float(bid[1])
|
276
288
|
clean_bids.append((price, qty))
|
277
|
-
|
278
289
|
if clean_bids:
|
279
|
-
|
280
|
-
total_qty = sum(qty for _, qty in clean_bids)
|
281
|
-
vwap_bid = sum(price * qty for price, qty in clean_bids) / total_qty if total_qty > 0 else float('nan')
|
282
|
-
|
283
|
-
# Calculate IV for VWAP
|
284
|
-
try:
|
285
|
-
vwap_bid_iv = voly.iv(vwap_bid * s, s, k, r, t, option_type)
|
286
|
-
except:
|
287
|
-
vwap_bid_iv = float('nan')
|
288
|
-
|
289
|
-
option_chain.at[idx, 'vwap_bid'] = vwap_bid
|
290
|
-
option_chain.at[idx, 'depth_bid_qty'] = total_qty
|
291
|
-
option_chain.at[idx, 'vwap_bid_iv'] = vwap_bid_iv
|
290
|
+
df.at[idx, 'bids'] = clean_bids
|
292
291
|
|
293
292
|
# Process ask side
|
294
293
|
if 'asks' in row and isinstance(row['asks'], list) and len(row['asks']) > 0:
|
295
|
-
# Clean up the ask data
|
296
|
-
|
297
|
-
for ask in row['asks'][:max_depth]: # Limit to max_depth levels
|
294
|
+
clean_asks = [] # Clean up the ask data
|
295
|
+
for ask in row['asks']:
|
298
296
|
if len(ask) >= 3:
|
299
297
|
# Extract price and quantity, removing 'new' if present
|
300
298
|
price = float(ask[1]) if ask[0] == 'new' else float(ask[0])
|
301
299
|
qty = float(ask[2]) if ask[0] == 'new' else float(ask[1])
|
302
300
|
clean_asks.append((price, qty))
|
303
|
-
|
304
301
|
if clean_asks:
|
305
|
-
|
306
|
-
total_qty = sum(qty for _, qty in clean_asks)
|
307
|
-
vwap_ask = sum(price * qty for price, qty in clean_asks) / total_qty if total_qty > 0 else float('nan')
|
308
|
-
|
309
|
-
# Calculate IV for VWAP
|
310
|
-
try:
|
311
|
-
vwap_ask_iv = voly.iv(vwap_ask * s, s, k, r, t, option_type)
|
312
|
-
except:
|
313
|
-
vwap_ask_iv = float('nan')
|
314
|
-
|
315
|
-
option_chain.at[idx, 'vwap_ask'] = vwap_ask
|
316
|
-
option_chain.at[idx, 'depth_ask_qty'] = total_qty
|
317
|
-
option_chain.at[idx, 'vwap_ask_iv'] = vwap_ask_iv
|
318
|
-
|
319
|
-
# Calculate mid VWAP if both bid and ask are available
|
320
|
-
if not np.isnan(option_chain.at[idx, 'vwap_bid']) and not np.isnan(option_chain.at[idx, 'vwap_ask']):
|
321
|
-
vwap_mid = (option_chain.at[idx, 'vwap_bid'] + option_chain.at[idx, 'vwap_ask']) / 2
|
302
|
+
df.at[idx, 'asks'] = clean_asks
|
322
303
|
|
323
|
-
|
324
|
-
|
325
|
-
|
326
|
-
|
327
|
-
vwap_mid_iv = float('nan')
|
304
|
+
df['bid_depth'] = df['bids']
|
305
|
+
df['ask_depth'] = df['asks']
|
306
|
+
df['open_interest'] = df['open_interest'] * df['spot_price']
|
307
|
+
df['volume'] = df['volume_usd']
|
328
308
|
|
329
|
-
|
330
|
-
|
309
|
+
df = df[['currency', 'spot_price',
|
310
|
+
'timestamp', 'instrument', 'maturity', 'strikes', 'flag', 'expiry',
|
311
|
+
'mark_iv', 'bid_iv', 'ask_iv', 'mark_price', 'bid_price', 'ask_price', 'bid_amount', 'ask_amount', 'bid_depth', 'ask_depth',
|
312
|
+
'delta', 'gamma', 'vega', 'theta', 'rho',
|
313
|
+
'interest_rate', 'open_interest', 'volume']]
|
331
314
|
|
332
|
-
|
333
|
-
bid_qty = option_chain.at[idx, 'depth_bid_qty'] if not np.isnan(option_chain.at[idx, 'depth_bid_qty']) else 0
|
334
|
-
ask_qty = option_chain.at[idx, 'depth_ask_qty'] if not np.isnan(option_chain.at[idx, 'depth_ask_qty']) else 0
|
335
|
-
option_chain.at[idx, 'depth_liquidity'] = bid_qty + ask_qty
|
336
|
-
|
337
|
-
option_chain = option_chain.replace(float('inf'), float('nan'))
|
315
|
+
logger.info(f"Processing complete!")
|
338
316
|
|
339
|
-
return
|
317
|
+
return df
|
340
318
|
|
341
319
|
|
342
320
|
@catch_exception
|
343
321
|
async def fetch_option_chain(exchange: str = 'deribit',
|
344
|
-
currency: str = 'BTC'
|
345
|
-
depth: bool = False) -> pd.DataFrame:
|
322
|
+
currency: str = 'BTC') -> pd.DataFrame:
|
346
323
|
"""
|
347
324
|
Fetch option chain data from the specified exchange.
|
348
325
|
|
349
326
|
Parameters:
|
350
327
|
exchange (str): Exchange to fetch data from (currently only 'deribit' is supported)
|
351
328
|
currency (str): Currency to fetch options for (e.g., 'BTC', 'ETH')
|
352
|
-
depth (bool): Whether to include full order book depth. Else, just top of book.
|
353
329
|
|
354
330
|
Returns:
|
355
331
|
pd.DataFrame: Processed option chain data
|
@@ -370,8 +346,4 @@ async def fetch_option_chain(exchange: str = 'deribit',
|
|
370
346
|
# Process data
|
371
347
|
processed_data = process_option_chain(raw_data, currency)
|
372
348
|
|
373
|
-
# Remove order book depth if not needed
|
374
|
-
if not depth and 'bids' in processed_data.columns and 'asks' in processed_data.columns:
|
375
|
-
processed_data = processed_data.drop(columns=['bids', 'asks'])
|
376
|
-
|
377
349
|
return processed_data
|
voly/core/fit.py
CHANGED
@@ -26,16 +26,16 @@ class SVICalibrator:
|
|
26
26
|
def __init__(self, option_chain, currency, num_points=2000):
|
27
27
|
self.option_chain = option_chain
|
28
28
|
self.currency = currency
|
29
|
-
self.s = option_chain['
|
29
|
+
self.s = option_chain['spot_price'].iloc[0]
|
30
30
|
self.r = option_chain['interest_rate'].iloc[0] if 'interest_rate' in option_chain else 0.0
|
31
|
-
self.groups = option_chain.groupby('
|
31
|
+
self.groups = option_chain.groupby('expiry')
|
32
32
|
self.params_dict = {}
|
33
33
|
self.results_data = {}
|
34
34
|
self.num_points = num_points
|
35
35
|
|
36
36
|
# Initialize results data template
|
37
37
|
self.field_names = [
|
38
|
-
's', 't', 'r', '
|
38
|
+
's', 't', 'r', 'expiry', 'maturity', 'a', 'b', 'm', 'rho', 'sigma',
|
39
39
|
'nu', 'psi', 'p', 'c', 'nu_tilde', 'log_min_strike', 'usd_min_strike',
|
40
40
|
'fit_success', 'butterfly_arbitrage_free', 'calendar_arbitrage_free',
|
41
41
|
'rmse', 'mae', 'r2', 'max_error', 'loss', 'n_points'
|
@@ -45,14 +45,14 @@ class SVICalibrator:
|
|
45
45
|
for field in self.field_names:
|
46
46
|
self.results_data[field] = []
|
47
47
|
|
48
|
-
def failed_calibration(self,
|
48
|
+
def failed_calibration(self, expiry, maturity, t, n_points):
|
49
49
|
"""Create an empty result for failed calibration"""
|
50
50
|
return {
|
51
51
|
's': float(self.s),
|
52
52
|
't': float(t),
|
53
53
|
'r': float(self.r),
|
54
|
-
'
|
55
|
-
'
|
54
|
+
'expiry': expiry,
|
55
|
+
'maturity': maturity,
|
56
56
|
'fit_success': False,
|
57
57
|
'calendar_arbitrage_free': True, # Updated later
|
58
58
|
'loss': float(np.inf),
|
@@ -67,7 +67,7 @@ class SVICalibrator:
|
|
67
67
|
def filter_market_data(self, group):
|
68
68
|
"""Filter and prepare market data"""
|
69
69
|
# Filter for call options only
|
70
|
-
group = group[group['
|
70
|
+
group = group[group['flag'] == 'C']
|
71
71
|
|
72
72
|
# Handle duplicated IVs by keeping the row closest to log_moneyness=0
|
73
73
|
duplicated_iv = group[group.duplicated('mark_iv', keep=False)]
|
@@ -81,7 +81,7 @@ class SVICalibrator:
|
|
81
81
|
group = pd.concat([unique_iv, cleaned_dupes])
|
82
82
|
|
83
83
|
# Extract basic data
|
84
|
-
|
84
|
+
maturity = group['maturity'].iloc[0]
|
85
85
|
t = group['t'].iloc[0]
|
86
86
|
K = group['strikes'].values
|
87
87
|
iv = group['mark_iv'].values
|
@@ -93,7 +93,7 @@ class SVICalibrator:
|
|
93
93
|
mask = ~np.isnan(w) & ~np.isnan(vega) & ~np.isnan(k) & (iv >= 0)
|
94
94
|
k, w, vega, iv, K = k[mask], w[mask], vega[mask], iv[mask], K[mask]
|
95
95
|
|
96
|
-
return
|
96
|
+
return maturity, t, k, w, vega, iv, K
|
97
97
|
|
98
98
|
def calculate_model_stats(self, params, t, k, iv):
|
99
99
|
"""Calculate all model statistics from parameters"""
|
@@ -139,14 +139,14 @@ class SVICalibrator:
|
|
139
139
|
'max_error': float(max_error)
|
140
140
|
}
|
141
141
|
|
142
|
-
def process_maturity(self,
|
142
|
+
def process_maturity(self, expiry, group):
|
143
143
|
"""Process single maturity for SVI calibration"""
|
144
144
|
# Clean and prepare market data
|
145
|
-
|
145
|
+
maturity, t, k, w, vega, iv, K = self.filter_market_data(group)
|
146
146
|
|
147
147
|
# Not enough data points for fitting
|
148
148
|
if len(k) <= 5:
|
149
|
-
result = self.failed_calibration(
|
149
|
+
result = self.failed_calibration(expiry, maturity, t, len(k))
|
150
150
|
logger.error(f'\033[31mFAILED\033[0m for {maturity} (insufficient data points)')
|
151
151
|
self.update_results(result)
|
152
152
|
return maturity
|
@@ -156,7 +156,7 @@ class SVICalibrator:
|
|
156
156
|
|
157
157
|
# If fitting failed
|
158
158
|
if np.isnan(params[0]):
|
159
|
-
result = self.failed_calibration(
|
159
|
+
result = self.failed_calibration(expiry, maturity, t, len(k))
|
160
160
|
logger.error(f'\033[31mFAILED\033[0m for {maturity}')
|
161
161
|
self.update_results(result)
|
162
162
|
return maturity
|
@@ -172,8 +172,8 @@ class SVICalibrator:
|
|
172
172
|
's': float(self.s),
|
173
173
|
't': float(t),
|
174
174
|
'r': float(self.r),
|
175
|
-
'
|
176
|
-
'
|
175
|
+
'expiry': expiry,
|
176
|
+
'maturity': maturity,
|
177
177
|
'fit_success': True,
|
178
178
|
'calendar_arbitrage_free': True, # Updated later
|
179
179
|
'loss': float(loss),
|
@@ -201,16 +201,16 @@ class SVICalibrator:
|
|
201
201
|
# Process all maturities in parallel
|
202
202
|
with ThreadPoolExecutor() as executor:
|
203
203
|
futures = [
|
204
|
-
executor.submit(self.process_maturity,
|
205
|
-
for
|
204
|
+
executor.submit(self.process_maturity, expiry, group)
|
205
|
+
for expiry, group in self.groups
|
206
206
|
]
|
207
207
|
for future in futures:
|
208
208
|
future.result()
|
209
209
|
|
210
210
|
# Create results DataFrame and mapping for updates
|
211
|
-
fit_results = pd.DataFrame(self.results_data, index=self.results_data['
|
211
|
+
fit_results = pd.DataFrame(self.results_data, index=self.results_data['maturity'])
|
212
212
|
fit_results = fit_results.sort_values(by='t')
|
213
|
-
|
213
|
+
maturity_dict = {row['expiry']: idx for idx, row in fit_results.iterrows()}
|
214
214
|
|
215
215
|
# Check for calendar arbitrage
|
216
216
|
sorted_maturities = sorted(self.params_dict.keys(), key=lambda x: self.params_dict[x][0])
|
@@ -220,20 +220,20 @@ class SVICalibrator:
|
|
220
220
|
|
221
221
|
# Update calendar arbitrage status
|
222
222
|
for mat in sorted_maturities:
|
223
|
-
mat_name =
|
223
|
+
mat_name = maturity_dict[mat]
|
224
224
|
fit_results.at[mat_name, 'calendar_arbitrage_free'] = calendar_arbitrage_free
|
225
225
|
|
226
226
|
# Correct calendar arbitrage violations
|
227
|
-
self.correct_calendar_arbitrage(sorted_maturities, fit_results,
|
227
|
+
self.correct_calendar_arbitrage(sorted_maturities, fit_results, maturity_dict)
|
228
228
|
|
229
229
|
# Clean up results and report execution time
|
230
|
-
fit_results = fit_results.drop(columns='
|
230
|
+
fit_results = fit_results.drop(columns='maturity')
|
231
231
|
end_time = time.time()
|
232
232
|
logger.info(f"Total model execution time: {end_time - start_time:.4f} seconds")
|
233
233
|
|
234
234
|
return fit_results
|
235
235
|
|
236
|
-
def correct_calendar_arbitrage(self, sorted_maturities, fit_results,
|
236
|
+
def correct_calendar_arbitrage(self, sorted_maturities, fit_results, maturity_dict):
|
237
237
|
"""Handle calendar arbitrage corrections"""
|
238
238
|
for i in range(1, len(sorted_maturities)):
|
239
239
|
mat2 = sorted_maturities[i]
|
@@ -259,7 +259,7 @@ class SVICalibrator:
|
|
259
259
|
|
260
260
|
# Calculate new stats and update results
|
261
261
|
stats = self.calculate_model_stats(new_params, t2, k, iv)
|
262
|
-
mat2_name =
|
262
|
+
mat2_name = maturity_dict[mat2]
|
263
263
|
|
264
264
|
# Update all stats at once
|
265
265
|
for key, value in stats.items():
|
@@ -273,7 +273,7 @@ class SVICalibrator:
|
|
273
273
|
|
274
274
|
# Update final status
|
275
275
|
for mat in sorted_maturities:
|
276
|
-
mat_name =
|
276
|
+
mat_name = maturity_dict[mat]
|
277
277
|
fit_results.at[mat_name, 'calendar_arbitrage_free'] = calendar_arbitrage_free
|
278
278
|
|
279
279
|
|
@@ -287,7 +287,7 @@ def fit_model(option_chain: pd.DataFrame, num_points: int = 2000) -> pd.DataFram
|
|
287
287
|
- num_points: Number of points for k_grid and plotting
|
288
288
|
|
289
289
|
Returns:
|
290
|
-
- fit_results: DataFrame with all fit results and performance metrics as columns,
|
290
|
+
- fit_results: DataFrame with all fit results and performance metrics as columns, maturities as index
|
291
291
|
"""
|
292
292
|
currency = option_chain['currency'].iloc[0] if 'currency' in option_chain.columns else 'Unknown'
|
293
293
|
|
voly/core/interpolate.py
CHANGED
@@ -77,12 +77,12 @@ def interpolate_model(fit_results: pd.DataFrame,
|
|
77
77
|
|
78
78
|
# Calculate maturity dates
|
79
79
|
now = dt.datetime.now()
|
80
|
-
|
80
|
+
expiries = []
|
81
81
|
for days in target_days:
|
82
|
-
|
83
|
-
|
82
|
+
expiry = now + dt.timedelta(days=days)
|
83
|
+
expiries.append(expiry)
|
84
84
|
|
85
|
-
interpolated_df['
|
85
|
+
interpolated_df['expiry'] = expiries
|
86
86
|
|
87
87
|
# Sort fit_results by ytm
|
88
88
|
sorted_fit_results = fit_results.iloc[sorted_indices]
|
@@ -118,7 +118,7 @@ def interpolate_model(fit_results: pd.DataFrame,
|
|
118
118
|
interpolated_df[param] = f(target_years)
|
119
119
|
|
120
120
|
# Ensure consistent ordering of columns with expected structure
|
121
|
-
expected_columns = ['s', 't', 'r', '
|
121
|
+
expected_columns = ['s', 't', 'r', 'expiry', 'a', 'b', 'm', 'rho', 'sigma',
|
122
122
|
'nu', 'psi', 'p', 'c', 'nu_tilde']
|
123
123
|
|
124
124
|
# Create final column order based on available columns
|
voly/formulas.py
CHANGED
@@ -18,7 +18,7 @@ def vectorize_inputs(func):
|
|
18
18
|
For invalid inputs (K <= 0, o <= 0, or t <= 0), returns np.nan instead of trying to compute.
|
19
19
|
"""
|
20
20
|
|
21
|
-
def wrapper(s, K, r, o, t,
|
21
|
+
def wrapper(s, K, r, o, t, flag='call'):
|
22
22
|
# Check if inputs are scalar
|
23
23
|
K_scalar = np.isscalar(K)
|
24
24
|
o_scalar = np.isscalar(o)
|
@@ -34,7 +34,7 @@ def vectorize_inputs(func):
|
|
34
34
|
|
35
35
|
# If all inputs are scalar, use the original function directly
|
36
36
|
if K_scalar and o_scalar and t_scalar:
|
37
|
-
return func(s, K, r, o, t,
|
37
|
+
return func(s, K, r, o, t, flag)
|
38
38
|
|
39
39
|
# For arrays, we need to apply the function element-wise
|
40
40
|
# Instead of using np.vectorize directly, we'll create a custom vectorized function
|
@@ -45,7 +45,7 @@ def vectorize_inputs(func):
|
|
45
45
|
return np.nan # Return NaN for invalid inputs
|
46
46
|
else:
|
47
47
|
# Only call the function if inputs are valid
|
48
|
-
return func(s, K_val, r, o_val, t_val,
|
48
|
+
return func(s, K_val, r, o_val, t_val, flag)
|
49
49
|
|
50
50
|
# Now use numpy's vectorize on our safe function
|
51
51
|
vectorized_func = np.vectorize(safe_vectorized_func)
|
@@ -58,24 +58,24 @@ def vectorize_inputs(func):
|
|
58
58
|
|
59
59
|
@catch_exception
|
60
60
|
@vectorize_inputs
|
61
|
-
def d1(s: float, K: float, r: float, o: float, t: float,
|
62
|
-
#
|
61
|
+
def d1(s: float, K: float, r: float, o: float, t: float, flag: str = 'call') -> float:
|
62
|
+
# flag is ignored in this function but included for compatibility
|
63
63
|
return (np.log(s / K) + (r + o ** 2 / 2) * t) / (o * np.sqrt(t))
|
64
64
|
|
65
65
|
|
66
66
|
@catch_exception
|
67
67
|
@vectorize_inputs
|
68
|
-
def d2(s: float, K: float, r: float, o: float, t: float,
|
69
|
-
#
|
70
|
-
return d1(s, K, r, o, t,
|
68
|
+
def d2(s: float, K: float, r: float, o: float, t: float, flag: str = 'call') -> float:
|
69
|
+
# flag is ignored in this function but included for compatibility
|
70
|
+
return d1(s, K, r, o, t, flag) - o * np.sqrt(t)
|
71
71
|
|
72
72
|
|
73
73
|
@catch_exception
|
74
74
|
@vectorize_inputs
|
75
|
-
def bs(s: float, K: float, r: float, o: float, t: float,
|
75
|
+
def bs(s: float, K: float, r: float, o: float, t: float, flag: str = 'call') -> float:
|
76
76
|
if o <= 0 or t <= 0:
|
77
77
|
# Intrinsic value at expiry
|
78
|
-
if
|
78
|
+
if flag.lower() in ["call", "c"]:
|
79
79
|
return max(0, s - K)
|
80
80
|
else:
|
81
81
|
return max(0, K - s)
|
@@ -83,7 +83,7 @@ def bs(s: float, K: float, r: float, o: float, t: float, option_type: str = 'cal
|
|
83
83
|
d1_val = d1(s, K, r, o, t)
|
84
84
|
d2_val = d2(s, K, r, o, t)
|
85
85
|
|
86
|
-
if
|
86
|
+
if flag.lower() in ["call", "c"]:
|
87
87
|
return s * norm.cdf(d1_val) - K * np.exp(-r * t) * norm.cdf(d2_val)
|
88
88
|
else: # put
|
89
89
|
return K * np.exp(-r * t) * norm.cdf(-d2_val) - s * norm.cdf(-d1_val)
|
@@ -91,17 +91,17 @@ def bs(s: float, K: float, r: float, o: float, t: float, option_type: str = 'cal
|
|
91
91
|
|
92
92
|
@catch_exception
|
93
93
|
@vectorize_inputs
|
94
|
-
def delta(s: float, K: float, r: float, o: float, t: float,
|
94
|
+
def delta(s: float, K: float, r: float, o: float, t: float, flag: str = 'call') -> float:
|
95
95
|
if o <= 0 or t <= 0:
|
96
96
|
# At expiry, delta is either 0 or 1 for call, 0 or -1 for put
|
97
|
-
if
|
97
|
+
if flag.lower() in ["call", "c"]:
|
98
98
|
return 1.0 if s > K else 0.0
|
99
99
|
else:
|
100
100
|
return -1.0 if s < K else 0.0
|
101
101
|
|
102
102
|
d1_val = d1(s, K, r, o, t)
|
103
103
|
|
104
|
-
if
|
104
|
+
if flag.lower() in ["call", "c"]:
|
105
105
|
return norm.cdf(d1_val)
|
106
106
|
else: # put
|
107
107
|
return norm.cdf(d1_val) - 1.0
|
@@ -109,29 +109,29 @@ def delta(s: float, K: float, r: float, o: float, t: float, option_type: str = '
|
|
109
109
|
|
110
110
|
@catch_exception
|
111
111
|
@vectorize_inputs
|
112
|
-
def gamma(s: float, K: float, r: float, o: float, t: float,
|
113
|
-
d1_val = d1(s, K, r, o, t,
|
112
|
+
def gamma(s: float, K: float, r: float, o: float, t: float, flag: str = 'call') -> float:
|
113
|
+
d1_val = d1(s, K, r, o, t, flag)
|
114
114
|
return norm.pdf(d1_val) / (s * o * np.sqrt(t))
|
115
115
|
|
116
116
|
|
117
117
|
@catch_exception
|
118
118
|
@vectorize_inputs
|
119
|
-
def vega(s: float, K: float, r: float, o: float, t: float,
|
120
|
-
d1_val = d1(s, K, r, o, t,
|
119
|
+
def vega(s: float, K: float, r: float, o: float, t: float, flag: str = 'call') -> float:
|
120
|
+
d1_val = d1(s, K, r, o, t, flag)
|
121
121
|
return s * norm.pdf(d1_val) * np.sqrt(t) / 100 # Divided by 100 for 1% change
|
122
122
|
|
123
123
|
|
124
124
|
@catch_exception
|
125
125
|
@vectorize_inputs
|
126
|
-
def theta(s: float, K: float, r: float, o: float, t: float,
|
127
|
-
d1_val = d1(s, K, r, o, t,
|
128
|
-
d2_val = d2(s, K, r, o, t,
|
126
|
+
def theta(s: float, K: float, r: float, o: float, t: float, flag: str = 'call') -> float:
|
127
|
+
d1_val = d1(s, K, r, o, t, flag)
|
128
|
+
d2_val = d2(s, K, r, o, t, flag)
|
129
129
|
|
130
130
|
# First part of theta (same for both call and put)
|
131
131
|
theta_part1 = -s * norm.pdf(d1_val) * o / (2 * np.sqrt(t))
|
132
132
|
|
133
133
|
# Second part depends on option type
|
134
|
-
if
|
134
|
+
if flag.lower() in ["call", "c"]:
|
135
135
|
theta_part2 = -r * K * np.exp(-r * t) * norm.cdf(d2_val)
|
136
136
|
else: # put
|
137
137
|
theta_part2 = r * K * np.exp(-r * t) * norm.cdf(-d2_val)
|
@@ -142,10 +142,10 @@ def theta(s: float, K: float, r: float, o: float, t: float, option_type: str = '
|
|
142
142
|
|
143
143
|
@catch_exception
|
144
144
|
@vectorize_inputs
|
145
|
-
def rho(s: float, K: float, r: float, o: float, t: float,
|
146
|
-
d2_val = d2(s, K, r, o, t,
|
145
|
+
def rho(s: float, K: float, r: float, o: float, t: float, flag: str = 'call') -> float:
|
146
|
+
d2_val = d2(s, K, r, o, t, flag)
|
147
147
|
|
148
|
-
if
|
148
|
+
if flag.lower() in ["call", "c"]:
|
149
149
|
return K * t * np.exp(-r * t) * norm.cdf(d2_val) / 100
|
150
150
|
else: # put
|
151
151
|
return -K * t * np.exp(-r * t) * norm.cdf(-d2_val) / 100
|
@@ -153,33 +153,33 @@ def rho(s: float, K: float, r: float, o: float, t: float, option_type: str = 'ca
|
|
153
153
|
|
154
154
|
@catch_exception
|
155
155
|
@vectorize_inputs
|
156
|
-
def vanna(s: float, K: float, r: float, o: float, t: float,
|
157
|
-
d1_val = d1(s, K, r, o, t,
|
158
|
-
d2_val = d2(s, K, r, o, t,
|
156
|
+
def vanna(s: float, K: float, r: float, o: float, t: float, flag: str = 'call') -> float:
|
157
|
+
d1_val = d1(s, K, r, o, t, flag)
|
158
|
+
d2_val = d2(s, K, r, o, t, flag)
|
159
159
|
|
160
160
|
return -norm.pdf(d1_val) * d2_val / o
|
161
161
|
|
162
162
|
|
163
163
|
@catch_exception
|
164
164
|
@vectorize_inputs
|
165
|
-
def volga(s: float, K: float, r: float, o: float, t: float,
|
166
|
-
d1_val = d1(s, K, r, o, t,
|
167
|
-
d2_val = d2(s, K, r, o, t,
|
165
|
+
def volga(s: float, K: float, r: float, o: float, t: float, flag: str = 'call') -> float:
|
166
|
+
d1_val = d1(s, K, r, o, t, flag)
|
167
|
+
d2_val = d2(s, K, r, o, t, flag)
|
168
168
|
|
169
169
|
return s * norm.pdf(d1_val) * np.sqrt(t) * d1_val * d2_val / o
|
170
170
|
|
171
171
|
|
172
172
|
@catch_exception
|
173
173
|
@vectorize_inputs
|
174
|
-
def charm(s: float, K: float, r: float, o: float, t: float,
|
175
|
-
d1_val = d1(s, K, r, o, t,
|
176
|
-
d2_val = d2(s, K, r, o, t,
|
174
|
+
def charm(s: float, K: float, r: float, o: float, t: float, flag: str = 'call') -> float:
|
175
|
+
d1_val = d1(s, K, r, o, t, flag)
|
176
|
+
d2_val = d2(s, K, r, o, t, flag)
|
177
177
|
|
178
178
|
# First term is the same for calls and puts
|
179
179
|
term1 = -norm.pdf(d1_val) * d1_val / (2 * t)
|
180
180
|
|
181
181
|
# Second term depends on option type
|
182
|
-
if
|
182
|
+
if flag.lower() in ["call", "c"]:
|
183
183
|
term2 = -r * np.exp(-r * t) * norm.cdf(d2_val)
|
184
184
|
else: # put
|
185
185
|
term2 = r * np.exp(-r * t) * norm.cdf(-d2_val)
|
@@ -191,24 +191,24 @@ def charm(s: float, K: float, r: float, o: float, t: float, option_type: str = '
|
|
191
191
|
@catch_exception
|
192
192
|
@vectorize_inputs
|
193
193
|
def greeks(s: float, K: float, r: float, o: float, t: float,
|
194
|
-
|
194
|
+
flag: str = 'call') -> Dict[str, float]:
|
195
195
|
return {
|
196
|
-
'price': bs(s, K, r, o, t,
|
197
|
-
'delta': delta(s, K, r, o, t,
|
198
|
-
'gamma': gamma(s, K, r, o, t,
|
199
|
-
'vega': vega(s, K, r, o, t,
|
200
|
-
'theta': theta(s, K, r, o, t,
|
201
|
-
'rho': rho(s, K, r, o, t,
|
202
|
-
'vanna': vanna(s, K, r, o, t,
|
203
|
-
'volga': volga(s, K, r, o, t,
|
204
|
-
'charm': charm(s, K, r, o, t,
|
196
|
+
'price': bs(s, K, r, o, t, flag),
|
197
|
+
'delta': delta(s, K, r, o, t, flag),
|
198
|
+
'gamma': gamma(s, K, r, o, t, flag),
|
199
|
+
'vega': vega(s, K, r, o, t, flag),
|
200
|
+
'theta': theta(s, K, r, o, t, flag),
|
201
|
+
'rho': rho(s, K, r, o, t, flag),
|
202
|
+
'vanna': vanna(s, K, r, o, t, flag),
|
203
|
+
'volga': volga(s, K, r, o, t, flag),
|
204
|
+
'charm': charm(s, K, r, o, t, flag)
|
205
205
|
}
|
206
206
|
|
207
207
|
|
208
208
|
@catch_exception
|
209
209
|
@vectorize_inputs
|
210
210
|
def iv(option_price: float, s: float, K: float, r: float, t: float,
|
211
|
-
|
211
|
+
flag: str = 'call') -> float:
|
212
212
|
"""
|
213
213
|
Calculate implied volatility using py_volib for vectorized computation.
|
214
214
|
|
@@ -218,14 +218,14 @@ def iv(option_price: float, s: float, K: float, r: float, t: float,
|
|
218
218
|
- K: Strike price
|
219
219
|
- r: Risk-free rate
|
220
220
|
- t: Time to expiry in years
|
221
|
-
-
|
221
|
+
- flag: 'call' or 'put'
|
222
222
|
|
223
223
|
Returns:
|
224
224
|
- Implied volatility
|
225
225
|
"""
|
226
226
|
|
227
227
|
# Check if option price is within theoretical bounds
|
228
|
-
if
|
228
|
+
if flag.lower() in ["call", "c"]:
|
229
229
|
intrinsic = max(0, s - K * np.exp(-r * t))
|
230
230
|
if option_price < intrinsic:
|
231
231
|
return np.nan # Price below intrinsic value
|
@@ -238,7 +238,7 @@ def iv(option_price: float, s: float, K: float, r: float, t: float,
|
|
238
238
|
if option_price >= K:
|
239
239
|
return np.inf # Price exceeds strike
|
240
240
|
|
241
|
-
flag = 'c' if
|
241
|
+
flag = 'c' if flag.lower() in ["call", "c"] else 'p'
|
242
242
|
|
243
243
|
iv_value = implied_volatility(
|
244
244
|
price=option_price,
|
@@ -0,0 +1,20 @@
|
|
1
|
+
voly/__init__.py,sha256=8xyDk7rFCn_MOD5hxuv5cxxKZvBVRiSIM7TgaMPpwpw,211
|
2
|
+
voly/client.py,sha256=H4BY5n1isHOqMYX-Koe8-WKBKE6zxbdDYopDF58BPBI,14533
|
3
|
+
voly/exceptions.py,sha256=PBsbn1vNMvKcCJwwJ4lBO6glD85jo1h2qiEmD7ArAjs,92
|
4
|
+
voly/formulas.py,sha256=Jx2QSTkN3S_X1YWYXN3T0gBcxMKB_VLsqgDPg32ApmM,10833
|
5
|
+
voly/models.py,sha256=CGJQr13Uie7iwtx2hjViN9lMXeRN_uOqzp4u8NPaTlA,9282
|
6
|
+
voly/core/__init__.py,sha256=bu6fS2I1Pj9fPPnl-zY3L7NqrZSY5Zy6NY2uMUvdhKs,183
|
7
|
+
voly/core/charts.py,sha256=6MSU0z01fPOVSssodxdnFqchzDfupSmXq_e71WwDmVQ,12635
|
8
|
+
voly/core/data.py,sha256=4E-zF2wJwumbWcrGVOS59ebApRKnxyyUXCTTriUk45Y,12389
|
9
|
+
voly/core/fit.py,sha256=R3aDgCjvZ30PujoE9pgnGQXPQOTTMbk7_-RQ0ZMx5ig,13918
|
10
|
+
voly/core/hd.py,sha256=dSv197RmSWFWbRRdoBzMrD_poT7ZiJ8hdD_wKE-Li_M,13559
|
11
|
+
voly/core/interpolate.py,sha256=GAkrqaar7A0D6UMipXQUp4vSHRfb44TmCG5Xiv9elXg,5176
|
12
|
+
voly/core/rnd.py,sha256=wiZ5OIjPDf1Th5_sQ9CZG5JgAo3EL8f63T_Rj1_VP-0,13214
|
13
|
+
voly/utils/__init__.py,sha256=E05mWatyC-PDOsCxQV1p5Xi1IgpOomxrNURyCx_gB-w,200
|
14
|
+
voly/utils/density.py,sha256=ONpRli-IaJDgOZ2sb27HHFc9_tkkGSATKl94JODd86A,5879
|
15
|
+
voly/utils/logger.py,sha256=4-_2bVJmq17Q0d7Rd2mPg1AeR8gxv6EPvcmBDMFWcSM,1744
|
16
|
+
voly-0.0.228.dist-info/licenses/LICENSE,sha256=wcHIVbE12jfcBOai_wqBKY6xvNQU5E909xL1zZNq_2Q,1065
|
17
|
+
voly-0.0.228.dist-info/METADATA,sha256=_21vNdPGISj2HC7IKCjC47LHjMq2F1qPbKOEF9oFbb0,4115
|
18
|
+
voly-0.0.228.dist-info/WHEEL,sha256=ck4Vq1_RXyvS4Jt6SI0Vz6fyVs4GWg7AINwpsaGEgPE,91
|
19
|
+
voly-0.0.228.dist-info/top_level.txt,sha256=ZfLw2sSxF-LrKAkgGjOmeTcw6_gD-30zvtdEY5W4B7c,5
|
20
|
+
voly-0.0.228.dist-info/RECORD,,
|
voly-0.0.226.dist-info/RECORD
DELETED
@@ -1,20 +0,0 @@
|
|
1
|
-
voly/__init__.py,sha256=8xyDk7rFCn_MOD5hxuv5cxxKZvBVRiSIM7TgaMPpwpw,211
|
2
|
-
voly/client.py,sha256=F5jRdmEfxoE2RGHryCntRFrKLlyS7W974jEtEcBz8Co,14410
|
3
|
-
voly/exceptions.py,sha256=PBsbn1vNMvKcCJwwJ4lBO6glD85jo1h2qiEmD7ArAjs,92
|
4
|
-
voly/formulas.py,sha256=Jn9hBoIx6PGv9k4lm8PeGM4lxFJkrLau8LpnXatdQPM,11176
|
5
|
-
voly/models.py,sha256=CGJQr13Uie7iwtx2hjViN9lMXeRN_uOqzp4u8NPaTlA,9282
|
6
|
-
voly/core/__init__.py,sha256=bu6fS2I1Pj9fPPnl-zY3L7NqrZSY5Zy6NY2uMUvdhKs,183
|
7
|
-
voly/core/charts.py,sha256=2S-BfCo30aj1_xlNLqF-za5rQWxF_mWKIdtdOe5bgbw,12735
|
8
|
-
voly/core/data.py,sha256=SNF87C7-r-1IbKwf7rAhXkJ6X305yo7fCDJDdkwz3NM,14103
|
9
|
-
voly/core/fit.py,sha256=bVyx7qMgFFpTUjgoCUs58ppmeNN2CORnqPKbGUpV9xw,14081
|
10
|
-
voly/core/hd.py,sha256=dSv197RmSWFWbRRdoBzMrD_poT7ZiJ8hdD_wKE-Li_M,13559
|
11
|
-
voly/core/interpolate.py,sha256=-cNChFpuLnCSMOmfW2ldXxePgQXi-pxcjJvF2yImD1w,5222
|
12
|
-
voly/core/rnd.py,sha256=wiZ5OIjPDf1Th5_sQ9CZG5JgAo3EL8f63T_Rj1_VP-0,13214
|
13
|
-
voly/utils/__init__.py,sha256=E05mWatyC-PDOsCxQV1p5Xi1IgpOomxrNURyCx_gB-w,200
|
14
|
-
voly/utils/density.py,sha256=ONpRli-IaJDgOZ2sb27HHFc9_tkkGSATKl94JODd86A,5879
|
15
|
-
voly/utils/logger.py,sha256=4-_2bVJmq17Q0d7Rd2mPg1AeR8gxv6EPvcmBDMFWcSM,1744
|
16
|
-
voly-0.0.226.dist-info/licenses/LICENSE,sha256=wcHIVbE12jfcBOai_wqBKY6xvNQU5E909xL1zZNq_2Q,1065
|
17
|
-
voly-0.0.226.dist-info/METADATA,sha256=rLI85wsZs9RYkI2B-4-87qXHiKoarN-PEFrSfD6OdJE,4115
|
18
|
-
voly-0.0.226.dist-info/WHEEL,sha256=ck4Vq1_RXyvS4Jt6SI0Vz6fyVs4GWg7AINwpsaGEgPE,91
|
19
|
-
voly-0.0.226.dist-info/top_level.txt,sha256=ZfLw2sSxF-LrKAkgGjOmeTcw6_gD-30zvtdEY5W4B7c,5
|
20
|
-
voly-0.0.226.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|