asian_option/
asian_option.rs1mod common;
6
7use chrono::NaiveDate;
8use rustyqlib::core::trade::PutOrCall;
9use rustyqlib::core::traits::Instrument;
10use rustyqlib::equity::asian::{
11 geometric_asian_price, turnbull_wakeman_price, AsianStrikeType, AveragingType,
12};
13use rustyqlib::equity::builder::EquityOptionBuilder;
14use rustyqlib::equity::montecarlo::DiscretizationScheme;
15use rustyqlib::equity::utils::Engine;
16
17const SPOT: f64 = 100.0;
18const STRIKE: f64 = 100.0;
19const VOL: f64 = 0.30;
20const RATE: f64 = 0.05;
21const DIV: f64 = 0.02;
22
23fn base() -> EquityOptionBuilder {
24 EquityOptionBuilder::new()
25 .symbol("ASIAN")
26 .spot(SPOT)
27 .strike(STRIKE)
28 .flat_vol(VOL)
29 .flat_rate(RATE)
30 .dividend_yield(DIV)
31 .valuation_date(NaiveDate::from_ymd_opt(2026, 1, 1).unwrap())
32 .maturity_date(NaiveDate::from_ymd_opt(2027, 1, 1).unwrap())
33}
34
35fn main() {
36 common::title("ASIAN OPTIONS — S=100 K=100 sigma=30% r=5% q=2% T=1y");
37
38 common::section("Fixed strike (average price) call");
39 common::table_header();
40 common::row(
41 "Geometric, analytic (exact)",
42 &base()
43 .asian(PutOrCall::Call, AveragingType::Geometric, AsianStrikeType::FixedStrike)
44 .engine(Engine::BlackScholes)
45 .build(),
46 );
47 common::row(
48 "Geometric, Monte Carlo",
49 &base()
50 .asian(PutOrCall::Call, AveragingType::Geometric, AsianStrikeType::FixedStrike)
51 .engine(Engine::MonteCarlo)
52 .paths(50_000)
53 .build(),
54 );
55 common::row(
56 "Arithmetic, Turnbull-Wakeman",
57 &base()
58 .asian(PutOrCall::Call, AveragingType::Arithmetic, AsianStrikeType::FixedStrike)
59 .engine(Engine::BlackScholes)
60 .build(),
61 );
62 common::row(
63 "Arithmetic, MC + geometric CV",
64 &base()
65 .asian(PutOrCall::Call, AveragingType::Arithmetic, AsianStrikeType::FixedStrike)
66 .engine(Engine::MonteCarlo)
67 .paths(50_000)
68 .build(),
69 );
70
71 common::section("Control variate effect (same path count)");
72 common::table_header();
73 let with_cv = base()
74 .asian(PutOrCall::Call, AveragingType::Arithmetic, AsianStrikeType::FixedStrike)
75 .engine(Engine::MonteCarlo)
76 .paths(20_000)
77 .build();
78 let without_cv = base()
79 .asian(PutOrCall::Call, AveragingType::Arithmetic, AsianStrikeType::FixedStrike)
80 .engine(Engine::MonteCarlo)
81 .paths(20_000)
82 .mc_config({
83 let mut c = rustyqlib::equity::montecarlo::MonteCarloConfig::default();
85 c.paths = 20_000;
86 c.scheme = DiscretizationScheme::Euler;
87 c.time_steps = 100;
88 c
89 })
90 .build();
91 common::row("with geometric control variate", &with_cv);
92 common::row("without (Euler path route)", &without_cv);
93 common::note("compare the std err column: the CV collapses the variance");
94
95 common::section("Floating strike (average strike)");
96 common::table_header();
97 for pc in [PutOrCall::Call, PutOrCall::Put] {
98 common::row(
99 &format!("Monte Carlo, {pc:?}"),
100 &base()
101 .asian(pc, AveragingType::Arithmetic, AsianStrikeType::FloatingStrike)
102 .engine(Engine::MonteCarlo)
103 .paths(50_000)
104 .build(),
105 );
106 common::row(
107 &format!("Analytic (unsupported), {pc:?}"),
108 &base()
109 .asian(pc, AveragingType::Arithmetic, AsianStrikeType::FloatingStrike)
110 .engine(Engine::BlackScholes)
111 .build(),
112 );
113 }
114
115 common::section("Orderings and limits");
116 let vanilla = base().vanilla(PutOrCall::Call).engine(Engine::BlackScholes).build().npv();
117 let geo = geometric_asian_price(SPOT, STRIKE, RATE, DIV, VOL, 1.0, None, PutOrCall::Call);
118 let arith = turnbull_wakeman_price(SPOT, STRIKE, RATE, DIV, VOL, 1.0, PutOrCall::Call);
119 println!(" geometric {geo:.6} < arithmetic {arith:.6} < vanilla {vanilla:.6}");
120 common::note("AM-GM: the arithmetic average dominates the geometric one");
121 common::note("averaging reduces effective volatility (sigma^2 T / 3), so both sit below vanilla");
122 common::check(
123 "discrete geometric (n=1e5) -> continuous",
124 geometric_asian_price(SPOT, STRIKE, RATE, DIV, VOL, 1.0, Some(100_000), PutOrCall::Call),
125 geo,
126 1e-3,
127 );
128
129 common::section("Averaging frequency (geometric, exact)");
130 for n in [4usize, 12, 52, 252] {
131 let price =
132 geometric_asian_price(SPOT, STRIKE, RATE, DIV, VOL, 1.0, Some(n), PutOrCall::Call);
133 println!(" {n:>4} fixings: {price:.6}");
134 }
135 println!();
136}