use crate::OptionType;
pub trait PricerExt: super::time::TimeExt {
fn calculate_call_put(&self) -> (f64, f64);
fn calculate_price(&self) -> f64;
fn implied_volatility(&self, _c_price: f64, _option_type: OptionType) -> f64 {
f64::NAN
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Greeks {
pub delta: f64,
pub gamma: f64,
pub vega: f64,
pub theta: f64,
pub rho: f64,
pub vanna: f64,
pub charm: f64,
pub volga: f64,
pub veta: f64,
}
impl Default for Greeks {
fn default() -> Self {
Self::nan()
}
}
impl Greeks {
pub const fn nan() -> Self {
Self {
delta: f64::NAN,
gamma: f64::NAN,
vega: f64::NAN,
theta: f64::NAN,
rho: f64::NAN,
vanna: f64::NAN,
charm: f64::NAN,
volga: f64::NAN,
veta: f64::NAN,
}
}
pub const COMPONENT_NAMES: [&'static str; 9] = [
"delta", "gamma", "vega", "theta", "rho", "vanna", "charm", "volga", "veta",
];
pub fn as_array(&self) -> [f64; 9] {
[
self.delta, self.gamma, self.vega, self.theta, self.rho, self.vanna, self.charm, self.volga,
self.veta,
]
}
}
#[cfg(feature = "viz")]
impl stochastic_rs_viz::Plottable<f64> for Greeks {
fn n_components(&self) -> usize {
9
}
fn component_name(&self, idx: usize) -> String {
Self::COMPONENT_NAMES
.get(idx)
.map(|s| (*s).to_string())
.unwrap_or_default()
}
fn component(&self, idx: usize) -> Vec<f64> {
self
.as_array()
.get(idx)
.copied()
.map(|v| vec![v])
.unwrap_or_default()
}
fn len(&self) -> usize {
1
}
}
pub trait ModelPricer {
fn price_call(&self, s: f64, k: f64, r: f64, q: f64, tau: f64) -> f64;
fn price_put(&self, s: f64, k: f64, r: f64, q: f64, tau: f64) -> f64 {
let call = self.price_call(s, k, r, q, tau);
call - s * (-q * tau).exp() + k * (-r * tau).exp()
}
fn price_option(&self, s: f64, k: f64, r: f64, q: f64, tau: f64, option_type: OptionType) -> f64 {
match option_type {
OptionType::Call => self.price_call(s, k, r, q, tau),
OptionType::Put => self.price_put(s, k, r, q, tau),
}
}
}
pub trait GreeksExt {
fn delta(&self) -> f64;
fn gamma(&self) -> f64 {
f64::NAN
}
fn vega(&self) -> f64 {
f64::NAN
}
fn theta(&self) -> f64 {
f64::NAN
}
fn rho(&self) -> f64 {
f64::NAN
}
fn vanna(&self) -> f64 {
f64::NAN
}
fn charm(&self) -> f64 {
f64::NAN
}
fn volga(&self) -> f64 {
f64::NAN
}
fn veta(&self) -> f64 {
f64::NAN
}
fn greeks(&self) -> Greeks {
Greeks {
delta: self.delta(),
gamma: self.gamma(),
vega: self.vega(),
theta: self.theta(),
rho: self.rho(),
vanna: self.vanna(),
charm: self.charm(),
volga: self.volga(),
veta: self.veta(),
}
}
}
#[cfg(test)]
mod greeks_array_tests {
use super::Greeks;
#[test]
fn as_array_matches_component_names_order() {
let g = Greeks {
delta: 0.5,
gamma: 0.1,
vega: 0.2,
theta: -0.05,
rho: 0.3,
vanna: 0.4,
charm: 0.05,
volga: 0.6,
veta: -0.02,
};
let arr = g.as_array();
assert_eq!(arr.len(), Greeks::COMPONENT_NAMES.len());
assert_eq!(arr[0], g.delta);
assert_eq!(arr[8], g.veta);
}
}
#[cfg(all(test, feature = "viz"))]
mod viz_tests {
use stochastic_rs_viz::Plottable;
use super::Greeks;
#[test]
fn plottable_for_greeks_exposes_nine_components() {
let g = Greeks {
delta: 0.5,
gamma: 0.1,
vega: 0.2,
theta: -0.05,
rho: 0.3,
vanna: 0.4,
charm: 0.05,
volga: 0.6,
veta: -0.02,
};
assert_eq!(g.n_components(), 9);
assert_eq!(g.len(), 1);
assert!(!g.is_empty());
assert_eq!(g.component_name(0), "delta");
assert_eq!(g.component_name(8), "veta");
assert_eq!(g.component_name(99), "");
assert_eq!(g.component(0), vec![0.5]);
assert_eq!(g.component(2), vec![0.2]);
assert!(g.component(99).is_empty());
}
}