1use crate::core::trade::PutOrCall;
11use crate::core::utils::N;
12use super::blackscholes::bs_price;
13
14#[derive(Debug, Clone, Copy, PartialEq, Eq)]
16pub enum BarrierDirection {
17 Up,
18 Down,
19}
20
21#[derive(Debug, Clone, Copy, PartialEq, Eq)]
24pub enum KnockType {
25 In,
26 Out,
27}
28
29#[allow(clippy::too_many_arguments)]
34pub fn barrier_price(
35 s: f64,
36 k: f64,
37 h: f64,
38 r: f64,
39 q: f64,
40 sigma: f64,
41 t: f64,
42 direction: BarrierDirection,
43 knock: KnockType,
44 put_or_call: PutOrCall,
45) -> f64 {
46 assert!(s > 0.0 && k > 0.0 && h > 0.0 && sigma > 0.0 && t > 0.0);
47 let down = direction == BarrierDirection::Down;
48 let knocked_now = if down { s <= h } else { s >= h };
49 if knocked_now {
50 return match knock {
51 KnockType::Out => 0.0,
52 KnockType::In => bs_price(s, k, r, q, sigma, t, put_or_call),
53 };
54 }
55
56 let call = put_or_call == PutOrCall::Call;
57 let phi: f64 = if call { 1.0 } else { -1.0 };
58 let eta: f64 = if down { 1.0 } else { -1.0 };
59 let st = sigma * t.sqrt();
60 let mu = (r - q - 0.5 * sigma * sigma) / (sigma * sigma);
61 let df_q = (-q * t).exp();
62 let df_r = (-r * t).exp();
63 let hs = h / s;
64
65 let x1 = (s / k).ln() / st + (1.0 + mu) * st;
66 let x2 = (s / h).ln() / st + (1.0 + mu) * st;
67 let y1 = (h * h / (s * k)).ln() / st + (1.0 + mu) * st;
68 let y2 = (h / s).ln() / st + (1.0 + mu) * st;
69
70 let a = phi * s * df_q * N(phi * x1) - phi * k * df_r * N(phi * x1 - phi * st);
71 let b = phi * s * df_q * N(phi * x2) - phi * k * df_r * N(phi * x2 - phi * st);
72 let c = phi * s * df_q * hs.powf(2.0 * (mu + 1.0)) * N(eta * y1)
73 - phi * k * df_r * hs.powf(2.0 * mu) * N(eta * y1 - eta * st);
74 let d = phi * s * df_q * hs.powf(2.0 * (mu + 1.0)) * N(eta * y2)
75 - phi * k * df_r * hs.powf(2.0 * mu) * N(eta * y2 - eta * st);
76
77 let k_above_barrier = k >= h;
78 let knock_in = match (call, down) {
79 (true, true) => if k_above_barrier { c } else { a - b + d },
80 (true, false) => if k_above_barrier { a } else { b - c + d },
81 (false, true) => if k_above_barrier { b - c + d } else { a },
82 (false, false) => if k_above_barrier { a - b + d } else { c },
83 };
84 match knock {
85 KnockType::In => knock_in,
86 KnockType::Out => bs_price(s, k, r, q, sigma, t, put_or_call) - knock_in,
88 }
89}
90
91#[cfg(test)]
92mod tests {
93 use super::*;
94
95 const S: f64 = 100.0;
96 const R: f64 = 0.05;
97 const Q: f64 = 0.02;
98 const SIG: f64 = 0.3;
99 const T: f64 = 1.0;
100
101 #[test]
102 fn matches_independent_oracle_goldens() {
103 use BarrierDirection::*;
104 use KnockType::*;
105 use PutOrCall::*;
106 let cases = [
108 (Down, In, Call, 90.0, 4.5095197744),
109 (Down, Out, Call, 90.0, 8.5107614943),
110 (Down, In, Put, 90.0, 10.0710164338),
111 (Down, Out, Put, 90.0, 0.0523399543),
112 (Up, In, Call, 120.0, 12.5974705742),
113 (Up, Out, Call, 120.0, 0.4228106946),
114 (Up, In, Put, 120.0, 1.4297711810),
115 (Up, Out, Put, 120.0, 8.6935852071),
116 ];
117 for (direction, knock, pc, h, expected) in cases {
118 let price = barrier_price(S, 100.0, h, R, Q, SIG, T, direction, knock, pc);
119 assert!(
120 (price - expected).abs() < 1e-8,
121 "{direction:?} {knock:?} {pc:?} H={h}: {price} vs {expected}"
122 );
123 }
124 }
125
126 #[test]
127 fn in_plus_out_equals_vanilla() {
128 for pc in [PutOrCall::Call, PutOrCall::Put] {
129 for k in [90.0, 100.0, 110.0] {
130 for (direction, h) in [
131 (BarrierDirection::Down, 80.0),
132 (BarrierDirection::Down, 99.0),
133 (BarrierDirection::Up, 101.0),
134 (BarrierDirection::Up, 130.0),
135 ] {
136 let vanilla = bs_price(S, k, R, Q, SIG, T, pc);
137 let ki = barrier_price(S, k, h, R, Q, SIG, T, direction, KnockType::In, pc);
138 let ko = barrier_price(S, k, h, R, Q, SIG, T, direction, KnockType::Out, pc);
139 assert!(
140 (ki + ko - vanilla).abs() < 1e-10,
141 "{pc:?} K={k} {direction:?} H={h}: {ki} + {ko} != {vanilla}"
142 );
143 }
144 }
145 }
146 }
147
148 #[test]
149 fn far_barriers_reduce_to_vanilla_or_zero() {
150 let vanilla_call = bs_price(S, 100.0, R, Q, SIG, T, PutOrCall::Call);
151 let ko = barrier_price(S, 100.0, 1e-4, R, Q, SIG, T, BarrierDirection::Down, KnockType::Out, PutOrCall::Call);
153 let ki = barrier_price(S, 100.0, 1e-4, R, Q, SIG, T, BarrierDirection::Down, KnockType::In, PutOrCall::Call);
154 assert!((ko - vanilla_call).abs() < 1e-9);
155 assert!(ki.abs() < 1e-9);
156 let ko_up = barrier_price(S, 100.0, 1e6, R, Q, SIG, T, BarrierDirection::Up, KnockType::Out, PutOrCall::Call);
157 assert!((ko_up - vanilla_call).abs() < 1e-9);
158 }
159
160 #[test]
161 fn already_knocked_positions() {
162 let vanilla = bs_price(S, 100.0, R, Q, SIG, T, PutOrCall::Call);
163 let ko = barrier_price(S, 100.0, 100.0, R, Q, SIG, T, BarrierDirection::Down, KnockType::Out, PutOrCall::Call);
165 let ki = barrier_price(S, 100.0, 100.0, R, Q, SIG, T, BarrierDirection::Down, KnockType::In, PutOrCall::Call);
166 assert_eq!(ko, 0.0);
167 assert!((ki - vanilla).abs() < 1e-12);
168 }
169
170 #[test]
171 fn up_out_call_with_strike_above_barrier_is_worthless() {
172 let price = barrier_price(
174 S, 110.0, 105.0, R, Q, SIG, T,
175 BarrierDirection::Up, KnockType::Out, PutOrCall::Call,
176 );
177 assert!(price.abs() < 1e-12, "{price}");
178 }
179}