voly 0.0.12__py3-none-any.whl → 0.0.14__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 CHANGED
@@ -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
- moneyness_range: Tuple[float, float] = (-2, 2),
299
- num_points: int = 500,
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
- - moneyness_range: (min, max) range for moneyness grid
308
- - num_points: Number of points for moneyness grid
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
- moneyness_range=moneyness_range,
321
- num_points=num_points
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
  # -------------------------------------------------------------------------
voly/core/fit.py CHANGED
@@ -14,6 +14,7 @@ from voly.utils.logger import logger, catch_exception
14
14
  from voly.exceptions import VolyError
15
15
  from voly.models import SVIModel
16
16
  import warnings
17
+
17
18
  warnings.filterwarnings("ignore")
18
19
 
19
20
 
@@ -50,11 +51,11 @@ def calculate_residuals(params: List[float],
50
51
 
51
52
 
52
53
  @catch_exception
53
- def optimize_svi_parameters(market_data: pd.DataFrame,
54
- initial_params: Optional[List[float]] = None,
55
- param_bounds: Optional[Tuple] = None) -> Dict[str, Dict[str, Any]]:
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]]:
56
57
  """
57
- Optimize SVI parameters for all unique expiries in the market data.
58
+ Fit SVI parameters for all unique expiries in the market data.
58
59
 
59
60
  Parameters:
60
61
  - market_data: DataFrame with market data
@@ -62,11 +63,8 @@ def optimize_svi_parameters(market_data: pd.DataFrame,
62
63
  - param_bounds: Bounds for parameters (default: from SVIModel)
63
64
 
64
65
  Returns:
65
- - Dictionary of optimization results by maturity name
66
+ - Tuple of (fit_performance DataFrame, params_dict)
66
67
  """
67
- results = {}
68
- unique_expiries = sorted(market_data['yte'].unique())
69
-
70
68
  # Use defaults if not provided
71
69
  if initial_params is None:
72
70
  initial_params = SVIModel.DEFAULT_INITIAL_PARAMS
@@ -74,37 +72,77 @@ def optimize_svi_parameters(market_data: pd.DataFrame,
74
72
  if param_bounds is None:
75
73
  param_bounds = SVIModel.DEFAULT_PARAM_BOUNDS
76
74
 
77
- for t_dte in unique_expiries:
75
+ # Initialize data for fit performance
76
+ fit_data = {
77
+ 'Maturity': [],
78
+ 'DTE': [],
79
+ 'Success': [],
80
+ 'Cost': [],
81
+ 'Optimality': [],
82
+ 'RMSE': [],
83
+ 'MAE': [],
84
+ 'R²': [],
85
+ 'Max Error': [],
86
+ 'Number of Points': []
87
+ }
88
+
89
+ # Dictionary to store parameters
90
+ params_dict = {}
91
+
92
+ # Get unique expiries
93
+ unique_expiries = sorted(market_data['yte'].unique())
94
+
95
+ for yte in unique_expiries:
78
96
  # Get maturity name for reporting
79
- expiry_data = market_data[market_data['yte'] == t_dte]
97
+ expiry_data = market_data[market_data['yte'] == yte]
80
98
  maturity_name = expiry_data['maturity_name'].iloc[0]
81
99
  dte_value = expiry_data['dte'].iloc[0]
82
100
 
83
- logger.info(f"Optimizing for {maturity_name} (DTE: {dte_value:.1f}, YTE: {t_dte:.4f})...")
101
+ logger.info(f"Optimizing for {maturity_name} (DTE: {dte_value:.1f})...")
84
102
 
85
103
  # Optimize SVI parameters
86
104
  try:
87
105
  result = least_squares(
88
106
  calculate_residuals,
89
107
  initial_params,
90
- args=(t_dte, market_data, SVIModel),
108
+ args=(yte, market_data, SVIModel),
91
109
  bounds=param_bounds,
92
110
  max_nfev=1000
93
111
  )
94
112
  except Exception as e:
95
113
  raise VolyError(f"Optimization failed for {maturity_name}: {str(e)}")
96
114
 
97
- # Store results with maturity name as key
98
- results[maturity_name] = {
99
- 'params': result.x,
100
- 'success': result.success,
101
- 'cost': result.cost,
102
- 'optimality': result.optimality,
103
- 'message': result.message,
104
- 'yte': t_dte,
115
+ # Get parameters
116
+ params = result.x
117
+ params_dict[maturity_name] = {
118
+ 'params': params,
105
119
  'dte': dte_value
106
120
  }
107
121
 
122
+ # Calculate model predictions for statistics
123
+ w_svi = np.array([SVIModel.svi(x, *params) for x in expiry_data['log_moneyness']])
124
+ iv_model = np.sqrt(w_svi / yte)
125
+ iv_market = expiry_data['mark_iv'].values
126
+
127
+ # Calculate statistics
128
+ rmse = np.sqrt(mean_squared_error(iv_market, iv_model))
129
+ mae = mean_absolute_error(iv_market, iv_model)
130
+ r2 = r2_score(iv_market, iv_model)
131
+ max_error = np.max(np.abs(iv_market - iv_model))
132
+ num_points = len(expiry_data)
133
+
134
+ # Add to fit data
135
+ fit_data['Maturity'].append(maturity_name)
136
+ fit_data['DTE'].append(dte_value)
137
+ fit_data['Success'].append(result.success)
138
+ fit_data['Cost'].append(result.cost)
139
+ fit_data['Optimality'].append(result.optimality)
140
+ fit_data['RMSE'].append(rmse)
141
+ fit_data['MAE'].append(mae)
142
+ fit_data['R²'].append(r2)
143
+ fit_data['Max Error'].append(max_error)
144
+ fit_data['Number of Points'].append(num_points)
145
+
108
146
  if result.success:
109
147
  logger.info(f'Optimization for {maturity_name} (DTE: {dte_value:.1f}): SUCCESS')
110
148
  else:
@@ -112,17 +150,20 @@ def optimize_svi_parameters(market_data: pd.DataFrame,
112
150
 
113
151
  logger.info('------------------------------------------')
114
152
 
115
- return results
153
+ # Create DataFrame with all fit performance data
154
+ fit_performance = pd.DataFrame(fit_data)
155
+
156
+ return fit_performance, params_dict
116
157
 
117
158
 
118
159
  @catch_exception
119
- def create_parameters_matrix(optimization_results: Dict[str, Dict[str, Any]]) -> Tuple[pd.DataFrame, pd.DataFrame]:
160
+ def create_parameters_matrix(params_dict: Dict[str, Dict]) -> Tuple[pd.DataFrame, pd.DataFrame]:
120
161
  """
121
162
  Create matrices of optimized parameters for each expiry.
122
163
  Uses maturity names as column names.
123
164
 
124
165
  Parameters:
125
- - optimization_results: Dictionary of optimization results by maturity name
166
+ - params_dict: Dictionary of parameter results by maturity name
126
167
 
127
168
  Returns:
128
169
  - Tuple of DataFrames with optimized parameters:
@@ -130,8 +171,8 @@ def create_parameters_matrix(optimization_results: Dict[str, Dict[str, Any]]) ->
130
171
  2. Jump-Wing parameters (nu, psi, p, c, nu_tilde)
131
172
  """
132
173
  # Get maturity names in order by DTE
133
- maturity_names = sorted(optimization_results.keys(),
134
- key=lambda x: optimization_results[x]['dte'])
174
+ maturity_names = sorted(params_dict.keys(),
175
+ key=lambda x: params_dict[x]['dte'])
135
176
 
136
177
  # Create DataFrame for raw parameters with maturity names as columns
137
178
  raw_param_matrix = pd.DataFrame(
@@ -151,7 +192,7 @@ def create_parameters_matrix(optimization_results: Dict[str, Dict[str, Any]]) ->
151
192
 
152
193
  # Fill the matrices with optimized parameters
153
194
  for maturity_name in maturity_names:
154
- result = optimization_results[maturity_name]
195
+ result = params_dict[maturity_name]
155
196
 
156
197
  # Extract raw SVI parameters
157
198
  a, b, sigma, rho, m = result['params']
@@ -179,155 +220,71 @@ def create_parameters_matrix(optimization_results: Dict[str, Dict[str, Any]]) ->
179
220
 
180
221
 
181
222
  @catch_exception
182
- def generate_implied_volatility_surface(
183
- param_matrix: pd.DataFrame,
184
- moneyness_range: Tuple[float, float] = (-2, 2),
185
- num_points: int = 500
186
- ) -> Tuple[np.ndarray, Dict[float, np.ndarray]]:
187
- """
188
- Generate implied volatility surface using optimized SVI parameters.
189
-
190
- Parameters:
191
- - param_matrix: Matrix of optimized SVI parameters with maturity names as columns
192
- - moneyness_range: (min, max) range for moneyness grid
193
- - num_points: Number of points for moneyness grid
194
-
195
- Returns:
196
- - Moneyness grid and implied volatility surface
197
- """
198
- # Generate moneyness grid
199
- min_m, max_m = moneyness_range
200
- moneyness_values = np.linspace(min_m, max_m, num=num_points)
201
- implied_volatility_surface = {}
202
-
203
- # Get YTE values from the parameter matrix attributes
204
- yte_values = param_matrix.attrs['yte_values']
205
-
206
- # Generate implied volatility for each expiry
207
- for maturity_name, yte in yte_values.items():
208
- svi_params = param_matrix[maturity_name].values
209
- w_svi = [SVIModel.svi(x, *svi_params) for x in moneyness_values]
210
- implied_volatility_surface[yte] = np.sqrt(np.array(w_svi) / yte)
211
-
212
- return moneyness_values, implied_volatility_surface
213
-
214
-
215
- @catch_exception
216
- def calculate_fit_statistics(market_data: pd.DataFrame, param_matrix: pd.DataFrame) -> pd.DataFrame:
223
+ def fit_model(market_data: pd.DataFrame,
224
+ model_name: str = 'svi',
225
+ initial_params: Optional[List[float]] = None,
226
+ param_bounds: Optional[Tuple] = None) -> Dict[str, Any]:
217
227
  """
218
- Calculate fitting accuracy statistics for each expiry.
228
+ Fit a volatility model to market data.
219
229
 
220
230
  Parameters:
221
231
  - market_data: DataFrame with market data
222
- - param_matrix: Matrix of optimized SVI parameters with maturity names as columns
232
+ - model_name: Type of model to fit (default: 'svi')
233
+ - initial_params: Optional initial parameters for optimization (default: model's defaults)
234
+ - param_bounds: Optional parameter bounds for optimization (default: model's defaults)
223
235
 
224
236
  Returns:
225
- - DataFrame with fitting statistics
237
+ - Dictionary with fitting results
226
238
  """
227
- # Get YTE values from the parameter matrix attributes
228
- yte_values = param_matrix.attrs['yte_values']
229
- dte_values = param_matrix.attrs['dte_values']
230
-
231
- # Initialize lists for statistics
232
- maturity_name_list = []
233
- dte_list = []
234
- yte_list = []
235
- rmse_list = []
236
- mae_list = []
237
- r2_list = []
238
- max_error_list = []
239
- num_points_list = []
240
-
241
- # Calculate statistics for each expiry
242
- for maturity_name, yte in yte_values.items():
243
- # Filter market data for the specific expiry
244
- expiry_data = market_data[market_data['yte'] == yte]
245
- dte_value = dte_values[maturity_name]
246
-
247
- # Calculate SVI model predictions
248
- svi_params = param_matrix[maturity_name].values
249
- w_svi = np.array([SVIModel.svi(x, *svi_params) for x in expiry_data['log_moneyness']])
250
- iv_model = np.sqrt(w_svi / yte)
239
+ if model_name.lower() != 'svi':
240
+ raise VolyError(f"Model type '{model_name}' is not supported. Currently only 'svi' is available.")
251
241
 
252
- # Get actual market implied volatilities
253
- iv_market = expiry_data['mark_iv'].values
242
+ # Step 1: Fit model parameters and get performance metrics in one step
243
+ fit_performance, params_dict = fit_svi_parameters(
244
+ market_data,
245
+ initial_params=initial_params,
246
+ param_bounds=param_bounds
247
+ )
254
248
 
255
- # Calculate statistics
256
- rmse = np.sqrt(mean_squared_error(iv_market, iv_model))
257
- mae = mean_absolute_error(iv_market, iv_model)
258
- r2 = r2_score(iv_market, iv_model)
259
- max_error = np.max(np.abs(iv_market - iv_model))
260
- num_points = len(expiry_data)
249
+ # Step 2: Create parameter matrices
250
+ raw_param_matrix, jw_param_matrix = create_parameters_matrix(params_dict)
261
251
 
262
- # Append to lists
263
- maturity_name_list.append(maturity_name)
264
- dte_list.append(dte_value)
265
- yte_list.append(yte)
266
- rmse_list.append(rmse)
267
- mae_list.append(mae)
268
- r2_list.append(r2)
269
- max_error_list.append(max_error)
270
- num_points_list.append(num_points)
271
-
272
- # Create DataFrame with statistics
273
- stats_df = pd.DataFrame({
274
- 'Maturity': maturity_name_list,
275
- 'DTE': dte_list,
276
- 'YTE': yte_list,
277
- 'RMSE': rmse_list,
278
- 'MAE': mae_list,
279
- 'R²': r2_list,
280
- 'Max Error': max_error_list,
281
- 'Number of Points': num_points_list
282
- })
283
-
284
- return stats_df
252
+ return {
253
+ 'raw_param_matrix': raw_param_matrix,
254
+ 'jw_param_matrix': jw_param_matrix,
255
+ 'fit_performance': fit_performance,
256
+ }
285
257
 
286
258
 
287
259
  @catch_exception
288
- def fit_model(market_data: pd.DataFrame,
289
- model_name: str = 'svi',
290
- moneyness_range: Tuple[float, float] = (-2, 2),
291
- num_points: int = 500) -> Dict[str, Any]:
260
+ def get_surface(
261
+ param_matrix: pd.DataFrame,
262
+ log_moneyness: Tuple[float, float, int] = (-2, 2, 500)
263
+ ) -> Tuple[np.ndarray, Dict[float, np.ndarray], np.ndarray]:
292
264
  """
293
- Fit a volatility model to market data.
265
+ Generate implied volatility surface using optimized SVI parameters.
294
266
 
295
267
  Parameters:
296
- - market_data: DataFrame with market data
297
- - model_name: Type of model to fit (default: 'svi')
298
- - moneyness_range: (min, max) range for moneyness grid
299
- - num_points: Number of points for moneyness grid
268
+ - param_matrix: Matrix of optimized SVI parameters from fit_results
269
+ - log_moneyness: Tuple of (min, max, num_points) for the moneyness grid
300
270
 
301
271
  Returns:
302
- - Dictionary with fitting results
272
+ - Tuple of (moneyness_grid, iv_surface, unique_expiries)
303
273
  """
304
- if model_name.lower() != 'svi':
305
- raise VolyError(f"Model type '{model_name}' is not supported. Currently only 'svi' is available.")
306
-
307
- # Step 1: Optimize model parameters
308
- optimization_results = optimize_svi_parameters(market_data)
274
+ # Extract moneyness parameters
275
+ min_m, max_m, num_points = log_moneyness
309
276
 
310
- # Step 2: Create parameter matrices
311
- raw_param_matrix, jw_param_matrix = create_parameters_matrix(optimization_results)
312
-
313
- # Step 3: Generate implied volatility surface
314
- moneyness_grid, iv_surface = generate_implied_volatility_surface(
315
- raw_param_matrix, moneyness_range, num_points
316
- )
277
+ # Generate moneyness grid
278
+ moneyness_values = np.linspace(min_m, max_m, num=num_points)
279
+ implied_volatility_surface = {}
317
280
 
318
- # Step 4: Calculate fitting statistics
319
- stats_df = calculate_fit_statistics(market_data, raw_param_matrix)
281
+ # Get YTE values from the parameter matrix attributes
282
+ yte_values = param_matrix.attrs['yte_values']
320
283
 
321
- # Step 5: Get unique expiries in sorted order (in years)
322
- unique_expiries_years = np.array(sorted(market_data['yte'].unique()))
284
+ # Generate implied volatility for each expiry
285
+ for maturity_name, yte in yte_values.items():
286
+ svi_params = param_matrix[maturity_name].values
287
+ w_svi = [SVIModel.svi(x, *svi_params) for x in moneyness_values]
288
+ implied_volatility_surface[yte] = np.sqrt(np.array(w_svi) / yte)
323
289
 
324
- # Return all results in a dictionary
325
- return {
326
- 'optimization_results': optimization_results,
327
- 'raw_param_matrix': raw_param_matrix,
328
- 'jw_param_matrix': jw_param_matrix,
329
- 'moneyness_grid': moneyness_grid,
330
- 'iv_surface': iv_surface,
331
- 'stats_df': stats_df,
332
- 'unique_expiries': unique_expiries_years,
333
- }
290
+ return moneyness_values, implied_volatility_surface
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: voly
3
- Version: 0.0.12
3
+ Version: 0.0.14
4
4
  Summary: Options & volatility research package
5
5
  Author-email: Manu de Cara <manu.de.cara@gmail.com>
6
6
  License: MIT
@@ -1,18 +1,18 @@
1
1
  voly/__init__.py,sha256=8xyDk7rFCn_MOD5hxuv5cxxKZvBVRiSIM7TgaMPpwpw,211
2
- voly/client.py,sha256=i7wPy02L1y21giYUVAQTWkCuNAwykCUAWlJ-D1pA4YM,17493
2
+ voly/client.py,sha256=ueQnorQLNk71p_1XymuNFZvWZILxXygRnvTNeU927mg,18088
3
3
  voly/exceptions.py,sha256=PBsbn1vNMvKcCJwwJ4lBO6glD85jo1h2qiEmD7ArAjs,92
4
4
  voly/formulas.py,sha256=aG_HSq_a4j7TcuKiINlHSpmNdmfZa_fzYbAk8EGt954,7427
5
5
  voly/models.py,sha256=YJ12aamLz_-aOni4Qm0_XV9u4bjKK3vfJz0J2gc1h0o,3565
6
6
  voly/core/__init__.py,sha256=GU6l7hpxJfitPx9jnmBtcb_QIeqOO8liZsSbLXXSbq8,384
7
7
  voly/core/charts.py,sha256=GF55IS-aZfcc_0yoSPRPIPBPcJhFD1El18wNCo_mI_A,29918
8
8
  voly/core/data.py,sha256=Dfk-ByHpdteUiLXr0p-wRLr3VAmdyjdDBKwjwdTgCjA,9939
9
- voly/core/fit.py,sha256=pCP1WL1zlMEgJw5aZuPYGGIfH2F9GD8QsN_oj6ZLeHo,11333
9
+ voly/core/fit.py,sha256=pqHZVPKZhDYWR-SglD866W-PneXuOiwvn-DxGytq93c,9790
10
10
  voly/core/interpolate.py,sha256=ztVIePJZOh-CIbn69wkh1JW2rKywNe2FEewRN0zcSAo,8185
11
11
  voly/core/rnd.py,sha256=-xBVzvM9sMIBtfOfWyBJKtiVcBShSGTNNp2PZFOD5j0,12155
12
12
  voly/utils/__init__.py,sha256=E05mWatyC-PDOsCxQV1p5Xi1IgpOomxrNURyCx_gB-w,200
13
13
  voly/utils/logger.py,sha256=4-_2bVJmq17Q0d7Rd2mPg1AeR8gxv6EPvcmBDMFWcSM,1744
14
- voly-0.0.12.dist-info/LICENSE,sha256=wcHIVbE12jfcBOai_wqBKY6xvNQU5E909xL1zZNq_2Q,1065
15
- voly-0.0.12.dist-info/METADATA,sha256=GxT2PrLToCnIj-pA5K030JjzQZFMkPUH1kN_Y-3cdIs,4092
16
- voly-0.0.12.dist-info/WHEEL,sha256=52BFRY2Up02UkjOa29eZOS2VxUrpPORXg1pkohGGUS8,91
17
- voly-0.0.12.dist-info/top_level.txt,sha256=ZfLw2sSxF-LrKAkgGjOmeTcw6_gD-30zvtdEY5W4B7c,5
18
- voly-0.0.12.dist-info/RECORD,,
14
+ voly-0.0.14.dist-info/LICENSE,sha256=wcHIVbE12jfcBOai_wqBKY6xvNQU5E909xL1zZNq_2Q,1065
15
+ voly-0.0.14.dist-info/METADATA,sha256=N7DYJ9IWB7df1Lf3-Uuodlw4Ls-ar448dCHTP-INxjg,4092
16
+ voly-0.0.14.dist-info/WHEEL,sha256=52BFRY2Up02UkjOa29eZOS2VxUrpPORXg1pkohGGUS8,91
17
+ voly-0.0.14.dist-info/top_level.txt,sha256=ZfLw2sSxF-LrKAkgGjOmeTcw6_gD-30zvtdEY5W4B7c,5
18
+ voly-0.0.14.dist-info/RECORD,,
File without changes
File without changes