use crate::error::GreeksError;
use crate::greeks::Greeks;
use crate::model::position::Position;
use rust_decimal::Decimal;
use rust_decimal_macros::dec;
use serde::{Deserialize, Serialize};
use std::fmt;
use utoipa::ToSchema;
#[derive(Debug, Clone, Default, Serialize, Deserialize, ToSchema)]
pub struct PortfolioGreeks {
pub delta: Decimal,
pub gamma: Decimal,
pub theta: Decimal,
pub vega: Decimal,
pub rho: Decimal,
}
impl PortfolioGreeks {
pub fn new(
delta: Decimal,
gamma: Decimal,
theta: Decimal,
vega: Decimal,
rho: Decimal,
) -> Self {
Self {
delta,
gamma,
theta,
vega,
rho,
}
}
pub fn from_positions(positions: &[Position]) -> Result<Self, GreeksError> {
let mut greeks = Self::default();
for pos in positions {
let qty = pos.option.quantity.to_dec();
let sign = if pos.option.is_long() {
dec!(1)
} else {
dec!(-1)
};
let mult = qty * sign;
greeks.delta += pos.option.delta()? * mult;
greeks.gamma += pos.option.gamma()? * mult;
greeks.theta += pos.option.theta()? * mult;
greeks.vega += pos.option.vega()? * mult;
greeks.rho += pos.option.rho()? * mult;
}
Ok(greeks)
}
pub fn from_positions_with_underlying(
positions: &[Position],
underlying_quantity: Decimal,
) -> Result<Self, GreeksError> {
let mut greeks = Self::from_positions(positions)?;
greeks.delta += underlying_quantity;
Ok(greeks)
}
#[must_use]
pub fn is_delta_neutral(&self, tolerance: Decimal) -> bool {
self.delta.abs() <= tolerance
}
#[must_use]
pub fn is_gamma_neutral(&self, tolerance: Decimal) -> bool {
self.gamma.abs() <= tolerance
}
#[must_use]
pub fn is_vega_neutral(&self, tolerance: Decimal) -> bool {
self.vega.abs() <= tolerance
}
#[must_use]
pub fn delta_gap(&self, target: Decimal) -> Decimal {
target - self.delta
}
#[must_use]
pub fn gamma_gap(&self, target: Decimal) -> Decimal {
target - self.gamma
}
pub fn add(&mut self, other: &PortfolioGreeks) {
self.delta += other.delta;
self.gamma += other.gamma;
self.theta += other.theta;
self.vega += other.vega;
self.rho += other.rho;
}
#[must_use]
pub fn combined(&self, other: &PortfolioGreeks) -> PortfolioGreeks {
PortfolioGreeks {
delta: self.delta + other.delta,
gamma: self.gamma + other.gamma,
theta: self.theta + other.theta,
vega: self.vega + other.vega,
rho: self.rho + other.rho,
}
}
}
impl fmt::Display for PortfolioGreeks {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
writeln!(f, "Portfolio Greeks:")?;
writeln!(f, " Delta: {:.4}", self.delta)?;
writeln!(f, " Gamma: {:.6}", self.gamma)?;
writeln!(f, " Theta: {:.4}", self.theta)?;
writeln!(f, " Vega: {:.4}", self.vega)?;
writeln!(f, " Rho: {:.4}", self.rho)?;
Ok(())
}
}
#[derive(Debug, Clone, Default, Serialize, Deserialize, ToSchema)]
pub struct AdjustmentTarget {
pub delta: Option<Decimal>,
pub gamma: Option<Decimal>,
pub vega: Option<Decimal>,
pub theta: Option<Decimal>,
}
impl AdjustmentTarget {
#[must_use]
pub fn delta_neutral() -> Self {
Self {
delta: Some(Decimal::ZERO),
..Default::default()
}
}
#[must_use]
pub fn delta_gamma_neutral() -> Self {
Self {
delta: Some(Decimal::ZERO),
gamma: Some(Decimal::ZERO),
..Default::default()
}
}
#[must_use]
pub fn full_neutral() -> Self {
Self {
delta: Some(Decimal::ZERO),
gamma: Some(Decimal::ZERO),
vega: Some(Decimal::ZERO),
theta: None,
}
}
#[must_use]
pub fn with_delta(mut self, delta: Decimal) -> Self {
self.delta = Some(delta);
self
}
#[must_use]
pub fn with_gamma(mut self, gamma: Decimal) -> Self {
self.gamma = Some(gamma);
self
}
#[must_use]
pub fn with_vega(mut self, vega: Decimal) -> Self {
self.vega = Some(vega);
self
}
#[must_use]
pub fn with_theta(mut self, theta: Decimal) -> Self {
self.theta = Some(theta);
self
}
#[must_use]
pub fn delta_gap(&self, current: &PortfolioGreeks) -> Decimal {
self.delta
.map(|t| t - current.delta)
.unwrap_or(Decimal::ZERO)
}
#[must_use]
pub fn gamma_gap(&self, current: &PortfolioGreeks) -> Option<Decimal> {
self.gamma.map(|t| t - current.gamma)
}
#[must_use]
pub fn vega_gap(&self, current: &PortfolioGreeks) -> Option<Decimal> {
self.vega.map(|t| t - current.vega)
}
#[must_use]
pub fn is_satisfied(&self, current: &PortfolioGreeks, tolerance: Decimal) -> bool {
let delta_ok = self
.delta
.map(|t| (current.delta - t).abs() <= tolerance)
.unwrap_or(true);
let gamma_ok = self
.gamma
.map(|t| (current.gamma - t).abs() <= tolerance)
.unwrap_or(true);
let vega_ok = self
.vega
.map(|t| (current.vega - t).abs() <= tolerance)
.unwrap_or(true);
let theta_ok = self
.theta
.map(|t| (current.theta - t).abs() <= tolerance)
.unwrap_or(true);
delta_ok && gamma_ok && vega_ok && theta_ok
}
}
impl fmt::Display for AdjustmentTarget {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
writeln!(f, "Adjustment Target:")?;
if let Some(d) = self.delta {
writeln!(f, " Delta: {:.4}", d)?;
}
if let Some(g) = self.gamma {
writeln!(f, " Gamma: {:.6}", g)?;
}
if let Some(v) = self.vega {
writeln!(f, " Vega: {:.4}", v)?;
}
if let Some(t) = self.theta {
writeln!(f, " Theta: {:.4}", t)?;
}
Ok(())
}
}
#[cfg(test)]
mod tests_portfolio_greeks {
use super::*;
#[test]
fn test_portfolio_greeks_default() {
let greeks = PortfolioGreeks::default();
assert_eq!(greeks.delta, Decimal::ZERO);
assert_eq!(greeks.gamma, Decimal::ZERO);
assert_eq!(greeks.theta, Decimal::ZERO);
assert_eq!(greeks.vega, Decimal::ZERO);
assert_eq!(greeks.rho, Decimal::ZERO);
}
#[test]
fn test_portfolio_greeks_new() {
let greeks =
PortfolioGreeks::new(dec!(0.5), dec!(0.02), dec!(-0.05), dec!(0.15), dec!(0.01));
assert_eq!(greeks.delta, dec!(0.5));
assert_eq!(greeks.gamma, dec!(0.02));
assert_eq!(greeks.theta, dec!(-0.05));
assert_eq!(greeks.vega, dec!(0.15));
assert_eq!(greeks.rho, dec!(0.01));
}
#[test]
fn test_is_delta_neutral() {
let greeks =
PortfolioGreeks::new(dec!(0.005), dec!(0.02), dec!(-0.05), dec!(0.15), dec!(0.01));
assert!(greeks.is_delta_neutral(dec!(0.01)));
assert!(!greeks.is_delta_neutral(dec!(0.001)));
}
#[test]
fn test_is_gamma_neutral() {
let greeks =
PortfolioGreeks::new(dec!(0.5), dec!(0.005), dec!(-0.05), dec!(0.15), dec!(0.01));
assert!(greeks.is_gamma_neutral(dec!(0.01)));
assert!(!greeks.is_gamma_neutral(dec!(0.001)));
}
#[test]
fn test_delta_gap() {
let greeks =
PortfolioGreeks::new(dec!(0.3), dec!(0.02), dec!(-0.05), dec!(0.15), dec!(0.01));
assert_eq!(greeks.delta_gap(Decimal::ZERO), dec!(-0.3));
assert_eq!(greeks.delta_gap(dec!(0.5)), dec!(0.2));
}
#[test]
fn test_combined() {
let greeks1 =
PortfolioGreeks::new(dec!(0.3), dec!(0.02), dec!(-0.05), dec!(0.15), dec!(0.01));
let greeks2 =
PortfolioGreeks::new(dec!(0.2), dec!(0.01), dec!(-0.03), dec!(0.10), dec!(0.005));
let combined = greeks1.combined(&greeks2);
assert_eq!(combined.delta, dec!(0.5));
assert_eq!(combined.gamma, dec!(0.03));
assert_eq!(combined.theta, dec!(-0.08));
assert_eq!(combined.vega, dec!(0.25));
assert_eq!(combined.rho, dec!(0.015));
}
#[test]
fn test_add() {
let mut greeks1 =
PortfolioGreeks::new(dec!(0.3), dec!(0.02), dec!(-0.05), dec!(0.15), dec!(0.01));
let greeks2 =
PortfolioGreeks::new(dec!(0.2), dec!(0.01), dec!(-0.03), dec!(0.10), dec!(0.005));
greeks1.add(&greeks2);
assert_eq!(greeks1.delta, dec!(0.5));
assert_eq!(greeks1.gamma, dec!(0.03));
}
}
#[cfg(test)]
mod tests_adjustment_target {
use super::*;
#[test]
fn test_delta_neutral() {
let target = AdjustmentTarget::delta_neutral();
assert_eq!(target.delta, Some(Decimal::ZERO));
assert_eq!(target.gamma, None);
assert_eq!(target.vega, None);
assert_eq!(target.theta, None);
}
#[test]
fn test_delta_gamma_neutral() {
let target = AdjustmentTarget::delta_gamma_neutral();
assert_eq!(target.delta, Some(Decimal::ZERO));
assert_eq!(target.gamma, Some(Decimal::ZERO));
assert_eq!(target.vega, None);
}
#[test]
fn test_full_neutral() {
let target = AdjustmentTarget::full_neutral();
assert_eq!(target.delta, Some(Decimal::ZERO));
assert_eq!(target.gamma, Some(Decimal::ZERO));
assert_eq!(target.vega, Some(Decimal::ZERO));
assert_eq!(target.theta, None);
}
#[test]
fn test_builder_methods() {
let target = AdjustmentTarget::default()
.with_delta(dec!(0.1))
.with_gamma(dec!(0.02))
.with_vega(dec!(0.5));
assert_eq!(target.delta, Some(dec!(0.1)));
assert_eq!(target.gamma, Some(dec!(0.02)));
assert_eq!(target.vega, Some(dec!(0.5)));
}
#[test]
fn test_delta_gap() {
let target = AdjustmentTarget::delta_neutral();
let greeks =
PortfolioGreeks::new(dec!(0.3), dec!(0.02), dec!(-0.05), dec!(0.15), dec!(0.01));
assert_eq!(target.delta_gap(&greeks), dec!(-0.3));
}
#[test]
fn test_gamma_gap() {
let target = AdjustmentTarget::delta_gamma_neutral();
let greeks =
PortfolioGreeks::new(dec!(0.3), dec!(0.02), dec!(-0.05), dec!(0.15), dec!(0.01));
assert_eq!(target.gamma_gap(&greeks), Some(dec!(-0.02)));
}
#[test]
fn test_is_satisfied() {
let target = AdjustmentTarget::delta_neutral();
let neutral_greeks =
PortfolioGreeks::new(dec!(0.005), dec!(0.02), dec!(-0.05), dec!(0.15), dec!(0.01));
assert!(target.is_satisfied(&neutral_greeks, dec!(0.01)));
let non_neutral_greeks =
PortfolioGreeks::new(dec!(0.5), dec!(0.02), dec!(-0.05), dec!(0.15), dec!(0.01));
assert!(!target.is_satisfied(&non_neutral_greeks, dec!(0.01)));
}
}