voly 0.0.184__tar.gz → 0.0.186__tar.gz
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-0.0.184/src/voly.egg-info → voly-0.0.186}/PKG-INFO +1 -1
- {voly-0.0.184 → voly-0.0.186}/pyproject.toml +2 -2
- {voly-0.0.184 → voly-0.0.186}/src/voly/core/fit.py +90 -139
- {voly-0.0.184 → voly-0.0.186}/src/voly/models.py +4 -4
- {voly-0.0.184 → voly-0.0.186/src/voly.egg-info}/PKG-INFO +1 -1
- {voly-0.0.184 → voly-0.0.186}/LICENSE +0 -0
- {voly-0.0.184 → voly-0.0.186}/README.md +0 -0
- {voly-0.0.184 → voly-0.0.186}/setup.cfg +0 -0
- {voly-0.0.184 → voly-0.0.186}/setup.py +0 -0
- {voly-0.0.184 → voly-0.0.186}/src/voly/__init__.py +0 -0
- {voly-0.0.184 → voly-0.0.186}/src/voly/client.py +0 -0
- {voly-0.0.184 → voly-0.0.186}/src/voly/core/__init__.py +0 -0
- {voly-0.0.184 → voly-0.0.186}/src/voly/core/charts.py +0 -0
- {voly-0.0.184 → voly-0.0.186}/src/voly/core/data.py +0 -0
- {voly-0.0.184 → voly-0.0.186}/src/voly/core/hd.py +0 -0
- {voly-0.0.184 → voly-0.0.186}/src/voly/core/interpolate.py +0 -0
- {voly-0.0.184 → voly-0.0.186}/src/voly/core/rnd.py +0 -0
- {voly-0.0.184 → voly-0.0.186}/src/voly/exceptions.py +0 -0
- {voly-0.0.184 → voly-0.0.186}/src/voly/formulas.py +0 -0
- {voly-0.0.184 → voly-0.0.186}/src/voly/utils/__init__.py +0 -0
- {voly-0.0.184 → voly-0.0.186}/src/voly/utils/density.py +0 -0
- {voly-0.0.184 → voly-0.0.186}/src/voly/utils/logger.py +0 -0
- {voly-0.0.184 → voly-0.0.186}/src/voly.egg-info/SOURCES.txt +0 -0
- {voly-0.0.184 → voly-0.0.186}/src/voly.egg-info/dependency_links.txt +0 -0
- {voly-0.0.184 → voly-0.0.186}/src/voly.egg-info/requires.txt +0 -0
- {voly-0.0.184 → voly-0.0.186}/src/voly.egg-info/top_level.txt +0 -0
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
4
4
|
|
5
5
|
[project]
|
6
6
|
name = "voly"
|
7
|
-
version = "0.0.
|
7
|
+
version = "0.0.186"
|
8
8
|
description = "Options & volatility research package"
|
9
9
|
readme = "README.md"
|
10
10
|
authors = [
|
@@ -60,7 +60,7 @@ line_length = 100
|
|
60
60
|
multi_line_output = 3
|
61
61
|
|
62
62
|
[tool.mypy]
|
63
|
-
python_version = "0.0.
|
63
|
+
python_version = "0.0.186"
|
64
64
|
warn_return_any = true
|
65
65
|
warn_unused_configs = true
|
66
66
|
disallow_untyped_defs = true
|
@@ -13,7 +13,9 @@ from voly.utils.logger import logger, catch_exception
|
|
13
13
|
from voly.formulas import get_domain
|
14
14
|
from voly.exceptions import VolyError
|
15
15
|
from voly.models import SVIModel
|
16
|
+
from concurrent.futures import ThreadPoolExecutor
|
16
17
|
import warnings
|
18
|
+
import time
|
17
19
|
|
18
20
|
warnings.filterwarnings("ignore")
|
19
21
|
|
@@ -21,14 +23,17 @@ warnings.filterwarnings("ignore")
|
|
21
23
|
@catch_exception
|
22
24
|
def fit_model(option_chain: pd.DataFrame) -> pd.DataFrame:
|
23
25
|
"""
|
24
|
-
Fit a volatility model to market data.
|
26
|
+
Fit a volatility model to market data with parallel processing.
|
25
27
|
|
26
28
|
Parameters:
|
27
29
|
- option_chain: DataFrame with market data
|
28
30
|
|
29
31
|
Returns:
|
30
|
-
- DataFrame with all fit results and performance metrics as columns,
|
32
|
+
- DataFrame with all fit results and performance metrics as columns, maturity_dates as index
|
31
33
|
"""
|
34
|
+
# Start overall timer
|
35
|
+
start_total = time.time()
|
36
|
+
|
32
37
|
# Define column names and their data types
|
33
38
|
column_dtypes = {
|
34
39
|
's': float,
|
@@ -61,39 +66,48 @@ def fit_model(option_chain: pd.DataFrame) -> pd.DataFrame:
|
|
61
66
|
'n_points': int
|
62
67
|
}
|
63
68
|
|
64
|
-
|
65
|
-
groups = option_chain.groupby('maturity_date')
|
66
|
-
unique_ts = sorted(option_chain['t'].unique())
|
69
|
+
s = option_chain['index_price'].iloc[0]
|
67
70
|
maturity_names = [option_chain[option_chain['t'] == t]['maturity_name'].iloc[0] for t in unique_ts]
|
68
|
-
|
69
|
-
|
71
|
+
groups = option_chain.groupby('maturity_date')
|
72
|
+
params_dict = {}
|
70
73
|
results_data = {col: [] for col in column_dtypes.keys()}
|
74
|
+
num_points = 2000 # Number of points for k_grid
|
75
|
+
|
76
|
+
def process_maturity(maturity, group):
|
77
|
+
"""Process single maturity for SVI calibration."""
|
78
|
+
group = group[group['option_type'] == 'C']
|
79
|
+
duplicated_iv = group[group.duplicated('mark_iv', keep=False)]
|
80
|
+
|
81
|
+
# For each duplicated IV, keep the row closest to log_moneyness=0
|
82
|
+
def keep_closest_to_zero(subgroup):
|
83
|
+
idx = (subgroup['log_moneyness'].abs()).idxmin()
|
84
|
+
return subgroup.loc[[idx]]
|
85
|
+
|
86
|
+
# Apply the function to each duplicated mark_iv group
|
87
|
+
cleaned_duplicated_iv = (
|
88
|
+
duplicated_iv.groupby('mark_iv', group_keys=False)
|
89
|
+
.apply(keep_closest_to_zero)
|
90
|
+
)
|
71
91
|
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
s = option_chain['index_price'].iloc[-1]
|
76
|
-
|
77
|
-
# Dictionary to track fit results by maturity for arbitrage checks
|
78
|
-
params_dict = {}
|
92
|
+
# Get rows with unique mark_iv (no duplicates)
|
93
|
+
unique_iv = group.drop_duplicates('mark_iv', keep=False)
|
79
94
|
|
80
|
-
|
81
|
-
|
82
|
-
#
|
83
|
-
maturity_data = option_chain[option_chain['t'] == t]
|
84
|
-
maturity_name = maturity_data['maturity_name'].iloc[0]
|
95
|
+
# Combine cleaned duplicates and unique rows
|
96
|
+
maturity_data = pd.concat([unique_iv, cleaned_duplicated_iv])
|
97
|
+
#maturity_name = maturity_data['maturity_name'].iloc[0]
|
85
98
|
maturity_date = maturity_data['maturity_date'].iloc[0]
|
86
99
|
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
vega = maturity_data['vega'].values if 'vega' in maturity_data.columns else np.ones_like(iv)
|
100
|
+
t = group['t'].iloc[0]
|
101
|
+
K = group['strikes'].values
|
102
|
+
iv = group['mark_iv'].values
|
103
|
+
vega = group['vega'].values if 'vega' in group.columns else np.ones_like(iv)
|
92
104
|
k = np.log(K / s)
|
93
105
|
w = (iv ** 2) * t
|
94
106
|
mask = ~np.isnan(w) & ~np.isnan(vega) & ~np.isnan(k) & (iv >= 0)
|
95
107
|
k, w, vega, iv = k[mask], w[mask], vega[mask], iv[mask]
|
96
108
|
|
109
|
+
logger.info(f"Processing maturity {maturity}, points after filtering: {len(k)}")
|
110
|
+
|
97
111
|
params = [np.nan] * 5
|
98
112
|
loss = np.inf
|
99
113
|
nu = psi = p = c = nu_tilde = np.nan
|
@@ -112,6 +126,7 @@ def fit_model(option_chain: pd.DataFrame) -> pd.DataFrame:
|
|
112
126
|
a, b, m, rho, sigma = params
|
113
127
|
a_scaled, b_scaled = a * t, b * t
|
114
128
|
|
129
|
+
# Transform to Jump-Wing parameters
|
115
130
|
nu, psi, p, c, nu_tilde = SVIModel.raw_to_jw_params(a_scaled, b_scaled, m, rho, sigma, t)
|
116
131
|
|
117
132
|
# Compute fit statistics
|
@@ -128,7 +143,7 @@ def fit_model(option_chain: pd.DataFrame) -> pd.DataFrame:
|
|
128
143
|
usd_min_strike = np.exp(log_min_strike) * s
|
129
144
|
|
130
145
|
# Butterfly arbitrage check
|
131
|
-
k_range = np.linspace(min(k), max(k),
|
146
|
+
k_range = np.linspace(min(k), max(k), num_points)
|
132
147
|
w_k = lambda k: SVIModel.svi(k, a_scaled, b_scaled, m, rho, sigma)
|
133
148
|
w_prime = lambda k: b_scaled * (rho + (k - m) / np.sqrt((k - m) ** 2 + sigma ** 2))
|
134
149
|
w_double_prime = lambda k: b_scaled * sigma ** 2 / ((k - m) ** 2 + sigma ** 2) ** (3 / 2)
|
@@ -143,10 +158,12 @@ def fit_model(option_chain: pd.DataFrame) -> pd.DataFrame:
|
|
143
158
|
break
|
144
159
|
|
145
160
|
# Log result
|
161
|
+
GREEN, RED, RESET = '\033[32m', '\033[31m', '\033[0m'
|
146
162
|
status = f'{GREEN}SUCCESS{RESET}' if not np.isnan(params[0]) else f'{RED}FAILED{RESET}'
|
147
|
-
logger.info(f'Optimization for {
|
163
|
+
logger.info(f'Optimization for {maturity}: {status}')
|
148
164
|
logger.info("================================================")
|
149
165
|
|
166
|
+
# Store results
|
150
167
|
results_data['s'].append(float(s))
|
151
168
|
results_data['u'].append(float(u))
|
152
169
|
results_data['t'].append(float(t))
|
@@ -176,6 +193,18 @@ def fit_model(option_chain: pd.DataFrame) -> pd.DataFrame:
|
|
176
193
|
results_data['loss'].append(float(loss))
|
177
194
|
results_data['n_points'].append(int(len(k)))
|
178
195
|
|
196
|
+
return maturity
|
197
|
+
|
198
|
+
# Parallel processing of maturities with timer
|
199
|
+
start_parallel = time.time()
|
200
|
+
with ThreadPoolExecutor() as executor:
|
201
|
+
futures = [executor.submit(process_maturity, maturity, group)
|
202
|
+
for maturity, group in groups]
|
203
|
+
for future in futures:
|
204
|
+
future.result()
|
205
|
+
end_parallel = time.time()
|
206
|
+
logger.info(f"Processing completed in {end_parallel - start_parallel:.4f} seconds")
|
207
|
+
|
179
208
|
# Create results DataFrame
|
180
209
|
results_df = pd.DataFrame(results_data, index=maturity_names)
|
181
210
|
|
@@ -187,12 +216,14 @@ def fit_model(option_chain: pd.DataFrame) -> pd.DataFrame:
|
|
187
216
|
except (ValueError, TypeError) as e:
|
188
217
|
logger.warning(f"Could not convert column {col} to {dtype}: {e}")
|
189
218
|
|
190
|
-
#
|
219
|
+
# Sort by time to maturity
|
220
|
+
results_df = results_df.sort_values(by='t')
|
221
|
+
|
222
|
+
# Calendar arbitrage check (pre-correction) with timer
|
191
223
|
logger.info("\nChecking calendar arbitrage (pre-correction)...")
|
192
|
-
k_grid = np.linspace(-2, 2,
|
224
|
+
k_grid = np.linspace(-2, 2, num_points)
|
193
225
|
sorted_maturities = sorted(params_dict.keys(), key=lambda x: params_dict[x][0])
|
194
226
|
calendar_arbitrage_free = True
|
195
|
-
|
196
227
|
for i in range(len(sorted_maturities) - 1):
|
197
228
|
mat1, mat2 = sorted_maturities[i], sorted_maturities[i + 1]
|
198
229
|
t1, params1 = params_dict[mat1]
|
@@ -205,33 +236,24 @@ def fit_model(option_chain: pd.DataFrame) -> pd.DataFrame:
|
|
205
236
|
|
206
237
|
group = groups.get_group(mat2)
|
207
238
|
K = group['strikes'].values
|
208
|
-
s = group['index_price'].iloc[0]
|
209
239
|
k_market = np.log(K / s)
|
210
240
|
mask = ~np.isnan(k_market)
|
211
|
-
k_check = np.unique(
|
212
|
-
np.concatenate([k_market[mask], np.linspace(min(k_market[mask]), max(k_market[mask]), 200)]))
|
241
|
+
k_check = np.unique(np.concatenate([k_market[mask], np.linspace(min(k_market[mask]), max(k_market[mask]), num_points)]))
|
213
242
|
|
214
243
|
for k_val in k_check:
|
215
244
|
w1 = SVIModel.svi(k_val, a1 * t1, b1 * t1, m1, rho1, sigma1)
|
216
245
|
w2 = SVIModel.svi(k_val, a2 * t2, b2 * t2, m2, rho2, sigma2)
|
217
246
|
if w2 < w1 - 1e-6:
|
218
|
-
logger.warning(f"Calendar arbitrage violation at t1={t1:.4f}, t2={t2:.4f}, k={k_val:.4f}: ")
|
219
|
-
logger.warning(f"w1={w1:.6f}, w2={w2:.6f}")
|
247
|
+
logger.warning(f"Calendar arbitrage violation at t1={t1:.4f}, t2={t2:.4f}, k={k_val:.4f}: w1={w1:.6f}, w2={w2:.6f}")
|
220
248
|
calendar_arbitrage_free = False
|
221
249
|
break
|
222
250
|
if not calendar_arbitrage_free:
|
223
251
|
break
|
224
252
|
|
225
253
|
for mat in sorted_maturities:
|
226
|
-
|
227
|
-
for i, maturity_name in enumerate(maturity_names):
|
228
|
-
if results_df.iloc[i]['maturity_date'] == mat:
|
229
|
-
idx = results_df.index[i]
|
230
|
-
break
|
231
|
-
if idx is not None:
|
232
|
-
results_df.at[idx, 'calendar_arbitrage_free'] = calendar_arbitrage_free
|
254
|
+
results_df.at[mat, 'calendar_arbitrage_free'] = calendar_arbitrage_free
|
233
255
|
|
234
|
-
# Calendar arbitrage correction
|
256
|
+
# Calendar arbitrage correction with timer
|
235
257
|
logger.info("\nPerforming calendar arbitrage correction...")
|
236
258
|
for i in range(1, len(sorted_maturities)):
|
237
259
|
mat2 = sorted_maturities[i]
|
@@ -243,7 +265,6 @@ def fit_model(option_chain: pd.DataFrame) -> pd.DataFrame:
|
|
243
265
|
continue
|
244
266
|
|
245
267
|
group = groups.get_group(mat2)
|
246
|
-
s = group['index_price'].iloc[0]
|
247
268
|
K = group['strikes'].values
|
248
269
|
iv = group['mark_iv'].values
|
249
270
|
vega = group['vega'].values if 'vega' in group.columns else np.ones_like(iv)
|
@@ -278,7 +299,7 @@ def fit_model(option_chain: pd.DataFrame) -> pd.DataFrame:
|
|
278
299
|
|
279
300
|
# Update butterfly arbitrage check
|
280
301
|
butterfly_arbitrage_free = True
|
281
|
-
k_range = np.linspace(min(k), max(k),
|
302
|
+
k_range = np.linspace(min(k), max(k), num_points)
|
282
303
|
w_k = lambda k: SVIModel.svi(k, a_scaled, b_scaled, m, rho, sigma)
|
283
304
|
w_prime = lambda k: b_scaled * (rho + (k - m) / np.sqrt((k - m) ** 2 + sigma ** 2))
|
284
305
|
w_double_prime = lambda k: b_scaled * sigma ** 2 / ((k - m) ** 2 + sigma ** 2) ** (3 / 2)
|
@@ -292,35 +313,26 @@ def fit_model(option_chain: pd.DataFrame) -> pd.DataFrame:
|
|
292
313
|
butterfly_arbitrage_free = False
|
293
314
|
break
|
294
315
|
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
|
300
|
-
|
301
|
-
|
302
|
-
|
303
|
-
|
304
|
-
|
305
|
-
|
306
|
-
|
307
|
-
|
308
|
-
|
309
|
-
|
310
|
-
|
311
|
-
|
312
|
-
|
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))
|
316
|
+
results_df.at[mat2, 'a'] = float(a_scaled)
|
317
|
+
results_df.at[mat2, 'b'] = float(b_scaled)
|
318
|
+
results_df.at[mat2, 'm'] = float(m)
|
319
|
+
results_df.at[mat2, 'rho'] = float(rho)
|
320
|
+
results_df.at[mat2, 'sigma'] = float(sigma)
|
321
|
+
results_df.at[mat2, 'nu'] = float(nu)
|
322
|
+
results_df.at[mat2, 'psi'] = float(psi)
|
323
|
+
results_df.at[mat2, 'p'] = float(p)
|
324
|
+
results_df.at[mat2, 'c'] = float(c)
|
325
|
+
results_df.at[mat2, 'nu_tilde'] = float(nu_tilde)
|
326
|
+
results_df.at[mat2, 'rmse'] = float(rmse)
|
327
|
+
results_df.at[mat2, 'mae'] = float(mae)
|
328
|
+
results_df.at[mat2, 'r2'] = float(r2)
|
329
|
+
results_df.at[mat2, 'max_error'] = float(max_error)
|
330
|
+
results_df.at[mat2, 'log_min_strike'] = float(log_min_strike)
|
331
|
+
results_df.at[mat2, 'usd_min_strike'] = float(usd_min_strike)
|
332
|
+
results_df.at[mat2, 'butterfly_arbitrage_free'] = butterfly_arbitrage_free
|
333
|
+
results_df.at[mat2, 'fit_success'] = bool(not np.isnan(a))
|
321
334
|
|
322
335
|
# Calendar arbitrage check (post-correction)
|
323
|
-
logger.info("\nChecking calendar arbitrage (post-correction)...")
|
324
336
|
calendar_arbitrage_free = True
|
325
337
|
for i in range(len(sorted_maturities) - 1):
|
326
338
|
mat1, mat2 = sorted_maturities[i], sorted_maturities[i + 1]
|
@@ -334,88 +346,27 @@ def fit_model(option_chain: pd.DataFrame) -> pd.DataFrame:
|
|
334
346
|
|
335
347
|
group = groups.get_group(mat2)
|
336
348
|
K = group['strikes'].values
|
337
|
-
s = group['index_price'].iloc[0]
|
338
349
|
k_market = np.log(K / s)
|
339
350
|
mask = ~np.isnan(k_market)
|
340
351
|
k_check = np.unique(np.concatenate(
|
341
|
-
[k_market[mask], np.linspace(min(k_market[mask]), max(k_market[mask]),
|
352
|
+
[k_market[mask], np.linspace(min(k_market[mask]), max(k_market[mask]), num_points)]))
|
342
353
|
|
343
354
|
for k_val in k_check:
|
344
355
|
w1 = SVIModel.svi(k_val, a1 * t1, b1 * t1, m1, rho1, sigma1)
|
345
356
|
w2 = SVIModel.svi(k_val, a2 * t2, b2 * t2, m2, rho2, sigma2)
|
346
357
|
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}")
|
358
|
+
logger.warning(f"Calendar arbitrage violation at t1={t1:.4f}, t2={t2:.4f}, k={k_val:.4f}: w1={w1:.6f}, w2={w2:.6f}")
|
349
359
|
calendar_arbitrage_free = False
|
350
360
|
break
|
351
361
|
if not calendar_arbitrage_free:
|
352
362
|
break
|
353
363
|
|
354
364
|
for mat in sorted_maturities:
|
355
|
-
|
356
|
-
|
357
|
-
|
358
|
-
|
359
|
-
|
360
|
-
if idx is not None:
|
361
|
-
results_df.at[idx, 'calendar_arbitrage_free'] = calendar_arbitrage_free
|
365
|
+
results_df.at[mat, 'calendar_arbitrage_free'] = calendar_arbitrage_free
|
366
|
+
|
367
|
+
# End overall timer and print total time
|
368
|
+
end_total = time.time()
|
369
|
+
logger.info(f"\nTotal execution time for SVI fit: {end_total - start_total:.4f} seconds")
|
362
370
|
|
363
371
|
logger.info("Model fitting complete.")
|
364
372
|
return results_df
|
365
|
-
|
366
|
-
|
367
|
-
@catch_exception
|
368
|
-
def get_iv_surface(model_results: pd.DataFrame,
|
369
|
-
domain_params: Tuple[float, float, int] = (-1.5, 1.5, 1000),
|
370
|
-
return_domain: str = 'log_moneyness') -> Tuple[Dict[str, np.ndarray], Dict[str, np.ndarray]]:
|
371
|
-
"""
|
372
|
-
Generate implied volatility surface using optimized SVI parameters.
|
373
|
-
|
374
|
-
Works with both regular fit_results and interpolated_results dataframes.
|
375
|
-
|
376
|
-
Parameters:
|
377
|
-
- model_results: DataFrame from fit_model() or interpolate_model(). Maturity names or DTM as Index
|
378
|
-
- domain_params: Tuple of (min, max, num_points) for the log-moneyness array
|
379
|
-
- return_domain: Domain for x-axis values ('log_moneyness', 'moneyness', 'returns', 'strikes', 'delta')
|
380
|
-
|
381
|
-
Returns:
|
382
|
-
- Tuple of (iv_surface, x_surface)
|
383
|
-
iv_surface: Dictionary mapping maturity/dtm names to IV arrays
|
384
|
-
x_surface: Dictionary mapping maturity/dtm names to requested x domain arrays
|
385
|
-
"""
|
386
|
-
# Check if required columns are present
|
387
|
-
required_columns = ['a', 'b', 'm', 'rho', 'sigma', 't']
|
388
|
-
missing_columns = [col for col in required_columns if col not in model_results.columns]
|
389
|
-
if missing_columns:
|
390
|
-
raise VolyError(f"Required columns missing in model_results: {missing_columns}")
|
391
|
-
|
392
|
-
# Generate implied volatility surface in log-moneyness domain
|
393
|
-
LM = np.linspace(domain_params[0], domain_params[1], domain_params[2])
|
394
|
-
|
395
|
-
iv_surface = {}
|
396
|
-
x_surface = {}
|
397
|
-
|
398
|
-
# Process each maturity/dtm
|
399
|
-
for i in model_results.index:
|
400
|
-
# Calculate SVI total implied variance and convert to IV
|
401
|
-
params = [
|
402
|
-
model_results.loc[i, 'a'],
|
403
|
-
model_results.loc[i, 'b'],
|
404
|
-
model_results.loc[i, 'm'],
|
405
|
-
model_results.loc[i, 'rho'],
|
406
|
-
model_results.loc[i, 'sigma']
|
407
|
-
]
|
408
|
-
s = model_results.loc[i, 's']
|
409
|
-
r = model_results.loc[i, 'r']
|
410
|
-
t = model_results.loc[i, 't']
|
411
|
-
|
412
|
-
# Calculate implied volatility
|
413
|
-
w = np.array([SVIModel.svi(x, *params) for x in LM])
|
414
|
-
o = np.sqrt(w / t)
|
415
|
-
iv_surface[i] = o
|
416
|
-
|
417
|
-
# Calculate x domain for this maturity/dtm
|
418
|
-
x = get_domain(domain_params, s, r, o, t, return_domain)
|
419
|
-
x_surface[i] = x
|
420
|
-
|
421
|
-
return iv_surface, x_surface
|
@@ -5,6 +5,9 @@ Volatility models for the Voly package.
|
|
5
5
|
import numpy as np
|
6
6
|
from numpy.linalg import solve
|
7
7
|
from typing import Tuple, Dict, List, Optional, Union
|
8
|
+
from voly.utils.logger import logger
|
9
|
+
from scipy.optimize import minimize
|
10
|
+
from sklearn.metrics import mean_squared_error
|
8
11
|
|
9
12
|
|
10
13
|
class SVIModel:
|
@@ -99,7 +102,7 @@ class SVIModel:
|
|
99
102
|
return loss
|
100
103
|
|
101
104
|
result = minimize(score, [sigma_init, m_init], bounds=[(0.001, None), (None, None)],
|
102
|
-
tol=1e-16, method="
|
105
|
+
tol=1e-16, method="SLSQP", options={'maxfun': 5000})
|
103
106
|
|
104
107
|
sigma, m = result.x
|
105
108
|
c, d, a_calib, loss = cls.calibration(tiv, vega, k, m, sigma)
|
@@ -120,8 +123,6 @@ class SVIModel:
|
|
120
123
|
if np.any(np.isnan(params)) or np.any(np.isnan(prev_params)):
|
121
124
|
return params
|
122
125
|
|
123
|
-
from scipy.optimize import minimize
|
124
|
-
|
125
126
|
a_init, b_init, m_init, rho_init, sigma_init = params
|
126
127
|
a_prev, b_prev, m_prev, rho_prev, sigma_prev = prev_params
|
127
128
|
k_constraint = np.unique(np.concatenate([k, np.linspace(min(k), max(k), len(k_grid))]))
|
@@ -129,7 +130,6 @@ class SVIModel:
|
|
129
130
|
def objective(x):
|
130
131
|
a, b, m, rho, sigma = x
|
131
132
|
w_model = cls.svi(k, a * t, b * t, m, rho, sigma)
|
132
|
-
from sklearn.metrics import mean_squared_error
|
133
133
|
fit_loss = mean_squared_error(tiv, w_model, sample_weight=vega)
|
134
134
|
param_deviation = sum(((x[i] - x_init) / max(abs(x_init), 1e-6)) ** 2
|
135
135
|
for i, x_init in enumerate([a_init, b_init, m_init, rho_init, sigma_init]))
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|