Skip to main content

rustyqlib/equity/
utils.rs

1use serde::Deserialize;
2use std::str::FromStr;
3use std::error::Error;
4use crate::core::trade::{PutOrCall};
5use std::fmt::Debug;
6use crate::core::utils::ContractStyle;
7
8///Enum for different engines to price options
9#[derive(PartialEq,Clone,Debug)]
10pub enum Engine{
11    BlackScholes,
12    MonteCarlo,
13    Binomial,
14    FiniteDifference,
15    /// Barone-Adesi-Whaley quadratic approximation for American vanillas.
16    BaroneAdesiWhaley,
17    /// Bjerksund-Stensland (2002) two-boundary approximation for
18    /// American vanillas — a lower bound, generally tighter than BAW.
19    BjerksundStensland,
20}
21
22/// The numerical method **with its own settings** — each variant carries
23/// exactly the configuration that engine consults, so an option never
24/// stores dead config for engines it does not use.
25#[derive(Debug, Clone, Copy, PartialEq)]
26pub enum PricingEngine {
27    /// Closed forms (Black-Scholes / Black-76 / Heston CF).
28    BlackScholes,
29    MonteCarlo(crate::equity::montecarlo::MonteCarloConfig),
30    Binomial(crate::core::lattice::LatticeConfig),
31    FiniteDifference(crate::equity::finite_difference::FdConfig),
32    BaroneAdesiWhaley,
33    BjerksundStensland,
34}
35
36impl PricingEngine {
37    /// The engine selector without its configuration.
38    pub fn kind(&self) -> Engine {
39        match self {
40            PricingEngine::BlackScholes => Engine::BlackScholes,
41            PricingEngine::MonteCarlo(_) => Engine::MonteCarlo,
42            PricingEngine::Binomial(_) => Engine::Binomial,
43            PricingEngine::FiniteDifference(_) => Engine::FiniteDifference,
44            PricingEngine::BaroneAdesiWhaley => Engine::BaroneAdesiWhaley,
45            PricingEngine::BjerksundStensland => Engine::BjerksundStensland,
46        }
47    }
48
49    /// Build from a selector with default per-engine configuration.
50    pub fn from_kind(kind: Engine) -> PricingEngine {
51        match kind {
52            Engine::BlackScholes => PricingEngine::BlackScholes,
53            Engine::MonteCarlo => PricingEngine::MonteCarlo(Default::default()),
54            Engine::Binomial => PricingEngine::Binomial(Default::default()),
55            Engine::FiniteDifference => PricingEngine::FiniteDifference(Default::default()),
56            Engine::BaroneAdesiWhaley => PricingEngine::BaroneAdesiWhaley,
57            Engine::BjerksundStensland => PricingEngine::BjerksundStensland,
58        }
59    }
60}
61
62/// The dynamics of the underlying — orthogonal to the numerical engine
63/// (Monte Carlo and finite difference both consult it). Heston carries
64/// its parameters, so "Heston selected but parameters missing" cannot be
65/// represented.
66#[derive(Debug, Clone, Copy, PartialEq, Default)]
67pub enum Model {
68    /// Black-Scholes dynamics on the option's vol surface.
69    #[default]
70    Gbm,
71    /// Dupire local volatility calibrated from the vol surface.
72    LocalVol,
73    /// Heston stochastic volatility.
74    Heston(crate::equity::heston::HestonParams),
75}
76
77impl Model {
78    pub fn is_heston(&self) -> bool {
79        matches!(self, Model::Heston(_))
80    }
81
82    /// The model under a parallel implied-vol shift — the model is a risk
83    /// factor owner like a surface or a curve. GBM and local vol read the
84    /// (already bumped) surface at pricing time, so they pass through
85    /// unchanged; Heston applies the library's vega convention: shift
86    /// `sqrt(v0)` and `sqrt(theta)` in parallel
87    /// ([`HestonParams::with_vol_shift`](crate::equity::heston::HestonParams::with_vol_shift)),
88    /// rather than recalibrating to the bumped surface.
89    pub fn with_vol_shift(&self, shift: f64) -> Model {
90        match self {
91            Model::Heston(params) => Model::Heston(params.with_vol_shift(shift)),
92            other => *other,
93        }
94    }
95
96    /// Parse from contract fields: the `mc_model` string plus the
97    /// `heston` parameter block (required when the model is Heston).
98    pub fn from_contract(
99        mc_model: Option<&str>,
100        heston: Option<crate::equity::heston::HestonParams>,
101    ) -> Result<Model, crate::core::errors::RustyQLibError> {
102        use crate::core::errors::RustyQLibError;
103        match mc_model.map(str::trim) {
104            None | Some("gbm") | Some("GBM") | Some("Gbm") => Ok(Model::Gbm),
105            Some("local_vol") | Some("localvol") | Some("LocalVol") | Some("local") => {
106                Ok(Model::LocalVol)
107            }
108            Some("heston") | Some("Heston") => {
109                let params = heston.ok_or_else(|| RustyQLibError::invalid_input(
110                    "heston",
111                    "heston parameters are required when mc_model = heston",
112                ))?;
113                params.validate()?;
114                Ok(Model::Heston(params))
115            }
116            Some(other) => Err(RustyQLibError::invalid_input(
117                "mc_model",
118                format!("unknown model '{other}' (use gbm, local_vol or heston)"),
119            )),
120        }
121    }
122}
123#[derive(Debug, Clone, Copy, PartialEq, Eq)]
124pub enum LongShort{
125    LONG,
126    SHORT
127}
128#[derive(Deserialize, Debug, Clone, Copy, PartialEq, Eq)]
129#[serde(rename_all = "snake_case")]
130pub enum PayoffType {
131    Vanilla,
132    Binary,
133    Barrier,
134    Asian,
135    ForwardStart,
136    Autocallable,
137    Lookback,
138    Accumulator,
139}
140impl FromStr for PayoffType {
141    type Err = Box<dyn Error>;
142    fn from_str(s: &str) -> Result<Self, Self::Err> {
143        match s.to_lowercase().as_str() {
144            "vanilla" => Ok(PayoffType::Vanilla),
145            "binary" => Ok(PayoffType::Binary),
146            "barrier" => Ok(PayoffType::Barrier),
147            "asian" => Ok(PayoffType::Asian),
148            "forward_start" | "forwardstart" => Ok(PayoffType::ForwardStart),
149            "autocallable" | "autocall" => Ok(PayoffType::Autocallable),
150            "lookback" => Ok(PayoffType::Lookback),
151            _ => Err("Invalid payoff type".into()),
152        }
153    }
154}
155
156
157/// Common interface linking all payoffs (Vanilla, Binary, Barrier, Asian).
158///
159/// Terminal payoffs implement [`payoff`](Payoff::payoff); path-dependent
160/// payoffs (Asian, Barrier) additionally override
161/// [`path_payoff`](Payoff::path_payoff), which defaults to evaluating the
162/// terminal payoff on the last point of the path. Engines only ever call
163/// these two methods, so a new payoff plugs into every engine at once.
164pub trait Payoff: Debug + Send + Sync {
165    /// Payoff for a given level of the underlying: the terminal spot for
166    /// European exercise, or the exercise spot for American.
167    fn payoff(&self, spot: f64, strike: f64) -> f64;
168
169    /// Payoff for a full simulated path (used by Monte Carlo). Terminal
170    /// payoffs default to the last point; Asian/Barrier override this.
171    /// The path excludes the initial spot (it starts at the first step).
172    fn path_payoff(&self, path: &[f64], strike: f64) -> f64 {
173        self.payoff(*path.last().expect("empty path"), strike)
174    }
175
176    /// True when the payoff depends on the whole path (Asian, Barrier), so
177    /// engines must simulate paths rather than terminal values.
178    fn is_path_dependent(&self) -> bool {
179        false
180    }
181
182    /// Intrinsic value at the given spot (the option's current market
183    /// spot at its contract strike).
184    fn payoff_amount(&self, spot: f64, strike: f64) -> f64 {
185        self.payoff(spot, strike)
186    }
187
188    /// Path payoff in AAD arithmetic — the mirror of
189    /// [`path_payoff`](Payoff::path_payoff) over tape variables, used by
190    /// the adjoint Monte Carlo Greeks
191    /// ([`montecarlo::aad_greeks`](crate::equity::montecarlo)). `None`
192    /// (the default) opts a payoff out: **discontinuous payoffs (barrier,
193    /// binary, autocallable) must stay out**, because the
194    /// almost-everywhere derivative of an indicator is zero — their
195    /// Greeks come from the bump stencils instead.
196    fn path_payoff_var<'t>(
197        &self,
198        _path: &[crate::core::aad::Var<'t>],
199        _strike: f64,
200    ) -> Option<crate::core::aad::Var<'t>> {
201        None
202    }
203
204    fn payoff_kind(&self) -> PayoffType;
205    fn put_or_call(&self) -> &PutOrCall;
206    fn exercise_style(&self)->&ContractStyle;
207
208    /// Downcast hook so pricers that need payoff-specific details (e.g. the
209    /// analytic pricer distinguishing cash- from asset-or-nothing binaries)
210    /// can recover the concrete payoff type.
211    fn as_any(&self) -> &dyn std::any::Any;
212
213    /// Clone through the trait object, so instruments holding a
214    /// `Box<dyn Payoff>` are cloneable (repricing a contract under another
215    /// market clones the instrument). Implementors write
216    /// `Box::new(self.clone())`.
217    fn clone_box(&self) -> Box<dyn Payoff>;
218}
219
220impl Clone for Box<dyn Payoff> {
221    fn clone(&self) -> Self {
222        self.clone_box()
223    }
224}