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// }