Skip to main content

rustyqlib/equity/
binomial.rs

1//extern crate ndarray;
2use super::vanila_option::{EquityOption};
3use super::utils::{Engine, Payoff};
4use crate::core::trade::{ PutOrCall, Transection};
5use crate::core::utils::{ContractStyle};
6use ndarray::Array2;
7
8/// Binomial tree model for European and American options
9pub fn npv(option: &EquityOption) -> f64 {
10    assert!(option.base.volatility() >= 0.0);
11    assert!(option.time_to_maturity() >= 0.0);
12    assert!(option.base.underlying_price.value >= 0.0);
13    let num_steps = 1000;
14
15    let dt = option.time_to_maturity() / num_steps as f64;
16    let risk_free_rate = option.base.risk_free_rate();
17    let discount_factor = (-risk_free_rate * dt).exp();
18    // Calculate parameters for the binomial tree
19    let u = (option.base.volatility()*dt.sqrt()).exp(); //up movement
20    let d = 1.0 / u; //down movement
21    let a_factor = ((risk_free_rate-option.base.carry_yield()) * dt).exp();
22    let p = (a_factor - d) / (u - d); //martingale probability
23    // Create a 2D array to represent the binomial tree
24    let mut tree = Array2::from_elem((num_steps + 1, num_steps + 1), 0.0);
25    let strike = option.base.strike_price;
26
27    // Terminal payoff via the Payoff trait: works for any terminal payoff
28    // (vanilla, binary, ...)
29    for j in 0..=num_steps {
30        let spot_price_j = option.base.effective_spot() * u.powi(num_steps as i32 - j as i32) * d.powi(j as i32);
31        tree[[j,num_steps]] = option.payoff.payoff(spot_price_j, strike);
32    }
33
34    match option.payoff.exercise_style() {
35        ContractStyle::European => {
36            for i in (0..num_steps).rev() {
37                for j in 0..=i {
38                    let discounted_option_price = discount_factor * (p * tree[[ j,i+1]] + (1.0 - p) * tree[[ j + 1,i+1]]);
39                    tree[[j,i]] = discounted_option_price;
40                }
41            }
42
43        }
44        ContractStyle::American => {
45
46            for i in (0..num_steps).rev() {
47                for j in 0..=i {
48                    let spot_price_i =  option.base.effective_spot() * u.powi(i as i32 - j as i32) * d.powi(j as i32);
49                    let discounted_option_price = discount_factor * (p * tree[[ j,i+1]] + (1.0 - p) * tree[[ j + 1,i+1]]);
50                    tree[[j,i]] = option.payoff.payoff(spot_price_i, strike).max(discounted_option_price);
51                }
52            }
53
54        }
55        _ => {
56            panic!("Invalid option style");
57        }
58    }
59
60
61    return tree[[0,0]];
62}
63
64// Write a unit test for the binomial tree model
65
66// #[cfg(test)]
67// mod tests {
68//     use assert_approx_eq::assert_approx_eq;
69//     use super::*;
70//     use crate::core::utils::{Contract,MarketData};
71//     use crate::core::trade::{OptionType,Transection};
72//     use crate::core::utils::{ContractStyle};
73//     use crate::equity::vanila_option::{EquityOption};
74//
75//     use chrono::{NaiveDate};
76//     use crate::core::traits::Instrument;
77//
78//
79//     #[test]
80//     fn test_binomial_tree() {
81//         let mut data = Contract {
82//             action: "PV".to_string(),
83//             market_data: Some(MarketData {
84//                 underlying_price: 100.0,
85//                 strike_price: 100.0,
86//                 volatility: Some(0.3),
87//                 option_price: Some(10.0),
88//                 risk_free_rate: Some(0.05),
89//                 dividend: Some(0.0),
90//                 maturity: "2024-01-01".to_string(),
91//                 option_type: "C".to_string(),
92//                 simulation: None
93//             }),
94//             pricer: "Binomial".to_string(),
95//             asset: "".to_string(),
96//             style: Some("European".to_string()),
97//             rate_data: None
98//         };
99//         let mut option = EquityOption::from_json(&data);
100//         option.valuation_date = NaiveDate::from_ymd(2023, 11, 06);
101//         //Call European test
102//         let npv = option.npv();
103//         assert_approx_eq!(npv, 5.058163, 1e-6);
104//         //Call American test
105//         option.option_type = OptionType::Call;
106//         option.style = ContractStyle::American;
107//         let npv = option.npv();
108//         assert_approx_eq!(npv, 5.058163, 1e-6);
109//
110//         //Put European test
111//         option.option_type = OptionType::Put;
112//         option.style = ContractStyle::European;
113//         option.valuation_date = NaiveDate::from_ymd(2023, 11, 07);
114//         let npv = option.npv();
115//         assert_approx_eq!(npv, 4.259022688, 1e-6);
116//
117//         //Put American test
118//         option.option_type = OptionType::Put;
119//         option.style = ContractStyle::American;
120//         let npv = option.npv();
121//         assert_approx_eq!(npv, 4.315832381, 1e-6);
122//     }
123// }