voly 0.0.155__py3-none-any.whl → 0.0.157__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 +15 -7
- voly/core/hd.py +217 -20
- voly/core/rnd.py +16 -2
- voly/utils/density.py +50 -1
- {voly-0.0.155.dist-info → voly-0.0.157.dist-info}/METADATA +1 -1
- {voly-0.0.155.dist-info → voly-0.0.157.dist-info}/RECORD +9 -9
- {voly-0.0.155.dist-info → voly-0.0.157.dist-info}/WHEEL +0 -0
- {voly-0.0.155.dist-info → voly-0.0.157.dist-info}/licenses/LICENSE +0 -0
- {voly-0.0.155.dist-info → voly-0.0.157.dist-info}/top_level.txt +0 -0
voly/client.py
CHANGED
@@ -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'
|
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'
|
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
|
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
|
)
|
voly/core/hd.py
CHANGED
@@ -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
|
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
|
-
#
|
118
|
-
|
160
|
+
# Get scaled returns
|
161
|
+
scaled_returns, dte_returns = calculate_historical_returns(df_hist, n_periods)
|
119
162
|
|
120
|
-
#
|
121
|
-
|
122
|
-
|
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'
|
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
|
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 =
|
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
|
439
|
+
logger.info(f"Historical density calculation complete using {method} distribution")
|
243
440
|
|
244
441
|
return {
|
245
442
|
'pdf_surface': pdf_surface,
|
voly/core/rnd.py
CHANGED
@@ -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
|
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'
|
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
|
|
voly/utils/density.py
CHANGED
@@ -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,5 +1,5 @@
|
|
1
1
|
voly/__init__.py,sha256=8xyDk7rFCn_MOD5hxuv5cxxKZvBVRiSIM7TgaMPpwpw,211
|
2
|
-
voly/client.py,sha256
|
2
|
+
voly/client.py,sha256=-yE1_cBvjkK-BO_kKCYtn4WPbNOhAzT0hsfykU5LvQQ,14761
|
3
3
|
voly/exceptions.py,sha256=PBsbn1vNMvKcCJwwJ4lBO6glD85jo1h2qiEmD7ArAjs,92
|
4
4
|
voly/formulas.py,sha256=G_soRiPwQlHy6milOAj6TdmBWr-fNZpMvm0joXAMZ90,10767
|
5
5
|
voly/models.py,sha256=o-pHujGfr5Gn8ItckMzLI4Q8yaX9FQaV8UjCxv2zgTY,3364
|
@@ -7,14 +7,14 @@ voly/core/__init__.py,sha256=bu6fS2I1Pj9fPPnl-zY3L7NqrZSY5Zy6NY2uMUvdhKs,183
|
|
7
7
|
voly/core/charts.py,sha256=E21OZB5lTY4YL2flgaFJ6s5g3_ExtAQT2zryZZxLPyM,12735
|
8
8
|
voly/core/data.py,sha256=pDeuYhP0GX4RbtlqByvsE3rfHcIkix0BU5MLW8sKIeI,8935
|
9
9
|
voly/core/fit.py,sha256=Tb9eeG7e_2dQTcqt6aqEwFrZdy6jR9rSNqe6tzOdVhQ,9245
|
10
|
-
voly/core/hd.py,sha256=
|
10
|
+
voly/core/hd.py,sha256=kAQO2ft6vmobW60mokhoZbzVElYC_wy1OeBXhmeCtAg,14850
|
11
11
|
voly/core/interpolate.py,sha256=JkK172-FXyhesW3hY4pEeuJWG3Bugq7QZXbeKoRpLuo,5305
|
12
|
-
voly/core/rnd.py,sha256=
|
12
|
+
voly/core/rnd.py,sha256=GoC3m1Q46Wnk5tV_mstr-3_aktHeue6BBLh4DQTciW0,13307
|
13
13
|
voly/utils/__init__.py,sha256=E05mWatyC-PDOsCxQV1p5Xi1IgpOomxrNURyCx_gB-w,200
|
14
|
-
voly/utils/density.py,sha256=
|
14
|
+
voly/utils/density.py,sha256=q0fX4im9TGwMCZ32Hzdv8CNh56KnJo8bmG5w0gVWZH8,5879
|
15
15
|
voly/utils/logger.py,sha256=4-_2bVJmq17Q0d7Rd2mPg1AeR8gxv6EPvcmBDMFWcSM,1744
|
16
|
-
voly-0.0.
|
17
|
-
voly-0.0.
|
18
|
-
voly-0.0.
|
19
|
-
voly-0.0.
|
20
|
-
voly-0.0.
|
16
|
+
voly-0.0.157.dist-info/licenses/LICENSE,sha256=wcHIVbE12jfcBOai_wqBKY6xvNQU5E909xL1zZNq_2Q,1065
|
17
|
+
voly-0.0.157.dist-info/METADATA,sha256=5RTJLbGrsh2EQQ4jTBqIGoYF6UDI4_4UnRHsSEDSMM8,4115
|
18
|
+
voly-0.0.157.dist-info/WHEEL,sha256=CmyFI0kx5cdEMTLiONQRbGQwjIoR1aIYB7eCAQ4KPJ0,91
|
19
|
+
voly-0.0.157.dist-info/top_level.txt,sha256=ZfLw2sSxF-LrKAkgGjOmeTcw6_gD-30zvtdEY5W4B7c,5
|
20
|
+
voly-0.0.157.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|