Skip to main content

barrier_option/
barrier_option.rs

1//! Barrier options: all eight knock-in / knock-out types.
2//!
3//! Run with:  cargo run --release --example barrier_option
4
5mod common;
6
7use chrono::NaiveDate;
8use rustyqlib::core::trade::PutOrCall;
9use rustyqlib::core::traits::Instrument;
10use rustyqlib::equity::barrier::{barrier_price, BarrierDirection, KnockType};
11use rustyqlib::equity::builder::EquityOptionBuilder;
12use rustyqlib::equity::montecarlo::McModel;
13use rustyqlib::equity::utils::Engine;
14use rustyqlib::core::vols::VolSurface;
15use rustyqlib::core::daycount::DayCountConvention;
16use rustyqlib::core::curves::Tenor;
17
18const SPOT: f64 = 100.0;
19const STRIKE: f64 = 100.0;
20const VOL: f64 = 0.30;
21const RATE: f64 = 0.05;
22const DIV: f64 = 0.02;
23
24fn asof() -> NaiveDate {
25    NaiveDate::from_ymd_opt(2026, 1, 1).unwrap()
26}
27
28fn base() -> EquityOptionBuilder {
29    EquityOptionBuilder::new()
30        .symbol("BARRIER")
31        .spot(SPOT)
32        .strike(STRIKE)
33        .flat_vol(VOL)
34        .flat_rate(RATE)
35        .dividend_yield(DIV)
36        .valuation_date(asof())
37        .maturity_date(NaiveDate::from_ymd_opt(2027, 1, 1).unwrap())
38}
39
40fn main() {
41    common::title("BARRIER OPTIONS — S=100 K=100 sigma=30% r=5% q=2% T=1y");
42
43    common::section("All eight types, analytic (Reiner-Rubinstein)");
44    common::table_header();
45    for (dir, knock, pc, level) in [
46        (BarrierDirection::Down, KnockType::In, PutOrCall::Call, 90.0),
47        (BarrierDirection::Down, KnockType::Out, PutOrCall::Call, 90.0),
48        (BarrierDirection::Down, KnockType::In, PutOrCall::Put, 90.0),
49        (BarrierDirection::Down, KnockType::Out, PutOrCall::Put, 90.0),
50        (BarrierDirection::Up, KnockType::In, PutOrCall::Call, 120.0),
51        (BarrierDirection::Up, KnockType::Out, PutOrCall::Call, 120.0),
52        (BarrierDirection::Up, KnockType::In, PutOrCall::Put, 120.0),
53        (BarrierDirection::Up, KnockType::Out, PutOrCall::Put, 120.0),
54    ] {
55        common::row(
56            &format!("{dir:?}-and-{knock:?} {pc:?} H={level}"),
57            &base().barrier(pc, dir, knock, level).engine(Engine::BlackScholes).build(),
58        );
59    }
60
61    common::section("Engine comparison: down-and-out call, H=90");
62    common::table_header();
63    for (label, engine) in [
64        ("Analytical (Reiner-Rubinstein)", Engine::BlackScholes),
65        ("Finite difference (absorbing)", Engine::FiniteDifference),
66        ("Monte Carlo (Brownian bridge)", Engine::MonteCarlo),
67        ("Binomial (unsupported)", Engine::Binomial),
68    ] {
69        common::row(
70            label,
71            &base()
72                .barrier(PutOrCall::Call, BarrierDirection::Down, KnockType::Out, 90.0)
73                .engine(engine)
74                .build(),
75        );
76    }
77    common::note("MC applies a bridge crossing correction, so monitoring is effectively continuous");
78
79    common::section("In-out parity: KI + KO = vanilla");
80    let vanilla = base().vanilla(PutOrCall::Call).engine(Engine::BlackScholes).build();
81    for level in [80.0, 90.0, 99.0] {
82        let ki = base()
83            .barrier(PutOrCall::Call, BarrierDirection::Down, KnockType::In, level)
84            .engine(Engine::BlackScholes)
85            .build();
86        let ko = base()
87            .barrier(PutOrCall::Call, BarrierDirection::Down, KnockType::Out, level)
88            .engine(Engine::BlackScholes)
89            .build();
90        common::check(
91            &format!("H={level}: KI + KO"),
92            ki.npv() + ko.npv(),
93            vanilla.npv(),
94            1e-10,
95        );
96    }
97
98    common::section("Limits");
99    common::check(
100        "far barrier: KO call -> vanilla",
101        barrier_price(SPOT, STRIKE, 1e-4, RATE, DIV, VOL, 1.0, BarrierDirection::Down, KnockType::Out, PutOrCall::Call),
102        vanilla.npv(),
103        1e-9,
104    );
105    common::check(
106        "up-and-out call with K >= H is worthless",
107        barrier_price(SPOT, 110.0, 105.0, RATE, DIV, VOL, 1.0, BarrierDirection::Up, KnockType::Out, PutOrCall::Call),
108        0.0,
109        1e-12,
110    );
111    common::check(
112        "spot at barrier: KO = 0",
113        base()
114            .barrier(PutOrCall::Call, BarrierDirection::Down, KnockType::Out, SPOT)
115            .engine(Engine::BlackScholes)
116            .build()
117            .npv(),
118        0.0,
119        1e-12,
120    );
121
122    common::section("Barrier level sweep: down-and-out call");
123    common::table_header();
124    for level in [50.0, 70.0, 85.0, 95.0, 99.0] {
125        common::row(
126            &format!("H={level}"),
127            &base()
128                .barrier(PutOrCall::Call, BarrierDirection::Down, KnockType::Out, level)
129                .engine(Engine::BlackScholes)
130                .build(),
131        );
132    }
133    common::note("value decreases as the barrier approaches spot; delta can exceed 1 near it");
134
135    common::section("Smile matters: down-and-out call under local vol");
136    let skewed = VolSurface::from_strike_grid(
137        &[Tenor::YearFraction(0.5), Tenor::YearFraction(1.0), Tenor::YearFraction(2.0)],
138        &[70.0, 85.0, 100.0, 115.0, 130.0],
139        &[
140            vec![0.38, 0.34, 0.30, 0.28, 0.27],
141            vec![0.37, 0.34, 0.30, 0.29, 0.28],
142            vec![0.36, 0.33, 0.30, 0.29, 0.28],
143        ],
144        asof(),
145        DayCountConvention::Act365,
146    )
147    .unwrap();
148    common::table_header();
149    common::row(
150        "GBM (flat 30%)",
151        &base()
152            .barrier(PutOrCall::Call, BarrierDirection::Down, KnockType::Out, 90.0)
153            .engine(Engine::MonteCarlo)
154            .paths(50_000)
155            .build(),
156    );
157    common::row(
158        "Local vol (skewed surface)",
159        &base()
160            .vol_surface(skewed)
161            .barrier(PutOrCall::Call, BarrierDirection::Down, KnockType::Out, 90.0)
162            .engine(Engine::MonteCarlo)
163            .model(McModel::LocalVol)
164            .paths(50_000)
165            .build(),
166    );
167    common::note("downside skew raises the knock-out probability, lowering the price");
168    println!();
169}