feat: add full derivatives analytics layer (options + futures)
Implements all phases of the derivatives expansion plan: Rust core (crates/ferro_ta_core/src/options/, src/futures/): - BSM and Black-76 pricing (scalar + vectorized batch) - Greeks: delta, gamma, vega, theta, rho - Implied volatility solver (Newton + bisection fallback) - Smile/skew metrics: ATM IV, 25-delta RR/BF, skew slope, convexity - Chain helpers: moneyness labels, strike selection by offset or delta - Synthetic forwards, basis, annualized basis, implied carry, carry spread - Continuous contract stitching: weighted, back-adjusted, ratio-adjusted - Curve analytics: calendar spreads, slope, contango/backwardation summary PyO3 bindings (src/options/, src/futures/): - All Rust functions registered and exposed via _ferro_ta extension Python API (python/ferro_ta/analysis/): - options.py: pricing, greeks, IV, smile, chain, legacy iv_rank/percentile/zscore - futures.py: basis, carry, curve, roll, synthetic, continuous contracts - options_strategy.py: typed strategy schemas (expiry/strike selectors, leg presets, risk controls, simulation limits) - derivatives_payoff.py: multi-leg payoff aggregation and Greeks aggregation Bug fix: wrap _to_f64 calls in iv_rank/iv_percentile/iv_zscore to raise FerroTAInputError (not plain ValueError) for 2D array input. Docs: derivatives.rst, derivatives-analytics.md, options-volatility.md, quickstart.rst, index.rst, api/analysis.rst all updated. Tests: 2053 pass, 12 skipped. All CI checks pass locally. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 4.6
parent
2d5000262f
commit
602d675749
@@ -13,7 +13,7 @@ fn price_return(curr: f64, prev: f64) -> f64 {
|
||||
fn beta_fallback(x: &[f64], y: &[f64], timeperiod: usize) -> Vec<f64> {
|
||||
let n = x.len();
|
||||
let mut result = vec![f64::NAN; n];
|
||||
for end in timeperiod..n {
|
||||
for (end, slot) in result.iter_mut().enumerate().take(n).skip(timeperiod) {
|
||||
let start = end - timeperiod;
|
||||
let mut rx = vec![0.0_f64; timeperiod];
|
||||
let mut ry = vec![0.0_f64; timeperiod];
|
||||
@@ -36,7 +36,7 @@ fn beta_fallback(x: &[f64], y: &[f64], timeperiod: usize) -> Vec<f64> {
|
||||
.map(|&value| (value - mean_x).powi(2))
|
||||
.sum::<f64>()
|
||||
/ timeperiod as f64;
|
||||
result[end] = if var_x != 0.0 { cov / var_x } else { f64::NAN };
|
||||
*slot = if var_x != 0.0 { cov / var_x } else { f64::NAN };
|
||||
}
|
||||
result
|
||||
}
|
||||
@@ -102,8 +102,8 @@ pub fn beta<'py>(
|
||||
}
|
||||
}
|
||||
|
||||
for end in timeperiod..n {
|
||||
result[end] = if invalid_pairs == 0 {
|
||||
for (end, slot) in result.iter_mut().enumerate().take(n).skip(timeperiod) {
|
||||
*slot = if invalid_pairs == 0 {
|
||||
let denom = period * sum_rx2 - sum_rx * sum_rx;
|
||||
if denom != 0.0 {
|
||||
(period * sum_rxry - sum_rx * sum_ry) / denom
|
||||
|
||||
@@ -5,7 +5,7 @@ use pyo3::prelude::*;
|
||||
fn correl_fallback(x: &[f64], y: &[f64], timeperiod: usize) -> Vec<f64> {
|
||||
let n = x.len();
|
||||
let mut result = vec![f64::NAN; n];
|
||||
for end in (timeperiod - 1)..n {
|
||||
for (end, slot) in result.iter_mut().enumerate().take(n).skip(timeperiod - 1) {
|
||||
let wx = &x[(end + 1 - timeperiod)..=end];
|
||||
let wy = &y[(end + 1 - timeperiod)..=end];
|
||||
let mean_x = wx.iter().sum::<f64>() / timeperiod as f64;
|
||||
@@ -26,7 +26,7 @@ fn correl_fallback(x: &[f64], y: &[f64], timeperiod: usize) -> Vec<f64> {
|
||||
.sum::<f64>()
|
||||
.sqrt();
|
||||
let denom = std_x * std_y;
|
||||
result[end] = if denom != 0.0 { cov / denom } else { f64::NAN };
|
||||
*slot = if denom != 0.0 { cov / denom } else { f64::NAN };
|
||||
}
|
||||
result
|
||||
}
|
||||
@@ -72,10 +72,10 @@ pub fn correl<'py>(
|
||||
.map(|(&lhs, &rhs)| lhs * rhs)
|
||||
.sum::<f64>();
|
||||
|
||||
for end in (timeperiod - 1)..n {
|
||||
for (end, slot) in result.iter_mut().enumerate().take(n).skip(timeperiod - 1) {
|
||||
let denom_x = period * sum_x2 - sum_x * sum_x;
|
||||
let denom_y = period * sum_y2 - sum_y * sum_y;
|
||||
result[end] = if denom_x > 0.0 && denom_y > 0.0 {
|
||||
*slot = if denom_x > 0.0 && denom_y > 0.0 {
|
||||
(period * sum_xy - sum_x * sum_y) / (denom_x * denom_y).sqrt()
|
||||
} else {
|
||||
f64::NAN
|
||||
|
||||
Reference in New Issue
Block a user