voly 0.0.86__py3-none-any.whl → 0.0.87__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 +18 -201
- voly/core/charts.py +10 -517
- voly/core/data.py +1 -4
- voly/core/fit.py +34 -42
- voly/core/interpolate.py +5 -6
- voly/core/rnd.py +255 -334
- voly/formulas.py +27 -29
- voly/models.py +0 -2
- {voly-0.0.86.dist-info → voly-0.0.87.dist-info}/METADATA +1 -1
- voly-0.0.87.dist-info/RECORD +18 -0
- {voly-0.0.86.dist-info → voly-0.0.87.dist-info}/WHEEL +1 -1
- voly-0.0.86.dist-info/RECORD +0 -18
- {voly-0.0.86.dist-info → voly-0.0.87.dist-info}/LICENSE +0 -0
- {voly-0.0.86.dist-info → voly-0.0.87.dist-info}/top_level.txt +0 -0
voly/core/fit.py
CHANGED
|
@@ -20,13 +20,13 @@ warnings.filterwarnings("ignore")
|
|
|
20
20
|
|
|
21
21
|
|
|
22
22
|
@catch_exception
|
|
23
|
-
def calculate_residuals(params: List[float],
|
|
23
|
+
def calculate_residuals(params: List[float], t: float, option_chain: pd.DataFrame,
|
|
24
24
|
model: Any = SVIModel) -> np.ndarray:
|
|
25
25
|
"""Calculate residuals between market and model implied volatilities."""
|
|
26
|
-
maturity_data = option_chain[option_chain['
|
|
26
|
+
maturity_data = option_chain[option_chain['t'] == t]
|
|
27
27
|
w = np.array([model.svi(x, *params) for x in maturity_data['log_moneyness']])
|
|
28
28
|
iv_actual = maturity_data['mark_iv'].values
|
|
29
|
-
return iv_actual - np.sqrt(w /
|
|
29
|
+
return iv_actual - np.sqrt(w / t)
|
|
30
30
|
|
|
31
31
|
|
|
32
32
|
@catch_exception
|
|
@@ -57,22 +57,21 @@ def fit_model(option_chain: pd.DataFrame,
|
|
|
57
57
|
column_dtypes = {
|
|
58
58
|
's': float,
|
|
59
59
|
'u': float,
|
|
60
|
+
't': float,
|
|
61
|
+
'r': float,
|
|
62
|
+
'oi': float,
|
|
63
|
+
'volume': float,
|
|
60
64
|
'maturity_date': 'datetime64[ns]',
|
|
61
|
-
'dtm': float,
|
|
62
|
-
'ytm': float,
|
|
63
65
|
'a': float,
|
|
64
66
|
'b': float,
|
|
67
|
+
'sigma': float,
|
|
65
68
|
'rho': float,
|
|
66
69
|
'm': float,
|
|
67
|
-
'sigma': float,
|
|
68
70
|
'nu': float,
|
|
69
71
|
'psi': float,
|
|
70
72
|
'p': float,
|
|
71
73
|
'c': float,
|
|
72
74
|
'nu_tilde': float,
|
|
73
|
-
'oi': float,
|
|
74
|
-
'volume': float,
|
|
75
|
-
'r': float,
|
|
76
75
|
'fit_success': bool,
|
|
77
76
|
'cost': float,
|
|
78
77
|
'optimality': float,
|
|
@@ -84,8 +83,8 @@ def fit_model(option_chain: pd.DataFrame,
|
|
|
84
83
|
}
|
|
85
84
|
|
|
86
85
|
# Get unique maturities and sort them
|
|
87
|
-
|
|
88
|
-
maturity_names = [option_chain[option_chain['
|
|
86
|
+
unique_ts = sorted(option_chain['t'].unique())
|
|
87
|
+
maturity_names = [option_chain[option_chain['t'] == t]['maturity_name'].iloc[0] for t in unique_ts]
|
|
89
88
|
|
|
90
89
|
# Store results in a dictionary first
|
|
91
90
|
results_data = {col: [] for col in column_dtypes.keys()}
|
|
@@ -95,11 +94,10 @@ def fit_model(option_chain: pd.DataFrame,
|
|
|
95
94
|
|
|
96
95
|
s = option_chain['index_price'].iloc[-1]
|
|
97
96
|
|
|
98
|
-
for
|
|
97
|
+
for t in unique_ts:
|
|
99
98
|
# Get data for this maturity
|
|
100
|
-
maturity_data = option_chain[option_chain['
|
|
99
|
+
maturity_data = option_chain[option_chain['t'] == t]
|
|
101
100
|
maturity_name = maturity_data['maturity_name'].iloc[0]
|
|
102
|
-
dtm = maturity_data['dtm'].iloc[0]
|
|
103
101
|
|
|
104
102
|
logger.info(f"Optimizing for {maturity_name}...")
|
|
105
103
|
|
|
@@ -108,7 +106,7 @@ def fit_model(option_chain: pd.DataFrame,
|
|
|
108
106
|
result = least_squares(
|
|
109
107
|
calculate_residuals,
|
|
110
108
|
initial_params,
|
|
111
|
-
args=(
|
|
109
|
+
args=(t, option_chain, SVIModel),
|
|
112
110
|
bounds=param_bounds,
|
|
113
111
|
max_nfev=1000
|
|
114
112
|
)
|
|
@@ -120,7 +118,7 @@ def fit_model(option_chain: pd.DataFrame,
|
|
|
120
118
|
|
|
121
119
|
# Calculate model predictions for statistics
|
|
122
120
|
w = np.array([SVIModel.svi(x, *result.x) for x in maturity_data['log_moneyness']])
|
|
123
|
-
iv_model = np.sqrt(w /
|
|
121
|
+
iv_model = np.sqrt(w / t)
|
|
124
122
|
iv_market = maturity_data['mark_iv'].values
|
|
125
123
|
|
|
126
124
|
# Calculate statistics
|
|
@@ -138,27 +136,26 @@ def fit_model(option_chain: pd.DataFrame,
|
|
|
138
136
|
r = maturity_data['interest_rate'].iloc[0] if 'interest_rate' in maturity_data.columns else 0
|
|
139
137
|
|
|
140
138
|
# Calculate Jump-Wing parameters
|
|
141
|
-
nu, psi, p, c, nu_tilde = SVIModel.raw_to_jw_params(a, b, sigma, rho, m,
|
|
139
|
+
nu, psi, p, c, nu_tilde = SVIModel.raw_to_jw_params(a, b, sigma, rho, m, t)
|
|
142
140
|
|
|
143
141
|
# Store values in the results dictionary with proper types
|
|
144
142
|
results_data['s'].append(float(s))
|
|
145
143
|
results_data['u'].append(float(u))
|
|
144
|
+
results_data['t'].append(float(t))
|
|
145
|
+
results_data['r'].append(float(r))
|
|
146
|
+
results_data['oi'].append(float(oi))
|
|
147
|
+
results_data['volume'].append(float(volume))
|
|
146
148
|
results_data['maturity_date'].append(maturity_data['maturity_date'].iloc[0])
|
|
147
|
-
results_data['dtm'].append(float(dtm))
|
|
148
|
-
results_data['ytm'].append(float(ytm))
|
|
149
149
|
results_data['a'].append(float(a))
|
|
150
150
|
results_data['b'].append(float(b))
|
|
151
|
-
results_data['rho'].append(float(rho))
|
|
152
|
-
results_data['m'].append(float(m))
|
|
153
151
|
results_data['sigma'].append(float(sigma))
|
|
152
|
+
results_data['m'].append(float(m))
|
|
153
|
+
results_data['rho'].append(float(rho))
|
|
154
154
|
results_data['nu'].append(float(nu))
|
|
155
155
|
results_data['psi'].append(float(psi))
|
|
156
156
|
results_data['p'].append(float(p))
|
|
157
157
|
results_data['c'].append(float(c))
|
|
158
158
|
results_data['nu_tilde'].append(float(nu_tilde))
|
|
159
|
-
results_data['oi'].append(float(oi))
|
|
160
|
-
results_data['volume'].append(float(volume))
|
|
161
|
-
results_data['r'].append(float(r))
|
|
162
159
|
results_data['fit_success'].append(bool(result.success))
|
|
163
160
|
results_data['cost'].append(float(result.cost))
|
|
164
161
|
results_data['optimality'].append(float(result.optimality))
|
|
@@ -189,7 +186,7 @@ def fit_model(option_chain: pd.DataFrame,
|
|
|
189
186
|
|
|
190
187
|
@catch_exception
|
|
191
188
|
def get_iv_surface(model_results: pd.DataFrame,
|
|
192
|
-
|
|
189
|
+
domain_params: Tuple[float, float, int] = (-1.5, 1.5, 1000),
|
|
193
190
|
return_domain: str = 'log_moneyness') -> Tuple[Dict[str, np.ndarray], Dict[str, np.ndarray]]:
|
|
194
191
|
"""
|
|
195
192
|
Generate implied volatility surface using optimized SVI parameters.
|
|
@@ -198,8 +195,8 @@ def get_iv_surface(model_results: pd.DataFrame,
|
|
|
198
195
|
|
|
199
196
|
Parameters:
|
|
200
197
|
- model_results: DataFrame from fit_model() or interpolate_model(). Maturity names or DTM as Index
|
|
201
|
-
-
|
|
202
|
-
- return_domain: Domain for x-axis values ('log_moneyness', 'moneyness', 'strikes', 'delta')
|
|
198
|
+
- domain_params: Tuple of (min, max, num_points) for the log-moneyness array
|
|
199
|
+
- return_domain: Domain for x-axis values ('log_moneyness', 'moneyness', 'returns', 'strikes', 'delta')
|
|
203
200
|
|
|
204
201
|
Returns:
|
|
205
202
|
- Tuple of (iv_surface, x_surface)
|
|
@@ -207,14 +204,13 @@ def get_iv_surface(model_results: pd.DataFrame,
|
|
|
207
204
|
x_surface: Dictionary mapping maturity/dtm names to requested x domain arrays
|
|
208
205
|
"""
|
|
209
206
|
# Check if required columns are present
|
|
210
|
-
required_columns = ['a', 'b', '
|
|
207
|
+
required_columns = ['a', 'b', 'sigma', 'rho', 'm', 't']
|
|
211
208
|
missing_columns = [col for col in required_columns if col not in model_results.columns]
|
|
212
209
|
if missing_columns:
|
|
213
210
|
raise VolyError(f"Required columns missing in model_results: {missing_columns}")
|
|
214
211
|
|
|
215
212
|
# Generate implied volatility surface in log-moneyness domain
|
|
216
|
-
|
|
217
|
-
log_moneyness_array = np.linspace(min_m, max_m, num=num_points)
|
|
213
|
+
LM = np.linspace(domain_params[0], domain_params[1], domain_params[2])
|
|
218
214
|
|
|
219
215
|
iv_surface = {}
|
|
220
216
|
x_surface = {}
|
|
@@ -229,21 +225,17 @@ def get_iv_surface(model_results: pd.DataFrame,
|
|
|
229
225
|
model_results.loc[i, 'rho'],
|
|
230
226
|
model_results.loc[i, 'm']
|
|
231
227
|
]
|
|
232
|
-
|
|
228
|
+
s = model_results.loc[i, 's'],
|
|
229
|
+
r = model_results.loc[i, 'r'],
|
|
230
|
+
t = model_results.loc[i, 't']
|
|
233
231
|
|
|
234
232
|
# Calculate implied volatility
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
iv_surface[i] =
|
|
233
|
+
w = np.array([SVIModel.svi(x, *params) for x in LM])
|
|
234
|
+
o = np.sqrt(w / t)
|
|
235
|
+
iv_surface[i] = o
|
|
238
236
|
|
|
239
237
|
# Calculate x domain for this maturity/dtm
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
return_domain=return_domain,
|
|
243
|
-
s=model_results.loc[i, 's'],
|
|
244
|
-
r=model_results.loc[i, 'r'],
|
|
245
|
-
iv_array=iv_array,
|
|
246
|
-
ytm=ytm
|
|
247
|
-
)
|
|
238
|
+
x = get_domain(domain_params, s, o, r, t, return_domain)
|
|
239
|
+
x_surface[i] = x
|
|
248
240
|
|
|
249
241
|
return iv_surface, x_surface
|
voly/core/interpolate.py
CHANGED
|
@@ -36,7 +36,7 @@ def interpolate_model(fit_results: pd.DataFrame,
|
|
|
36
36
|
raise VolyError("Fit results DataFrame is empty or None")
|
|
37
37
|
|
|
38
38
|
# Extract years to maturity from fit_results
|
|
39
|
-
original_ytms = fit_results['
|
|
39
|
+
original_ytms = fit_results['t'].values
|
|
40
40
|
|
|
41
41
|
if len(original_ytms) < 2:
|
|
42
42
|
raise VolyError("Need at least two maturities in fit_results to interpolate")
|
|
@@ -68,14 +68,13 @@ def interpolate_model(fit_results: pd.DataFrame,
|
|
|
68
68
|
"Extrapolation may give unreliable results.")
|
|
69
69
|
|
|
70
70
|
# Columns to interpolate
|
|
71
|
-
param_columns = ['u', 'a', 'b', '
|
|
71
|
+
param_columns = ['u', 'a', 'b', 'sigma', 'rho', 'm', 'nu', 'psi', 'p', 'c', 'nu_tilde']
|
|
72
72
|
|
|
73
73
|
# Create empty DataFrame for interpolated results
|
|
74
74
|
interpolated_df = pd.DataFrame(index=[f"{day}d" for day in target_days])
|
|
75
75
|
|
|
76
76
|
# Generate YTM and maturity dates for interpolated results
|
|
77
|
-
interpolated_df['
|
|
78
|
-
interpolated_df['ytm'] = [day / 365.25 for day in target_days]
|
|
77
|
+
interpolated_df['t'] = target_years
|
|
79
78
|
|
|
80
79
|
# Calculate maturity dates
|
|
81
80
|
now = dt.datetime.now()
|
|
@@ -120,8 +119,8 @@ def interpolate_model(fit_results: pd.DataFrame,
|
|
|
120
119
|
interpolated_df[param] = f(target_years)
|
|
121
120
|
|
|
122
121
|
# Ensure consistent ordering of columns with expected structure
|
|
123
|
-
expected_columns = ['s', 'u', '
|
|
124
|
-
'nu', 'psi', 'p', 'c', 'nu_tilde'
|
|
122
|
+
expected_columns = ['s', 'u', 't', 'r', 'maturity_date', 'a', 'b', 'sigma', 'rho', 'm',
|
|
123
|
+
'nu', 'psi', 'p', 'c', 'nu_tilde']
|
|
125
124
|
|
|
126
125
|
# Create final column order based on available columns
|
|
127
126
|
column_order = [col for col in expected_columns if col in interpolated_df.columns]
|