implied_vol/builder/
implied_black_volatility.rs1use crate::{SpecialFn, lets_be_rational};
2use bon::Builder;
3
4#[derive(Builder)]
21#[builder(const, derive(Clone, Debug),
22finish_fn(name = build_unchecked,
23doc{
24})
31)]
32pub struct ImpliedBlackVolatility {
33 forward: f64,
34 strike: f64,
35 expiry: f64,
36 is_call: bool,
37 option_price: f64,
38}
39
40impl<S: implied_black_volatility_builder::IsComplete> ImpliedBlackVolatilityBuilder<S> {
41 pub const fn build(self) -> Option<ImpliedBlackVolatility> {
55 let implied_black_volatility = self.build_unchecked();
56
57 if !(implied_black_volatility.forward > 0.0)
58 || implied_black_volatility.forward.is_infinite()
59 {
60 return None;
61 }
62 if !(implied_black_volatility.strike > 0.0) || implied_black_volatility.strike.is_infinite()
63 {
64 return None;
65 }
66 if !(implied_black_volatility.expiry >= 0.0) {
67 return None;
68 }
69 if !(implied_black_volatility.option_price >= 0.0)
70 || implied_black_volatility.option_price.is_infinite()
71 {
72 return None;
73 }
74 Some(implied_black_volatility)
75 }
76}
77
78impl ImpliedBlackVolatility {
79 #[must_use]
110 #[inline(always)]
111 pub fn calculate<SpFn: SpecialFn>(&self) -> Option<f64> {
112 if self.is_call {
113 lets_be_rational::implied_black_volatility_input_unchecked::<SpFn, true>(
114 self.option_price,
115 self.forward,
116 self.strike,
117 self.expiry,
118 )
119 } else {
120 lets_be_rational::implied_black_volatility_input_unchecked::<SpFn, false>(
121 self.option_price,
122 self.forward,
123 self.strike,
124 self.expiry,
125 )
126 }
127 }
128}
129
130#[cfg(test)]
131mod tests {
132 use crate::DefaultSpecialFn;
133 use crate::builder::implied_black_volatility::ImpliedBlackVolatility;
134
135 #[test]
136 const fn normal_const() {
137 const PRICE: f64 = 10.0;
138 const F: f64 = 100.0;
139 const K: f64 = 100.0;
140 const T: f64 = 1.0;
141 const Q: bool = true;
142
143 const IV_BUILDER: Option<ImpliedBlackVolatility> = ImpliedBlackVolatility::builder()
144 .option_price(PRICE)
145 .forward(F)
146 .strike(K)
147 .expiry(T)
148 .is_call(Q)
149 .build();
150 assert!(IV_BUILDER.is_some());
151 }
152
153 #[test]
154 fn strike_anomaly() {
155 for k in [f64::NAN, f64::INFINITY, f64::NEG_INFINITY, 0.0] {
156 let price = 100.0;
157 let f = 100.0;
158 let t = 1.0;
159 const Q: bool = true;
160 assert!(
161 ImpliedBlackVolatility::builder()
162 .option_price(price)
163 .forward(f)
164 .strike(k)
165 .expiry(t)
166 .is_call(Q)
167 .build()
168 .is_none()
169 );
170 }
171 }
172
173 #[test]
174 fn strike_boundary() {
175 let price = 100.0;
176 let f = 100.0;
177 let k = f64::MIN_POSITIVE;
178 let t = 1.0;
179 const Q: bool = true;
180 assert!(
181 ImpliedBlackVolatility::builder()
182 .option_price(price)
183 .forward(f)
184 .strike(k)
185 .expiry(t)
186 .is_call(Q)
187 .build()
188 .is_some()
189 );
190 }
191
192 #[test]
193 fn forward_anomaly() {
194 for f in [f64::NAN, f64::INFINITY, f64::NEG_INFINITY, 0.0] {
195 let price = 100.0;
196 let k = 100.0;
197 let t = 1.0;
198 const Q: bool = true;
199 assert!(
200 ImpliedBlackVolatility::builder()
201 .option_price(price)
202 .forward(f)
203 .strike(k)
204 .expiry(t)
205 .is_call(Q)
206 .build()
207 .is_none()
208 );
209 }
210 }
211
212 #[test]
213 fn price_anomaly() {
214 for price in [f64::NAN, f64::INFINITY, f64::NEG_INFINITY] {
215 let f = 100.0;
216 let t = 1.0;
217 let k = 100.0;
218 const Q: bool = true;
219 assert!(
220 ImpliedBlackVolatility::builder()
221 .option_price(price)
222 .forward(f)
223 .strike(k)
224 .expiry(t)
225 .is_call(Q)
226 .build()
227 .is_none()
228 );
229 }
230 }
231
232 #[test]
233 fn price_below_intrinsic() {
234 let price = 10.0;
235 let f = 120.0;
236 let t = 1.0;
237 let k = 100.0;
238 const Q: bool = true;
239 let iv_builder = ImpliedBlackVolatility::builder()
240 .option_price(price)
241 .forward(f)
242 .strike(k)
243 .expiry(t)
244 .is_call(Q)
245 .build();
246 assert!(iv_builder.is_some());
247 let iv = iv_builder.unwrap();
248 assert!(iv.calculate::<DefaultSpecialFn>().is_none());
249 }
250
251 #[test]
252 fn time_anomaly() {
253 for t in [f64::NAN, f64::NEG_INFINITY] {
254 let price = 10.0;
255 let f = 100.0;
256 let k = 100.0;
257 const Q: bool = true;
258 assert!(
259 ImpliedBlackVolatility::builder()
260 .option_price(price)
261 .forward(f)
262 .strike(k)
263 .expiry(t)
264 .is_call(Q)
265 .build()
266 .is_none()
267 );
268 }
269 }
270
271 #[test]
272 fn time_zero() {
273 let price = 10.0;
275 let f = 120.0;
276 let k = 100.0;
277 let t = 0.0;
278 let q = true;
279
280 let vol = ImpliedBlackVolatility::builder()
281 .option_price(price)
282 .forward(f)
283 .strike(k)
284 .expiry(t)
285 .is_call(q)
286 .build()
287 .unwrap()
288 .calculate::<DefaultSpecialFn>();
289 assert!(vol.is_none());
290
291 let price = 20.0;
292 let f = 120.0;
293 let k = 100.0;
294 let t = 0.0;
295
296 let vol = ImpliedBlackVolatility::builder()
297 .option_price(price)
298 .forward(f)
299 .strike(k)
300 .expiry(t)
301 .is_call(q)
302 .build()
303 .unwrap()
304 .calculate::<DefaultSpecialFn>();
305 assert_eq!(vol.unwrap(), 0.0);
306 }
307
308 #[test]
309 fn time_inf() {
310 let price = 10.0;
311 let f = 100.0;
312 let k = 100.0;
313 let t = f64::INFINITY;
314 const Q: bool = true;
315
316 let vol = ImpliedBlackVolatility::builder()
317 .option_price(price)
318 .forward(f)
319 .strike(k)
320 .expiry(t)
321 .is_call(Q)
322 .build()
323 .unwrap()
324 .calculate::<DefaultSpecialFn>()
325 .unwrap();
326 assert_eq!(vol, 0.0);
327 }
328}