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:
@@ -29,7 +29,7 @@ Usage
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Callable
|
||||
from collections.abc import Callable, Sequence
|
||||
|
||||
import numpy as np
|
||||
from numpy.typing import ArrayLike
|
||||
@@ -120,7 +120,9 @@ def _extract_timeperiod(
|
||||
|
||||
|
||||
def compute_many(
|
||||
indicators: list[str | tuple[str, dict[str, object]] | tuple[str, dict[str, object], object]],
|
||||
indicators: Sequence[
|
||||
str | tuple[str, dict[str, object]] | tuple[str, dict[str, object], object]
|
||||
],
|
||||
*,
|
||||
close: ArrayLike,
|
||||
high: ArrayLike | None = None,
|
||||
@@ -138,7 +140,9 @@ def compute_many(
|
||||
close_arr = np.ascontiguousarray(close, dtype=np.float64)
|
||||
high_arr = None if high is None else np.ascontiguousarray(high, dtype=np.float64)
|
||||
low_arr = None if low is None else np.ascontiguousarray(low, dtype=np.float64)
|
||||
volume_arr = None if volume is None else np.ascontiguousarray(volume, dtype=np.float64)
|
||||
volume_arr = (
|
||||
None if volume is None else np.ascontiguousarray(volume, dtype=np.float64)
|
||||
)
|
||||
|
||||
normalized = [_normalize_indicator_spec(spec) for spec in indicators]
|
||||
results: list[object | None] = [None] * len(normalized)
|
||||
@@ -161,11 +165,7 @@ def compute_many(
|
||||
continue
|
||||
|
||||
hlc_period = _extract_timeperiod(name, kwargs, _HLC_FASTPATH_DEFAULTS)
|
||||
if (
|
||||
hlc_period is not None
|
||||
and high_arr is not None
|
||||
and low_arr is not None
|
||||
):
|
||||
if hlc_period is not None and high_arr is not None and low_arr is not None:
|
||||
hlc_indices.append(idx)
|
||||
hlc_names.append(name)
|
||||
hlc_periods.append(hlc_period)
|
||||
@@ -196,7 +196,9 @@ def compute_many(
|
||||
|
||||
if high_arr is not None and low_arr is not None:
|
||||
try:
|
||||
results[idx] = _registry_run(name, high_arr, low_arr, close_arr, **kwargs)
|
||||
results[idx] = _registry_run(
|
||||
name, high_arr, low_arr, close_arr, **kwargs
|
||||
)
|
||||
continue
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
Reference in New Issue
Block a user