1use implied_vol::{implied_black_volatility, norm_cdf, norm_pdf};
17
18#[repr(C)]
19#[derive(Clone, Copy, Debug, PartialEq, PartialOrd)]
20#[cfg_attr(
21 feature = "python",
22 pyo3::pyclass(module = "nautilus_trader.core.nautilus_pyo3.model")
23)]
24pub struct BlackScholesGreeksResult {
25 pub price: f64,
26 pub delta: f64,
27 pub gamma: f64,
28 pub vega: f64,
29 pub theta: f64,
30}
31
32#[allow(clippy::too_many_arguments)]
35pub fn black_scholes_greeks(
36 s: f64,
37 r: f64,
38 b: f64,
39 sigma: f64,
40 is_call: bool,
41 k: f64,
42 t: f64,
43 multiplier: f64,
44) -> BlackScholesGreeksResult {
45 let phi = if is_call { 1.0 } else { -1.0 };
46 let scaled_vol = sigma * t.sqrt();
47 let d1 = ((s / k).ln() + (b + 0.5 * sigma.powi(2)) * t) / scaled_vol;
48 let d2 = d1 - scaled_vol;
49 let cdf_phi_d1 = norm_cdf(phi * d1);
50 let cdf_phi_d2 = norm_cdf(phi * d2);
51 let dist_d1 = norm_pdf(d1);
52 let df = ((b - r) * t).exp();
53 let s_t = s * df;
54 let k_t = k * (-r * t).exp();
55
56 let price = multiplier * phi * (s_t * cdf_phi_d1 - k_t * cdf_phi_d2);
57 let delta = multiplier * phi * df * cdf_phi_d1;
58 let gamma = multiplier * df * dist_d1 / (s * scaled_vol);
59 let vega = multiplier * s_t * t.sqrt() * dist_d1 * 0.01; let theta = multiplier
61 * (s_t * (-dist_d1 * sigma / (2.0 * t.sqrt()) - phi * (b - r) * cdf_phi_d1)
62 - phi * r * k_t * cdf_phi_d2)
63 * 0.0027378507871321013; BlackScholesGreeksResult {
66 price,
67 delta,
68 gamma,
69 vega,
70 theta,
71 }
72}
73
74pub fn imply_vol(s: f64, r: f64, b: f64, is_call: bool, k: f64, t: f64, price: f64) -> f64 {
75 let forward = s * b.exp();
76 let forward_price = price * (r * t).exp();
77
78 implied_black_volatility(forward_price, forward, k, t, is_call)
79}
80
81#[repr(C)]
82#[derive(Clone, Copy, Debug, PartialEq, PartialOrd)]
83#[cfg_attr(
84 feature = "python",
85 pyo3::pyclass(module = "nautilus_trader.core.nautilus_pyo3.model")
86)]
87pub struct ImplyVolAndGreeksResult {
88 pub vol: f64,
89 pub price: f64,
90 pub delta: f64,
91 pub gamma: f64,
92 pub vega: f64,
93 pub theta: f64,
94}
95
96#[allow(clippy::too_many_arguments)]
97pub fn imply_vol_and_greeks(
98 s: f64,
99 r: f64,
100 b: f64,
101 is_call: bool,
102 k: f64,
103 t: f64,
104 price: f64,
105 multiplier: f64,
106) -> ImplyVolAndGreeksResult {
107 let vol = imply_vol(s, r, b, is_call, k, t, price);
108 let greeks = black_scholes_greeks(s, r, b, vol, is_call, k, t, multiplier);
109
110 ImplyVolAndGreeksResult {
111 vol,
112 price: greeks.price,
113 delta: greeks.delta,
114 gamma: greeks.gamma,
115 vega: greeks.vega,
116 theta: greeks.theta,
117 }
118}
119
120#[cfg(test)]
125mod tests {
126 use rstest::rstest;
127
128 use super::*;
129
130 #[rstest]
131 fn test_greeks_accuracy_call() {
132 let s = 100.0;
133 let k = 100.1;
134 let t = 1.0;
135 let r = 0.01;
136 let b = 0.005;
137 let sigma = 0.2;
138 let is_call = true;
139 let eps = 1e-3;
140
141 let greeks = black_scholes_greeks(s, r, b, sigma, is_call, k, t, 1.0);
142
143 let price0 = |s: f64| black_scholes_greeks(s, r, b, sigma, is_call, k, t, 1.0).price;
144
145 let delta_bnr = (price0(s + eps) - price0(s - eps)) / (2.0 * eps);
146 let gamma_bnr = (price0(s + eps) + price0(s - eps) - 2.0 * price0(s)) / (eps * eps);
147 let vega_bnr = (black_scholes_greeks(s, r, b, sigma + eps, is_call, k, t, 1.0).price
148 - black_scholes_greeks(s, r, b, sigma - eps, is_call, k, t, 1.0).price)
149 / (2.0 * eps)
150 / 100.0;
151 let theta_bnr = (black_scholes_greeks(s, r, b, sigma, is_call, k, t - eps, 1.0).price
152 - black_scholes_greeks(s, r, b, sigma, is_call, k, t + eps, 1.0).price)
153 / (2.0 * eps)
154 / 365.25;
155
156 let tolerance = 1e-5;
157 assert!(
158 (greeks.delta - delta_bnr).abs() < tolerance,
159 "Delta difference exceeds tolerance"
160 );
161 assert!(
162 (greeks.gamma - gamma_bnr).abs() < tolerance,
163 "Gamma difference exceeds tolerance"
164 );
165 assert!(
166 (greeks.vega - vega_bnr).abs() < tolerance,
167 "Vega difference exceeds tolerance"
168 );
169 assert!(
170 (greeks.theta - theta_bnr).abs() < tolerance,
171 "Theta difference exceeds tolerance"
172 );
173 }
174
175 #[rstest]
176 fn test_greeks_accuracy_put() {
177 let s = 100.0;
178 let k = 100.1;
179 let t = 1.0;
180 let r = 0.01;
181 let b = 0.005;
182 let sigma = 0.2;
183 let is_call = false;
184 let eps = 1e-3;
185
186 let greeks = black_scholes_greeks(s, r, b, sigma, is_call, k, t, 1.0);
187
188 let price0 = |s: f64| black_scholes_greeks(s, r, b, sigma, is_call, k, t, 1.0).price;
189
190 let delta_bnr = (price0(s + eps) - price0(s - eps)) / (2.0 * eps);
191 let gamma_bnr = (price0(s + eps) + price0(s - eps) - 2.0 * price0(s)) / (eps * eps);
192 let vega_bnr = (black_scholes_greeks(s, r, b, sigma + eps, is_call, k, t, 1.0).price
193 - black_scholes_greeks(s, r, b, sigma - eps, is_call, k, t, 1.0).price)
194 / (2.0 * eps)
195 / 100.0;
196 let theta_bnr = (black_scholes_greeks(s, r, b, sigma, is_call, k, t - eps, 1.0).price
197 - black_scholes_greeks(s, r, b, sigma, is_call, k, t + eps, 1.0).price)
198 / (2.0 * eps)
199 / 365.25;
200
201 let tolerance = 1e-5;
202 assert!(
203 (greeks.delta - delta_bnr).abs() < tolerance,
204 "Delta difference exceeds tolerance"
205 );
206 assert!(
207 (greeks.gamma - gamma_bnr).abs() < tolerance,
208 "Gamma difference exceeds tolerance"
209 );
210 assert!(
211 (greeks.vega - vega_bnr).abs() < tolerance,
212 "Vega difference exceeds tolerance"
213 );
214 assert!(
215 (greeks.theta - theta_bnr).abs() < tolerance,
216 "Theta difference exceeds tolerance"
217 );
218 }
219
220 #[rstest]
221 fn test_imply_vol_and_greeks_accuracy_call() {
222 let s = 100.0;
223 let k = 100.1;
224 let t = 1.0;
225 let r = 0.01;
226 let b = 0.005;
227 let sigma = 0.2;
228 let is_call = true;
229
230 let base_greeks = black_scholes_greeks(s, r, b, sigma, is_call, k, t, 1.0);
231 let price = base_greeks.price;
232
233 let implied_result = imply_vol_and_greeks(s, r, b, is_call, k, t, price, 1.0);
234
235 let tolerance = 1e-5;
236 assert!(
237 (implied_result.vol - sigma).abs() < tolerance,
238 "Vol difference exceeds tolerance"
239 );
240 assert!(
241 (implied_result.price - base_greeks.price).abs() < tolerance,
242 "Price difference exceeds tolerance"
243 );
244 assert!(
245 (implied_result.delta - base_greeks.delta).abs() < tolerance,
246 "Delta difference exceeds tolerance"
247 );
248 assert!(
249 (implied_result.gamma - base_greeks.gamma).abs() < tolerance,
250 "Gamma difference exceeds tolerance"
251 );
252 assert!(
253 (implied_result.vega - base_greeks.vega).abs() < tolerance,
254 "Vega difference exceeds tolerance"
255 );
256 assert!(
257 (implied_result.theta - base_greeks.theta).abs() < tolerance,
258 "Theta difference exceeds tolerance"
259 );
260 }
261
262 #[rstest]
263 fn test_imply_vol_and_greeks_accuracy_put() {
264 let s = 100.0;
265 let k = 100.1;
266 let t = 1.0;
267 let r = 0.01;
268 let b = 0.005;
269 let sigma = 0.2;
270 let is_call = false;
271
272 let base_greeks = black_scholes_greeks(s, r, b, sigma, is_call, k, t, 1.0);
273 let price = base_greeks.price;
274
275 let implied_result = imply_vol_and_greeks(s, r, b, is_call, k, t, price, 1.0);
276
277 let tolerance = 1e-5;
278 assert!(
279 (implied_result.vol - sigma).abs() < tolerance,
280 "Vol difference exceeds tolerance"
281 );
282 assert!(
283 (implied_result.price - base_greeks.price).abs() < tolerance,
284 "Price difference exceeds tolerance"
285 );
286 assert!(
287 (implied_result.delta - base_greeks.delta).abs() < tolerance,
288 "Delta difference exceeds tolerance"
289 );
290 assert!(
291 (implied_result.gamma - base_greeks.gamma).abs() < tolerance,
292 "Gamma difference exceeds tolerance"
293 );
294 assert!(
295 (implied_result.vega - base_greeks.vega).abs() < tolerance,
296 "Vega difference exceeds tolerance"
297 );
298 assert!(
299 (implied_result.theta - base_greeks.theta).abs() < tolerance,
300 "Theta difference exceeds tolerance"
301 );
302 }
303}