use crate::derivatives::forex::basic::CurrencyValue;
use crate::error::Result;
use crate::markets::interestrate::market_context::IrMarketContext;
use serde::{Deserialize, Serialize};
#[derive(Copy, Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
pub enum CapFloorKind {
Cap,
Floor,
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
pub enum CapStyle {
ForwardLooking,
BackwardCompounded,
}
pub fn caplet_total_variance(
style: CapStyle,
sigma: f64,
yf_t_to_start: f64,
yf_t_to_end: f64,
) -> f64 {
if yf_t_to_end <= 0.0 {
return 0.0;
}
let sig2 = sigma * sigma;
match style {
CapStyle::ForwardLooking => sig2 * yf_t_to_start.max(0.0),
CapStyle::BackwardCompounded => {
let te_minus_ts = yf_t_to_end - yf_t_to_start;
if yf_t_to_start >= 0.0 {
sig2 * (yf_t_to_start + te_minus_ts / 3.0)
} else {
sig2 * yf_t_to_end.powi(3) / (3.0 * te_minus_ts * te_minus_ts)
}
}
}
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, Default, Deserialize, Serialize)]
pub enum RateShiftMode {
#[default]
Zeros,
Instruments,
Forwards,
Swaps,
}
pub const DEFAULT_RATE_SHIFT_BP: f64 = 10.0;
pub const DEFAULT_VOL_SHIFT_BP: f64 = 1.0;
pub trait IRDerivatives {
fn mtm(&self, market: &IrMarketContext) -> Result<CurrencyValue>;
fn dv01(&self, market: &IrMarketContext) -> Result<f64>;
fn gamma(
&self,
market: &IrMarketContext,
rate_shift_bp: f64,
mode: RateShiftMode,
) -> Result<f64>;
fn vega(&self, market: &IrMarketContext, vol_shift_bp: f64) -> Result<f64>;
fn modified_duration(&self, market: &IrMarketContext) -> Result<f64> {
let pv = self.mtm(market)?.value;
if pv.abs() < 1.0e-12 {
return Ok(0.0);
}
let dv01 = self.dv01(market)?;
Ok(-dv01 * 1.0e4 / pv)
}
}
#[cfg(test)]
mod tests {
use super::{CapStyle, caplet_total_variance};
#[test]
fn forward_variance_is_linear_in_time() {
let sig = 0.01;
let v = caplet_total_variance(CapStyle::ForwardLooking, sig, 2.0, 2.25);
assert!((v - sig * sig * 2.0).abs() < 1e-15);
}
#[test]
fn backward_variance_exceeds_forward_variance() {
let sig = 0.01;
let v_fwd = caplet_total_variance(CapStyle::ForwardLooking, sig, 2.0, 2.25);
let v_bwd = caplet_total_variance(CapStyle::BackwardCompounded, sig, 2.0, 2.25);
assert!((v_bwd - v_fwd - sig * sig * 0.25 / 3.0).abs() < 1e-15);
}
#[test]
fn backward_variance_during_accrual() {
let sig = 0.02;
let v = caplet_total_variance(CapStyle::BackwardCompounded, sig, -0.10, 0.15);
let expected = sig * sig * 0.15_f64.powi(3) / (3.0 * 0.25_f64.powi(2));
assert!((v - expected).abs() < 1e-15);
}
#[test]
fn expired_variance_is_zero() {
assert_eq!(
caplet_total_variance(CapStyle::BackwardCompounded, 0.01, -0.5, -0.1),
0.0
);
}
}