voly 0.0.11__tar.gz → 0.0.13__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.11/src/voly.egg-info → voly-0.0.13}/PKG-INFO +1 -1
- {voly-0.0.11 → voly-0.0.13}/pyproject.toml +2 -2
- {voly-0.0.11 → voly-0.0.13}/src/voly/client.py +33 -15
- {voly-0.0.11 → voly-0.0.13}/src/voly/core/data.py +3 -6
- {voly-0.0.11 → voly-0.0.13}/src/voly/core/fit.py +116 -156
- {voly-0.0.11 → voly-0.0.13/src/voly.egg-info}/PKG-INFO +1 -1
- {voly-0.0.11 → voly-0.0.13}/LICENSE +0 -0
- {voly-0.0.11 → voly-0.0.13}/README.md +0 -0
- {voly-0.0.11 → voly-0.0.13}/setup.cfg +0 -0
- {voly-0.0.11 → voly-0.0.13}/setup.py +0 -0
- {voly-0.0.11 → voly-0.0.13}/src/voly/__init__.py +0 -0
- {voly-0.0.11 → voly-0.0.13}/src/voly/core/__init__.py +0 -0
- {voly-0.0.11 → voly-0.0.13}/src/voly/core/charts.py +0 -0
- {voly-0.0.11 → voly-0.0.13}/src/voly/core/interpolate.py +0 -0
- {voly-0.0.11 → voly-0.0.13}/src/voly/core/rnd.py +0 -0
- {voly-0.0.11 → voly-0.0.13}/src/voly/exceptions.py +0 -0
- {voly-0.0.11 → voly-0.0.13}/src/voly/formulas.py +0 -0
- {voly-0.0.11 → voly-0.0.13}/src/voly/models.py +0 -0
- {voly-0.0.11 → voly-0.0.13}/src/voly/utils/__init__.py +0 -0
- {voly-0.0.11 → voly-0.0.13}/src/voly/utils/logger.py +0 -0
- {voly-0.0.11 → voly-0.0.13}/src/voly.egg-info/SOURCES.txt +0 -0
- {voly-0.0.11 → voly-0.0.13}/src/voly.egg-info/dependency_links.txt +0 -0
- {voly-0.0.11 → voly-0.0.13}/src/voly.egg-info/requires.txt +0 -0
- {voly-0.0.11 → voly-0.0.13}/src/voly.egg-info/top_level.txt +0 -0
- {voly-0.0.11 → voly-0.0.13}/tests/test_client.py +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.13"
|
|
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.13"
|
|
64
64
|
warn_return_any = true
|
|
65
65
|
warn_unused_configs = true
|
|
66
66
|
disallow_untyped_defs = true
|
|
@@ -18,7 +18,7 @@ from voly.formulas import (
|
|
|
18
18
|
bs, delta, gamma, vega, theta, rho, vanna, volga, charm, greeks, iv
|
|
19
19
|
)
|
|
20
20
|
from voly.core.data import fetch_option_chain, process_option_chain
|
|
21
|
-
from voly.core.fit import fit_model
|
|
21
|
+
from voly.core.fit import fit_model, get_surface
|
|
22
22
|
from voly.core.rnd import calculate_rnd, calculate_pdf, calculate_cdf, calculate_strike_probability
|
|
23
23
|
from voly.core.interpolate import interpolate_model
|
|
24
24
|
from voly.core.charts import (
|
|
@@ -295,18 +295,16 @@ class VolyClient:
|
|
|
295
295
|
@staticmethod
|
|
296
296
|
def fit_model(market_data: pd.DataFrame,
|
|
297
297
|
model_name: str = 'svi',
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
plot: bool = False) -> Dict[str, Any]:
|
|
298
|
+
initial_params: Optional[List[float]] = None,
|
|
299
|
+
param_bounds: Optional[Tuple] = None) -> Dict[str, Any]:
|
|
301
300
|
"""
|
|
302
301
|
Fit a volatility model to market data.
|
|
303
302
|
|
|
304
303
|
Parameters:
|
|
305
304
|
- market_data: DataFrame with market data
|
|
306
305
|
- model_name: Name of model to fit (default: 'svi')
|
|
307
|
-
-
|
|
308
|
-
-
|
|
309
|
-
- plot: Whether to generate and return plots
|
|
306
|
+
- initial_params: Optional initial parameters for optimization
|
|
307
|
+
- param_bounds: Optional parameter bounds for optimization
|
|
310
308
|
|
|
311
309
|
Returns:
|
|
312
310
|
- Dictionary with fitting results and optional plots
|
|
@@ -317,18 +315,38 @@ class VolyClient:
|
|
|
317
315
|
fit_results = fit_model(
|
|
318
316
|
market_data=market_data,
|
|
319
317
|
model_name=model_name,
|
|
320
|
-
|
|
321
|
-
|
|
318
|
+
initial_params=initial_params,
|
|
319
|
+
param_bounds=param_bounds
|
|
322
320
|
)
|
|
323
321
|
|
|
324
|
-
# Generate plots if requested
|
|
325
|
-
if plot:
|
|
326
|
-
logger.info("Generating model fitting plots")
|
|
327
|
-
plots = generate_all_plots(fit_results, market_data=market_data)
|
|
328
|
-
fit_results['plots'] = plots
|
|
329
|
-
|
|
330
322
|
return fit_results
|
|
331
323
|
|
|
324
|
+
@staticmethod
|
|
325
|
+
def get_surface(param_matrix: pd.DataFrame,
|
|
326
|
+
log_moneyness: Tuple[float, float, int] = (-2, 2, 500)) -> Dict[str, Any]:
|
|
327
|
+
"""
|
|
328
|
+
Generate implied volatility surface using optimized SVI parameters.
|
|
329
|
+
|
|
330
|
+
Parameters:
|
|
331
|
+
- param_matrix: Matrix of optimized SVI parameters from fit_results['raw_param_matrix']
|
|
332
|
+
- log_moneyness: Tuple of (min, max, num_points) for the moneyness grid
|
|
333
|
+
|
|
334
|
+
Returns:
|
|
335
|
+
- Dictionary with surface generation results
|
|
336
|
+
"""
|
|
337
|
+
logger.info("Generating implied volatility surface")
|
|
338
|
+
|
|
339
|
+
# Generate the surface
|
|
340
|
+
moneyness_grid, iv_surface = get_surface(
|
|
341
|
+
param_matrix=param_matrix,
|
|
342
|
+
log_moneyness=log_moneyness
|
|
343
|
+
)
|
|
344
|
+
|
|
345
|
+
return {
|
|
346
|
+
'moneyness_grid': moneyness_grid,
|
|
347
|
+
'iv_surface': iv_surface
|
|
348
|
+
}
|
|
349
|
+
|
|
332
350
|
# -------------------------------------------------------------------------
|
|
333
351
|
# Risk-Neutral Density (RND)
|
|
334
352
|
# -------------------------------------------------------------------------
|
|
@@ -174,7 +174,7 @@ async def get_deribit_data(currency: str = "BTC") -> pd.DataFrame:
|
|
|
174
174
|
raise ConnectionError(f"WebSocket connection error: {str(e)}")
|
|
175
175
|
|
|
176
176
|
total_time = time.time() - total_start
|
|
177
|
-
logger.info(f"Total
|
|
177
|
+
logger.info(f"Total fetching time: {total_time:.2f}s")
|
|
178
178
|
|
|
179
179
|
if not all_data:
|
|
180
180
|
raise VolyError("No data collected from Deribit")
|
|
@@ -195,7 +195,7 @@ def process_option_chain(df: pd.DataFrame, currency: str, min_dte: float = 2.0)
|
|
|
195
195
|
Returns:
|
|
196
196
|
pd.DataFrame: Processed option chain data
|
|
197
197
|
"""
|
|
198
|
-
logger.info(f"Processing
|
|
198
|
+
logger.info(f"Processing data for {currency}...")
|
|
199
199
|
|
|
200
200
|
# Extract instrument details
|
|
201
201
|
# Format is typically BTC-DDMMMYY-STRIKE-C/P or ETH-DDMMMYY-STRIKE-C/P
|
|
@@ -248,10 +248,7 @@ def process_option_chain(df: pd.DataFrame, currency: str, min_dte: float = 2.0)
|
|
|
248
248
|
# Calculate log-moneyness
|
|
249
249
|
df['log_moneyness'] = np.log(df['underlying_price'] / df['strike'])
|
|
250
250
|
|
|
251
|
-
|
|
252
|
-
original_rows = len(df)
|
|
253
|
-
df = df.dropna(subset=['mark_iv', 'log_moneyness', 'yte'])
|
|
254
|
-
logger.info(f"Removed {original_rows - len(df)} rows with missing data")
|
|
251
|
+
logger.info(f"Processing complete!")
|
|
255
252
|
|
|
256
253
|
return df
|
|
257
254
|
|
|
@@ -13,6 +13,9 @@ from sklearn.metrics import mean_squared_error, mean_absolute_error, r2_score
|
|
|
13
13
|
from voly.utils.logger import logger, catch_exception
|
|
14
14
|
from voly.exceptions import VolyError
|
|
15
15
|
from voly.models import SVIModel
|
|
16
|
+
import warnings
|
|
17
|
+
|
|
18
|
+
warnings.filterwarnings("ignore")
|
|
16
19
|
|
|
17
20
|
|
|
18
21
|
@catch_exception
|
|
@@ -48,11 +51,11 @@ def calculate_residuals(params: List[float],
|
|
|
48
51
|
|
|
49
52
|
|
|
50
53
|
@catch_exception
|
|
51
|
-
def
|
|
52
|
-
|
|
53
|
-
|
|
54
|
+
def fit_svi_parameters(market_data: pd.DataFrame,
|
|
55
|
+
initial_params: Optional[List[float]] = None,
|
|
56
|
+
param_bounds: Optional[Tuple] = None) -> Tuple[pd.DataFrame, Dict[str, Dict]]:
|
|
54
57
|
"""
|
|
55
|
-
|
|
58
|
+
Fit SVI parameters for all unique expiries in the market data.
|
|
56
59
|
|
|
57
60
|
Parameters:
|
|
58
61
|
- market_data: DataFrame with market data
|
|
@@ -60,11 +63,8 @@ def optimize_svi_parameters(market_data: pd.DataFrame,
|
|
|
60
63
|
- param_bounds: Bounds for parameters (default: from SVIModel)
|
|
61
64
|
|
|
62
65
|
Returns:
|
|
63
|
-
-
|
|
66
|
+
- Tuple of (fit_performance DataFrame, params_dict)
|
|
64
67
|
"""
|
|
65
|
-
results = {}
|
|
66
|
-
unique_expiries = sorted(market_data['yte'].unique())
|
|
67
|
-
|
|
68
68
|
# Use defaults if not provided
|
|
69
69
|
if initial_params is None:
|
|
70
70
|
initial_params = SVIModel.DEFAULT_INITIAL_PARAMS
|
|
@@ -72,37 +72,78 @@ def optimize_svi_parameters(market_data: pd.DataFrame,
|
|
|
72
72
|
if param_bounds is None:
|
|
73
73
|
param_bounds = SVIModel.DEFAULT_PARAM_BOUNDS
|
|
74
74
|
|
|
75
|
-
for
|
|
75
|
+
# Initialize data for fit performance
|
|
76
|
+
fit_data = {
|
|
77
|
+
'Maturity': [],
|
|
78
|
+
'DTE': [],
|
|
79
|
+
'YTE': [],
|
|
80
|
+
'Success': [],
|
|
81
|
+
'Cost': [],
|
|
82
|
+
'Optimality': [],
|
|
83
|
+
'RMSE': [],
|
|
84
|
+
'MAE': [],
|
|
85
|
+
'R²': [],
|
|
86
|
+
'Max Error': [],
|
|
87
|
+
'Number of Points': []
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
# Dictionary to store parameters
|
|
91
|
+
params_dict = {}
|
|
92
|
+
|
|
93
|
+
# Get unique expiries
|
|
94
|
+
unique_expiries = sorted(market_data['yte'].unique())
|
|
95
|
+
|
|
96
|
+
for yte in unique_expiries:
|
|
76
97
|
# Get maturity name for reporting
|
|
77
|
-
expiry_data = market_data[market_data['yte'] ==
|
|
98
|
+
expiry_data = market_data[market_data['yte'] == yte]
|
|
78
99
|
maturity_name = expiry_data['maturity_name'].iloc[0]
|
|
79
100
|
dte_value = expiry_data['dte'].iloc[0]
|
|
80
101
|
|
|
81
|
-
logger.info(f"Optimizing for {maturity_name} (DTE: {dte_value:.1f}
|
|
102
|
+
logger.info(f"Optimizing for {maturity_name} (DTE: {dte_value:.1f})...")
|
|
82
103
|
|
|
83
104
|
# Optimize SVI parameters
|
|
84
105
|
try:
|
|
85
106
|
result = least_squares(
|
|
86
107
|
calculate_residuals,
|
|
87
108
|
initial_params,
|
|
88
|
-
args=(
|
|
109
|
+
args=(yte, market_data, SVIModel),
|
|
89
110
|
bounds=param_bounds,
|
|
90
111
|
max_nfev=1000
|
|
91
112
|
)
|
|
92
113
|
except Exception as e:
|
|
93
114
|
raise VolyError(f"Optimization failed for {maturity_name}: {str(e)}")
|
|
94
115
|
|
|
95
|
-
#
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
'
|
|
99
|
-
'cost': result.cost,
|
|
100
|
-
'optimality': result.optimality,
|
|
101
|
-
'message': result.message,
|
|
102
|
-
'yte': t_dte,
|
|
116
|
+
# Get parameters
|
|
117
|
+
params = result.x
|
|
118
|
+
params_dict[maturity_name] = {
|
|
119
|
+
'params': params,
|
|
103
120
|
'dte': dte_value
|
|
104
121
|
}
|
|
105
122
|
|
|
123
|
+
# Calculate model predictions for statistics
|
|
124
|
+
w_svi = np.array([SVIModel.svi(x, *params) for x in expiry_data['log_moneyness']])
|
|
125
|
+
iv_model = np.sqrt(w_svi / yte)
|
|
126
|
+
iv_market = expiry_data['mark_iv'].values
|
|
127
|
+
|
|
128
|
+
# Calculate statistics
|
|
129
|
+
rmse = np.sqrt(mean_squared_error(iv_market, iv_model))
|
|
130
|
+
mae = mean_absolute_error(iv_market, iv_model)
|
|
131
|
+
r2 = r2_score(iv_market, iv_model)
|
|
132
|
+
max_error = np.max(np.abs(iv_market - iv_model))
|
|
133
|
+
num_points = len(expiry_data)
|
|
134
|
+
|
|
135
|
+
# Add to fit data
|
|
136
|
+
fit_data['Maturity'].append(maturity_name)
|
|
137
|
+
fit_data['DTE'].append(dte_value)
|
|
138
|
+
fit_data['Success'].append(result.success)
|
|
139
|
+
fit_data['Cost'].append(result.cost)
|
|
140
|
+
fit_data['Optimality'].append(result.optimality)
|
|
141
|
+
fit_data['RMSE'].append(rmse)
|
|
142
|
+
fit_data['MAE'].append(mae)
|
|
143
|
+
fit_data['R²'].append(r2)
|
|
144
|
+
fit_data['Max Error'].append(max_error)
|
|
145
|
+
fit_data['Number of Points'].append(num_points)
|
|
146
|
+
|
|
106
147
|
if result.success:
|
|
107
148
|
logger.info(f'Optimization for {maturity_name} (DTE: {dte_value:.1f}): SUCCESS')
|
|
108
149
|
else:
|
|
@@ -110,17 +151,20 @@ def optimize_svi_parameters(market_data: pd.DataFrame,
|
|
|
110
151
|
|
|
111
152
|
logger.info('------------------------------------------')
|
|
112
153
|
|
|
113
|
-
|
|
154
|
+
# Create DataFrame with all fit performance data
|
|
155
|
+
fit_performance = pd.DataFrame(fit_data)
|
|
156
|
+
|
|
157
|
+
return fit_performance, params_dict
|
|
114
158
|
|
|
115
159
|
|
|
116
160
|
@catch_exception
|
|
117
|
-
def create_parameters_matrix(
|
|
161
|
+
def create_parameters_matrix(params_dict: Dict[str, Dict]) -> Tuple[pd.DataFrame, pd.DataFrame]:
|
|
118
162
|
"""
|
|
119
163
|
Create matrices of optimized parameters for each expiry.
|
|
120
164
|
Uses maturity names as column names.
|
|
121
165
|
|
|
122
166
|
Parameters:
|
|
123
|
-
-
|
|
167
|
+
- params_dict: Dictionary of parameter results by maturity name
|
|
124
168
|
|
|
125
169
|
Returns:
|
|
126
170
|
- Tuple of DataFrames with optimized parameters:
|
|
@@ -128,8 +172,8 @@ def create_parameters_matrix(optimization_results: Dict[str, Dict[str, Any]]) ->
|
|
|
128
172
|
2. Jump-Wing parameters (nu, psi, p, c, nu_tilde)
|
|
129
173
|
"""
|
|
130
174
|
# Get maturity names in order by DTE
|
|
131
|
-
maturity_names = sorted(
|
|
132
|
-
key=lambda x:
|
|
175
|
+
maturity_names = sorted(params_dict.keys(),
|
|
176
|
+
key=lambda x: params_dict[x]['dte'])
|
|
133
177
|
|
|
134
178
|
# Create DataFrame for raw parameters with maturity names as columns
|
|
135
179
|
raw_param_matrix = pd.DataFrame(
|
|
@@ -149,7 +193,7 @@ def create_parameters_matrix(optimization_results: Dict[str, Dict[str, Any]]) ->
|
|
|
149
193
|
|
|
150
194
|
# Fill the matrices with optimized parameters
|
|
151
195
|
for maturity_name in maturity_names:
|
|
152
|
-
result =
|
|
196
|
+
result = params_dict[maturity_name]
|
|
153
197
|
|
|
154
198
|
# Extract raw SVI parameters
|
|
155
199
|
a, b, sigma, rho, m = result['params']
|
|
@@ -161,7 +205,7 @@ def create_parameters_matrix(optimization_results: Dict[str, Dict[str, Any]]) ->
|
|
|
161
205
|
dte_values[maturity_name] = result['dte']
|
|
162
206
|
|
|
163
207
|
# Calculate JW parameters
|
|
164
|
-
nu, psi, p, c, nu_tilde = SVIModel.
|
|
208
|
+
nu, psi, p, c, nu_tilde = SVIModel.raw_to_jw_params(a, b, sigma, rho, m, yte)
|
|
165
209
|
jw_param_matrix[maturity_name] = [nu, psi, p, c, nu_tilde]
|
|
166
210
|
|
|
167
211
|
# Store YTE and DTE as attributes in all DataFrames for reference
|
|
@@ -177,155 +221,71 @@ def create_parameters_matrix(optimization_results: Dict[str, Dict[str, Any]]) ->
|
|
|
177
221
|
|
|
178
222
|
|
|
179
223
|
@catch_exception
|
|
180
|
-
def
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
) -> Tuple[np.ndarray, Dict[float, np.ndarray]]:
|
|
185
|
-
"""
|
|
186
|
-
Generate implied volatility surface using optimized SVI parameters.
|
|
187
|
-
|
|
188
|
-
Parameters:
|
|
189
|
-
- param_matrix: Matrix of optimized SVI parameters with maturity names as columns
|
|
190
|
-
- moneyness_range: (min, max) range for moneyness grid
|
|
191
|
-
- num_points: Number of points for moneyness grid
|
|
192
|
-
|
|
193
|
-
Returns:
|
|
194
|
-
- Moneyness grid and implied volatility surface
|
|
195
|
-
"""
|
|
196
|
-
# Generate moneyness grid
|
|
197
|
-
min_m, max_m = moneyness_range
|
|
198
|
-
moneyness_values = np.linspace(min_m, max_m, num=num_points)
|
|
199
|
-
implied_volatility_surface = {}
|
|
200
|
-
|
|
201
|
-
# Get YTE values from the parameter matrix attributes
|
|
202
|
-
yte_values = param_matrix.attrs['yte_values']
|
|
203
|
-
|
|
204
|
-
# Generate implied volatility for each expiry
|
|
205
|
-
for maturity_name, yte in yte_values.items():
|
|
206
|
-
svi_params = param_matrix[maturity_name].values
|
|
207
|
-
w_svi = [SVIModel.svi(x, *svi_params) for x in moneyness_values]
|
|
208
|
-
implied_volatility_surface[yte] = np.sqrt(np.array(w_svi) / yte)
|
|
209
|
-
|
|
210
|
-
return moneyness_values, implied_volatility_surface
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
@catch_exception
|
|
214
|
-
def calculate_fit_statistics(market_data: pd.DataFrame, param_matrix: pd.DataFrame) -> pd.DataFrame:
|
|
224
|
+
def fit_model(market_data: pd.DataFrame,
|
|
225
|
+
model_name: str = 'svi',
|
|
226
|
+
initial_params: Optional[List[float]] = None,
|
|
227
|
+
param_bounds: Optional[Tuple] = None) -> Dict[str, Any]:
|
|
215
228
|
"""
|
|
216
|
-
|
|
229
|
+
Fit a volatility model to market data.
|
|
217
230
|
|
|
218
231
|
Parameters:
|
|
219
232
|
- market_data: DataFrame with market data
|
|
220
|
-
-
|
|
233
|
+
- model_name: Type of model to fit (default: 'svi')
|
|
234
|
+
- initial_params: Optional initial parameters for optimization (default: model's defaults)
|
|
235
|
+
- param_bounds: Optional parameter bounds for optimization (default: model's defaults)
|
|
221
236
|
|
|
222
237
|
Returns:
|
|
223
|
-
-
|
|
238
|
+
- Dictionary with fitting results
|
|
224
239
|
"""
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
dte_values = param_matrix.attrs['dte_values']
|
|
228
|
-
|
|
229
|
-
# Initialize lists for statistics
|
|
230
|
-
maturity_name_list = []
|
|
231
|
-
dte_list = []
|
|
232
|
-
yte_list = []
|
|
233
|
-
rmse_list = []
|
|
234
|
-
mae_list = []
|
|
235
|
-
r2_list = []
|
|
236
|
-
max_error_list = []
|
|
237
|
-
num_points_list = []
|
|
238
|
-
|
|
239
|
-
# Calculate statistics for each expiry
|
|
240
|
-
for maturity_name, yte in yte_values.items():
|
|
241
|
-
# Filter market data for the specific expiry
|
|
242
|
-
expiry_data = market_data[market_data['yte'] == yte]
|
|
243
|
-
dte_value = dte_values[maturity_name]
|
|
244
|
-
|
|
245
|
-
# Calculate SVI model predictions
|
|
246
|
-
svi_params = param_matrix[maturity_name].values
|
|
247
|
-
w_svi = np.array([SVIModel.svi(x, *svi_params) for x in expiry_data['log_moneyness']])
|
|
248
|
-
iv_model = np.sqrt(w_svi / yte)
|
|
240
|
+
if model_name.lower() != 'svi':
|
|
241
|
+
raise VolyError(f"Model type '{model_name}' is not supported. Currently only 'svi' is available.")
|
|
249
242
|
|
|
250
|
-
|
|
251
|
-
|
|
243
|
+
# Step 1: Fit model parameters and get performance metrics in one step
|
|
244
|
+
fit_performance, params_dict = fit_svi_parameters(
|
|
245
|
+
market_data,
|
|
246
|
+
initial_params=initial_params,
|
|
247
|
+
param_bounds=param_bounds
|
|
248
|
+
)
|
|
252
249
|
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
mae = mean_absolute_error(iv_market, iv_model)
|
|
256
|
-
r2 = r2_score(iv_market, iv_model)
|
|
257
|
-
max_error = np.max(np.abs(iv_market - iv_model))
|
|
258
|
-
num_points = len(expiry_data)
|
|
250
|
+
# Step 2: Create parameter matrices
|
|
251
|
+
raw_param_matrix, jw_param_matrix = create_parameters_matrix(params_dict)
|
|
259
252
|
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
mae_list.append(mae)
|
|
266
|
-
r2_list.append(r2)
|
|
267
|
-
max_error_list.append(max_error)
|
|
268
|
-
num_points_list.append(num_points)
|
|
269
|
-
|
|
270
|
-
# Create DataFrame with statistics
|
|
271
|
-
stats_df = pd.DataFrame({
|
|
272
|
-
'Maturity': maturity_name_list,
|
|
273
|
-
'DTE': dte_list,
|
|
274
|
-
'YTE': yte_list,
|
|
275
|
-
'RMSE': rmse_list,
|
|
276
|
-
'MAE': mae_list,
|
|
277
|
-
'R²': r2_list,
|
|
278
|
-
'Max Error': max_error_list,
|
|
279
|
-
'Number of Points': num_points_list
|
|
280
|
-
})
|
|
281
|
-
|
|
282
|
-
return stats_df
|
|
253
|
+
return {
|
|
254
|
+
'raw_param_matrix': raw_param_matrix,
|
|
255
|
+
'jw_param_matrix': jw_param_matrix,
|
|
256
|
+
'fit_performance': fit_performance,
|
|
257
|
+
}
|
|
283
258
|
|
|
284
259
|
|
|
285
260
|
@catch_exception
|
|
286
|
-
def
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
261
|
+
def get_surface(
|
|
262
|
+
param_matrix: pd.DataFrame,
|
|
263
|
+
log_moneyness: Tuple[float, float, int] = (-2, 2, 500)
|
|
264
|
+
) -> Tuple[np.ndarray, Dict[float, np.ndarray], np.ndarray]:
|
|
290
265
|
"""
|
|
291
|
-
|
|
266
|
+
Generate implied volatility surface using optimized SVI parameters.
|
|
292
267
|
|
|
293
268
|
Parameters:
|
|
294
|
-
-
|
|
295
|
-
-
|
|
296
|
-
- moneyness_range: (min, max) range for moneyness grid
|
|
297
|
-
- num_points: Number of points for moneyness grid
|
|
269
|
+
- param_matrix: Matrix of optimized SVI parameters from fit_results
|
|
270
|
+
- log_moneyness: Tuple of (min, max, num_points) for the moneyness grid
|
|
298
271
|
|
|
299
272
|
Returns:
|
|
300
|
-
-
|
|
273
|
+
- Tuple of (moneyness_grid, iv_surface, unique_expiries)
|
|
301
274
|
"""
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
# Step 1: Optimize model parameters
|
|
306
|
-
optimization_results = optimize_svi_parameters(market_data)
|
|
275
|
+
# Extract moneyness parameters
|
|
276
|
+
min_m, max_m, num_points = log_moneyness
|
|
307
277
|
|
|
308
|
-
#
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
# Step 3: Generate implied volatility surface
|
|
312
|
-
moneyness_grid, iv_surface = generate_implied_volatility_surface(
|
|
313
|
-
raw_param_matrix, moneyness_range, num_points
|
|
314
|
-
)
|
|
278
|
+
# Generate moneyness grid
|
|
279
|
+
moneyness_values = np.linspace(min_m, max_m, num=num_points)
|
|
280
|
+
implied_volatility_surface = {}
|
|
315
281
|
|
|
316
|
-
#
|
|
317
|
-
|
|
282
|
+
# Get YTE values from the parameter matrix attributes
|
|
283
|
+
yte_values = param_matrix.attrs['yte_values']
|
|
318
284
|
|
|
319
|
-
#
|
|
320
|
-
|
|
285
|
+
# Generate implied volatility for each expiry
|
|
286
|
+
for maturity_name, yte in yte_values.items():
|
|
287
|
+
svi_params = param_matrix[maturity_name].values
|
|
288
|
+
w_svi = [SVIModel.svi(x, *svi_params) for x in moneyness_values]
|
|
289
|
+
implied_volatility_surface[yte] = np.sqrt(np.array(w_svi) / yte)
|
|
321
290
|
|
|
322
|
-
|
|
323
|
-
return {
|
|
324
|
-
'optimization_results': optimization_results,
|
|
325
|
-
'raw_param_matrix': raw_param_matrix,
|
|
326
|
-
'jw_param_matrix': jw_param_matrix,
|
|
327
|
-
'moneyness_grid': moneyness_grid,
|
|
328
|
-
'iv_surface': iv_surface,
|
|
329
|
-
'stats_df': stats_df,
|
|
330
|
-
'unique_expiries': unique_expiries_years,
|
|
331
|
-
}
|
|
291
|
+
return moneyness_values, implied_volatility_surface
|
|
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
|