use polars::prelude::*;
use polars::frame::DataFrame;
use std::f64::consts::PI;
pub fn calculate_delta(
df: &DataFrame,
price_column: &str,
strike_column: &str,
iv_column: &str,
time_column: &str,
is_call_column: &str,
) -> PolarsResult<Series> {
let price = df.column(price_column)?.f64()?;
let strike = df.column(strike_column)?.f64()?;
let iv = df.column(iv_column)?.f64()?;
let time = df.column(time_column)?.f64()?;
let is_call = df.column(is_call_column)?.bool()?;
let len = df.height();
let mut delta_values = vec![f64::NAN; len];
for i in 0..len {
let s = price.get(i).unwrap_or(f64::NAN);
let k = strike.get(i).unwrap_or(f64::NAN);
let v = iv.get(i).unwrap_or(f64::NAN);
let t = time.get(i).unwrap_or(f64::NAN);
let call = is_call.get(i).unwrap_or(false);
if s.is_nan() || k.is_nan() || v.is_nan() || t.is_nan() || t <= 0.0 {
continue;
}
let d1 = ((s / k).ln() + (0.5 * v * v) * t) / (v * t.sqrt());
let delta = if call {
norm_cdf(d1)
} else {
norm_cdf(d1) - 1.0
};
delta_values[i] = delta;
}
Ok(Series::new("delta".into(), delta_values))
}
pub fn calculate_gamma(
df: &DataFrame,
price_column: &str,
strike_column: &str,
iv_column: &str,
time_column: &str,
) -> PolarsResult<Series> {
let price = df.column(price_column)?.f64()?;
let strike = df.column(strike_column)?.f64()?;
let iv = df.column(iv_column)?.f64()?;
let time = df.column(time_column)?.f64()?;
let len = df.height();
let mut gamma_values = vec![f64::NAN; len];
for i in 0..len {
let s = price.get(i).unwrap_or(f64::NAN);
let k = strike.get(i).unwrap_or(f64::NAN);
let v = iv.get(i).unwrap_or(f64::NAN);
let t = time.get(i).unwrap_or(f64::NAN);
if s.is_nan() || k.is_nan() || v.is_nan() || t.is_nan() || t <= 0.0 {
continue;
}
let d1 = ((s / k).ln() + (0.5 * v * v) * t) / (v * t.sqrt());
let gamma = norm_pdf(d1) / (s * v * t.sqrt());
gamma_values[i] = gamma;
}
Ok(Series::new("gamma".into(), gamma_values))
}
pub fn calculate_theta(
df: &DataFrame,
price_column: &str,
strike_column: &str,
iv_column: &str,
time_column: &str,
rate_column: &str,
is_call_column: &str,
) -> PolarsResult<Series> {
let price = df.column(price_column)?.f64()?;
let strike = df.column(strike_column)?.f64()?;
let iv = df.column(iv_column)?.f64()?;
let time = df.column(time_column)?.f64()?;
let rate = df.column(rate_column)?.f64()?;
let is_call = df.column(is_call_column)?.bool()?;
let len = df.height();
let mut theta_values = vec![f64::NAN; len];
for i in 0..len {
let s = price.get(i).unwrap_or(f64::NAN);
let k = strike.get(i).unwrap_or(f64::NAN);
let v = iv.get(i).unwrap_or(f64::NAN);
let t = time.get(i).unwrap_or(f64::NAN);
let r = rate.get(i).unwrap_or(f64::NAN);
let call = is_call.get(i).unwrap_or(false);
if s.is_nan() || k.is_nan() || v.is_nan() || t.is_nan() || r.is_nan() || t <= 0.0 {
continue;
}
let d1 = ((s / k).ln() + (r + 0.5 * v * v) * t) / (v * t.sqrt());
let d2 = d1 - v * t.sqrt();
let theta = if call {
-(s * v * norm_pdf(d1)) / (2.0 * t.sqrt()) - r * k * (-r * t).exp() * norm_cdf(d2)
} else {
-(s * v * norm_pdf(d1)) / (2.0 * t.sqrt()) + r * k * (-r * t).exp() * norm_cdf(-d2)
};
theta_values[i] = theta / 365.0;
}
Ok(Series::new("theta".into(), theta_values))
}
pub fn calculate_vega(
df: &DataFrame,
price_column: &str,
strike_column: &str,
iv_column: &str,
time_column: &str,
rate_column: &str,
) -> PolarsResult<Series> {
let price = df.column(price_column)?.f64()?;
let strike = df.column(strike_column)?.f64()?;
let iv = df.column(iv_column)?.f64()?;
let time = df.column(time_column)?.f64()?;
let rate = df.column(rate_column)?.f64()?;
let len = df.height();
let mut vega_values = vec![f64::NAN; len];
for i in 0..len {
let s = price.get(i).unwrap_or(f64::NAN);
let k = strike.get(i).unwrap_or(f64::NAN);
let v = iv.get(i).unwrap_or(f64::NAN);
let t = time.get(i).unwrap_or(f64::NAN);
let r = rate.get(i).unwrap_or(f64::NAN);
if s.is_nan() || k.is_nan() || v.is_nan() || t.is_nan() || r.is_nan() || t <= 0.0 {
continue;
}
let d1 = ((s / k).ln() + (r + 0.5 * v * v) * t) / (v * t.sqrt());
let vega = 0.01 * s * t.sqrt() * norm_pdf(d1);
vega_values[i] = vega;
}
Ok(Series::new("vega".into(), vega_values))
}
pub fn calculate_gamma_exposure(
df: &DataFrame,
gamma_column: &str,
contracts_column: &str,
multiplier_column: &str,
) -> PolarsResult<Series> {
let gamma = df.column(gamma_column)?.f64()?;
let contracts = df.column(contracts_column)?.i64()?;
let multiplier = df.column(multiplier_column)?.f64()?;
let len = df.height();
let mut gamma_exposure = vec![f64::NAN; len];
for i in 0..len {
let g = gamma.get(i).unwrap_or(f64::NAN);
let c = contracts.get(i).unwrap_or(0);
let m = multiplier.get(i).unwrap_or(f64::NAN);
if g.is_nan() || m.is_nan() {
continue;
}
gamma_exposure[i] = g * c as f64 * m;
}
Ok(Series::new("gamma_exposure".into(), gamma_exposure))
}
fn norm_pdf(x: f64) -> f64 {
(-(x * x) / 2.0).exp() / (2.0 * PI).sqrt()
}
fn norm_cdf(x: f64) -> f64 {
if x > 6.0 {
1.0
} else if x < -6.0 {
0.0
} else {
let b1 = 0.31938153;
let b2 = -0.356563782;
let b3 = 1.781477937;
let b4 = -1.821255978;
let b5 = 1.330274429;
let p = 0.2316419;
let c = 0.39894228;
let t = 1.0 / (1.0 + p * x.abs());
let poly = t * (b1 + t * (b2 + t * (b3 + t * (b4 + t * b5))));
if x >= 0.0 {
1.0 - c * (-x * x / 2.0).exp() * poly
} else {
c * (-x * x / 2.0).exp() * poly
}
}
}
pub fn add_greeks_indicators(df: &mut DataFrame) -> PolarsResult<()> {
let required_columns = [
"price", "strike", "iv", "time_to_expiry", "rate", "is_call"
];
for &col in required_columns.iter() {
if !df.schema().contains(col) {
return Err(PolarsError::ComputeError(
format!("Required column '{}' not found", col).into(),
));
}
}
let delta = calculate_delta(df, "price", "strike", "iv", "time_to_expiry", "is_call")?;
df.with_column(delta)?;
let gamma = calculate_gamma(df, "price", "strike", "iv", "time_to_expiry")?;
df.with_column(gamma)?;
let theta = calculate_theta(df, "price", "strike", "iv", "time_to_expiry", "rate", "is_call")?;
df.with_column(theta)?;
let vega = calculate_vega(df, "price", "strike", "iv", "time_to_expiry", "rate")?;
df.with_column(vega)?;
if df.schema().contains("contracts") && df.schema().contains("multiplier") {
let gamma_exposure = calculate_gamma_exposure(df, "gamma", "contracts", "multiplier")?;
df.with_column(gamma_exposure)?;
}
Ok(())
}