voly 0.0.95__tar.gz → 0.0.97__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.95/src/voly.egg-info → voly-0.0.97}/PKG-INFO +1 -1
- {voly-0.0.95 → voly-0.0.97}/pyproject.toml +2 -2
- {voly-0.0.95 → voly-0.0.97}/src/voly/client.py +26 -188
- {voly-0.0.95 → voly-0.0.97}/src/voly/formulas.py +88 -88
- {voly-0.0.95 → voly-0.0.97/src/voly.egg-info}/PKG-INFO +1 -1
- {voly-0.0.95 → voly-0.0.97}/LICENSE +0 -0
- {voly-0.0.95 → voly-0.0.97}/README.md +0 -0
- {voly-0.0.95 → voly-0.0.97}/setup.cfg +0 -0
- {voly-0.0.95 → voly-0.0.97}/setup.py +0 -0
- {voly-0.0.95 → voly-0.0.97}/src/voly/__init__.py +0 -0
- {voly-0.0.95 → voly-0.0.97}/src/voly/core/__init__.py +0 -0
- {voly-0.0.95 → voly-0.0.97}/src/voly/core/charts.py +0 -0
- {voly-0.0.95 → voly-0.0.97}/src/voly/core/data.py +0 -0
- {voly-0.0.95 → voly-0.0.97}/src/voly/core/fit.py +0 -0
- {voly-0.0.95 → voly-0.0.97}/src/voly/core/interpolate.py +0 -0
- {voly-0.0.95 → voly-0.0.97}/src/voly/core/rnd.py +0 -0
- {voly-0.0.95 → voly-0.0.97}/src/voly/exceptions.py +0 -0
- {voly-0.0.95 → voly-0.0.97}/src/voly/models.py +0 -0
- {voly-0.0.95 → voly-0.0.97}/src/voly/utils/__init__.py +0 -0
- {voly-0.0.95 → voly-0.0.97}/src/voly/utils/logger.py +0 -0
- {voly-0.0.95 → voly-0.0.97}/src/voly.egg-info/SOURCES.txt +0 -0
- {voly-0.0.95 → voly-0.0.97}/src/voly.egg-info/dependency_links.txt +0 -0
- {voly-0.0.95 → voly-0.0.97}/src/voly.egg-info/requires.txt +0 -0
- {voly-0.0.95 → voly-0.0.97}/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.97"
|
|
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.97"
|
|
64
64
|
warn_return_any = true
|
|
65
65
|
warn_unused_configs = true
|
|
66
66
|
disallow_untyped_defs = true
|
|
@@ -87,231 +87,69 @@ class VolyClient:
|
|
|
87
87
|
# -------------------------------------------------------------------------
|
|
88
88
|
|
|
89
89
|
@staticmethod
|
|
90
|
-
def
|
|
91
|
-
return SVIModel.svi(log_moneyness_array, a, b, sigma, rho, m)
|
|
92
|
-
|
|
93
|
-
@staticmethod
|
|
94
|
-
def svi_d(log_moneyness_array: float, a: float, b: float, sigma: float, rho: float, m: float) -> float:
|
|
95
|
-
return SVIModel.svi_d(log_moneyness_array, a, b, sigma, rho, m)
|
|
96
|
-
|
|
97
|
-
@staticmethod
|
|
98
|
-
def svi_dd(log_moneyness_array: float, a: float, b: float, sigma: float, rho: float, m: float) -> float:
|
|
99
|
-
return SVIModel.svi_dd(log_moneyness_array, a, b, sigma, rho, m)
|
|
100
|
-
|
|
101
|
-
@staticmethod
|
|
102
|
-
def d1(s: float, k: float, r: float, vol: float, t: float,
|
|
90
|
+
def d1(s: float, K: float, r: float, o: float, t: float,
|
|
103
91
|
option_type: str = 'call') -> float:
|
|
104
|
-
return d1(s,
|
|
92
|
+
return d1(s, K, r, o, t, option_type)
|
|
105
93
|
|
|
106
94
|
@staticmethod
|
|
107
|
-
def d2(s: float,
|
|
95
|
+
def d2(s: float, K: float, r: float, o: float, t: float,
|
|
108
96
|
option_type: str = 'call') -> float:
|
|
109
|
-
return d2(s,
|
|
97
|
+
return d2(s, K, r, o, t, option_type)
|
|
110
98
|
|
|
111
99
|
@staticmethod
|
|
112
|
-
def bs(s: float,
|
|
100
|
+
def bs(s: float, K: float, r: float, o: float, t: float,
|
|
113
101
|
option_type: str = 'call') -> float:
|
|
114
|
-
|
|
115
|
-
Calculate Black-Scholes option price.
|
|
116
|
-
|
|
117
|
-
Parameters:
|
|
118
|
-
- s: Underlying price
|
|
119
|
-
- k: Strike price
|
|
120
|
-
- r: Risk-free rate
|
|
121
|
-
- vol: Volatility
|
|
122
|
-
- t: Time to expiry in years
|
|
123
|
-
- option_type: 'call' or 'put'
|
|
124
|
-
|
|
125
|
-
Returns:
|
|
126
|
-
- Option price
|
|
127
|
-
"""
|
|
128
|
-
return bs(s, k, r, vol, t, option_type)
|
|
102
|
+
return bs(s, K, r, o, t, option_type)
|
|
129
103
|
|
|
130
104
|
@staticmethod
|
|
131
|
-
def delta(s: float,
|
|
105
|
+
def delta(s: float, K: float, r: float, o: float, t: float,
|
|
132
106
|
option_type: str = 'call') -> float:
|
|
133
|
-
|
|
134
|
-
Calculate option delta.
|
|
135
|
-
|
|
136
|
-
Parameters:
|
|
137
|
-
- s: Underlying price
|
|
138
|
-
- k: Strike price
|
|
139
|
-
- r: Risk-free rate
|
|
140
|
-
- vol: Volatility
|
|
141
|
-
- t: Time to expiry in years
|
|
142
|
-
- option_type: 'call' or 'put'
|
|
143
|
-
|
|
144
|
-
Returns:
|
|
145
|
-
- Delta value
|
|
146
|
-
"""
|
|
147
|
-
return delta(s, k, r, vol, t, option_type)
|
|
107
|
+
return delta(s, K, r, o, t, option_type)
|
|
148
108
|
|
|
149
109
|
@staticmethod
|
|
150
|
-
def gamma(s: float,
|
|
110
|
+
def gamma(s: float, K: float, r: float, o: float, t: float,
|
|
151
111
|
option_type: str = 'call') -> float:
|
|
152
|
-
|
|
153
|
-
Calculate option gamma.
|
|
154
|
-
|
|
155
|
-
Parameters:
|
|
156
|
-
- s: Underlying price
|
|
157
|
-
- k: Strike price
|
|
158
|
-
- r: Risk-free rate
|
|
159
|
-
- vol: Volatility
|
|
160
|
-
- t: Time to expiry in years
|
|
161
|
-
|
|
162
|
-
Returns:
|
|
163
|
-
- Gamma value
|
|
164
|
-
"""
|
|
165
|
-
return gamma(s, k, r, vol, t, option_type)
|
|
112
|
+
return gamma(s, K, r, o, t, option_type)
|
|
166
113
|
|
|
167
114
|
@staticmethod
|
|
168
|
-
def vega(s: float,
|
|
115
|
+
def vega(s: float, K: float, r: float, o: float, t: float,
|
|
169
116
|
option_type: str = 'call') -> float:
|
|
170
|
-
|
|
171
|
-
Calculate option vega.
|
|
172
|
-
|
|
173
|
-
Parameters:
|
|
174
|
-
- s: Underlying price
|
|
175
|
-
- k: Strike price
|
|
176
|
-
- r: Risk-free rate
|
|
177
|
-
- vol: Volatility
|
|
178
|
-
- t: Time to expiry in years
|
|
179
|
-
|
|
180
|
-
Returns:
|
|
181
|
-
- Vega value (for 1% change in volatility)
|
|
182
|
-
"""
|
|
183
|
-
return vega(s, k, r, vol, t, option_type)
|
|
117
|
+
return vega(s, K, r, o, t, option_type)
|
|
184
118
|
|
|
185
119
|
@staticmethod
|
|
186
|
-
def theta(s: float,
|
|
120
|
+
def theta(s: float, K: float, r: float, o: float, t: float,
|
|
187
121
|
option_type: str = 'call') -> float:
|
|
188
|
-
|
|
189
|
-
Calculate option theta.
|
|
190
|
-
|
|
191
|
-
Parameters:
|
|
192
|
-
- s: Underlying price
|
|
193
|
-
- k: Strike price
|
|
194
|
-
- r: Risk-free rate
|
|
195
|
-
- vol: Volatility
|
|
196
|
-
- t: Time to expiry in years
|
|
197
|
-
- option_type: 'call' or 'put'
|
|
198
|
-
|
|
199
|
-
Returns:
|
|
200
|
-
- Theta value (per day)
|
|
201
|
-
"""
|
|
202
|
-
return theta(s, k, r, vol, t, option_type)
|
|
122
|
+
return theta(s, K, r, o, t, option_type)
|
|
203
123
|
|
|
204
124
|
@staticmethod
|
|
205
|
-
def rho(s: float,
|
|
125
|
+
def rho(s: float, K: float, r: float, o: float, t: float,
|
|
206
126
|
option_type: str = 'call') -> float:
|
|
207
|
-
|
|
208
|
-
Calculate option rho.
|
|
209
|
-
|
|
210
|
-
Parameters:
|
|
211
|
-
- s: Underlying price
|
|
212
|
-
- k: Strike price
|
|
213
|
-
- r: Risk-free rate
|
|
214
|
-
- vol: Volatility
|
|
215
|
-
- t: Time to expiry in years
|
|
216
|
-
- option_type: 'call' or 'put'
|
|
217
|
-
|
|
218
|
-
Returns:
|
|
219
|
-
- Rho value (for 1% change in interest rate)
|
|
220
|
-
"""
|
|
221
|
-
return rho(s, k, r, vol, t, option_type)
|
|
127
|
+
return rho(s, K, r, o, t, option_type)
|
|
222
128
|
|
|
223
129
|
@staticmethod
|
|
224
|
-
def vanna(s: float,
|
|
130
|
+
def vanna(s: float, K: float, r: float, o: float, t: float,
|
|
225
131
|
option_type: str = 'call') -> float:
|
|
226
|
-
|
|
227
|
-
Calculate option vanna.
|
|
228
|
-
|
|
229
|
-
Parameters:
|
|
230
|
-
- s: Underlying price
|
|
231
|
-
- k: Strike price
|
|
232
|
-
- r: Risk-free rate
|
|
233
|
-
- vol: Volatility
|
|
234
|
-
- t: Time to expiry in years
|
|
235
|
-
|
|
236
|
-
Returns:
|
|
237
|
-
- Vanna value
|
|
238
|
-
"""
|
|
239
|
-
return vanna(s, k, r, vol, t, option_type)
|
|
132
|
+
return vanna(s, K, r, o, t, option_type)
|
|
240
133
|
|
|
241
134
|
@staticmethod
|
|
242
|
-
def volga(s: float,
|
|
135
|
+
def volga(s: float, K: float, r: float, o: float, t: float,
|
|
243
136
|
option_type: str = 'call') -> float:
|
|
244
|
-
|
|
245
|
-
Calculate option volga (vomma).
|
|
246
|
-
|
|
247
|
-
Parameters:
|
|
248
|
-
- s: Underlying price
|
|
249
|
-
- k: Strike price
|
|
250
|
-
- r: Risk-free rate
|
|
251
|
-
- vol: Volatility
|
|
252
|
-
- t: Time to expiry in years
|
|
253
|
-
|
|
254
|
-
Returns:
|
|
255
|
-
- Volga value
|
|
256
|
-
"""
|
|
257
|
-
return volga(s, k, r, vol, t, option_type)
|
|
137
|
+
return volga(s, K, r, o, t, option_type)
|
|
258
138
|
|
|
259
139
|
@staticmethod
|
|
260
|
-
def charm(s: float,
|
|
140
|
+
def charm(s: float, K: float, r: float, o: float, t: float,
|
|
261
141
|
option_type: str = 'call') -> float:
|
|
262
|
-
|
|
263
|
-
Calculate option charm (delta decay).
|
|
264
|
-
|
|
265
|
-
Parameters:
|
|
266
|
-
- s: Underlying price
|
|
267
|
-
- k: Strike price
|
|
268
|
-
- r: Risk-free rate
|
|
269
|
-
- vol: Volatility
|
|
270
|
-
- t: Time to expiry in years
|
|
271
|
-
- option_type: 'call' or 'put'
|
|
272
|
-
|
|
273
|
-
Returns:
|
|
274
|
-
- Charm value (per day)
|
|
275
|
-
"""
|
|
276
|
-
return charm(s, k, r, vol, t, option_type)
|
|
142
|
+
return charm(s, K, r, o, t, option_type)
|
|
277
143
|
|
|
278
144
|
@staticmethod
|
|
279
|
-
def greeks(s: float,
|
|
145
|
+
def greeks(s: float, K: float, r: float, o: float, t: float,
|
|
280
146
|
option_type: str = 'call') -> Dict[str, float]:
|
|
281
|
-
|
|
282
|
-
Calculate all option Greeks.
|
|
283
|
-
|
|
284
|
-
Parameters:
|
|
285
|
-
- s: Underlying price
|
|
286
|
-
- k: Strike price
|
|
287
|
-
- r: Risk-free rate
|
|
288
|
-
- vol: Volatility
|
|
289
|
-
- t: Time to expiry in years
|
|
290
|
-
- option_type: 'call' or 'put'
|
|
291
|
-
|
|
292
|
-
Returns:
|
|
293
|
-
- Dictionary with all Greeks (price, delta, gamma, vega, theta, rho, vanna, volga, charm)
|
|
294
|
-
"""
|
|
295
|
-
return greeks(s, k, r, vol, t, option_type)
|
|
147
|
+
return greeks(s, K, r, o, t, option_type)
|
|
296
148
|
|
|
297
149
|
@staticmethod
|
|
298
|
-
def iv(option_price: float, s: float,
|
|
150
|
+
def iv(option_price: float, s: float, K: float, r: float, t: float,
|
|
299
151
|
option_type: str = 'call') -> float:
|
|
300
|
-
|
|
301
|
-
Calculate implied volatility.
|
|
302
|
-
|
|
303
|
-
Parameters:
|
|
304
|
-
- option_price: Market price of the option
|
|
305
|
-
- s: Underlying price
|
|
306
|
-
- k: Strike price
|
|
307
|
-
- r: Risk-free rate
|
|
308
|
-
- t: Time to expiry in years
|
|
309
|
-
- option_type: 'call' or 'put'
|
|
310
|
-
|
|
311
|
-
Returns:
|
|
312
|
-
- Implied volatility
|
|
313
|
-
"""
|
|
314
|
-
return iv(option_price, s, k, r, t, option_type)
|
|
152
|
+
return iv(option_price, s, K, r, t, option_type)
|
|
315
153
|
|
|
316
154
|
# -------------------------------------------------------------------------
|
|
317
155
|
# Model Fitting
|
|
@@ -15,72 +15,72 @@ def vectorize_inputs(func):
|
|
|
15
15
|
"""
|
|
16
16
|
Decorator to vectorize Black-Scholes functions to handle both scalar and array inputs.
|
|
17
17
|
"""
|
|
18
|
-
def wrapper(s,
|
|
18
|
+
def wrapper(s, K, r, o, t, option_type='call'):
|
|
19
19
|
# Check if inputs are scalar
|
|
20
|
-
|
|
21
|
-
|
|
20
|
+
K_scalar = np.isscalar(K)
|
|
21
|
+
o_scalar = np.isscalar(o)
|
|
22
22
|
|
|
23
23
|
# If both inputs are scalar, use the original function directly
|
|
24
|
-
if
|
|
25
|
-
return func(s,
|
|
24
|
+
if K_scalar and o_scalar:
|
|
25
|
+
return func(s, K, r, o, t, option_type)
|
|
26
26
|
|
|
27
27
|
# Use NumPy's vectorize to handle array inputs
|
|
28
|
-
vectorized_func = np.vectorize(lambda
|
|
29
|
-
func(s,
|
|
28
|
+
vectorized_func = np.vectorize(lambda K_val, o_val:
|
|
29
|
+
func(s, K_val, r, o_val, t, option_type))
|
|
30
30
|
|
|
31
31
|
# Call the vectorized function with the inputs
|
|
32
|
-
return vectorized_func(
|
|
32
|
+
return vectorized_func(K, o)
|
|
33
33
|
|
|
34
34
|
return wrapper
|
|
35
35
|
|
|
36
36
|
|
|
37
37
|
@catch_exception
|
|
38
38
|
@vectorize_inputs
|
|
39
|
-
def d1(s: float,
|
|
39
|
+
def d1(s: float, K: float, r: float, o: float, t: float, option_type: str = 'call') -> float:
|
|
40
40
|
# option_type is ignored in this function but included for compatibility
|
|
41
|
-
if
|
|
41
|
+
if o <= 0 or t <= 0:
|
|
42
42
|
return np.nan
|
|
43
|
-
return (np.log(s /
|
|
43
|
+
return (np.log(s / K) + (r + o ** 2 / 2) * t) / (o * np.sqrt(t))
|
|
44
44
|
|
|
45
45
|
@catch_exception
|
|
46
46
|
@vectorize_inputs
|
|
47
|
-
def d2(s: float,
|
|
47
|
+
def d2(s: float, K: float, r: float, o: float, t: float, option_type: str = 'call') -> float:
|
|
48
48
|
# option_type is ignored in this function but included for compatibility
|
|
49
|
-
if
|
|
49
|
+
if o <= 0 or t <= 0:
|
|
50
50
|
return np.nan
|
|
51
|
-
return d1(s,
|
|
51
|
+
return d1(s, K, r, o, t, option_type) - o * np.sqrt(t)
|
|
52
52
|
|
|
53
53
|
|
|
54
54
|
@catch_exception
|
|
55
55
|
@vectorize_inputs
|
|
56
|
-
def bs(s: float,
|
|
57
|
-
if
|
|
56
|
+
def bs(s: float, K: float, r: float, o: float, t: float, option_type: str = 'call') -> float:
|
|
57
|
+
if o <= 0 or t <= 0:
|
|
58
58
|
# Intrinsic value at expiry
|
|
59
59
|
if option_type.lower() in ["call", "c"]:
|
|
60
|
-
return max(0, s -
|
|
60
|
+
return max(0, s - K)
|
|
61
61
|
else:
|
|
62
|
-
return max(0,
|
|
62
|
+
return max(0, K - s)
|
|
63
63
|
|
|
64
|
-
d1_val = d1(s,
|
|
65
|
-
d2_val = d2(s,
|
|
64
|
+
d1_val = d1(s, K, r, o, t)
|
|
65
|
+
d2_val = d2(s, K, r, o, t)
|
|
66
66
|
|
|
67
67
|
if option_type.lower() in ["call", "c"]:
|
|
68
|
-
return s * norm.cdf(d1_val) -
|
|
68
|
+
return s * norm.cdf(d1_val) - K * np.exp(-r * t) * norm.cdf(d2_val)
|
|
69
69
|
else: # put
|
|
70
|
-
return
|
|
70
|
+
return K * np.exp(-r * t) * norm.cdf(-d2_val) - s * norm.cdf(-d1_val)
|
|
71
71
|
|
|
72
72
|
|
|
73
73
|
@catch_exception
|
|
74
74
|
@vectorize_inputs
|
|
75
|
-
def delta(s: float,
|
|
76
|
-
if
|
|
75
|
+
def delta(s: float, K: float, r: float, o: float, t: float, option_type: str = 'call') -> float:
|
|
76
|
+
if o <= 0 or t <= 0:
|
|
77
77
|
# At expiry, delta is either 0 or 1 for call, 0 or -1 for put
|
|
78
78
|
if option_type.lower() in ["call", "c"]:
|
|
79
|
-
return 1.0 if s >
|
|
79
|
+
return 1.0 if s > K else 0.0
|
|
80
80
|
else:
|
|
81
|
-
return -1.0 if s <
|
|
81
|
+
return -1.0 if s < K else 0.0
|
|
82
82
|
|
|
83
|
-
d1_val = d1(s,
|
|
83
|
+
d1_val = d1(s, K, r, o, t)
|
|
84
84
|
|
|
85
85
|
if option_type.lower() in ["call", "c"]:
|
|
86
86
|
return norm.cdf(d1_val)
|
|
@@ -90,41 +90,41 @@ def delta(s: float, k: float, r: float, vol: float, t: float, option_type: str =
|
|
|
90
90
|
|
|
91
91
|
@catch_exception
|
|
92
92
|
@vectorize_inputs
|
|
93
|
-
def gamma(s: float,
|
|
94
|
-
if
|
|
93
|
+
def gamma(s: float, K: float, r: float, o: float, t: float, option_type: str = 'call') -> float:
|
|
94
|
+
if o <= 0 or t <= 0:
|
|
95
95
|
return 0.0
|
|
96
96
|
|
|
97
|
-
d1_val = d1(s,
|
|
98
|
-
return norm.pdf(d1_val) / (s *
|
|
97
|
+
d1_val = d1(s, K, r, o, t, option_type)
|
|
98
|
+
return norm.pdf(d1_val) / (s * o * np.sqrt(t))
|
|
99
99
|
|
|
100
100
|
|
|
101
101
|
@catch_exception
|
|
102
102
|
@vectorize_inputs
|
|
103
|
-
def vega(s: float,
|
|
104
|
-
if
|
|
103
|
+
def vega(s: float, K: float, r: float, o: float, t: float, option_type: str = 'call') -> float:
|
|
104
|
+
if o <= 0 or t <= 0:
|
|
105
105
|
return 0.0
|
|
106
106
|
|
|
107
|
-
d1_val = d1(s,
|
|
107
|
+
d1_val = d1(s, K, r, o, t, option_type)
|
|
108
108
|
return s * norm.pdf(d1_val) * np.sqrt(t) / 100 # Divided by 100 for 1% change
|
|
109
109
|
|
|
110
110
|
|
|
111
111
|
@catch_exception
|
|
112
112
|
@vectorize_inputs
|
|
113
|
-
def theta(s: float,
|
|
114
|
-
if
|
|
113
|
+
def theta(s: float, K: float, r: float, o: float, t: float, option_type: str = 'call') -> float:
|
|
114
|
+
if o <= 0 or t <= 0:
|
|
115
115
|
return 0.0
|
|
116
116
|
|
|
117
|
-
d1_val = d1(s,
|
|
118
|
-
d2_val = d2(s,
|
|
117
|
+
d1_val = d1(s, K, r, o, t, option_type)
|
|
118
|
+
d2_val = d2(s, K, r, o, t, option_type)
|
|
119
119
|
|
|
120
120
|
# First part of theta (same for both call and put)
|
|
121
|
-
theta_part1 = -s * norm.pdf(d1_val) *
|
|
121
|
+
theta_part1 = -s * norm.pdf(d1_val) * o / (2 * np.sqrt(t))
|
|
122
122
|
|
|
123
123
|
# Second part depends on option type
|
|
124
124
|
if option_type.lower() in ["call", "c"]:
|
|
125
|
-
theta_part2 = -r *
|
|
125
|
+
theta_part2 = -r * K * np.exp(-r * t) * norm.cdf(d2_val)
|
|
126
126
|
else: # put
|
|
127
|
-
theta_part2 = r *
|
|
127
|
+
theta_part2 = r * K * np.exp(-r * t) * norm.cdf(-d2_val)
|
|
128
128
|
|
|
129
129
|
# Return theta per day (t is in years)
|
|
130
130
|
return (theta_part1 + theta_part2) / 365.0
|
|
@@ -132,50 +132,50 @@ def theta(s: float, k: float, r: float, vol: float, t: float, option_type: str =
|
|
|
132
132
|
|
|
133
133
|
@catch_exception
|
|
134
134
|
@vectorize_inputs
|
|
135
|
-
def rho(s: float,
|
|
136
|
-
if
|
|
135
|
+
def rho(s: float, K: float, r: float, o: float, t: float, option_type: str = 'call') -> float:
|
|
136
|
+
if o <= 0 or t <= 0:
|
|
137
137
|
return 0.0
|
|
138
138
|
|
|
139
|
-
d2_val = d2(s,
|
|
139
|
+
d2_val = d2(s, K, r, o, t, option_type)
|
|
140
140
|
|
|
141
141
|
if option_type.lower() in ["call", "c"]:
|
|
142
|
-
return
|
|
142
|
+
return K * t * np.exp(-r * t) * norm.cdf(d2_val) / 100
|
|
143
143
|
else: # put
|
|
144
|
-
return -
|
|
144
|
+
return -K * t * np.exp(-r * t) * norm.cdf(-d2_val) / 100
|
|
145
145
|
|
|
146
146
|
|
|
147
147
|
@catch_exception
|
|
148
148
|
@vectorize_inputs
|
|
149
|
-
def vanna(s: float,
|
|
150
|
-
if
|
|
149
|
+
def vanna(s: float, K: float, r: float, o: float, t: float, option_type: str = 'call') -> float:
|
|
150
|
+
if o <= 0 or t <= 0:
|
|
151
151
|
return 0.0
|
|
152
152
|
|
|
153
|
-
d1_val = d1(s,
|
|
154
|
-
d2_val = d2(s,
|
|
153
|
+
d1_val = d1(s, K, r, o, t, option_type)
|
|
154
|
+
d2_val = d2(s, K, r, o, t, option_type)
|
|
155
155
|
|
|
156
|
-
return -norm.pdf(d1_val) * d2_val /
|
|
156
|
+
return -norm.pdf(d1_val) * d2_val / o
|
|
157
157
|
|
|
158
158
|
|
|
159
159
|
@catch_exception
|
|
160
160
|
@vectorize_inputs
|
|
161
|
-
def volga(s: float,
|
|
162
|
-
if
|
|
161
|
+
def volga(s: float, K: float, r: float, o: float, t: float, option_type: str = 'call') -> float:
|
|
162
|
+
if o <= 0 or t <= 0:
|
|
163
163
|
return 0.0
|
|
164
164
|
|
|
165
|
-
d1_val = d1(s,
|
|
166
|
-
d2_val = d2(s,
|
|
165
|
+
d1_val = d1(s, K, r, o, t, option_type)
|
|
166
|
+
d2_val = d2(s, K, r, o, t, option_type)
|
|
167
167
|
|
|
168
|
-
return s * norm.pdf(d1_val) * np.sqrt(t) * d1_val * d2_val /
|
|
168
|
+
return s * norm.pdf(d1_val) * np.sqrt(t) * d1_val * d2_val / o
|
|
169
169
|
|
|
170
170
|
|
|
171
171
|
@catch_exception
|
|
172
172
|
@vectorize_inputs
|
|
173
|
-
def charm(s: float,
|
|
174
|
-
if
|
|
173
|
+
def charm(s: float, K: float, r: float, o: float, t: float, option_type: str = 'call') -> float:
|
|
174
|
+
if o <= 0 or t <= 0:
|
|
175
175
|
return 0.0
|
|
176
176
|
|
|
177
|
-
d1_val = d1(s,
|
|
178
|
-
d2_val = d2(s,
|
|
177
|
+
d1_val = d1(s, K, r, o, t, option_type)
|
|
178
|
+
d2_val = d2(s, K, r, o, t, option_type)
|
|
179
179
|
|
|
180
180
|
# First term is the same for calls and puts
|
|
181
181
|
term1 = -norm.pdf(d1_val) * d1_val / (2 * t)
|
|
@@ -192,34 +192,34 @@ def charm(s: float, k: float, r: float, vol: float, t: float, option_type: str =
|
|
|
192
192
|
|
|
193
193
|
@catch_exception
|
|
194
194
|
@vectorize_inputs
|
|
195
|
-
def greeks(s: float,
|
|
195
|
+
def greeks(s: float, K: float, r: float, o: float, t: float,
|
|
196
196
|
option_type: str = 'call') -> Dict[str, float]:
|
|
197
197
|
return {
|
|
198
|
-
'price': bs(s,
|
|
199
|
-
'delta': delta(s,
|
|
200
|
-
'gamma': gamma(s,
|
|
201
|
-
'vega': vega(s,
|
|
202
|
-
'theta': theta(s,
|
|
203
|
-
'rho': rho(s,
|
|
204
|
-
'vanna': vanna(s,
|
|
205
|
-
'volga': volga(s,
|
|
206
|
-
'charm': charm(s,
|
|
198
|
+
'price': bs(s, K, r, o, t, option_type),
|
|
199
|
+
'delta': delta(s, K, r, o, t, option_type),
|
|
200
|
+
'gamma': gamma(s, K, r, o, t, option_type),
|
|
201
|
+
'vega': vega(s, K, r, o, t, option_type),
|
|
202
|
+
'theta': theta(s, K, r, o, t, option_type),
|
|
203
|
+
'rho': rho(s, K, r, o, t, option_type),
|
|
204
|
+
'vanna': vanna(s, K, r, o, t, option_type),
|
|
205
|
+
'volga': volga(s, K, r, o, t, option_type),
|
|
206
|
+
'charm': charm(s, K, r, o, t, option_type)
|
|
207
207
|
}
|
|
208
208
|
|
|
209
209
|
|
|
210
210
|
@catch_exception
|
|
211
211
|
@vectorize_inputs
|
|
212
|
-
def iv(option_price: float, s: float,
|
|
212
|
+
def iv(option_price: float, s: float, K: float, r: float, t: float,
|
|
213
213
|
option_type: str = 'call', precision: float = 1e-8,
|
|
214
214
|
max_iterations: int = 100) -> float:
|
|
215
215
|
"""
|
|
216
216
|
Calculate implied volatility using Newton-Raphson method.
|
|
217
217
|
|
|
218
218
|
Parameters:
|
|
219
|
-
- option_price:
|
|
219
|
+
- option_price: MarKet price of the option
|
|
220
220
|
- s: Underlying price
|
|
221
|
-
-
|
|
222
|
-
- r:
|
|
221
|
+
- K: Strike price
|
|
222
|
+
- r: RisK-free rate
|
|
223
223
|
- t: Time to expiry in years
|
|
224
224
|
- option_type: 'call' or 'put'
|
|
225
225
|
- precision: Desired precision
|
|
@@ -233,46 +233,46 @@ def iv(option_price: float, s: float, k: float, r: float, t: float,
|
|
|
233
233
|
|
|
234
234
|
# Check if option price is within theoretical bounds
|
|
235
235
|
if option_type.lower() in ["call", "c"]:
|
|
236
|
-
intrinsic = max(0, s -
|
|
236
|
+
intrinsic = max(0, s - K * np.exp(-r * t))
|
|
237
237
|
if option_price < intrinsic:
|
|
238
238
|
return np.nan # Price below intrinsic value
|
|
239
239
|
if option_price >= s:
|
|
240
240
|
return np.inf # Price exceeds underlying
|
|
241
241
|
else: # put
|
|
242
|
-
intrinsic = max(0,
|
|
242
|
+
intrinsic = max(0, K * np.exp(-r * t) - s)
|
|
243
243
|
if option_price < intrinsic:
|
|
244
244
|
return np.nan # Price below intrinsic value
|
|
245
|
-
if option_price >=
|
|
245
|
+
if option_price >= K:
|
|
246
246
|
return np.inf # Price exceeds strike
|
|
247
247
|
|
|
248
248
|
# Initial guess - Manaster and Koehler (1982) method
|
|
249
|
-
|
|
249
|
+
o = np.sqrt(2 * np.pi / t) * option_price / s
|
|
250
250
|
|
|
251
251
|
# Ensure initial guess is reasonable
|
|
252
|
-
|
|
252
|
+
o = max(0.001, min(o, 5.0))
|
|
253
253
|
|
|
254
254
|
for _ in range(max_iterations):
|
|
255
255
|
# Calculate option price and vega with current volatility
|
|
256
|
-
price = bs(s,
|
|
257
|
-
v = vega(s,
|
|
256
|
+
price = bs(s, K, r, o, t, option_type)
|
|
257
|
+
v = vega(s, K, r, o, t, option_type)
|
|
258
258
|
|
|
259
259
|
# Calculate price difference
|
|
260
260
|
price_diff = price - option_price
|
|
261
261
|
|
|
262
262
|
# Check if precision reached
|
|
263
263
|
if abs(price_diff) < precision:
|
|
264
|
-
return
|
|
264
|
+
return o
|
|
265
265
|
|
|
266
266
|
# Avoid division by zero
|
|
267
267
|
if abs(v) < 1e-10:
|
|
268
268
|
# Change direction based on whether price is too high or too low
|
|
269
|
-
|
|
269
|
+
o = o * 1.5 if price_diff < 0 else o * 0.5
|
|
270
270
|
else:
|
|
271
271
|
# Newton-Raphson update
|
|
272
|
-
|
|
272
|
+
o = o - price_diff / (v * 100) # Vega is for 1% change
|
|
273
273
|
|
|
274
274
|
# Ensure volatility stays in reasonable bounds
|
|
275
|
-
|
|
275
|
+
o = max(0.001, min(o, 5.0))
|
|
276
276
|
|
|
277
277
|
# If we reach here, we didn't converge
|
|
278
278
|
return np.nan
|
|
@@ -286,7 +286,7 @@ def get_domain(domain_params: Tuple[float, float, int] = (-1.5, 1.5, 1000),
|
|
|
286
286
|
t: float = None,
|
|
287
287
|
return_domain: str = 'log_moneyness') -> np.ndarray:
|
|
288
288
|
"""
|
|
289
|
-
Compute the x-domain for a given return type (log-moneyness, moneyness, strikes, or delta).
|
|
289
|
+
Compute the x-domain for a given return type (log-moneyness, moneyness, returns, strikes, or delta).
|
|
290
290
|
|
|
291
291
|
Parameters:
|
|
292
292
|
-----------
|
|
@@ -336,7 +336,7 @@ def get_domain(domain_params: Tuple[float, float, int] = (-1.5, 1.5, 1000),
|
|
|
336
336
|
|
|
337
337
|
elif return_domain == 'delta':
|
|
338
338
|
# Check for required parameters
|
|
339
|
-
required_params = {'s': s, '
|
|
339
|
+
required_params = {'s': s, 'r': r, 'o': o, 't': t}
|
|
340
340
|
missing_params = [param for param, value in required_params.items() if value is None]
|
|
341
341
|
if missing_params:
|
|
342
342
|
raise ValueError(f"The following parameters are required for return_domain='delta': {missing_params}")
|
|
@@ -355,4 +355,4 @@ def get_domain(domain_params: Tuple[float, float, int] = (-1.5, 1.5, 1000),
|
|
|
355
355
|
|
|
356
356
|
else:
|
|
357
357
|
raise ValueError(
|
|
358
|
-
f"Invalid return_domain: {return_domain}. Must be one of ['log_moneyness', 'moneyness', 'returns', '
|
|
358
|
+
f"Invalid return_domain: {return_domain}. Must be one of ['log_moneyness', 'moneyness', 'returns', 'striKes', 'delta'].")
|
|
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
|