voly 0.0.104__tar.gz → 0.0.105__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.
- {voly-0.0.104/src/voly.egg-info → voly-0.0.105}/PKG-INFO +1 -1
- {voly-0.0.104 → voly-0.0.105}/pyproject.toml +2 -2
- {voly-0.0.104 → voly-0.0.105}/src/voly/client.py +4 -0
- {voly-0.0.104 → voly-0.0.105}/src/voly/formulas.py +48 -0
- {voly-0.0.104 → voly-0.0.105/src/voly.egg-info}/PKG-INFO +1 -1
- {voly-0.0.104 → voly-0.0.105}/LICENSE +0 -0
- {voly-0.0.104 → voly-0.0.105}/README.md +0 -0
- {voly-0.0.104 → voly-0.0.105}/setup.cfg +0 -0
- {voly-0.0.104 → voly-0.0.105}/setup.py +0 -0
- {voly-0.0.104 → voly-0.0.105}/src/voly/__init__.py +0 -0
- {voly-0.0.104 → voly-0.0.105}/src/voly/core/__init__.py +0 -0
- {voly-0.0.104 → voly-0.0.105}/src/voly/core/charts.py +0 -0
- {voly-0.0.104 → voly-0.0.105}/src/voly/core/data.py +0 -0
- {voly-0.0.104 → voly-0.0.105}/src/voly/core/fit.py +0 -0
- {voly-0.0.104 → voly-0.0.105}/src/voly/core/interpolate.py +0 -0
- {voly-0.0.104 → voly-0.0.105}/src/voly/core/rnd.py +0 -0
- {voly-0.0.104 → voly-0.0.105}/src/voly/exceptions.py +0 -0
- {voly-0.0.104 → voly-0.0.105}/src/voly/models.py +0 -0
- {voly-0.0.104 → voly-0.0.105}/src/voly/utils/__init__.py +0 -0
- {voly-0.0.104 → voly-0.0.105}/src/voly/utils/logger.py +0 -0
- {voly-0.0.104 → voly-0.0.105}/src/voly.egg-info/SOURCES.txt +0 -0
- {voly-0.0.104 → voly-0.0.105}/src/voly.egg-info/dependency_links.txt +0 -0
- {voly-0.0.104 → voly-0.0.105}/src/voly.egg-info/requires.txt +0 -0
- {voly-0.0.104 → voly-0.0.105}/src/voly.egg-info/top_level.txt +0 -0
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "voly"
|
|
7
|
-
version = "0.0.
|
|
7
|
+
version = "0.0.105"
|
|
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.
|
|
63
|
+
python_version = "0.0.105"
|
|
64
64
|
warn_return_any = true
|
|
65
65
|
warn_unused_configs = true
|
|
66
66
|
disallow_untyped_defs = true
|
|
@@ -155,6 +155,10 @@ class VolyClient:
|
|
|
155
155
|
option_type: str = 'call') -> float:
|
|
156
156
|
return iv(option_price, s, K, r, t, option_type)
|
|
157
157
|
|
|
158
|
+
@staticmethod
|
|
159
|
+
def pdf_to_calls(pdf_K: np.ndarray, s: float, K: np.ndarray, r: float, t: float):
|
|
160
|
+
return pdf_to_calls(pdf_K, s, K, r, t)
|
|
161
|
+
|
|
158
162
|
# -------------------------------------------------------------------------
|
|
159
163
|
# Model Fitting
|
|
160
164
|
# -------------------------------------------------------------------------
|
|
@@ -356,3 +356,51 @@ def get_domain(domain_params: Tuple[float, float, int] = (-1.5, 1.5, 1000),
|
|
|
356
356
|
else:
|
|
357
357
|
raise ValueError(
|
|
358
358
|
f"Invalid return_domain: {return_domain}. Must be one of ['log_moneyness', 'moneyness', 'returns', 'striKes', 'delta'].")
|
|
359
|
+
|
|
360
|
+
|
|
361
|
+
@catch_exception
|
|
362
|
+
def pdf_to_calls(pdf_K, s, K, r, t):
|
|
363
|
+
# Step 0: Normalize the PDF
|
|
364
|
+
dK = np.diff(K, prepend=K[0])
|
|
365
|
+
total_area = np.sum(pdf_K * dK)
|
|
366
|
+
pdf_K_normalized = pdf_K / total_area
|
|
367
|
+
|
|
368
|
+
# Step 1: Recover the second derivative
|
|
369
|
+
c2_recovered = pdf_K_normalized / np.exp(r * t)
|
|
370
|
+
|
|
371
|
+
# Step 2: Double integration using NumPy
|
|
372
|
+
# First integration (c2 -> c1)
|
|
373
|
+
c1_recovered = np.zeros_like(K)
|
|
374
|
+
for i in range(1, len(K)):
|
|
375
|
+
c1_recovered[i] = c1_recovered[i - 1] + 0.5 * (c2_recovered[i] + c2_recovered[i - 1]) * (K[i] - K[i - 1])
|
|
376
|
+
|
|
377
|
+
# Second integration (c1 -> c)
|
|
378
|
+
c_recovered_base = np.zeros_like(K)
|
|
379
|
+
for i in range(1, len(K)):
|
|
380
|
+
c_recovered_base[i] = c_recovered_base[i - 1] + 0.5 * (c1_recovered[i] + c1_recovered[i - 1]) * (
|
|
381
|
+
K[i] - K[i - 1])
|
|
382
|
+
|
|
383
|
+
# Determine the integration constants based on boundary conditions
|
|
384
|
+
# Find the lowest strike (deep ITM) and highest strike (deep OTM)
|
|
385
|
+
K_min = K[0]
|
|
386
|
+
K_max = K[-1]
|
|
387
|
+
|
|
388
|
+
# Boundary conditions:
|
|
389
|
+
# c(K_max) should be close to 0
|
|
390
|
+
# c(K_min) should be close to s - K_min*exp(-r*t)
|
|
391
|
+
c_min_target = max(0, s - K_min * np.exp(-r * t))
|
|
392
|
+
c_max_target = 0 # deep OTM call is worthless
|
|
393
|
+
|
|
394
|
+
# Solve for a and b in c_recovered = c_recovered_base + a*K + b
|
|
395
|
+
A = np.array([[1, K_min], [1, K_max]])
|
|
396
|
+
b_vec = np.array([c_min_target - c_recovered_base[0], c_max_target - c_recovered_base[-1]])
|
|
397
|
+
integration_constants = np.linalg.solve(A, b_vec)
|
|
398
|
+
|
|
399
|
+
# Apply the correction
|
|
400
|
+
ic_b, ic_a = integration_constants[0], integration_constants[1] # ic_a is slope, ic_b is intercept
|
|
401
|
+
c_recovered = c_recovered_base + ic_b + ic_a * K
|
|
402
|
+
|
|
403
|
+
# Ensure non-negative call prices
|
|
404
|
+
c_recovered = np.maximum(c_recovered, 0)
|
|
405
|
+
|
|
406
|
+
return c_recovered, ic_a, ic_b
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|