voly 0.0.87__tar.gz → 0.0.90__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.87/src/voly.egg-info → voly-0.0.90}/PKG-INFO +1 -1
- {voly-0.0.87 → voly-0.0.90}/pyproject.toml +2 -2
- {voly-0.0.87 → voly-0.0.90}/src/voly/client.py +2 -3
- {voly-0.0.87 → voly-0.0.90}/src/voly/core/fit.py +1 -1
- {voly-0.0.87 → voly-0.0.90}/src/voly/core/rnd.py +1 -1
- {voly-0.0.87 → voly-0.0.90}/src/voly/models.py +0 -4
- {voly-0.0.87 → voly-0.0.90/src/voly.egg-info}/PKG-INFO +1 -1
- {voly-0.0.87 → voly-0.0.90}/src/voly.egg-info/SOURCES.txt +1 -2
- voly-0.0.87/tests/test_client.py +0 -244
- {voly-0.0.87 → voly-0.0.90}/LICENSE +0 -0
- {voly-0.0.87 → voly-0.0.90}/README.md +0 -0
- {voly-0.0.87 → voly-0.0.90}/setup.cfg +0 -0
- {voly-0.0.87 → voly-0.0.90}/setup.py +0 -0
- {voly-0.0.87 → voly-0.0.90}/src/voly/__init__.py +0 -0
- {voly-0.0.87 → voly-0.0.90}/src/voly/core/__init__.py +0 -0
- {voly-0.0.87 → voly-0.0.90}/src/voly/core/charts.py +0 -0
- {voly-0.0.87 → voly-0.0.90}/src/voly/core/data.py +0 -0
- {voly-0.0.87 → voly-0.0.90}/src/voly/core/interpolate.py +0 -0
- {voly-0.0.87 → voly-0.0.90}/src/voly/exceptions.py +0 -0
- {voly-0.0.87 → voly-0.0.90}/src/voly/formulas.py +0 -0
- {voly-0.0.87 → voly-0.0.90}/src/voly/utils/__init__.py +0 -0
- {voly-0.0.87 → voly-0.0.90}/src/voly/utils/logger.py +0 -0
- {voly-0.0.87 → voly-0.0.90}/src/voly.egg-info/dependency_links.txt +0 -0
- {voly-0.0.87 → voly-0.0.90}/src/voly.egg-info/requires.txt +0 -0
- {voly-0.0.87 → voly-0.0.90}/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.90"
|
|
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.90"
|
|
64
64
|
warn_return_any = true
|
|
65
65
|
warn_unused_configs = true
|
|
66
66
|
disallow_untyped_defs = true
|
|
@@ -19,12 +19,11 @@ from voly.formulas import (
|
|
|
19
19
|
)
|
|
20
20
|
from voly.core.data import fetch_option_chain, process_option_chain
|
|
21
21
|
from voly.core.fit import fit_model, get_iv_surface
|
|
22
|
-
from voly.core.rnd import get_rnd_surface
|
|
22
|
+
from voly.core.rnd import get_rnd_surface
|
|
23
23
|
from voly.core.interpolate import interpolate_model
|
|
24
24
|
from voly.core.charts import (
|
|
25
25
|
plot_all_smiles, plot_raw_parameters, plot_jw_parameters, plot_fit_performance, plot_3d_surface,
|
|
26
|
-
plot_fit_performance
|
|
27
|
-
plot_rnd_3d, plot_rnd_statistics, plot_interpolated_surface
|
|
26
|
+
plot_fit_performance
|
|
28
27
|
)
|
|
29
28
|
|
|
30
29
|
|
|
@@ -11,7 +11,7 @@ from typing import List, Tuple, Dict, Optional, Union, Any
|
|
|
11
11
|
from scipy.optimize import least_squares
|
|
12
12
|
from sklearn.metrics import mean_squared_error, mean_absolute_error, r2_score
|
|
13
13
|
from voly.utils.logger import logger, catch_exception
|
|
14
|
-
from voly.formulas import
|
|
14
|
+
from voly.formulas import get_domain
|
|
15
15
|
from voly.exceptions import VolyError
|
|
16
16
|
from voly.models import SVIModel
|
|
17
17
|
import warnings
|
|
@@ -9,7 +9,7 @@ from typing import Dict, List, Tuple, Optional, Union, Any
|
|
|
9
9
|
from voly.utils.logger import logger, catch_exception
|
|
10
10
|
from voly.exceptions import VolyError
|
|
11
11
|
from voly.models import SVIModel
|
|
12
|
-
from voly.formulas import
|
|
12
|
+
from voly.formulas import get_domain
|
|
13
13
|
|
|
14
14
|
|
|
15
15
|
# Breeden-Litzenberger Method
|
|
@@ -5,10 +5,6 @@ Volatility models for the Voly package.
|
|
|
5
5
|
import numpy as np
|
|
6
6
|
from typing import Tuple, Dict, List, Optional, Union
|
|
7
7
|
|
|
8
|
-
# Configuration settings
|
|
9
|
-
DEFAULT_MONEYNESS_RANGE = (-2, 2)
|
|
10
|
-
DEFAULT_MONEYNESS_POINTS = 500
|
|
11
|
-
|
|
12
8
|
class SVIModel:
|
|
13
9
|
"""
|
|
14
10
|
Stochastic Volatility Inspired (SVI) model.
|
voly-0.0.87/tests/test_client.py
DELETED
|
@@ -1,244 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Comprehensive tests for the VolyClient class with detailed output.
|
|
3
|
-
This file demonstrates expected values and provides informative output.
|
|
4
|
-
"""
|
|
5
|
-
|
|
6
|
-
import unittest
|
|
7
|
-
import numpy as np
|
|
8
|
-
from voly import VolyClient
|
|
9
|
-
import sys
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
class VolyClientTestCase(unittest.TestCase):
|
|
13
|
-
"""
|
|
14
|
-
Test cases for the VolyClient class with expected values and detailed output.
|
|
15
|
-
"""
|
|
16
|
-
|
|
17
|
-
def setUp(self):
|
|
18
|
-
"""Set up test fixtures."""
|
|
19
|
-
self.voly = VolyClient()
|
|
20
|
-
# Add a divider line before each test for clearer output
|
|
21
|
-
print("\n" + "=" * 80)
|
|
22
|
-
|
|
23
|
-
def test_bs_pricing_with_expected_values(self):
|
|
24
|
-
"""Test Black-Scholes pricing with expected values."""
|
|
25
|
-
print("\nTEST: Black-Scholes Pricing")
|
|
26
|
-
|
|
27
|
-
# Test parameters
|
|
28
|
-
s = 100.0
|
|
29
|
-
k = 100.0
|
|
30
|
-
r = 0.05
|
|
31
|
-
vol = 0.2
|
|
32
|
-
t = 1.0
|
|
33
|
-
|
|
34
|
-
# Expected values (pre-calculated)
|
|
35
|
-
expected_call = 10.45
|
|
36
|
-
expected_put = 5.57
|
|
37
|
-
|
|
38
|
-
# Calculate actual values
|
|
39
|
-
actual_call = self.voly.bs(s=s, k=k, r=r, vol=vol, t=t, option_type='call')
|
|
40
|
-
actual_put = self.voly.bs(s=s, k=k, r=r, vol=vol, t=t, option_type='put')
|
|
41
|
-
|
|
42
|
-
# Print actual vs expected
|
|
43
|
-
print(f"Parameters: S={s}, K={k}, r={r}, vol={vol}, t={t}")
|
|
44
|
-
print(
|
|
45
|
-
f"Call Price: Actual={actual_call:.4f}, Expected={expected_call:.4f}, Diff={abs(actual_call - expected_call):.6f}")
|
|
46
|
-
print(
|
|
47
|
-
f"Put Price: Actual={actual_put:.4f}, Expected={expected_put:.4f}, Diff={abs(actual_put - expected_put):.6f}")
|
|
48
|
-
|
|
49
|
-
# Check put-call parity
|
|
50
|
-
pcp_diff = actual_call - actual_put - s + k * np.exp(-r * t)
|
|
51
|
-
print(f"Put-Call Parity Check: {pcp_diff:.8f} (should be close to 0)")
|
|
52
|
-
|
|
53
|
-
# Assertions with tolerance
|
|
54
|
-
self.assertAlmostEqual(actual_call, expected_call, delta=0.01,
|
|
55
|
-
msg=f"Call price {actual_call:.4f} doesn't match expected {expected_call:.4f}")
|
|
56
|
-
self.assertAlmostEqual(actual_put, expected_put, delta=0.01,
|
|
57
|
-
msg=f"Put price {actual_put:.4f} doesn't match expected {expected_put:.4f}")
|
|
58
|
-
self.assertAlmostEqual(pcp_diff, 0, delta=1e-10,
|
|
59
|
-
msg="Put-call parity violated")
|
|
60
|
-
|
|
61
|
-
def test_delta_values_across_moneyness(self):
|
|
62
|
-
"""Test delta values across different moneyness levels."""
|
|
63
|
-
print("\nTEST: Delta Values Across Moneyness")
|
|
64
|
-
|
|
65
|
-
# Test parameters
|
|
66
|
-
s = 100.0
|
|
67
|
-
r = 0.05
|
|
68
|
-
vol = 0.2
|
|
69
|
-
t = 1.0
|
|
70
|
-
|
|
71
|
-
# Define test cases: strike, expected call delta, expected put delta
|
|
72
|
-
test_cases = [
|
|
73
|
-
(50.0, 0.9999, -0.0001), # Deep ITM call / Deep OTM put
|
|
74
|
-
(75.0, 0.9631, -0.0369), # ITM call / OTM put
|
|
75
|
-
(100.0, 0.6368, -0.3632), # ATM
|
|
76
|
-
(125.0, 0.2219, -0.7781), # OTM call / ITM put
|
|
77
|
-
(150.0, 0.0467, -0.9533) # Deep OTM call / Deep ITM put
|
|
78
|
-
]
|
|
79
|
-
|
|
80
|
-
print(f"Parameters: S={s}, r={r}, vol={vol}, t={t}")
|
|
81
|
-
print("\nDelta Values:")
|
|
82
|
-
print(f"{'Strike':<10} {'Call Delta':<15} {'Expected':<15} {'Put Delta':<15} {'Expected':<15}")
|
|
83
|
-
print("-" * 70)
|
|
84
|
-
|
|
85
|
-
for strike, exp_call_delta, exp_put_delta in test_cases:
|
|
86
|
-
call_delta = self.voly.delta(s=s, k=strike, r=r, vol=vol, t=t, option_type='call')
|
|
87
|
-
put_delta = self.voly.delta(s=s, k=strike, r=r, vol=vol, t=t, option_type='put')
|
|
88
|
-
|
|
89
|
-
print(f"{strike:<10.1f} {call_delta:<15.4f} {exp_call_delta:<15.4f} "
|
|
90
|
-
f"{put_delta:<15.4f} {exp_put_delta:<15.4f}")
|
|
91
|
-
|
|
92
|
-
# Check deltas are within expected range
|
|
93
|
-
self.assertAlmostEqual(call_delta, exp_call_delta, delta=0.01,
|
|
94
|
-
msg=f"Call delta for K={strike} incorrect")
|
|
95
|
-
self.assertAlmostEqual(put_delta, exp_put_delta, delta=0.01,
|
|
96
|
-
msg=f"Put delta for K={strike} incorrect")
|
|
97
|
-
|
|
98
|
-
# Check put-call delta relationship: call_delta - put_delta = 1
|
|
99
|
-
self.assertAlmostEqual(call_delta - put_delta, 1.0, delta=1e-10,
|
|
100
|
-
msg="Delta relationship violated")
|
|
101
|
-
|
|
102
|
-
def test_all_greeks_values(self):
|
|
103
|
-
"""Test all Greeks calculation with expected values."""
|
|
104
|
-
print("\nTEST: All Greeks Values")
|
|
105
|
-
|
|
106
|
-
# Test parameters
|
|
107
|
-
s = 100.0
|
|
108
|
-
k = 100.0
|
|
109
|
-
r = 0.05
|
|
110
|
-
vol = 0.2
|
|
111
|
-
t = 1.0
|
|
112
|
-
|
|
113
|
-
# Calculate all Greeks for a call
|
|
114
|
-
call_greeks = self.voly.greeks(s=s, k=k, r=r, vol=vol, t=t, option_type='call')
|
|
115
|
-
|
|
116
|
-
# Expected values (calculated using standard Black-Scholes formulas)
|
|
117
|
-
expected_greeks = {
|
|
118
|
-
'price': 10.45,
|
|
119
|
-
'delta': 0.637,
|
|
120
|
-
'gamma': 0.019,
|
|
121
|
-
'vega': 0.375,
|
|
122
|
-
'theta': -0.018,
|
|
123
|
-
'rho': 0.52,
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
print(f"Parameters: S={s}, K={k}, r={r}, vol={vol}, t={t}")
|
|
127
|
-
print("\nCall Option Greeks:")
|
|
128
|
-
print(f"{'Greek':<10} {'Actual':<15} {'Expected':<15} {'Diff':<15}")
|
|
129
|
-
print("-" * 55)
|
|
130
|
-
|
|
131
|
-
for greek, expected in expected_greeks.items():
|
|
132
|
-
actual = call_greeks[greek]
|
|
133
|
-
print(f"{greek.capitalize():<10} {actual:<15.6f} {expected:<15.6f} {abs(actual - expected):<15.6f}")
|
|
134
|
-
|
|
135
|
-
# Assert with appropriate tolerance
|
|
136
|
-
self.assertAlmostEqual(actual, expected, delta=max(0.01, expected * 0.05),
|
|
137
|
-
msg=f"{greek.capitalize()} value incorrect")
|
|
138
|
-
|
|
139
|
-
# Additional checks for other Greeks
|
|
140
|
-
print("\nAdditional Greeks:")
|
|
141
|
-
for greek in ['vanna', 'volga', 'charm']:
|
|
142
|
-
if greek in call_greeks:
|
|
143
|
-
print(f"{greek.capitalize():<10} {call_greeks[greek]:<15.6f}")
|
|
144
|
-
|
|
145
|
-
# Check basic relationships
|
|
146
|
-
# Gamma should be positive for both calls and puts
|
|
147
|
-
self.assertGreater(call_greeks['gamma'], 0, "Gamma should be positive")
|
|
148
|
-
|
|
149
|
-
# Vega should be positive for both calls and puts
|
|
150
|
-
self.assertGreater(call_greeks['vega'], 0, "Vega should be positive")
|
|
151
|
-
|
|
152
|
-
# Theta is typically negative for calls and puts (time decay)
|
|
153
|
-
self.assertLess(call_greeks['theta'], 0, "Theta should be negative for calls")
|
|
154
|
-
|
|
155
|
-
def test_implied_volatility_calculation(self):
|
|
156
|
-
"""Test implied volatility calculation with known prices."""
|
|
157
|
-
print("\nTEST: Implied Volatility Calculation")
|
|
158
|
-
|
|
159
|
-
# Define test cases
|
|
160
|
-
test_cases = [
|
|
161
|
-
# S, K, r, vol, t
|
|
162
|
-
(100.0, 100.0, 0.05, 0.2, 1.0), # ATM
|
|
163
|
-
(100.0, 90.0, 0.05, 0.25, 0.5), # ITM
|
|
164
|
-
(100.0, 110.0, 0.05, 0.3, 0.25) # OTM
|
|
165
|
-
]
|
|
166
|
-
|
|
167
|
-
print(f"{'S':<8} {'K':<8} {'r':<8} {'t':<8} {'Input Vol':<12} {'Option Price':<15} "
|
|
168
|
-
f"{'Implied Vol':<15} {'Diff':<10}")
|
|
169
|
-
print("-" * 90)
|
|
170
|
-
|
|
171
|
-
for s, k, r, vol, t in test_cases:
|
|
172
|
-
# Calculate option price with known volatility
|
|
173
|
-
call_price = self.voly.bs(s=s, k=k, r=r, vol=vol, t=t, option_type='call')
|
|
174
|
-
|
|
175
|
-
# Calculate implied volatility from the price
|
|
176
|
-
try:
|
|
177
|
-
implied_vol = self.voly.iv(option_price=call_price, s=s, k=k, r=r, t=t, option_type='call')
|
|
178
|
-
vol_diff = abs(vol - implied_vol)
|
|
179
|
-
print(f"{s:<8.1f} {k:<8.1f} {r:<8.3f} {t:<8.2f} {vol:<12.4f} {call_price:<15.6f} "
|
|
180
|
-
f"{implied_vol:<15.6f} {vol_diff:<10.6f}")
|
|
181
|
-
|
|
182
|
-
# Assert implied vol matches input vol
|
|
183
|
-
self.assertAlmostEqual(vol, implied_vol, delta=0.0001,
|
|
184
|
-
msg=f"Implied volatility {implied_vol:.6f} doesn't match input {vol:.6f}")
|
|
185
|
-
except Exception as e:
|
|
186
|
-
print(f"{s:<8.1f} {k:<8.1f} {r:<8.3f} {t:<8.2f} {vol:<12.4f} {call_price:<15.6f} "
|
|
187
|
-
f"ERROR: {str(e)}")
|
|
188
|
-
self.fail(f"Implied volatility calculation failed: {str(e)}")
|
|
189
|
-
|
|
190
|
-
def test_bs_pricing_extreme_cases(self):
|
|
191
|
-
"""Test Black-Scholes pricing under extreme conditions."""
|
|
192
|
-
print("\nTEST: Black-Scholes Pricing - Extreme Cases")
|
|
193
|
-
|
|
194
|
-
# Test zero volatility
|
|
195
|
-
zero_vol_call = self.voly.bs(s=100, k=90, r=0.05, vol=0, t=1, option_type='call')
|
|
196
|
-
zero_vol_put = self.voly.bs(s=100, k=110, r=0.05, vol=0, t=1, option_type='put')
|
|
197
|
-
|
|
198
|
-
print("Zero Volatility:")
|
|
199
|
-
print(f"Call (S=100, K=90): {zero_vol_call:.4f} (should equal intrinsic value 10)")
|
|
200
|
-
print(f"Put (S=100, K=110): {zero_vol_put:.4f} (should equal intrinsic value 10)")
|
|
201
|
-
|
|
202
|
-
self.assertAlmostEqual(zero_vol_call, 10.0, delta=0.01,
|
|
203
|
-
msg="Zero vol ITM call should equal intrinsic value")
|
|
204
|
-
self.assertAlmostEqual(zero_vol_put, 10.0, delta=0.01,
|
|
205
|
-
msg="Zero vol ITM put should equal intrinsic value")
|
|
206
|
-
|
|
207
|
-
# Test zero time to expiry
|
|
208
|
-
zero_time_call = self.voly.bs(s=100, k=90, r=0.05, vol=0.2, t=0, option_type='call')
|
|
209
|
-
zero_time_put = self.voly.bs(s=100, k=110, r=0.05, vol=0.2, t=0, option_type='put')
|
|
210
|
-
|
|
211
|
-
print("\nZero Time to Expiry:")
|
|
212
|
-
print(f"Call (S=100, K=90): {zero_time_call:.4f} (should equal intrinsic value 10)")
|
|
213
|
-
print(f"Put (S=100, K=110): {zero_time_put:.4f} (should equal intrinsic value 10)")
|
|
214
|
-
|
|
215
|
-
self.assertAlmostEqual(zero_time_call, 10.0, delta=0.01,
|
|
216
|
-
msg="Zero time ITM call should equal intrinsic value")
|
|
217
|
-
self.assertAlmostEqual(zero_time_put, 10.0, delta=0.01,
|
|
218
|
-
msg="Zero time ITM put should equal intrinsic value")
|
|
219
|
-
|
|
220
|
-
# Test deep ITM and OTM
|
|
221
|
-
deep_itm_call = self.voly.bs(s=100, k=50, r=0.05, vol=0.2, t=1, option_type='call')
|
|
222
|
-
deep_otm_call = self.voly.bs(s=100, k=200, r=0.05, vol=0.2, t=1, option_type='call')
|
|
223
|
-
|
|
224
|
-
print("\nDeep ITM/OTM:")
|
|
225
|
-
print(f"Deep ITM Call (S=100, K=50): {deep_itm_call:.4f}")
|
|
226
|
-
print(f"Deep OTM Call (S=100, K=200): {deep_otm_call:.4f}")
|
|
227
|
-
|
|
228
|
-
self.assertGreater(deep_itm_call, 50.0,
|
|
229
|
-
msg="Deep ITM call should be greater than intrinsic value")
|
|
230
|
-
self.assertGreater(deep_otm_call, 0.0,
|
|
231
|
-
msg="Deep OTM call should have positive value")
|
|
232
|
-
self.assertLess(deep_otm_call, 5.0,
|
|
233
|
-
msg="Deep OTM call should have small value")
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
if __name__ == '__main__':
|
|
237
|
-
# More detailed output
|
|
238
|
-
print("\nVOLY CLIENT DETAILED TEST SUITE")
|
|
239
|
-
print("=" * 80)
|
|
240
|
-
print(f"Testing against expected values for options pricing and Greeks")
|
|
241
|
-
print("-" * 80)
|
|
242
|
-
|
|
243
|
-
# Run tests with more verbose output
|
|
244
|
-
unittest.main(verbosity=2)
|
|
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
|