Skip to main content

rustyqlib/equity/
equity_forward.rs

1use crate::core::errors::RustyQLibError;
2use chrono::NaiveDate;
3use crate::core::data_models::EquityForwardData;
4use crate::core::quotes::Quote;
5use crate::core::traits::Instrument;
6use crate::equity::utils::LongShort;
7///A forward contract is an agreement between two parties to buy or sell, as the case may be,
8/// a commodity (or financial instrument or currency or any other underlying)
9/// on a pre-determined future date at a price agreed when the contract is entered into.
10pub struct EquityForward {
11
12    pub symbol: String,
13    pub currency: Option<String>,
14    pub exchange: Option<String>,
15    pub name: Option<String>,
16    pub cusip: Option<String>,
17    pub isin: Option<String>,
18    pub settlement_type: Option<String>,
19
20    pub underlying_price: Quote,
21    pub forward_price: Quote, //Forward price you actually locked in.
22    pub risk_free_rate: f64,
23    pub dividend_yield: f64,
24    /// Continuous stock borrow (repo) cost; part of the carry.
25    pub borrow_cost: f64,
26    pub maturity_date: NaiveDate,
27    pub valuation_date: NaiveDate,
28    pub long_short:LongShort,
29    pub notional:f64
30}
31impl EquityForward  {
32    /// Build from contract data, panicking on any invalid field. Fallible
33    /// callers should use [`EquityForward::try_from_json`].
34    pub fn from_json(data: &EquityForwardData) -> Box<Self> {
35        Self::try_from_json(data).unwrap_or_else(|e| panic!("{e}"))
36    }
37
38    pub fn try_from_json(data: &EquityForwardData) -> Result<Box<Self>, RustyQLibError> {
39        let today =
40            crate::core::data_models::parse_valuation_date(data.base.valuation_date.as_deref())?;
41        let maturity_date = NaiveDate::parse_from_str(&data.maturity, "%Y-%m-%d")
42            .map_err(|_| RustyQLibError::invalid_input(
43                "maturity",
44                format!("invalid date '{}' (expected YYYY-MM-DD)", data.maturity),
45            ))?;
46
47        let underlying_price = Quote::new(data.base.underlying_price);
48        let entry_quote = Quote::new(data.entry_price.unwrap_or(0.0));
49        let risk_free_rate = data.base.risk_free_rate.unwrap_or(0.0);
50        let dividend = data.dividend.unwrap_or(0.0);
51        let long_short = data.base.long_short.unwrap_or(1);
52        let position = match long_short{
53            1=>LongShort::LONG,
54            -1=>LongShort::SHORT,
55            _=>LongShort::LONG,
56        };
57        Ok(Box::new(Self {
58            symbol:data.base.symbol.clone(),
59            currency: data.base.currency.clone(),
60            exchange:data.base.exchange.clone(),
61            name: data.base.name.clone(),
62            cusip: data.base.cusip.clone(),
63            isin: data.base.isin.clone(),
64            settlement_type: data.base.settlement_type.clone(),
65
66
67            underlying_price: underlying_price,
68            forward_price:entry_quote,
69            risk_free_rate: risk_free_rate,
70            dividend_yield: dividend,
71            borrow_cost: data.base.borrow_cost.unwrap_or(0.0),
72            maturity_date: maturity_date,
73            valuation_date: today,
74            notional:data.notional.unwrap_or(1.0),
75            long_short:position
76        }))
77    }
78
79    fn time_to_maturity(&self) -> f64 {
80        let days = (self.maturity_date - self.valuation_date).num_days();
81        (days as f64) / 365.0
82    }
83    fn forward(&self)->f64{
84        let discount_df = 1.0/(self.risk_free_rate*self.time_to_maturity()).exp();
85        let dividend_df = 1.0/((self.dividend_yield + self.borrow_cost)*self.time_to_maturity()).exp();
86        let forward = self.underlying_price.value()*dividend_df/discount_df;
87        forward
88    }
89}
90impl Instrument for EquityForward {
91    fn try_npv(&self) -> Result<f64, crate::core::errors::RustyQLibError> {
92        // e −r(T−t) (Ft −K),
93        let df_r = 1.0/(self.risk_free_rate*self.time_to_maturity()).exp();
94        let share = self.notional/self.forward_price.value();
95        Ok(match self.long_short{
96            LongShort::LONG => (self.forward()-self.forward_price.value()) * share *df_r,
97            LongShort::SHORT => -(self.forward()-self.forward_price.value()) * share *df_r,
98        })
99    }
100
101    fn price(&self) -> Result<crate::core::results::PricingResult, crate::core::errors::RustyQLibError> {
102        Ok(crate::core::results::PricingResult {
103            pv: self.try_npv()?,
104            greeks: crate::core::results::Greeks { delta: self.delta(), ..Default::default() },
105            std_err: None,
106        })
107    }
108}
109
110impl EquityForward{
111    pub fn delta(&self) -> f64 { 1.0 }
112    pub fn gamma(&self) -> f64 { 0.0 }
113    pub fn vega(&self) -> f64  { 0.0 }
114    pub fn theta(&self) -> f64 { 0.0 }
115    pub fn rho(&self) -> f64   { 0.0 }
116    pub fn vanna(&self) -> f64 { 0.0 }
117    pub fn charm(&self) -> f64 { 0.0 }
118    pub fn gamma_p(&self) -> f64 { 0.0 }
119    pub fn zomma(&self) -> f64 { 0.0 }
120}