danielhstahl / black_scholes_rust

Rust for black scholes
https://danielhstahl.github.io/black_scholes_rust/dev/bench/
22 stars 8 forks source link

Proposal: Calc all greeks once & Decimals #5

Closed Zarathustra2 closed 2 years ago

Zarathustra2 commented 3 years ago

Currently one has to call 5 functions - gamma, delta, vega, theta, sigma/iv, and the parameters of the function take a float and don't support decimals.

Going forward I would like to add support for calculating greeks with decimals as input, https://docs.rs/rust_decimal/1.10.3/rust_decimal/

This can be added as a feature so users can decide whether they want to have the decimal feature or not.

Second, it would be nice if we could do something like this:

pub struct Greeks {
    #[cfg(feature = "decimal")]
    iv: Decimal,
    #[cfg(feature = "decimal")]
    delta: Decimal,
    #[cfg(feature = "decimal")]
    gamma: Decimal,
    #[cfg(feature = "decimal")]
    theta: Decimal,
    #[cfg(feature = "decimal")]
    vega: Decimal,  

    #[cfg(not(feature = "decimal"))]
    iv: f64,
    #[cfg(not(feature = "decimal"))]
    delta: f64,
    #[cfg(not(feature = "decimal"))]
    gamma: f64,
    #[cfg(not(feature = "decimal"))]
    theta: f64,
    #[cfg(not(feature = "decimal"))]
    vega: f64,
}

impl Greeks {
    // If iv is none then we will calculate it.
    #[cfg(feature = "decimal")]
    pub fn naked(is_call: bool, stock_price: Decimal, strike: Decimal, rate: Decimal, iv: Option<Decimal>, maturity: f64) -> Self {}
    #[cfg(not(feature = "decimal"))]
    pub fn naked(is_call: bool, stock_price: f64, strike: f64, rate: f64, iv: Option<f64>, maturity: f64) -> Self {}

    pub fn from_bs_option(ot: &BsOption) {
        let is_call = ot.is_call(); 
        let stock_price = ot.stock_price(); 
        let strike = ot.strike(); 
        let rate = ot.rate(); 
        let iv = ot.iv(); 
        let maturity = ot.maturity(); 

        Self::naked(is_call, stock_price, strike, rate, iv, maturity)
    }
}

trait BsOption {
    fn is_call(&self) -> bool;

    #[cfg(feature = "decimal")]
    fn stock_price(&self) -> Decimal;
    #[cfg(feature = "decimal")]
    fn strike(&self) -> Decimal;
    #[cfg(feature = "decimal")]
    fn rate(&self) -> Decimal;
    #[cfg(feature = "decimal")]
    fn iv(&self) -> Option<Decimal>;

    #[cfg(not(feature = "decimal"))]
    fn stock_price(&self) -> f64;
    #[cfg(not(feature = "decimal"))]
    fn strike(&self) -> f64;
    #[cfg(not(feature = "decimal"))]
    fn rate(&self) -> f64;
    #[cfg(not(feature = "decimal"))]
    fn iv(&self) -> Option<f64>;

    fn maturity(&self) -> f64;
}

Benefits would be we could optimize the code - if we are interested in all greeks - in a way that we don't have to recalculate d1/d2 for instance.

Let me know what you think @danielhstahl, I can take a shot at this if you want.

danielhstahl commented 3 years ago

The API change makes sense, feel free to work on it and PR.

Is decimal that big of a deal? Any loss in precision is dwarfed by model error :).

danielhstahl commented 2 years ago

I added a cached version that computes all prices and strikes at once.

Zarathustra2 commented 2 years ago

Wow, awesome! Thanks @danielhstahl :)

danielhstahl commented 2 years ago

The benchmark comparison between the cache and the "naive" calling of each price and greek individually: https://danielhstahl.github.io/black_scholes_rust/dev/bench/