Files
ferro-ta/src/options/digital.rs
T
Pratik Bhadane 3e0f289d51 chore: update ferro-ta version to 1.1.3 (#8)
- Bumped version numbers across Cargo.toml, Cargo.lock, pyproject.toml, and conda/meta.yaml to 1.1.3.
- Added new features including American option pricing, digital options, extended Greeks, and historical volatility estimators.
- Enhanced documentation and tests for new functionalities.
- Updated CHANGELOG.md to reflect changes for version 1.1.3.
2026-04-02 16:38:32 +05:30

165 lines
5.1 KiB
Rust

use numpy::{IntoPyArray, PyArray1, PyReadonlyArray1};
use pyo3::exceptions::PyValueError;
use pyo3::prelude::*;
use ferro_ta_core::options::digital::{
digital_greeks as core_digital_greeks, digital_price as core_digital_price, DigitalKind,
};
fn parse_digital_kind(s: &str) -> PyResult<DigitalKind> {
match s.to_ascii_lowercase().replace('-', "_").as_str() {
"cash_or_nothing" | "cash" => Ok(DigitalKind::CashOrNothing),
"asset_or_nothing" | "asset" => Ok(DigitalKind::AssetOrNothing),
_ => Err(PyValueError::new_err(
"digital_type must be 'cash_or_nothing' or 'asset_or_nothing'",
)),
}
}
#[pyfunction]
#[pyo3(signature = (underlying, strike, rate, time_to_expiry, volatility, option_type = "call", digital_type = "cash_or_nothing", carry = 0.0))]
#[allow(clippy::too_many_arguments)]
pub fn digital_price(
underlying: f64,
strike: f64,
rate: f64,
time_to_expiry: f64,
volatility: f64,
option_type: &str,
digital_type: &str,
carry: f64,
) -> PyResult<f64> {
let kind = super::parse_option_kind(option_type)?;
let dkind = parse_digital_kind(digital_type)?;
Ok(core_digital_price(
underlying,
strike,
rate,
carry,
time_to_expiry,
volatility,
kind,
dkind,
))
}
#[pyfunction]
#[pyo3(signature = (underlying, strike, rate, time_to_expiry, volatility, option_type = "call", digital_type = "cash_or_nothing", carry = None))]
#[allow(clippy::too_many_arguments)]
pub fn digital_price_batch<'py>(
py: Python<'py>,
underlying: PyReadonlyArray1<'py, f64>,
strike: PyReadonlyArray1<'py, f64>,
rate: PyReadonlyArray1<'py, f64>,
time_to_expiry: PyReadonlyArray1<'py, f64>,
volatility: PyReadonlyArray1<'py, f64>,
option_type: &str,
digital_type: &str,
carry: Option<PyReadonlyArray1<'py, f64>>,
) -> PyResult<Bound<'py, PyArray1<f64>>> {
let kind = super::parse_option_kind(option_type)?;
let dkind = parse_digital_kind(digital_type)?;
let underlying = underlying.as_slice()?;
let strike = strike.as_slice()?;
let rate = rate.as_slice()?;
let tte = time_to_expiry.as_slice()?;
let vol = volatility.as_slice()?;
let n = underlying.len();
let carry_vec = match carry {
Some(arr) => arr.as_slice()?.to_vec(),
None => vec![0.0; n],
};
let out: Vec<f64> = underlying
.iter()
.zip(strike.iter())
.zip(rate.iter())
.zip(tte.iter())
.zip(vol.iter())
.zip(carry_vec.iter())
.map(|(((((&u, &k), &r), &t), &v), &c)| core_digital_price(u, k, r, c, t, v, kind, dkind))
.collect();
Ok(out.into_pyarray(py))
}
#[pyfunction]
#[pyo3(signature = (underlying, strike, rate, time_to_expiry, volatility, option_type = "call", digital_type = "cash_or_nothing", carry = 0.0))]
#[allow(clippy::too_many_arguments)]
pub fn digital_greeks(
underlying: f64,
strike: f64,
rate: f64,
time_to_expiry: f64,
volatility: f64,
option_type: &str,
digital_type: &str,
carry: f64,
) -> PyResult<(f64, f64, f64)> {
let kind = super::parse_option_kind(option_type)?;
let dkind = parse_digital_kind(digital_type)?;
Ok(core_digital_greeks(
underlying,
strike,
rate,
carry,
time_to_expiry,
volatility,
kind,
dkind,
))
}
type GreekTriple<'py> = (
Bound<'py, PyArray1<f64>>,
Bound<'py, PyArray1<f64>>,
Bound<'py, PyArray1<f64>>,
);
#[pyfunction]
#[pyo3(signature = (underlying, strike, rate, time_to_expiry, volatility, option_type = "call", digital_type = "cash_or_nothing", carry = None))]
#[allow(clippy::too_many_arguments)]
pub fn digital_greeks_batch<'py>(
py: Python<'py>,
underlying: PyReadonlyArray1<'py, f64>,
strike: PyReadonlyArray1<'py, f64>,
rate: PyReadonlyArray1<'py, f64>,
time_to_expiry: PyReadonlyArray1<'py, f64>,
volatility: PyReadonlyArray1<'py, f64>,
option_type: &str,
digital_type: &str,
carry: Option<PyReadonlyArray1<'py, f64>>,
) -> PyResult<GreekTriple<'py>> {
let kind = super::parse_option_kind(option_type)?;
let dkind = parse_digital_kind(digital_type)?;
let underlying = underlying.as_slice()?;
let strike = strike.as_slice()?;
let rate = rate.as_slice()?;
let tte = time_to_expiry.as_slice()?;
let vol = volatility.as_slice()?;
let n = underlying.len();
let carry_vec = match carry {
Some(arr) => arr.as_slice()?.to_vec(),
None => vec![0.0; n],
};
let mut delta = Vec::with_capacity(n);
let mut gamma = Vec::with_capacity(n);
let mut vega = Vec::with_capacity(n);
for (((((&u, &k), &r), &t), &v), &c) in underlying
.iter()
.zip(strike.iter())
.zip(rate.iter())
.zip(tte.iter())
.zip(vol.iter())
.zip(carry_vec.iter())
{
let (d, g, ve) = core_digital_greeks(u, k, r, c, t, v, kind, dkind);
delta.push(d);
gamma.push(g);
vega.push(ve);
}
Ok((
delta.into_pyarray(py),
gamma.into_pyarray(py),
vega.into_pyarray(py),
))
}