|
| 1 | +/****************************************************************************** |
| 2 | + Author: Joaquín Béjar García |
| 3 | + Email: jb@taunais.com |
| 4 | + Date: 2026-04-26 |
| 5 | +******************************************************************************/ |
| 6 | + |
| 7 | +//! Garman–Kohlhagen pricing example. |
| 8 | +//! |
| 9 | +//! Demonstrates pricing European FX options using the Garman–Kohlhagen |
| 10 | +//! (1983) model: the Hull canonical example (USD/GBP, 4-month ATM), an |
| 11 | +//! ITM EUR/USD scenario with FX put-call-parity check, dispatch through |
| 12 | +//! the unified `PricingEngine`, and the symmetric-rate degenerate case. |
| 13 | +
|
| 14 | +use optionstratlib::model::types::{OptionStyle, OptionType, Side}; |
| 15 | +use optionstratlib::pricing::{PricingEngine, garman_kohlhagen, price_option}; |
| 16 | +use optionstratlib::{ExpirationDate, Options}; |
| 17 | +use positive::pos_or_panic; |
| 18 | +use rust_decimal::MathematicalOps; |
| 19 | +use rust_decimal_macros::dec; |
| 20 | +use tracing::info; |
| 21 | + |
| 22 | +fn main() -> Result<(), Box<dyn std::error::Error>> { |
| 23 | + tracing_subscriber::fmt() |
| 24 | + .with_env_filter("info") |
| 25 | + .without_time() |
| 26 | + .init(); |
| 27 | + |
| 28 | + info!("=== Garman-Kohlhagen FX Pricing Examples ==="); |
| 29 | + |
| 30 | + // ---- Example 1: Hull canonical USD/GBP example ---------------------- |
| 31 | + // S = K = 1.6 USD/GBP, r_d = 0.08, r_f = 0.11, sigma = 0.2, T = 4/12 |
| 32 | + // -> call ~ 0.0639 (Hull, "Options, Futures and Other Derivatives"). |
| 33 | + info!(""); |
| 34 | + info!("Example 1: Hull canonical USD/GBP (ATM, 4-month)"); |
| 35 | + let hull_t_days = 365.0 / 3.0; |
| 36 | + let option1 = Options::new( |
| 37 | + OptionType::European, |
| 38 | + Side::Long, |
| 39 | + "GBPUSD".to_string(), |
| 40 | + pos_or_panic!(1.6), |
| 41 | + ExpirationDate::Days(pos_or_panic!(hull_t_days)), |
| 42 | + pos_or_panic!(0.2), |
| 43 | + pos_or_panic!(1.0), |
| 44 | + pos_or_panic!(1.6), |
| 45 | + dec!(0.08), |
| 46 | + OptionStyle::Call, |
| 47 | + pos_or_panic!(0.11), |
| 48 | + None, |
| 49 | + ); |
| 50 | + let call_price = garman_kohlhagen(&option1)?; |
| 51 | + let mut put1 = option1.clone(); |
| 52 | + put1.option_style = OptionStyle::Put; |
| 53 | + let put_price = garman_kohlhagen(&put1)?; |
| 54 | + |
| 55 | + let years1 = option1.expiration_date.get_years()?.to_dec(); |
| 56 | + let df_d = (-option1.risk_free_rate * years1).exp(); |
| 57 | + let df_f = (-option1.dividend_yield.to_dec() * years1).exp(); |
| 58 | + let parity_lhs = call_price - put_price; |
| 59 | + let parity_rhs = |
| 60 | + option1.underlying_price.to_dec() * df_f - option1.strike_price.to_dec() * df_d; |
| 61 | + |
| 62 | + info!(" S = 1.6, K = 1.6, r_d = 0.08, r_f = 0.11, T = 1/3 yr, sigma = 0.2"); |
| 63 | + info!( |
| 64 | + " Call price = {} (Hull reference ~ 0.0639)", |
| 65 | + call_price |
| 66 | + ); |
| 67 | + info!(" Put price = {}", put_price); |
| 68 | + info!(" C - P = {}", parity_lhs); |
| 69 | + info!(" S e^(-r_f T) - K e^(-r_d T) = {}", parity_rhs); |
| 70 | + |
| 71 | + // ---- Example 2: ITM EUR/USD call ------------------------------------ |
| 72 | + info!(""); |
| 73 | + info!("Example 2: ITM EUR/USD call (FX parity check)"); |
| 74 | + let option2 = Options::new( |
| 75 | + OptionType::European, |
| 76 | + Side::Long, |
| 77 | + "EURUSD".to_string(), |
| 78 | + pos_or_panic!(1.20), |
| 79 | + ExpirationDate::Days(pos_or_panic!(180.0)), |
| 80 | + pos_or_panic!(0.10), |
| 81 | + pos_or_panic!(1.0), |
| 82 | + pos_or_panic!(1.25), |
| 83 | + dec!(0.045), |
| 84 | + OptionStyle::Call, |
| 85 | + pos_or_panic!(0.025), |
| 86 | + None, |
| 87 | + ); |
| 88 | + let call2 = garman_kohlhagen(&option2)?; |
| 89 | + let mut put2 = option2.clone(); |
| 90 | + put2.option_style = OptionStyle::Put; |
| 91 | + let put2_price = garman_kohlhagen(&put2)?; |
| 92 | + |
| 93 | + let years2 = option2.expiration_date.get_years()?.to_dec(); |
| 94 | + let df_d2 = (-option2.risk_free_rate * years2).exp(); |
| 95 | + let df_f2 = (-option2.dividend_yield.to_dec() * years2).exp(); |
| 96 | + let parity2 = option2.underlying_price.to_dec() * df_f2 - option2.strike_price.to_dec() * df_d2; |
| 97 | + |
| 98 | + info!(" S = 1.25, K = 1.20, r_d = 4.5%, r_f = 2.5%, T = 180d, sigma = 0.10"); |
| 99 | + info!(" Call price = {}", call2); |
| 100 | + info!(" Put price = {}", put2_price); |
| 101 | + info!(" C - P = {}", call2 - put2_price); |
| 102 | + info!(" Expected parity = {}", parity2); |
| 103 | + |
| 104 | + // ---- Example 3: Unified API dispatch -------------------------------- |
| 105 | + info!(""); |
| 106 | + info!("Example 3: Unified API via PricingEngine::ClosedFormGK"); |
| 107 | + let direct = garman_kohlhagen(&option2)?; |
| 108 | + let via_engine = price_option(&option2, &PricingEngine::ClosedFormGK)?; |
| 109 | + info!(" Direct garman_kohlhagen() = {}", direct); |
| 110 | + info!(" Via PricingEngine dispatch = {}", via_engine); |
| 111 | + |
| 112 | + // ---- Example 4: Symmetric rates collapse to forward parity ---------- |
| 113 | + info!(""); |
| 114 | + info!("Example 4: Symmetric rates (r_d = r_f) collapse to forward parity"); |
| 115 | + let option4 = Options::new( |
| 116 | + OptionType::European, |
| 117 | + Side::Long, |
| 118 | + "USDUSD".to_string(), |
| 119 | + pos_or_panic!(1.20), |
| 120 | + ExpirationDate::Days(pos_or_panic!(180.0)), |
| 121 | + pos_or_panic!(0.10), |
| 122 | + pos_or_panic!(1.0), |
| 123 | + pos_or_panic!(1.25), |
| 124 | + dec!(0.04), |
| 125 | + OptionStyle::Call, |
| 126 | + pos_or_panic!(0.04), |
| 127 | + None, |
| 128 | + ); |
| 129 | + let call4 = garman_kohlhagen(&option4)?; |
| 130 | + let mut put4 = option4.clone(); |
| 131 | + put4.option_style = OptionStyle::Put; |
| 132 | + let put4_price = garman_kohlhagen(&put4)?; |
| 133 | + let years4 = option4.expiration_date.get_years()?.to_dec(); |
| 134 | + let df = (-option4.risk_free_rate * years4).exp(); |
| 135 | + let collapsed = df * (option4.underlying_price.to_dec() - option4.strike_price.to_dec()); |
| 136 | + info!(" C - P = {}", call4 - put4_price); |
| 137 | + info!(" e^(-r T) (S - K) = {}", collapsed); |
| 138 | + |
| 139 | + Ok(()) |
| 140 | +} |
0 commit comments