voly 0.0.155__tar.gz → 0.0.157__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.
Files changed (26) hide show
  1. {voly-0.0.155/src/voly.egg-info → voly-0.0.157}/PKG-INFO +1 -1
  2. {voly-0.0.155 → voly-0.0.157}/pyproject.toml +2 -2
  3. {voly-0.0.155 → voly-0.0.157}/src/voly/client.py +15 -7
  4. {voly-0.0.155 → voly-0.0.157}/src/voly/core/hd.py +217 -20
  5. {voly-0.0.155 → voly-0.0.157}/src/voly/core/rnd.py +16 -2
  6. {voly-0.0.155 → voly-0.0.157}/src/voly/utils/density.py +50 -1
  7. {voly-0.0.155 → voly-0.0.157/src/voly.egg-info}/PKG-INFO +1 -1
  8. {voly-0.0.155 → voly-0.0.157}/LICENSE +0 -0
  9. {voly-0.0.155 → voly-0.0.157}/README.md +0 -0
  10. {voly-0.0.155 → voly-0.0.157}/setup.cfg +0 -0
  11. {voly-0.0.155 → voly-0.0.157}/setup.py +0 -0
  12. {voly-0.0.155 → voly-0.0.157}/src/voly/__init__.py +0 -0
  13. {voly-0.0.155 → voly-0.0.157}/src/voly/core/__init__.py +0 -0
  14. {voly-0.0.155 → voly-0.0.157}/src/voly/core/charts.py +0 -0
  15. {voly-0.0.155 → voly-0.0.157}/src/voly/core/data.py +0 -0
  16. {voly-0.0.155 → voly-0.0.157}/src/voly/core/fit.py +0 -0
  17. {voly-0.0.155 → voly-0.0.157}/src/voly/core/interpolate.py +0 -0
  18. {voly-0.0.155 → voly-0.0.157}/src/voly/exceptions.py +0 -0
  19. {voly-0.0.155 → voly-0.0.157}/src/voly/formulas.py +0 -0
  20. {voly-0.0.155 → voly-0.0.157}/src/voly/models.py +0 -0
  21. {voly-0.0.155 → voly-0.0.157}/src/voly/utils/__init__.py +0 -0
  22. {voly-0.0.155 → voly-0.0.157}/src/voly/utils/logger.py +0 -0
  23. {voly-0.0.155 → voly-0.0.157}/src/voly.egg-info/SOURCES.txt +0 -0
  24. {voly-0.0.155 → voly-0.0.157}/src/voly.egg-info/dependency_links.txt +0 -0
  25. {voly-0.0.155 → voly-0.0.157}/src/voly.egg-info/requires.txt +0 -0
  26. {voly-0.0.155 → voly-0.0.157}/src/voly.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: voly
3
- Version: 0.0.155
3
+ Version: 0.0.157
4
4
  Summary: Options & volatility research package
5
5
  Author-email: Manu de Cara <manu.de.cara@gmail.com>
6
6
  License: MIT
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "voly"
7
- version = "0.0.155"
7
+ version = "0.0.157"
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.155"
63
+ python_version = "0.0.157"
64
64
  warn_return_any = true
65
65
  warn_unused_configs = true
66
66
  disallow_untyped_defs = true
@@ -306,7 +306,8 @@ class VolyClient:
306
306
  def get_rnd_surface(model_results: pd.DataFrame,
307
307
  domain_params: Tuple[float, float, int] = (-1.5, 1.5, 1000),
308
308
  return_domain: str = 'log_moneyness',
309
- method: str = 'rookley') -> Dict[str, Any]:
309
+ method: str = 'rookley',
310
+ centered: bool = False) -> Dict[str, Any]:
310
311
  """
311
312
  Generate risk-neutral density surface from volatility surface parameters.
312
313
 
@@ -315,6 +316,7 @@ class VolyClient:
315
316
  - domain_params: Tuple of (min_log_moneyness, max_log_moneyness, num_points)
316
317
  - return_domain: Domain for results ('log_moneyness', 'moneyness', 'returns', 'strikes')
317
318
  - method: Method for RND estimation ('rookley' or 'breeden')
319
+ - centered: Whether to center distributions at their modes (peaks)
318
320
 
319
321
  Returns:
320
322
  - Dictionary with pdf_surface, cdf_surface, x_surface, and moments
@@ -325,7 +327,8 @@ class VolyClient:
325
327
  model_results=model_results,
326
328
  domain_params=domain_params,
327
329
  return_domain=return_domain,
328
- method=method
330
+ method=method,
331
+ centered=centered
329
332
  )
330
333
 
331
334
  # -------------------------------------------------------------------------
@@ -349,7 +352,6 @@ class VolyClient:
349
352
  Returns:
350
353
  - Historical price data with OHLCV columns and datetime index
351
354
  """
352
-
353
355
  return get_historical_data(
354
356
  currency=currency,
355
357
  lookback_days=lookback_days,
@@ -361,24 +363,30 @@ class VolyClient:
361
363
  def get_hd_surface(model_results: pd.DataFrame,
362
364
  df_hist: pd.DataFrame,
363
365
  domain_params: Tuple[float, float, int] = (-1.5, 1.5, 1000),
364
- return_domain: str = 'log_moneyness') -> Dict[str, Any]:
366
+ return_domain: str = 'log_moneyness',
367
+ method: str = 'normal',
368
+ centered: bool = False) -> Dict[str, Any]:
365
369
  """
366
- Generate historical density surface using normal distributions.
370
+ Generate historical density surface using various distribution methods.
367
371
 
368
372
  Parameters:
369
373
  - model_results: DataFrame with model parameters and maturities
370
374
  - df_hist: DataFrame with historical price data
371
375
  - domain_params: Tuple of (min_log_moneyness, max_log_moneyness, num_points)
372
376
  - return_domain: Domain for results ('log_moneyness', 'moneyness', 'returns', 'strikes')
377
+ - method: Method for density estimation ('normal', 'student_t', 'kde')
378
+ - centered: Whether to center distributions at their modes (peaks)
373
379
 
374
380
  Returns:
375
381
  - Dictionary with pdf_surface, cdf_surface, x_surface, and moments
376
382
  """
377
- logger.info("Calculating HD surface")
383
+ logger.info(f"Calculating HD surface using {method} method")
378
384
 
379
385
  return get_hd_surface(
380
386
  model_results=model_results,
381
387
  df_hist=df_hist,
382
388
  domain_params=domain_params,
383
- return_domain=return_domain
389
+ return_domain=return_domain,
390
+ method=method,
391
+ centered=centered
384
392
  )
@@ -9,10 +9,17 @@ import pandas as pd
9
9
  import datetime as dt
10
10
  from typing import Dict, Tuple, Any, Optional, List
11
11
  from scipy import stats
12
+ from scipy.stats import t as student_t
12
13
  from voly.utils.logger import logger, catch_exception
13
14
  from voly.exceptions import VolyError
14
15
  from voly.core.rnd import get_all_moments
15
- from voly.utils.density import prepare_domains, normalize_density, transform_to_domains, select_domain_results
16
+ from voly.utils.density import (
17
+ prepare_domains,
18
+ normalize_density,
19
+ transform_to_domains,
20
+ select_domain_results,
21
+ center_distributions
22
+ )
16
23
 
17
24
 
18
25
  @catch_exception
@@ -83,6 +90,42 @@ def get_historical_data(currency: str,
83
90
  return df_hist
84
91
 
85
92
 
93
+ @catch_exception
94
+ def calculate_historical_returns(df_hist: pd.DataFrame, n_periods: int) -> Tuple[np.ndarray, np.ndarray]:
95
+ """
96
+ Calculate historical returns and scale them appropriately.
97
+
98
+ Parameters:
99
+ -----------
100
+ df_hist : pd.DataFrame
101
+ Historical price data
102
+ n_periods : int
103
+ Number of periods to scale returns
104
+
105
+ Returns:
106
+ --------
107
+ Tuple[np.ndarray, np.ndarray]
108
+ (scaled_returns, raw_returns) tuple
109
+ """
110
+ # Calculate log returns
111
+ raw_returns = np.log(df_hist['close'] / df_hist['close'].shift(1)).dropna().values
112
+
113
+ # Filter historical data based on n_periods
114
+ if len(raw_returns) < n_periods:
115
+ logger.warning(f"Not enough historical data, using all {len(raw_returns)} points available")
116
+ dte_returns = raw_returns
117
+ else:
118
+ dte_returns = raw_returns[-n_periods:]
119
+
120
+ # Calculate scaling factor
121
+ scaling_factor = np.sqrt(n_periods)
122
+
123
+ # Scale returns for the maturity
124
+ scaled_returns = dte_returns * scaling_factor
125
+
126
+ return scaled_returns, dte_returns
127
+
128
+
86
129
  @catch_exception
87
130
  def calculate_normal_hd(df_hist: pd.DataFrame,
88
131
  t: float,
@@ -114,19 +157,12 @@ def calculate_normal_hd(df_hist: pd.DataFrame,
114
157
  LM = domains['log_moneyness']
115
158
  dx = domains['dx']
116
159
 
117
- # Calculate log returns
118
- returns = np.log(df_hist['close'] / df_hist['close'].shift(1)).dropna().values
160
+ # Get scaled returns
161
+ scaled_returns, dte_returns = calculate_historical_returns(df_hist, n_periods)
119
162
 
120
- # Filter historical data based on n_periods
121
- if len(returns) < n_periods:
122
- logger.warning(f"Not enough historical data, using all {len(returns)} points available")
123
- dte_returns = returns
124
- else:
125
- dte_returns = returns[-n_periods:]
126
-
127
- # Calculate scaled parameters for normal distribution
128
- mu_scaled = np.mean(dte_returns) * np.sqrt(n_periods)
129
- sigma_scaled = np.std(dte_returns) * np.sqrt(n_periods)
163
+ # Calculate parameters for normal distribution
164
+ mu_scaled = np.mean(scaled_returns)
165
+ sigma_scaled = np.std(scaled_returns)
130
166
 
131
167
  # Apply Girsanov adjustment to shift to risk-neutral measure
132
168
  expected_risk_neutral_mean = (r - 0.5 * sigma_scaled ** 2) * np.sqrt(t)
@@ -145,13 +181,149 @@ def calculate_normal_hd(df_hist: pd.DataFrame,
145
181
  return pdfs
146
182
 
147
183
 
184
+ @catch_exception
185
+ def calculate_student_t_hd(df_hist: pd.DataFrame,
186
+ t: float,
187
+ r: float,
188
+ n_periods: int,
189
+ domains: Dict[str, np.ndarray]) -> Dict[str, np.ndarray]:
190
+ """
191
+ Calculate historical density using Student's t-distribution based on historical returns.
192
+
193
+ Parameters:
194
+ -----------
195
+ df_hist : pd.DataFrame
196
+ Historical price data
197
+ t : float
198
+ Time to maturity in years
199
+ r : float
200
+ Risk-free rate
201
+ n_periods : int
202
+ Number of periods to scale returns
203
+ domains : Dict[str, np.ndarray]
204
+ Domain arrays
205
+
206
+ Returns:
207
+ --------
208
+ Dict[str, np.ndarray]
209
+ Dictionary of PDFs in different domains
210
+ """
211
+ # Extract log-moneyness domain
212
+ LM = domains['log_moneyness']
213
+ dx = domains['dx']
214
+
215
+ # Get scaled returns
216
+ scaled_returns, dte_returns = calculate_historical_returns(df_hist, n_periods)
217
+
218
+ # Calculate parameters for t-distribution
219
+ mu_scaled = np.mean(scaled_returns)
220
+ sigma_scaled = np.std(scaled_returns)
221
+
222
+ # Estimate excess kurtosis and calculate degrees of freedom
223
+ kurtosis = stats.kurtosis(dte_returns, fisher=True)
224
+
225
+ # Convert kurtosis to degrees of freedom (df)
226
+ # For t-distribution: kurtosis = 6/(df-4) for df > 4
227
+ # Solve for df: df = 6/kurtosis + 4
228
+ if kurtosis > 0:
229
+ df = min(max(6 / kurtosis + 4, 3), 30) # Bound between 3 and 30
230
+ else:
231
+ df = 5 # Default value if kurtosis calculation fails
232
+
233
+ logger.info(f"Estimated degrees of freedom for t-distribution: {df:.2f}")
234
+
235
+ # Apply Girsanov adjustment to shift to risk-neutral measure
236
+ expected_risk_neutral_mean = (r - 0.5 * sigma_scaled ** 2) * np.sqrt(t)
237
+ adjustment = mu_scaled - expected_risk_neutral_mean
238
+ mu_rn = mu_scaled - adjustment
239
+
240
+ # Scale parameter for t-distribution
241
+ # In scipy's t-distribution, the scale parameter is different from normal std
242
+ # For t-distribution: variance = (df/(df-2)) * scale^2
243
+ # So: scale = sqrt(variance * (df-2)/df)
244
+ scale = sigma_scaled * np.sqrt((df - 2) / df) if df > 2 else sigma_scaled
245
+
246
+ # Calculate PDF using t-distribution in log-moneyness domain
247
+ pdf_lm = student_t.pdf(LM, df=df, loc=mu_rn, scale=scale)
248
+
249
+ # Normalize the PDF
250
+ pdf_lm = normalize_density(pdf_lm, dx)
251
+
252
+ # Transform to other domains
253
+ pdfs = transform_to_domains(pdf_lm, domains)
254
+
255
+ return pdfs
256
+
257
+
258
+ @catch_exception
259
+ def calculate_kde_hd(df_hist: pd.DataFrame,
260
+ t: float,
261
+ r: float,
262
+ n_periods: int,
263
+ domains: Dict[str, np.ndarray]) -> Dict[str, np.ndarray]:
264
+ """
265
+ Calculate historical density using Kernel Density Estimation (KDE) based on historical returns.
266
+
267
+ Parameters:
268
+ -----------
269
+ df_hist : pd.DataFrame
270
+ Historical price data
271
+ t : float
272
+ Time to maturity in years
273
+ r : float
274
+ Risk-free rate
275
+ n_periods : int
276
+ Number of periods to scale returns
277
+ domains : Dict[str, np.ndarray]
278
+ Domain arrays
279
+
280
+ Returns:
281
+ --------
282
+ Dict[str, np.ndarray]
283
+ Dictionary of PDFs in different domains
284
+ """
285
+ # Extract log-moneyness domain
286
+ LM = domains['log_moneyness']
287
+ dx = domains['dx']
288
+
289
+ # Get scaled returns
290
+ scaled_returns, dte_returns = calculate_historical_returns(df_hist, n_periods)
291
+
292
+ # Calculate parameters (for Girsanov adjustment)
293
+ mu_scaled = np.mean(scaled_returns)
294
+ sigma_scaled = np.std(scaled_returns)
295
+
296
+ # Apply Girsanov adjustment to shift to risk-neutral measure
297
+ expected_risk_neutral_mean = (r - 0.5 * sigma_scaled ** 2) * np.sqrt(t)
298
+ adjustment = mu_scaled - expected_risk_neutral_mean
299
+
300
+ # Shift the returns to be risk-neutral
301
+ rn_returns = scaled_returns - adjustment + expected_risk_neutral_mean
302
+
303
+ # Fit KDE model using scipy's gaussian_kde with Scott's rule for bandwidth
304
+ kde = stats.gaussian_kde(rn_returns, bw_method='scott')
305
+
306
+ # Evaluate KDE at points in log-moneyness domain
307
+ pdf_lm = kde(LM)
308
+
309
+ # Normalize the PDF
310
+ pdf_lm = normalize_density(pdf_lm, dx)
311
+
312
+ # Transform to other domains
313
+ pdfs = transform_to_domains(pdf_lm, domains)
314
+
315
+ return pdfs
316
+
317
+
148
318
  @catch_exception
149
319
  def get_hd_surface(model_results: pd.DataFrame,
150
320
  df_hist: pd.DataFrame,
151
321
  domain_params: Tuple[float, float, int] = (-1.5, 1.5, 1000),
152
- return_domain: str = 'log_moneyness') -> Dict[str, Any]:
322
+ return_domain: str = 'log_moneyness',
323
+ method: str = 'normal',
324
+ centered: bool = False) -> Dict[str, Any]:
153
325
  """
154
- Generate historical density surface using normal distributions.
326
+ Generate historical density surface using various distribution methods.
155
327
 
156
328
  Parameters:
157
329
  -----------
@@ -163,6 +335,10 @@ def get_hd_surface(model_results: pd.DataFrame,
163
335
  (min_log_moneyness, max_log_moneyness, num_points)
164
336
  return_domain : str
165
337
  Domain for results ('log_moneyness', 'moneyness', 'returns', 'strikes')
338
+ method : str
339
+ Method for estimating density ('normal', 'student_t', 'kde')
340
+ centered : bool
341
+ Whether to center distributions at their modes (peaks)
166
342
 
167
343
  Returns:
168
344
  --------
@@ -183,6 +359,22 @@ def get_hd_surface(model_results: pd.DataFrame,
183
359
  if return_domain not in valid_domains:
184
360
  raise VolyError(f"Invalid return_domain: {return_domain}. Must be one of {valid_domains}")
185
361
 
362
+ # Validate method
363
+ valid_methods = ['normal', 'student_t', 'kde']
364
+ if method not in valid_methods:
365
+ raise VolyError(f"Invalid method: {method}. Must be one of {valid_methods}")
366
+
367
+ # Select calculation function based on method
368
+ if method == 'student_t':
369
+ calculate_hd = calculate_student_t_hd
370
+ logger.info("Using Student's t-distribution for historical density")
371
+ elif method == 'kde':
372
+ calculate_hd = calculate_kde_hd
373
+ logger.info("Using Kernel Density Estimation (KDE) for historical density")
374
+ else: # default to normal
375
+ calculate_hd = calculate_normal_hd
376
+ logger.info("Using normal distribution for historical density")
377
+
186
378
  # Determine granularity from data (minutes between data points)
187
379
  time_diff = (df_hist.index[1] - df_hist.index[0]).total_seconds() / 60
188
380
  minutes_per_period = max(1, int(time_diff))
@@ -208,8 +400,8 @@ def get_hd_surface(model_results: pd.DataFrame,
208
400
  # Prepare domains
209
401
  domains = prepare_domains(domain_params, s)
210
402
 
211
- # Calculate density
212
- pdfs = calculate_normal_hd(
403
+ # Calculate density using the selected method
404
+ pdfs = calculate_hd(
213
405
  df_hist=df_hist,
214
406
  t=t,
215
407
  r=r,
@@ -230,16 +422,21 @@ def get_hd_surface(model_results: pd.DataFrame,
230
422
  all_moments[i] = moments
231
423
 
232
424
  except Exception as e:
233
- logger.warning(f"Failed to calculate HD for maturity {i}: {str(e)}")
425
+ logger.warning(f"Failed to calculate HD for maturity {i} using {method} method: {str(e)}")
234
426
 
235
427
  # Check if we have any valid results
236
428
  if not pdf_surface:
237
- raise VolyError("No valid densities could be calculated. Check your input data.")
429
+ raise VolyError(f"No valid densities could be calculated using {method} method. Check your input data.")
430
+
431
+ # Center distributions if requested
432
+ if centered:
433
+ pdf_surface, cdf_surface = center_distributions(pdf_surface, cdf_surface, x_surface)
434
+ logger.info("Distributions have been centered at their modes")
238
435
 
239
436
  # Create DataFrame with moments
240
437
  moments = pd.DataFrame(all_moments).T
241
438
 
242
- logger.info("Historical density calculation complete using normal distribution")
439
+ logger.info(f"Historical density calculation complete using {method} distribution")
243
440
 
244
441
  return {
245
442
  'pdf_surface': pdf_surface,
@@ -11,7 +11,13 @@ from voly.exceptions import VolyError
11
11
  from voly.models import SVIModel
12
12
  from voly.formulas import bs, d1, d2
13
13
  from scipy import stats
14
- from voly.utils.density import prepare_domains, normalize_density, transform_to_domains, select_domain_results
14
+ from voly.utils.density import (
15
+ prepare_domains,
16
+ normalize_density,
17
+ transform_to_domains,
18
+ select_domain_results,
19
+ center_distributions
20
+ )
15
21
 
16
22
 
17
23
  @catch_exception
@@ -299,7 +305,8 @@ def get_all_moments(x, pdf, model_params=None):
299
305
  def get_rnd_surface(model_results: pd.DataFrame,
300
306
  domain_params: Tuple[float, float, int] = (-1.5, 1.5, 1000),
301
307
  return_domain: str = 'log_moneyness',
302
- method: str = 'rookley') -> Dict[str, Any]:
308
+ method: str = 'rookley',
309
+ centered: bool = False) -> Dict[str, Any]:
303
310
  """
304
311
  Generate risk-neutral density surface from volatility surface parameters.
305
312
 
@@ -313,6 +320,8 @@ def get_rnd_surface(model_results: pd.DataFrame,
313
320
  Domain for results ('log_moneyness', 'moneyness', 'returns', 'strikes')
314
321
  method : str
315
322
  Method for RND estimation ('rookley' or 'breeden')
323
+ centered : bool
324
+ Whether to center distributions at their modes (peaks)
316
325
 
317
326
  Returns:
318
327
  --------
@@ -379,6 +388,11 @@ def get_rnd_surface(model_results: pd.DataFrame,
379
388
  if not pdf_surface:
380
389
  raise VolyError("No valid densities could be calculated. Check your input data.")
381
390
 
391
+ # Center distributions if requested
392
+ if centered:
393
+ pdf_surface, cdf_surface = center_distributions(pdf_surface, cdf_surface, x_surface)
394
+ logger.info("Distributions have been centered at their modes")
395
+
382
396
  # Create DataFrame with moments
383
397
  moments = pd.DataFrame(all_moments).T
384
398
 
@@ -7,7 +7,7 @@ densities across different domains, used by both RND and HD calculations.
7
7
 
8
8
  import numpy as np
9
9
  from typing import Dict, Tuple, List, Any
10
- from scipy import stats
10
+ from scipy import stats, interpolate
11
11
  from voly.utils.logger import catch_exception
12
12
  from voly.formulas import d1, d2
13
13
 
@@ -153,3 +153,52 @@ def select_domain_results(pdfs: Dict[str, np.ndarray],
153
153
  cdf = pdfs['cdf']
154
154
 
155
155
  return pdf, cdf, x
156
+
157
+
158
+ @catch_exception
159
+ def center_distributions(pdf_surface: Dict[str, np.ndarray],
160
+ cdf_surface: Dict[str, np.ndarray],
161
+ x_surface: Dict[str, np.ndarray]) -> Tuple[Dict[str, np.ndarray], Dict[str, np.ndarray]]:
162
+ """
163
+ Center distributions so their peaks (modes) are at x=0 while maintaining the same domain.
164
+
165
+ This function shifts each distribution so that its mode (peak) is at x=0,
166
+ without changing the domain range. It uses interpolation to recalculate
167
+ the PDF and CDF values on the original domain.
168
+
169
+ Parameters:
170
+ -----------
171
+ pdf_surface : Dict[str, np.ndarray]
172
+ Dictionary of PDFs by maturity
173
+ cdf_surface : Dict[str, np.ndarray]
174
+ Dictionary of CDFs by maturity
175
+ x_surface : Dict[str, np.ndarray]
176
+ Dictionary of x-domains by maturity
177
+
178
+ Returns:
179
+ --------
180
+ Tuple[Dict[str, np.ndarray], Dict[str, np.ndarray]]
181
+ (centered_pdf_surface, centered_cdf_surface)
182
+ """
183
+ centered_pdf_surface = {}
184
+ centered_cdf_surface = {}
185
+
186
+ for maturity in pdf_surface.keys():
187
+ pdf = pdf_surface[maturity]
188
+ cdf = cdf_surface[maturity]
189
+ x = x_surface[maturity]
190
+
191
+ # Find the mode (peak) of the distribution
192
+ mode_idx = np.argmax(pdf)
193
+ mode = x[mode_idx]
194
+
195
+ # Create interpolation functions for the original distributions
196
+ f_pdf = interpolate.interp1d(x, pdf, bounds_error=False, fill_value=0)
197
+ f_cdf = interpolate.interp1d(x, cdf, bounds_error=False, fill_value=0)
198
+
199
+ # Evaluate the centered distributions on the original domain
200
+ # g(x) = f(x + mode) shifts the distribution so that the peak is at x=0
201
+ centered_pdf_surface[maturity] = f_pdf(x + mode)
202
+ centered_cdf_surface[maturity] = f_cdf(x + mode)
203
+
204
+ return centered_pdf_surface, centered_cdf_surface
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: voly
3
- Version: 0.0.155
3
+ Version: 0.0.157
4
4
  Summary: Options & volatility research package
5
5
  Author-email: Manu de Cara <manu.de.cara@gmail.com>
6
6
  License: MIT
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