voly 0.0.181__py3-none-any.whl → 0.0.183__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/core/fit.py CHANGED
@@ -232,134 +232,133 @@ def fit_model(option_chain: pd.DataFrame) -> pd.DataFrame:
232
232
  results_df.at[idx, 'calendar_arbitrage_free'] = calendar_arbitrage_free
233
233
 
234
234
  # Calendar arbitrage correction
235
- if not calendar_arbitrage_free:
236
- logger.info("\nPerforming calendar arbitrage correction...")
237
- for i in range(1, len(sorted_maturities)):
238
- mat2 = sorted_maturities[i]
239
- mat1 = sorted_maturities[i - 1]
240
- t2, params2 = params_dict[mat2]
241
- t1, params1 = params_dict[mat1]
242
-
243
- if np.any(np.isnan(params2)) or np.any(np.isnan(params1)):
244
- continue
245
-
246
- group = groups.get_group(mat2)
247
- s = group['index_price'].iloc[0]
248
- K = group['strikes'].values
249
- iv = group['mark_iv'].values
250
- vega = group['vega'].values if 'vega' in group.columns else np.ones_like(iv)
251
- k = np.log(K / s)
252
- w = (iv ** 2) * t2
253
- mask = ~np.isnan(w) & ~np.isnan(vega) & ~np.isnan(k) & (iv >= 0)
254
- k, w, vega, iv = k[mask], w[mask], vega[mask], iv[mask]
255
-
256
- new_params = SVIModel.correct_calendar_arbitrage(
257
- params=params2, t=t2, tiv=w, vega=vega, k=k,
258
- prev_params=params1, prev_t=t1, k_grid=k_grid
259
- )
260
-
261
- params_dict[mat2] = (t2, new_params)
262
-
263
- a, b, m, rho, sigma = new_params
264
- a_scaled, b_scaled = a * t2, b * t2
265
- nu, psi, p, c, nu_tilde = SVIModel.raw_to_jw_params(a_scaled, b_scaled, m, rho, sigma, t2)
266
-
267
- # Recompute fit statistics
268
- w_model = np.array([SVIModel.svi(x, a_scaled, b_scaled, m, rho, sigma) for x in k])
269
- iv_model = np.sqrt(w_model / t2)
270
- iv_market = iv
271
- rmse = np.sqrt(mean_squared_error(iv_market, iv_model))
272
- mae = mean_absolute_error(iv_market, iv_model)
273
- r2 = r2_score(iv_market, iv_model)
274
- max_error = np.max(np.abs(iv_market - iv_model))
275
-
276
- # Recompute min strike
277
- log_min_strike = SVIModel.svi_min_strike(sigma, rho, m)
278
- usd_min_strike = np.exp(log_min_strike) * s
279
-
280
- # Update butterfly arbitrage check
281
- butterfly_arbitrage_free = True
282
- k_range = np.linspace(min(k), max(k), 200)
283
- w_k = lambda k: SVIModel.svi(k, a_scaled, b_scaled, m, rho, sigma)
284
- w_prime = lambda k: b_scaled * (rho + (k - m) / np.sqrt((k - m) ** 2 + sigma ** 2))
285
- w_double_prime = lambda k: b_scaled * sigma ** 2 / ((k - m) ** 2 + sigma ** 2) ** (3 / 2)
286
-
287
- for k_val in k_range:
288
- wk = w_k(k_val)
289
- wp = w_prime(k_val)
290
- wpp = w_double_prime(k_val)
291
- g = (1 - (k_val * wp) / (2 * wk)) ** 2 - (wp ** 2) / 4 * (1 / wk + 1 / 4) + wpp / 2
292
- if g < 0:
293
- butterfly_arbitrage_free = False
294
- break
295
-
296
- # Find the correct index to update
297
- idx = None
298
- for j, maturity_name in enumerate(maturity_names):
299
- if results_df.iloc[j]['maturity_date'] == mat2:
300
- idx = results_df.index[j]
301
- break
302
-
303
- if idx is not None:
304
- results_df.at[idx, 'a'] = float(a_scaled)
305
- results_df.at[idx, 'b'] = float(b_scaled)
306
- results_df.at[idx, 'm'] = float(m)
307
- results_df.at[idx, 'rho'] = float(rho)
308
- results_df.at[idx, 'sigma'] = float(sigma)
309
- results_df.at[idx, 'nu'] = float(nu)
310
- results_df.at[idx, 'psi'] = float(psi)
311
- results_df.at[idx, 'p'] = float(p)
312
- results_df.at[idx, 'c'] = float(c)
313
- results_df.at[idx, 'nu_tilde'] = float(nu_tilde)
314
- results_df.at[idx, 'rmse'] = float(rmse)
315
- results_df.at[idx, 'mae'] = float(mae)
316
- results_df.at[idx, 'r2'] = float(r2)
317
- results_df.at[idx, 'max_error'] = float(max_error)
318
- results_df.at[idx, 'log_min_strike'] = float(log_min_strike)
319
- results_df.at[idx, 'usd_min_strike'] = float(usd_min_strike)
320
- results_df.at[idx, 'butterfly_arbitrage_free'] = butterfly_arbitrage_free
321
- results_df.at[idx, 'fit_success'] = bool(not np.isnan(a))
322
-
323
- # Calendar arbitrage check (post-correction)
324
- logger.info("\nChecking calendar arbitrage (post-correction)...")
325
- calendar_arbitrage_free = True
326
- for i in range(len(sorted_maturities) - 1):
327
- mat1, mat2 = sorted_maturities[i], sorted_maturities[i + 1]
328
- t1, params1 = params_dict[mat1]
329
- t2, params2 = params_dict[mat2]
330
- a1, b1, m1, rho1, sigma1 = params1
331
- a2, b2, m2, rho2, sigma2 = params2
332
-
333
- if np.isnan(a1) or np.isnan(a2):
334
- continue
335
-
336
- group = groups.get_group(mat2)
337
- K = group['strikes'].values
338
- s = group['index_price'].iloc[0]
339
- k_market = np.log(K / s)
340
- mask = ~np.isnan(k_market)
341
- k_check = np.unique(np.concatenate(
342
- [k_market[mask], np.linspace(min(k_market[mask]), max(k_market[mask]), 200)]))
343
-
344
- for k_val in k_check:
345
- w1 = SVIModel.svi(k_val, a1 * t1, b1 * t1, m1, rho1, sigma1)
346
- w2 = SVIModel.svi(k_val, a2 * t2, b2 * t2, m2, rho2, sigma2)
347
- if w2 < w1 - 1e-6:
348
- logger.warning(
349
- f"Calendar arbitrage violation at t1={t1:.4f}, t2={t2:.4f}, k={k_val:.4f}: w1={w1:.6f}, w2={w2:.6f}")
350
- calendar_arbitrage_free = False
351
- break
352
- if not calendar_arbitrage_free:
235
+ logger.info("\nPerforming calendar arbitrage correction...")
236
+ for i in range(1, len(sorted_maturities)):
237
+ mat2 = sorted_maturities[i]
238
+ mat1 = sorted_maturities[i - 1]
239
+ t2, params2 = params_dict[mat2]
240
+ t1, params1 = params_dict[mat1]
241
+
242
+ if np.any(np.isnan(params2)) or np.any(np.isnan(params1)):
243
+ continue
244
+
245
+ group = groups.get_group(mat2)
246
+ s = group['index_price'].iloc[0]
247
+ K = group['strikes'].values
248
+ iv = group['mark_iv'].values
249
+ vega = group['vega'].values if 'vega' in group.columns else np.ones_like(iv)
250
+ k = np.log(K / s)
251
+ w = (iv ** 2) * t2
252
+ mask = ~np.isnan(w) & ~np.isnan(vega) & ~np.isnan(k) & (iv >= 0)
253
+ k, w, vega, iv = k[mask], w[mask], vega[mask], iv[mask]
254
+
255
+ new_params = SVIModel.correct_calendar_arbitrage(
256
+ params=params2, t=t2, tiv=w, vega=vega, k=k,
257
+ prev_params=params1, prev_t=t1, k_grid=k_grid
258
+ )
259
+
260
+ params_dict[mat2] = (t2, new_params)
261
+
262
+ a, b, m, rho, sigma = new_params
263
+ a_scaled, b_scaled = a * t2, b * t2
264
+ nu, psi, p, c, nu_tilde = SVIModel.raw_to_jw_params(a_scaled, b_scaled, m, rho, sigma, t2)
265
+
266
+ # Recompute fit statistics
267
+ w_model = np.array([SVIModel.svi(x, a_scaled, b_scaled, m, rho, sigma) for x in k])
268
+ iv_model = np.sqrt(w_model / t2)
269
+ iv_market = iv
270
+ rmse = np.sqrt(mean_squared_error(iv_market, iv_model))
271
+ mae = mean_absolute_error(iv_market, iv_model)
272
+ r2 = r2_score(iv_market, iv_model)
273
+ max_error = np.max(np.abs(iv_market - iv_model))
274
+
275
+ # Recompute min strike
276
+ log_min_strike = SVIModel.svi_min_strike(sigma, rho, m)
277
+ usd_min_strike = np.exp(log_min_strike) * s
278
+
279
+ # Update butterfly arbitrage check
280
+ butterfly_arbitrage_free = True
281
+ k_range = np.linspace(min(k), max(k), 200)
282
+ w_k = lambda k: SVIModel.svi(k, a_scaled, b_scaled, m, rho, sigma)
283
+ w_prime = lambda k: b_scaled * (rho + (k - m) / np.sqrt((k - m) ** 2 + sigma ** 2))
284
+ w_double_prime = lambda k: b_scaled * sigma ** 2 / ((k - m) ** 2 + sigma ** 2) ** (3 / 2)
285
+
286
+ for k_val in k_range:
287
+ wk = w_k(k_val)
288
+ wp = w_prime(k_val)
289
+ wpp = w_double_prime(k_val)
290
+ g = (1 - (k_val * wp) / (2 * wk)) ** 2 - (wp ** 2) / 4 * (1 / wk + 1 / 4) + wpp / 2
291
+ if g < 0:
292
+ butterfly_arbitrage_free = False
293
+ break
294
+
295
+ # Find the correct index to update
296
+ idx = None
297
+ for j, maturity_name in enumerate(maturity_names):
298
+ if results_df.iloc[j]['maturity_date'] == mat2:
299
+ idx = results_df.index[j]
353
300
  break
354
301
 
355
- for mat in sorted_maturities:
356
- idx = None
357
- for j, maturity_name in enumerate(maturity_names):
358
- if results_df.iloc[j]['maturity_date'] == mat:
359
- idx = results_df.index[j]
360
- break
361
- if idx is not None:
362
- results_df.at[idx, 'calendar_arbitrage_free'] = calendar_arbitrage_free
302
+ if idx is not None:
303
+ results_df.at[idx, 'a'] = float(a_scaled)
304
+ results_df.at[idx, 'b'] = float(b_scaled)
305
+ results_df.at[idx, 'm'] = float(m)
306
+ results_df.at[idx, 'rho'] = float(rho)
307
+ results_df.at[idx, 'sigma'] = float(sigma)
308
+ results_df.at[idx, 'nu'] = float(nu)
309
+ results_df.at[idx, 'psi'] = float(psi)
310
+ results_df.at[idx, 'p'] = float(p)
311
+ results_df.at[idx, 'c'] = float(c)
312
+ results_df.at[idx, 'nu_tilde'] = float(nu_tilde)
313
+ results_df.at[idx, 'rmse'] = float(rmse)
314
+ results_df.at[idx, 'mae'] = float(mae)
315
+ results_df.at[idx, 'r2'] = float(r2)
316
+ results_df.at[idx, 'max_error'] = float(max_error)
317
+ results_df.at[idx, 'log_min_strike'] = float(log_min_strike)
318
+ results_df.at[idx, 'usd_min_strike'] = float(usd_min_strike)
319
+ results_df.at[idx, 'butterfly_arbitrage_free'] = butterfly_arbitrage_free
320
+ results_df.at[idx, 'fit_success'] = bool(not np.isnan(a))
321
+
322
+ # Calendar arbitrage check (post-correction)
323
+ logger.info("\nChecking calendar arbitrage (post-correction)...")
324
+ calendar_arbitrage_free = True
325
+ for i in range(len(sorted_maturities) - 1):
326
+ mat1, mat2 = sorted_maturities[i], sorted_maturities[i + 1]
327
+ t1, params1 = params_dict[mat1]
328
+ t2, params2 = params_dict[mat2]
329
+ a1, b1, m1, rho1, sigma1 = params1
330
+ a2, b2, m2, rho2, sigma2 = params2
331
+
332
+ if np.isnan(a1) or np.isnan(a2):
333
+ continue
334
+
335
+ group = groups.get_group(mat2)
336
+ K = group['strikes'].values
337
+ s = group['index_price'].iloc[0]
338
+ k_market = np.log(K / s)
339
+ mask = ~np.isnan(k_market)
340
+ k_check = np.unique(np.concatenate(
341
+ [k_market[mask], np.linspace(min(k_market[mask]), max(k_market[mask]), 200)]))
342
+
343
+ for k_val in k_check:
344
+ w1 = SVIModel.svi(k_val, a1 * t1, b1 * t1, m1, rho1, sigma1)
345
+ w2 = SVIModel.svi(k_val, a2 * t2, b2 * t2, m2, rho2, sigma2)
346
+ if w2 < w1 - 1e-6:
347
+ logger.warning(
348
+ f"Calendar arbitrage violation at t1={t1:.4f}, t2={t2:.4f}, k={k_val:.4f}: w1={w1:.6f}, w2={w2:.6f}")
349
+ calendar_arbitrage_free = False
350
+ break
351
+ if not calendar_arbitrage_free:
352
+ break
353
+
354
+ for mat in sorted_maturities:
355
+ idx = None
356
+ for j, maturity_name in enumerate(maturity_names):
357
+ if results_df.iloc[j]['maturity_date'] == mat:
358
+ idx = results_df.index[j]
359
+ break
360
+ if idx is not None:
361
+ results_df.at[idx, 'calendar_arbitrage_free'] = calendar_arbitrage_free
363
362
 
364
363
  logger.info("Model fitting complete.")
365
364
  return results_df
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: voly
3
- Version: 0.0.181
3
+ Version: 0.0.183
4
4
  Summary: Options & volatility research package
5
5
  Author-email: Manu de Cara <manu.de.cara@gmail.com>
6
6
  License: MIT
@@ -6,15 +6,15 @@ voly/models.py,sha256=wxqf9T4D2ORO7g3KcwxUL0E78fG69W29xqer3ccoUXo,6994
6
6
  voly/core/__init__.py,sha256=bu6fS2I1Pj9fPPnl-zY3L7NqrZSY5Zy6NY2uMUvdhKs,183
7
7
  voly/core/charts.py,sha256=2S-BfCo30aj1_xlNLqF-za5rQWxF_mWKIdtdOe5bgbw,12735
8
8
  voly/core/data.py,sha256=9v9iuE2XdIIlzoRAB7q1ol7YghBzBsPGAiwZ11oDuis,13650
9
- voly/core/fit.py,sha256=v_BNOlAs1Msv5Wghtuscfij_6qz4cVvboLOCIGEquTQ,17675
9
+ voly/core/fit.py,sha256=P4axtSrrdTIoXpGe2GsdP4bJi8e8t_S_yda3St_TEoI,17195
10
10
  voly/core/hd.py,sha256=UFAyLncNUHivpPAcko6IK1bC55mudVtdlRFfXp63HXE,14771
11
11
  voly/core/interpolate.py,sha256=JkK172-FXyhesW3hY4pEeuJWG3Bugq7QZXbeKoRpLuo,5305
12
12
  voly/core/rnd.py,sha256=GoC3m1Q46Wnk5tV_mstr-3_aktHeue6BBLh4DQTciW0,13307
13
13
  voly/utils/__init__.py,sha256=E05mWatyC-PDOsCxQV1p5Xi1IgpOomxrNURyCx_gB-w,200
14
14
  voly/utils/density.py,sha256=q0fX4im9TGwMCZ32Hzdv8CNh56KnJo8bmG5w0gVWZH8,5879
15
15
  voly/utils/logger.py,sha256=4-_2bVJmq17Q0d7Rd2mPg1AeR8gxv6EPvcmBDMFWcSM,1744
16
- voly-0.0.181.dist-info/licenses/LICENSE,sha256=wcHIVbE12jfcBOai_wqBKY6xvNQU5E909xL1zZNq_2Q,1065
17
- voly-0.0.181.dist-info/METADATA,sha256=7m41Sl1yCodC9BZWgXerN6-gnKFUGZEJ7hff3RN3HXg,4115
18
- voly-0.0.181.dist-info/WHEEL,sha256=pxyMxgL8-pra_rKaQ4drOZAegBVuX-G_4nRHjjgWbmo,91
19
- voly-0.0.181.dist-info/top_level.txt,sha256=ZfLw2sSxF-LrKAkgGjOmeTcw6_gD-30zvtdEY5W4B7c,5
20
- voly-0.0.181.dist-info/RECORD,,
16
+ voly-0.0.183.dist-info/licenses/LICENSE,sha256=wcHIVbE12jfcBOai_wqBKY6xvNQU5E909xL1zZNq_2Q,1065
17
+ voly-0.0.183.dist-info/METADATA,sha256=D4HB2YlkiMk6vpiczAcgNTPU8AsxEZZMCR0nZZLplFM,4115
18
+ voly-0.0.183.dist-info/WHEEL,sha256=pxyMxgL8-pra_rKaQ4drOZAegBVuX-G_4nRHjjgWbmo,91
19
+ voly-0.0.183.dist-info/top_level.txt,sha256=ZfLw2sSxF-LrKAkgGjOmeTcw6_gD-30zvtdEY5W4B7c,5
20
+ voly-0.0.183.dist-info/RECORD,,
File without changes