voly 0.0.1__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/__init__.py +10 -0
- voly/client.py +540 -0
- voly/core/__init__.py +11 -0
- voly/core/charts.py +984 -0
- voly/core/data.py +312 -0
- voly/core/fit.py +331 -0
- voly/core/interpolate.py +221 -0
- voly/core/rnd.py +389 -0
- voly/exceptions.py +3 -0
- voly/formulas.py +243 -0
- voly/models.py +86 -0
- voly/utils/__init__.py +8 -0
- voly/utils/logger.py +72 -0
- voly-0.0.1.dist-info/LICENSE +21 -0
- voly-0.0.1.dist-info/METADATA +132 -0
- voly-0.0.1.dist-info/RECORD +18 -0
- voly-0.0.1.dist-info/WHEEL +5 -0
- voly-0.0.1.dist-info/top_level.txt +1 -0
voly/__init__.py
ADDED
voly/client.py
ADDED
|
@@ -0,0 +1,540 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Main client interface for the Voly package.
|
|
3
|
+
|
|
4
|
+
This module provides the VolyClient class, which serves as the main
|
|
5
|
+
entry point for users to interact with the package functionality.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import asyncio
|
|
9
|
+
import pandas as pd
|
|
10
|
+
import numpy as np
|
|
11
|
+
from typing import Dict, List, Tuple, Optional, Union, Any, Callable
|
|
12
|
+
import plotly.graph_objects as go
|
|
13
|
+
|
|
14
|
+
from voly.utils.logger import logger, catch_exception, setup_file_logging
|
|
15
|
+
from voly.exceptions import VolyError, ValidationError, DataError
|
|
16
|
+
from voly.models import SVIModel
|
|
17
|
+
from voly.formulas import (
|
|
18
|
+
bs, delta, gamma, vega, theta, rho, vanna, volga, charm, greeks, iv, implied_underlying
|
|
19
|
+
)
|
|
20
|
+
from voly.core.data import fetch_option_chain, process_option_chain
|
|
21
|
+
from voly.core.fit import fit_model
|
|
22
|
+
from voly.core.rnd import calculate_rnd, calculate_pdf, calculate_cdf, calculate_strike_probability
|
|
23
|
+
from voly.core.interpolate import interpolate_model
|
|
24
|
+
from voly.core.charts import (
|
|
25
|
+
plot_volatility_smile, plot_3d_surface, plot_parameters, plot_fit_statistics,
|
|
26
|
+
plot_rnd, plot_pdf, plot_cdf, plot_rnd_all_expiries, plot_rnd_3d,
|
|
27
|
+
plot_rnd_statistics, generate_all_plots, plot_interpolated_surface
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class VolyClient:
|
|
32
|
+
def __init__(self, enable_file_logging: bool = False, logs_dir: str = "logs/"):
|
|
33
|
+
"""
|
|
34
|
+
Initialize the Voly client.
|
|
35
|
+
|
|
36
|
+
Parameters:
|
|
37
|
+
- enable_file_logging: Whether to enable file-based logging
|
|
38
|
+
- logs_dir: Directory for log files if file logging is enabled
|
|
39
|
+
"""
|
|
40
|
+
if enable_file_logging:
|
|
41
|
+
setup_file_logging(logs_dir)
|
|
42
|
+
|
|
43
|
+
logger.info("VolyClient initialized")
|
|
44
|
+
self._loop = None # For async operations
|
|
45
|
+
|
|
46
|
+
def _get_event_loop(self):
|
|
47
|
+
"""Get or create an event loop for async operations"""
|
|
48
|
+
try:
|
|
49
|
+
self._loop = asyncio.get_event_loop()
|
|
50
|
+
except RuntimeError:
|
|
51
|
+
self._loop = asyncio.new_event_loop()
|
|
52
|
+
asyncio.set_event_loop(self._loop)
|
|
53
|
+
return self._loop
|
|
54
|
+
|
|
55
|
+
# -------------------------------------------------------------------------
|
|
56
|
+
# Data Fetching and Processing
|
|
57
|
+
# -------------------------------------------------------------------------
|
|
58
|
+
|
|
59
|
+
def get_option_chain(self, exchange: str = 'deribit',
|
|
60
|
+
currency: str = 'BTC',
|
|
61
|
+
depth: bool = False) -> pd.DataFrame:
|
|
62
|
+
"""
|
|
63
|
+
Fetch option chain data from the specified exchange.
|
|
64
|
+
|
|
65
|
+
Parameters:
|
|
66
|
+
- exchange: Exchange to fetch data from (currently only 'deribit' is supported)
|
|
67
|
+
- currency: Currency to fetch options for (e.g., 'BTC', 'ETH')
|
|
68
|
+
- depth: Whether to include full order book depth
|
|
69
|
+
|
|
70
|
+
Returns:
|
|
71
|
+
- Processed option chain data as a pandas DataFrame
|
|
72
|
+
"""
|
|
73
|
+
logger.info(f"Fetching option chain data from {exchange} for {currency}")
|
|
74
|
+
|
|
75
|
+
loop = self._get_event_loop()
|
|
76
|
+
|
|
77
|
+
try:
|
|
78
|
+
option_chain = loop.run_until_complete(
|
|
79
|
+
fetch_option_chain(exchange, currency, depth)
|
|
80
|
+
)
|
|
81
|
+
return option_chain
|
|
82
|
+
except VolyError as e:
|
|
83
|
+
logger.error(f"Error fetching option chain: {str(e)}")
|
|
84
|
+
raise
|
|
85
|
+
|
|
86
|
+
# -------------------------------------------------------------------------
|
|
87
|
+
# Black-Scholes and Greeks Calculations
|
|
88
|
+
# -------------------------------------------------------------------------
|
|
89
|
+
|
|
90
|
+
@staticmethod
|
|
91
|
+
def bs(s: float, k: float, r: float, vol: float, t: float,
|
|
92
|
+
option_type: str = 'call') -> float:
|
|
93
|
+
"""
|
|
94
|
+
Calculate Black-Scholes option price.
|
|
95
|
+
|
|
96
|
+
Parameters:
|
|
97
|
+
- s: Underlying price
|
|
98
|
+
- k: Strike price
|
|
99
|
+
- r: Risk-free rate
|
|
100
|
+
- vol: Volatility
|
|
101
|
+
- t: Time to expiry in years
|
|
102
|
+
- option_type: 'call' or 'put'
|
|
103
|
+
|
|
104
|
+
Returns:
|
|
105
|
+
- Option price
|
|
106
|
+
"""
|
|
107
|
+
return bs(s, k, r, vol, t, option_type)
|
|
108
|
+
|
|
109
|
+
@staticmethod
|
|
110
|
+
def delta(s: float, k: float, r: float, vol: float, t: float,
|
|
111
|
+
option_type: str = 'call') -> float:
|
|
112
|
+
"""
|
|
113
|
+
Calculate option delta.
|
|
114
|
+
|
|
115
|
+
Parameters:
|
|
116
|
+
- s: Underlying price
|
|
117
|
+
- k: Strike price
|
|
118
|
+
- r: Risk-free rate
|
|
119
|
+
- vol: Volatility
|
|
120
|
+
- t: Time to expiry in years
|
|
121
|
+
- option_type: 'call' or 'put'
|
|
122
|
+
|
|
123
|
+
Returns:
|
|
124
|
+
- Delta value
|
|
125
|
+
"""
|
|
126
|
+
return delta(s, k, r, vol, t, option_type)
|
|
127
|
+
|
|
128
|
+
@staticmethod
|
|
129
|
+
def gamma(s: float, k: float, r: float, vol: float, t: float) -> float:
|
|
130
|
+
"""
|
|
131
|
+
Calculate option gamma.
|
|
132
|
+
|
|
133
|
+
Parameters:
|
|
134
|
+
- s: Underlying price
|
|
135
|
+
- k: Strike price
|
|
136
|
+
- r: Risk-free rate
|
|
137
|
+
- vol: Volatility
|
|
138
|
+
- t: Time to expiry in years
|
|
139
|
+
|
|
140
|
+
Returns:
|
|
141
|
+
- Gamma value
|
|
142
|
+
"""
|
|
143
|
+
return gamma(s, k, r, vol, t)
|
|
144
|
+
|
|
145
|
+
@staticmethod
|
|
146
|
+
def vega(s: float, k: float, r: float, vol: float, t: float) -> float:
|
|
147
|
+
"""
|
|
148
|
+
Calculate option vega.
|
|
149
|
+
|
|
150
|
+
Parameters:
|
|
151
|
+
- s: Underlying price
|
|
152
|
+
- k: Strike price
|
|
153
|
+
- r: Risk-free rate
|
|
154
|
+
- vol: Volatility
|
|
155
|
+
- t: Time to expiry in years
|
|
156
|
+
|
|
157
|
+
Returns:
|
|
158
|
+
- Vega value (for 1% change in volatility)
|
|
159
|
+
"""
|
|
160
|
+
return vega(s, k, r, vol, t)
|
|
161
|
+
|
|
162
|
+
@staticmethod
|
|
163
|
+
def theta(s: float, k: float, r: float, vol: float, t: float,
|
|
164
|
+
option_type: str = 'call') -> float:
|
|
165
|
+
"""
|
|
166
|
+
Calculate option theta.
|
|
167
|
+
|
|
168
|
+
Parameters:
|
|
169
|
+
- s: Underlying price
|
|
170
|
+
- k: Strike price
|
|
171
|
+
- r: Risk-free rate
|
|
172
|
+
- vol: Volatility
|
|
173
|
+
- t: Time to expiry in years
|
|
174
|
+
- option_type: 'call' or 'put'
|
|
175
|
+
|
|
176
|
+
Returns:
|
|
177
|
+
- Theta value (per day)
|
|
178
|
+
"""
|
|
179
|
+
return theta(s, k, r, vol, t, option_type)
|
|
180
|
+
|
|
181
|
+
@staticmethod
|
|
182
|
+
def rho(s: float, k: float, r: float, vol: float, t: float,
|
|
183
|
+
option_type: str = 'call') -> float:
|
|
184
|
+
"""
|
|
185
|
+
Calculate option rho.
|
|
186
|
+
|
|
187
|
+
Parameters:
|
|
188
|
+
- s: Underlying price
|
|
189
|
+
- k: Strike price
|
|
190
|
+
- r: Risk-free rate
|
|
191
|
+
- vol: Volatility
|
|
192
|
+
- t: Time to expiry in years
|
|
193
|
+
- option_type: 'call' or 'put'
|
|
194
|
+
|
|
195
|
+
Returns:
|
|
196
|
+
- Rho value (for 1% change in interest rate)
|
|
197
|
+
"""
|
|
198
|
+
return rho(s, k, r, vol, t, option_type)
|
|
199
|
+
|
|
200
|
+
@staticmethod
|
|
201
|
+
def vanna(s: float, k: float, r: float, vol: float, t: float) -> float:
|
|
202
|
+
"""
|
|
203
|
+
Calculate option vanna.
|
|
204
|
+
|
|
205
|
+
Parameters:
|
|
206
|
+
- s: Underlying price
|
|
207
|
+
- k: Strike price
|
|
208
|
+
- r: Risk-free rate
|
|
209
|
+
- vol: Volatility
|
|
210
|
+
- t: Time to expiry in years
|
|
211
|
+
|
|
212
|
+
Returns:
|
|
213
|
+
- Vanna value
|
|
214
|
+
"""
|
|
215
|
+
return vanna(s, k, r, vol, t)
|
|
216
|
+
|
|
217
|
+
@staticmethod
|
|
218
|
+
def volga(s: float, k: float, r: float, vol: float, t: float) -> float:
|
|
219
|
+
"""
|
|
220
|
+
Calculate option volga (vomma).
|
|
221
|
+
|
|
222
|
+
Parameters:
|
|
223
|
+
- s: Underlying price
|
|
224
|
+
- k: Strike price
|
|
225
|
+
- r: Risk-free rate
|
|
226
|
+
- vol: Volatility
|
|
227
|
+
- t: Time to expiry in years
|
|
228
|
+
|
|
229
|
+
Returns:
|
|
230
|
+
- Volga value
|
|
231
|
+
"""
|
|
232
|
+
return volga(s, k, r, vol, t)
|
|
233
|
+
|
|
234
|
+
@staticmethod
|
|
235
|
+
def charm(s: float, k: float, r: float, vol: float, t: float,
|
|
236
|
+
option_type: str = 'call') -> float:
|
|
237
|
+
"""
|
|
238
|
+
Calculate option charm (delta decay).
|
|
239
|
+
|
|
240
|
+
Parameters:
|
|
241
|
+
- s: Underlying price
|
|
242
|
+
- k: Strike price
|
|
243
|
+
- r: Risk-free rate
|
|
244
|
+
- vol: Volatility
|
|
245
|
+
- t: Time to expiry in years
|
|
246
|
+
- option_type: 'call' or 'put'
|
|
247
|
+
|
|
248
|
+
Returns:
|
|
249
|
+
- Charm value (per day)
|
|
250
|
+
"""
|
|
251
|
+
return charm(s, k, r, vol, t, option_type)
|
|
252
|
+
|
|
253
|
+
@staticmethod
|
|
254
|
+
def greeks(s: float, k: float, r: float, vol: float, t: float,
|
|
255
|
+
option_type: str = 'call') -> Dict[str, float]:
|
|
256
|
+
"""
|
|
257
|
+
Calculate all option Greeks.
|
|
258
|
+
|
|
259
|
+
Parameters:
|
|
260
|
+
- s: Underlying price
|
|
261
|
+
- k: Strike price
|
|
262
|
+
- r: Risk-free rate
|
|
263
|
+
- vol: Volatility
|
|
264
|
+
- t: Time to expiry in years
|
|
265
|
+
- option_type: 'call' or 'put'
|
|
266
|
+
|
|
267
|
+
Returns:
|
|
268
|
+
- Dictionary with all Greeks (price, delta, gamma, vega, theta, rho, vanna, volga, charm)
|
|
269
|
+
"""
|
|
270
|
+
return greeks(s, k, r, vol, t, option_type)
|
|
271
|
+
|
|
272
|
+
@staticmethod
|
|
273
|
+
def iv(option_price: float, s: float, k: float, r: float, t: float,
|
|
274
|
+
option_type: str = 'call') -> float:
|
|
275
|
+
"""
|
|
276
|
+
Calculate implied volatility.
|
|
277
|
+
|
|
278
|
+
Parameters:
|
|
279
|
+
- option_price: Market price of the option
|
|
280
|
+
- s: Underlying price
|
|
281
|
+
- k: Strike price
|
|
282
|
+
- r: Risk-free rate
|
|
283
|
+
- t: Time to expiry in years
|
|
284
|
+
- option_type: 'call' or 'put'
|
|
285
|
+
|
|
286
|
+
Returns:
|
|
287
|
+
- Implied volatility
|
|
288
|
+
"""
|
|
289
|
+
return iv(option_price, s, k, r, vol=None, t=t, option_type=option_type)
|
|
290
|
+
|
|
291
|
+
# -------------------------------------------------------------------------
|
|
292
|
+
# Model Fitting
|
|
293
|
+
# -------------------------------------------------------------------------
|
|
294
|
+
|
|
295
|
+
@staticmethod
|
|
296
|
+
def fit_model(market_data: pd.DataFrame,
|
|
297
|
+
model_type: str = 'svi',
|
|
298
|
+
moneyness_range: Tuple[float, float] = (-2, 2),
|
|
299
|
+
num_points: int = 500,
|
|
300
|
+
plot: bool = False) -> Dict[str, Any]:
|
|
301
|
+
"""
|
|
302
|
+
Fit a volatility model to market data.
|
|
303
|
+
|
|
304
|
+
Parameters:
|
|
305
|
+
- market_data: DataFrame with market data
|
|
306
|
+
- model_type: Type 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
|
|
310
|
+
|
|
311
|
+
Returns:
|
|
312
|
+
- Dictionary with fitting results and optional plots
|
|
313
|
+
"""
|
|
314
|
+
logger.info(f"Fitting {model_type.upper()} model to market data")
|
|
315
|
+
|
|
316
|
+
# Fit the model
|
|
317
|
+
fit_results = fit_model(
|
|
318
|
+
market_data=market_data,
|
|
319
|
+
model_type=model_type,
|
|
320
|
+
moneyness_range=moneyness_range,
|
|
321
|
+
num_points=num_points
|
|
322
|
+
)
|
|
323
|
+
|
|
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
|
+
return fit_results
|
|
331
|
+
|
|
332
|
+
# -------------------------------------------------------------------------
|
|
333
|
+
# Risk-Neutral Density (RND)
|
|
334
|
+
# -------------------------------------------------------------------------
|
|
335
|
+
|
|
336
|
+
@staticmethod
|
|
337
|
+
def rnd(fit_results: Dict[str, Any],
|
|
338
|
+
maturity: Optional[str] = None,
|
|
339
|
+
spot_price: float = 1.0,
|
|
340
|
+
plot: bool = False) -> Dict[str, Any]:
|
|
341
|
+
"""
|
|
342
|
+
Calculate risk-neutral density from fitted model.
|
|
343
|
+
|
|
344
|
+
Parameters:
|
|
345
|
+
- fit_results: Dictionary with fitting results from fit_model()
|
|
346
|
+
- maturity: Optional maturity name to calculate RND for a specific expiry
|
|
347
|
+
- spot_price: Current spot price
|
|
348
|
+
- plot: Whether to generate and return plots
|
|
349
|
+
|
|
350
|
+
Returns:
|
|
351
|
+
- Dictionary with RND results and optional plots
|
|
352
|
+
"""
|
|
353
|
+
logger.info("Calculating risk-neutral density")
|
|
354
|
+
|
|
355
|
+
# Calculate RND
|
|
356
|
+
rnd_results = calculate_rnd(fit_results, maturity, spot_price)
|
|
357
|
+
|
|
358
|
+
# Generate plots if requested
|
|
359
|
+
if plot:
|
|
360
|
+
logger.info("Generating RND plots")
|
|
361
|
+
plots = generate_all_plots(fit_results, rnd_results)
|
|
362
|
+
rnd_results['plots'] = plots
|
|
363
|
+
|
|
364
|
+
return rnd_results
|
|
365
|
+
|
|
366
|
+
@staticmethod
|
|
367
|
+
def pdf(rnd_results: Dict[str, Any],
|
|
368
|
+
maturity: Optional[str] = None,
|
|
369
|
+
plot: bool = False) -> Tuple[np.ndarray, np.ndarray]:
|
|
370
|
+
"""
|
|
371
|
+
Calculate probability density function (PDF) from RND results.
|
|
372
|
+
|
|
373
|
+
Parameters:
|
|
374
|
+
- rnd_results: Dictionary with RND results from rnd()
|
|
375
|
+
- maturity: Optional maturity name for a specific expiry
|
|
376
|
+
- plot: Whether to generate and return a plot
|
|
377
|
+
|
|
378
|
+
Returns:
|
|
379
|
+
- Tuple of (prices, pdf_values) and optional plot
|
|
380
|
+
"""
|
|
381
|
+
logger.info("Calculating PDF from RND")
|
|
382
|
+
|
|
383
|
+
# Extract required data
|
|
384
|
+
moneyness_grid = rnd_results['moneyness_grid']
|
|
385
|
+
rnd_surface = rnd_results['rnd_surface']
|
|
386
|
+
spot_price = rnd_results['spot_price']
|
|
387
|
+
|
|
388
|
+
# Select maturity
|
|
389
|
+
if maturity is None:
|
|
390
|
+
# Use first maturity if not specified
|
|
391
|
+
maturity = list(rnd_surface.keys())[0]
|
|
392
|
+
elif maturity not in rnd_surface:
|
|
393
|
+
raise ValidationError(f"Maturity '{maturity}' not found in RND results")
|
|
394
|
+
|
|
395
|
+
# Get RND values for the selected maturity
|
|
396
|
+
rnd_values = rnd_surface[maturity]
|
|
397
|
+
|
|
398
|
+
# Calculate PDF
|
|
399
|
+
prices, pdf_values = calculate_pdf(moneyness_grid, rnd_values, spot_price)
|
|
400
|
+
|
|
401
|
+
result = (prices, pdf_values)
|
|
402
|
+
|
|
403
|
+
# Generate plot if requested
|
|
404
|
+
if plot:
|
|
405
|
+
logger.info(f"Generating PDF plot for {maturity}")
|
|
406
|
+
pdf_plot = plot_pdf(
|
|
407
|
+
moneyness_grid, rnd_values, spot_price,
|
|
408
|
+
title=f"Probability Density Function - {maturity}"
|
|
409
|
+
)
|
|
410
|
+
result = (prices, pdf_values, pdf_plot)
|
|
411
|
+
|
|
412
|
+
return result
|
|
413
|
+
|
|
414
|
+
@staticmethod
|
|
415
|
+
def cdf(rnd_results: Dict[str, Any],
|
|
416
|
+
maturity: Optional[str] = None,
|
|
417
|
+
plot: bool = False) -> Tuple[np.ndarray, np.ndarray]:
|
|
418
|
+
"""
|
|
419
|
+
Calculate cumulative distribution function (CDF) from RND results.
|
|
420
|
+
|
|
421
|
+
Parameters:
|
|
422
|
+
- rnd_results: Dictionary with RND results from rnd()
|
|
423
|
+
- maturity: Optional maturity name for a specific expiry
|
|
424
|
+
- plot: Whether to generate and return a plot
|
|
425
|
+
|
|
426
|
+
Returns:
|
|
427
|
+
- Tuple of (prices, cdf_values) and optional plot
|
|
428
|
+
"""
|
|
429
|
+
logger.info("Calculating CDF from RND")
|
|
430
|
+
|
|
431
|
+
# Extract required data
|
|
432
|
+
moneyness_grid = rnd_results['moneyness_grid']
|
|
433
|
+
rnd_surface = rnd_results['rnd_surface']
|
|
434
|
+
spot_price = rnd_results['spot_price']
|
|
435
|
+
|
|
436
|
+
# Select maturity
|
|
437
|
+
if maturity is None:
|
|
438
|
+
# Use first maturity if not specified
|
|
439
|
+
maturity = list(rnd_surface.keys())[0]
|
|
440
|
+
elif maturity not in rnd_surface:
|
|
441
|
+
raise ValidationError(f"Maturity '{maturity}' not found in RND results")
|
|
442
|
+
|
|
443
|
+
# Get RND values for the selected maturity
|
|
444
|
+
rnd_values = rnd_surface[maturity]
|
|
445
|
+
|
|
446
|
+
# Calculate CDF
|
|
447
|
+
prices, cdf_values = calculate_cdf(moneyness_grid, rnd_values, spot_price)
|
|
448
|
+
|
|
449
|
+
result = (prices, cdf_values)
|
|
450
|
+
|
|
451
|
+
# Generate plot if requested
|
|
452
|
+
if plot:
|
|
453
|
+
logger.info(f"Generating CDF plot for {maturity}")
|
|
454
|
+
cdf_plot = plot_cdf(
|
|
455
|
+
moneyness_grid, rnd_values, spot_price,
|
|
456
|
+
title=f"Cumulative Distribution Function - {maturity}"
|
|
457
|
+
)
|
|
458
|
+
result = (prices, cdf_values, cdf_plot)
|
|
459
|
+
|
|
460
|
+
return result
|
|
461
|
+
|
|
462
|
+
@staticmethod
|
|
463
|
+
def probability(rnd_results: Dict[str, Any],
|
|
464
|
+
target_price: float,
|
|
465
|
+
maturity: Optional[str] = None,
|
|
466
|
+
direction: str = 'above') -> float:
|
|
467
|
+
"""
|
|
468
|
+
Calculate the probability of price being above or below a target price.
|
|
469
|
+
|
|
470
|
+
Parameters:
|
|
471
|
+
- rnd_results: Dictionary with RND results from rnd()
|
|
472
|
+
- target_price: Target price level
|
|
473
|
+
- maturity: Optional maturity name for a specific expiry
|
|
474
|
+
- direction: 'above' or 'below'
|
|
475
|
+
|
|
476
|
+
Returns:
|
|
477
|
+
- Probability (0 to 1)
|
|
478
|
+
"""
|
|
479
|
+
if direction not in ['above', 'below']:
|
|
480
|
+
raise ValidationError("Direction must be 'above' or 'below'")
|
|
481
|
+
|
|
482
|
+
# Extract required data
|
|
483
|
+
moneyness_grid = rnd_results['moneyness_grid']
|
|
484
|
+
rnd_surface = rnd_results['rnd_surface']
|
|
485
|
+
spot_price = rnd_results['spot_price']
|
|
486
|
+
|
|
487
|
+
# Select maturity
|
|
488
|
+
if maturity is None:
|
|
489
|
+
# Use first maturity if not specified
|
|
490
|
+
maturity = list(rnd_surface.keys())[0]
|
|
491
|
+
elif maturity not in rnd_surface:
|
|
492
|
+
raise ValidationError(f"Maturity '{maturity}' not found in RND results")
|
|
493
|
+
|
|
494
|
+
# Get RND values for the selected maturity
|
|
495
|
+
rnd_values = rnd_surface[maturity]
|
|
496
|
+
|
|
497
|
+
# Calculate probability
|
|
498
|
+
prob = calculate_strike_probability(
|
|
499
|
+
target_price, moneyness_grid, rnd_values, spot_price, direction
|
|
500
|
+
)
|
|
501
|
+
|
|
502
|
+
return prob
|
|
503
|
+
|
|
504
|
+
# -------------------------------------------------------------------------
|
|
505
|
+
# Interpolation
|
|
506
|
+
# -------------------------------------------------------------------------
|
|
507
|
+
|
|
508
|
+
@staticmethod
|
|
509
|
+
def interpolate(fit_results: Dict[str, Any],
|
|
510
|
+
specific_days: Optional[List[int]] = None,
|
|
511
|
+
num_points: int = 10,
|
|
512
|
+
method: str = 'cubic',
|
|
513
|
+
plot: bool = False) -> Dict[str, Any]:
|
|
514
|
+
"""
|
|
515
|
+
Interpolate a fitted model to specific days to expiry.
|
|
516
|
+
|
|
517
|
+
Parameters:
|
|
518
|
+
- fit_results: Dictionary with fitting results from fit_model()
|
|
519
|
+
- specific_days: Optional list of specific days to include (e.g., [7, 30, 90, 180])
|
|
520
|
+
- num_points: Number of points for regular grid if specific_days is None
|
|
521
|
+
- method: Interpolation method ('linear', 'cubic', 'pchip', etc.)
|
|
522
|
+
- plot: Whether to generate and return a plot
|
|
523
|
+
|
|
524
|
+
Returns:
|
|
525
|
+
- Dictionary with interpolation results and optional plot
|
|
526
|
+
"""
|
|
527
|
+
logger.info(f"Interpolating model with {method} method")
|
|
528
|
+
|
|
529
|
+
# Interpolate the model
|
|
530
|
+
interp_results = interpolate_model(
|
|
531
|
+
fit_results, specific_days, num_points, method
|
|
532
|
+
)
|
|
533
|
+
|
|
534
|
+
# Generate plot if requested
|
|
535
|
+
if plot:
|
|
536
|
+
logger.info("Generating interpolated surface plot")
|
|
537
|
+
interp_plot = plot_interpolated_surface(interp_results)
|
|
538
|
+
interp_results['plot'] = interp_plot
|
|
539
|
+
|
|
540
|
+
return interp_results
|
voly/core/__init__.py
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Core functionality for the Voly package.
|
|
3
|
+
|
|
4
|
+
This module contains the core functions for data fetching,
|
|
5
|
+
model fitting, surface interpolation, and risk-neutral density
|
|
6
|
+
estimation.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from voly.core.data import get_deribit_data, process_option_chain
|
|
10
|
+
from voly.core.fit import optimize_svi_parameters, create_parameters_matrix
|
|
11
|
+
from voly.core.rnd import calculate_risk_neutral_density
|