Analysis Modules
Parallel parameter sweeps
Grid-search a strategy over every parameter combination in one call. Signal generation runs once per combination in Python (it calls the Rust indicators); every backtest and metric then runs in Rust, in parallel, with the GIL released.
The result keeps the full returns matrix, so the same sweep feeds straight
into the overfitting diagnostics in ferro_ta.analysis.overfit:
>>> import numpy as np
>>> from ferro_ta.analysis.sweep import sweep
>>> close = np.cumprod(1 + np.random.default_rng(1).normal(0, 0.01, 2000)) * 100
>>> res = sweep(close, "sma_crossover", {"fast": [5, 10, 20], "slow": [50, 100, 200]})
>>> res.best_params("sharpe")
{'fast': 10, 'slow': 100}
>>> res.deflated_sharpe() # selection-bias corrected
>>> res.pbo(n_splits=10).pbo # probability of overfitting
- class ferro_ta.analysis.sweep.SweepResult(params, returns, metrics)[source]
Bases:
objectOutput of
sweep().- Parameters:
params (tuple[Mapping]) – Parameters of each combination (column order of
returns).returns (NDArray[np.float64], shape (n_bars, n_combos)) – Per-bar strategy returns of every combination.
metrics (dict[str, NDArray[np.float64]]) – Each performance metric as an array of length
n_combos.Immutable (arrays are read-only and mappings are read-only proxies.)
dict. (best_params() returns a fresh)
- best_index(metric='sharpe', *, minimize=False, radius=0)[source]
Column index of the best combination by
metric(NaN-aware).radius=0takes the single best cell.radius >= 1scores each cell by the mean of its grid neighbourhood first, so a broad stable region beats an isolated spike, which is usually noise.
- best_params(metric='sharpe', *, minimize=False, radius=0)[source]
Parameter dict of the best combination (see
best_index()).
- deflated_sharpe(metric='sharpe', *, minimize=False, radius=0)[source]
Deflated Sharpe Ratio of the combination selected by
metric.Deflates against all
n_trialscombinations, so the more you searched the higher the bar.metriconly picks the winner; the deflation itself always uses raw per-period Sharpe (no risk-free adjustment), as the DSR formula requires.
- pivot(metric, row, col)[source]
Heatmap-ready
(row_values, col_values, matrix)for two params.When other parameters also vary, each cell holds the best (max) value over them; cells with no combination are NaN.
- returns: NDArray[float64]
- ferro_ta.analysis.sweep.expand_grid(param_grid)[source]
Normalise a grid to a tuple of parameter dicts.
A mapping of
name -> valuesexpands to its Cartesian product (keys in insertion order); a sequence of dicts is used as-is.
- ferro_ta.analysis.sweep.sweep(close, strategy, param_grid, *, commission_per_trade=0.0, slippage_bps=0.0, initial_capital=100000.0, periods_per_year=252.0, risk_free_rate=0.0, parallel=True)[source]
Backtest
strategyfor every combination inparam_grid.- Parameters:
close (array-like) – Close prices.
strategy (str or callable) – Strategy name from
ferro_ta.analysis.backtest.backtest()’s registry, orfn(close, **params) -> signals. Names inRUST_BUILTIN_STRATEGIES(sma_crossover,rsi_30_70,macd_crossover) run entirely in Rust, signal generation included, and silently skip combinations that violate their constraints (e.g.fast >= slow), soSweepResult.paramslists only the combinations that ran. Callables run once per combination in Python and any exception they raise aborts the sweep.param_grid (mapping or sequence of mappings) –
{"fast": [5, 10], "slow": [50, 100]}(Cartesian product) or an explicit list of parameter dicts.commission_per_trade (float) – Costs, with the same semantics as
backtest().slippage_bps (float) – Costs, with the same semantics as
backtest().initial_capital (float) – Costs, with the same semantics as
backtest().periods_per_year (float) – Annualisation settings for the metrics.
risk_free_rate (float) – Annualisation settings for the metrics.
parallel (bool) – Run combinations across threads (default True).
- Return type:
Backtest-overfitting statistics
Run enough parameter combinations and one of them will look brilliant by chance. This module quantifies how much of a backtest’s edge survives that selection bias. Every heavy computation runs in Rust.
probabilistic_sharpe_ratio()— P(true Sharpe > benchmark), corrected for sample length, skewness and fat tails (Bailey & López de Prado, 2012).deflated_sharpe_ratio()— PSR measured against the best Sharpe you would expect from N zero-skill trials (Bailey & López de Prado, 2014).pbo()— Probability of Backtest Overfitting via combinatorially symmetric cross-validation (Bailey, Borwein, López de Prado & Zhu, 2017).purged_kfold()— k-fold splits with purge/embargo gaps for labels that overlap in time.
All Sharpe ratios in this module are per-period (mean / std of the raw returns, not annualised).
Example
>>> import numpy as np
>>> from ferro_ta.analysis.overfit import deflated_sharpe_ratio, pbo, per_period_sharpe
>>> rng = np.random.default_rng(0)
>>> trials = rng.normal(0.0, 0.01, size=(1000, 50)) # 50 noise strategies
>>> best = trials[:, trials.mean(0).argmax()]
>>> dsr = deflated_sharpe_ratio(best, trial_sharpes=per_period_sharpe(trials))
>>> result = pbo(trials, n_splits=10) # ~0.5 for pure noise
- class ferro_ta.analysis.overfit.PBOResult(pbo, logits, is_sharpe, oos_sharpe, best_index)[source]
Bases:
objectResult of
pbo(); arrays have one entry per IS/OOS combination.Immutable: arrays are read-only.
- Parameters:
pbo (float) – Probability of Backtest Overfitting — fraction of combinations where the in-sample winner landed at or below the out-of-sample median.
logits (NDArray[np.float64]) – Logit of the IS winner’s OOS relative rank (> 0 is good).
is_sharpe (NDArray[np.float64]) – Per-period Sharpe of the IS winner, in and out of sample. Regressing
oos_sharpeonis_sharpegives the performance degradation.oos_sharpe (NDArray[np.float64]) – Per-period Sharpe of the IS winner, in and out of sample. Regressing
oos_sharpeonis_sharpegives the performance degradation.best_index (NDArray[np.int64]) – Column chosen as IS winner in each combination.
- best_index: NDArray[int64]
- is_sharpe: NDArray[float64]
- logits: NDArray[float64]
- oos_sharpe: NDArray[float64]
- ferro_ta.analysis.overfit.deflated_sharpe_ratio(returns, *, trial_sharpes=None, n_trials=None, trial_sharpe_variance=None)[source]
Deflated Sharpe Ratio of the selected strategy.
Pass either
trial_sharpes(per-period Sharpe of every configuration tried, including the selected one) or bothn_trialsandtrial_sharpe_variance.A DSR above ~0.95 suggests the edge survives the multiple-testing correction; values near or below 0.5 mean the best result is consistent with luck.
- ferro_ta.analysis.overfit.expected_max_sharpe(n_trials, trial_sharpe_variance)[source]
Expected best per-period Sharpe among
n_trialszero-skill trials.
- ferro_ta.analysis.overfit.pbo(returns_2d, n_splits=16)[source]
Probability of Backtest Overfitting via CSCV.
- Parameters:
returns_2d (array-like, shape (n_bars, n_strategies)) – Per-period returns, one column per parameter configuration — e.g.
ferro_ta.analysis.sweep.SweepResult.returns.n_splits (int) – Even number of contiguous time blocks (2–20). Evaluates C(n_splits, n_splits / 2) combinations: 12 870 for the default 16.
- Return type:
- ferro_ta.analysis.overfit.per_period_sharpe(returns_2d)[source]
Per-period Sharpe (mean / sample std) of each column; 0 where std is 0.
- Parameters:
returns_2d (ArrayLike)
- Return type:
NDArray[float64]
- ferro_ta.analysis.overfit.probabilistic_sharpe_ratio(returns, benchmark_sharpe=0.0)[source]
Probability that the true per-period Sharpe exceeds
benchmark_sharpe.
- ferro_ta.analysis.overfit.purged_kfold(n_bars, n_folds=5, *, purge_bars=0, embargo_bars=0)[source]
Purged/embargoed k-fold as
(train_idx, test_idx)index arrays.purge_barsshould be at least your label horizon;embargo_barsguards against serial correlation leaking from test back into training.
- ferro_ta.analysis.overfit.purged_kfold_indices(n_bars, n_folds=5, *, purge_bars=0, embargo_bars=0)[source]
Fold boundaries, shape (n_folds, 4).
Columns are
[train_left_end, test_start, test_end, train_right_start]: train =[0, train_left_end) ∪ [train_right_start, n_bars).
ferro_ta.analysis.options — Rust-backed derivatives analytics for options.
This module preserves the legacy IV-series helpers and expands them with pricing, Greeks, implied-volatility inversion, smile analytics, and strike selection helpers suitable for research and simulation workflows.
- class ferro_ta.analysis.options.ExtendedGreeks(vanna, volga, charm, speed, color)[source]
Bases:
objectContainer for second-order and cross Greeks.
- Parameters:
- class ferro_ta.analysis.options.OptionGreeks(delta, gamma, vega, theta, rho)[source]
Bases:
objectContainer for first-order Greeks.
- Parameters:
- class ferro_ta.analysis.options.SmileMetrics(atm_iv, risk_reversal_25d, butterfly_25d, skew_slope, convexity)[source]
Bases:
objectSummary metrics for a single smile slice.
- Parameters:
- class ferro_ta.analysis.options.VolCone(windows, min, p25, median, p75, max)[source]
Bases:
objectHistorical realized vol distribution across window lengths.
- Parameters:
windows (NDArray[float64])
min (NDArray[float64])
p25 (NDArray[float64])
median (NDArray[float64])
p75 (NDArray[float64])
max (NDArray[float64])
- max: NDArray[float64]
- median: NDArray[float64]
- min: NDArray[float64]
- p25: NDArray[float64]
- p75: NDArray[float64]
- windows: NDArray[float64]
- ferro_ta.analysis.options.american_option_price(underlying, strike, rate, time_to_expiry, volatility, *, option_type='call', carry=0.0)[source]
American option price using the Barone-Adesi-Whaley (1987) approximation.
Accurate to within a few basis points for standard equity/index parameters. O(1) per evaluation — suitable for batch pricing or calibration.
- Parameters:
underlying (ArrayLike | float) – Current underlying (spot) price.
strike (ArrayLike | float) – Option strike price.
rate (ArrayLike | float) – Risk-free rate (annualised, decimal).
time_to_expiry (ArrayLike | float) – Time to expiry in years.
volatility (ArrayLike | float) – Implied volatility (annualised, decimal).
option_type (str) –
"call"(default) or"put".carry (ArrayLike | float) – Continuous carry / dividend yield (annualised, decimal). Default 0. For calls with
carry = 0(no dividends) early exercise is never optimal and the result equals the European BSM price.
- Returns:
American option price ≥ European BSM price.
- Return type:
float or NDArray[float64]
Notes
The BAW approximation uses a quadratic equation to find the critical exercise boundary S* via Newton-Raphson iteration, then adds the early exercise premium on top of the European price.
Reference: Barone-Adesi, G. & Whaley, R.E. (1987). “Efficient Analytic Approximation of American Option Values.” Journal of Finance, 42(2), 301–320.
See also
early_exercise_premiumDifference between American and European prices.
- ferro_ta.analysis.options.black_76_price(forward, strike, rate, time_to_expiry, volatility, *, option_type='call')[source]
Price options under Black-76.
- ferro_ta.analysis.options.black_scholes_price(spot, strike, rate, time_to_expiry, volatility, *, option_type='call', dividend_yield=0.0)[source]
Price options under Black-Scholes-Merton.
- ferro_ta.analysis.options.close_to_close_vol(close, window=20, trading_days_per_year=252.0)[source]
Rolling close-to-close realized volatility (annualised).
Baseline estimator — uses only closing prices. Less efficient than OHLC estimators but requires only daily close data.
- Parameters:
- Returns:
Same length as close. First
windowvalues are NaN.- Return type:
NDArray[float64]
Notes
Formula:
σ = √( Σᵢ ln²(Cᵢ/Cᵢ₋₁) / window × trading_days_per_year )
No Bessel correction is applied (population variance, not sample variance).
- ferro_ta.analysis.options.digital_option_greeks(underlying, strike, rate, time_to_expiry, volatility, *, option_type='call', digital_type='cash_or_nothing', carry=0.0)[source]
Delta, gamma, and vega for a digital option via numerical bumping.
Uses central finite differences (spot bump ε = spot × 10⁻³ for delta/gamma; vol bump ε = 10⁻³ for vega). Theta and rho are set to NaN.
- Parameters:
underlying (ArrayLike | float) – Same as
digital_option_price().strike (ArrayLike | float) – Same as
digital_option_price().rate (ArrayLike | float) – Same as
digital_option_price().time_to_expiry (ArrayLike | float) – Same as
digital_option_price().volatility (ArrayLike | float) – Same as
digital_option_price().option_type (str) – Same as
digital_option_price().carry (ArrayLike | float) – Same as
digital_option_price().digital_type (str) –
"cash_or_nothing"(default) or"asset_or_nothing".
- Returns:
Named tuple; only
delta,gamma,vegaare finite.thetaandrhoare NaN.- Return type:
- ferro_ta.analysis.options.digital_option_price(underlying, strike, rate, time_to_expiry, volatility, *, option_type='call', digital_type='cash_or_nothing', carry=0.0)[source]
Price a digital (binary) option under BSM.
- Parameters:
underlying (ArrayLike | float) – Current underlying (spot) price.
strike (ArrayLike | float) – Option strike price.
rate (ArrayLike | float) – Risk-free rate (annualised, decimal).
time_to_expiry (ArrayLike | float) – Time to expiry in years.
volatility (ArrayLike | float) – Implied volatility (annualised, decimal).
option_type (str) –
"call"(default) or"put".digital_type (str) –
"cash_or_nothing"(default) — pays 1 unit of cash if ITM at expiry; or"asset_or_nothing"— pays the underlying asset price.carry (ArrayLike | float) – Continuous carry / dividend yield (annualised, decimal). Default 0.
- Returns:
Option price. Returns a scalar when all inputs are scalars, or an array when any input is an array.
- Return type:
float or NDArray[float64]
Notes
Closed-form BSM formulas:
Cash-or-nothing call: e^{−rT} · N(d₂) Cash-or-nothing put: e^{−rT} · N(−d₂) Asset-or-nothing call: S · e^{−qT} · N(d₁) Asset-or-nothing put: S · e^{−qT} · N(−d₁)Put-call parity for cash-or-nothing: call + put = e^{−rT}. Put-call parity for asset-or-nothing: call + put = S · e^{−qT}.
Invalid inputs (non-positive spot/strike, negative time or vol) return NaN.
Early exercise premium: American price − European BSM price.
Represents the additional value an American option holder gains from the right to exercise before expiry. Always ≥ 0.
- Parameters:
underlying (ArrayLike | float) – Same as
american_option_price().strike (ArrayLike | float) – Same as
american_option_price().rate (ArrayLike | float) – Same as
american_option_price().time_to_expiry (ArrayLike | float) – Same as
american_option_price().volatility (ArrayLike | float) – Same as
american_option_price().option_type (str) – Same as
american_option_price().carry (ArrayLike | float) – Same as
american_option_price().
- Returns:
Premium ≥ 0. Typically 0 for calls with no dividends.
- Return type:
float or NDArray[float64]
Notes
For equity calls with zero carry (no dividends), early exercise is never optimal so the premium is ≈ 0. For puts (or calls on dividend-paying underlyings), the premium increases with in-the-moneyness, rate, and time to expiry.
- ferro_ta.analysis.options.expected_move(spot, iv, days_to_expiry, trading_days_per_year=252.0)[source]
Expected ±1σ move over days_to_expiry calendar days.
Uses the log-normal approximation:
upper_move = spot × e^{+σ√(days/trading_days)} − spot lower_move = spot × e^{−σ√(days/trading_days)} − spot- Parameters:
- Returns:
(lower_move, upper_move)— signed absolute price changes fromspot.lower_move < 0,upper_move > 0.- Return type:
Notes
Because of log-normal skew,
|upper_move| > |lower_move|.Examples
>>> from ferro_ta.analysis.options import expected_move >>> lower, upper = expected_move(100.0, 0.20, 30) >>> round(upper, 2) 7.14
- ferro_ta.analysis.options.extended_greeks(underlying, strike, rate, time_to_expiry, volatility, *, option_type='call', model='bsm', carry=0.0)[source]
Return vanna, volga, charm, speed, and color (second-order / cross Greeks).
All Greeks are computed via closed-form BSM formulas. Black-76 is not yet supported and returns NaN for all five values.
- Parameters:
underlying (ArrayLike | float) – Current underlying (spot) price.
strike (ArrayLike | float) – Option strike price.
rate (ArrayLike | float) – Risk-free rate (annualised, decimal — e.g.
0.05for 5 %).time_to_expiry (ArrayLike | float) – Time to expiry in years.
volatility (ArrayLike | float) – Implied volatility (annualised, decimal).
option_type (str) –
"call"(default) or"put".model (str) –
"bsm"(default)."black76"returns NaN for all fields.carry (ArrayLike | float) – Continuous carry / dividend yield (annualised, decimal). Default 0.
- Returns:
Named tuple with fields:
vanna — ∂Δ/∂σ: sensitivity of delta to a change in vol.
volga — ∂²V/∂σ² (vomma): sensitivity of vega to a change in vol.
charm — ∂Δ/∂t: daily rate of change in delta (theta of delta).
speed — ∂Γ/∂S: rate of change in gamma with respect to spot.
color — ∂Γ/∂t: daily rate of change in gamma.
- Return type:
Notes
Inputs may be scalars or broadcastable arrays. When arrays are supplied each field of the returned
ExtendedGreeksis anNDArray.Closed-form expressions (BSM, zero-carry):
vanna = -e^{-qT} · φ(d₁) · d₂ / σ volga = S · e^{-qT} · φ(d₁) · √T · d₁ · d₂ / σ charm = -e^{-qT} · φ(d₁) · [2(r-q)T - d₂·σ·√T] / (2T·σ·√T) speed = -Γ/S · (d₁/(σ√T) + 1) color = -Γ · [r-q + d₁·σ/(2√T) + (2(r-q)T - d₂·σ√T)·d₁/(2T·σ√T)]
- ferro_ta.analysis.options.garman_klass_vol(open, high, low, close, window=20, trading_days_per_year=252.0)[source]
Rolling Garman-Klass OHLC realized volatility estimator (annualised).
Extends Parkinson by incorporating the open-close return. ~7.4× more efficient than close-to-close. Does not handle overnight gaps.
- Parameters:
open (ArrayLike) – Arrays of daily OHLC prices (same length, ≥ window).
high (ArrayLike) – Arrays of daily OHLC prices (same length, ≥ window).
low (ArrayLike) – Arrays of daily OHLC prices (same length, ≥ window).
close (ArrayLike) – Arrays of daily OHLC prices (same length, ≥ window).
window (int) – Rolling look-back period in bars (default 20).
trading_days_per_year (float) – Annualisation factor (default 252).
- Returns:
Same length as close. First
window - 1values are NaN.- Return type:
NDArray[float64]
Notes
Per-bar contribution:
GK = 0.5·ln²(H/L) − (2·ln2 − 1)·ln²(C/O)
Reference: Garman, M.B. & Klass, M.J. (1980). “On the Estimation of Security Price Volatilities from Historical Data.” Journal of Business, 53(1).
- ferro_ta.analysis.options.greeks(underlying, strike, rate, time_to_expiry, volatility, *, option_type='call', model='bsm', carry=0.0)[source]
Return delta, gamma, vega, theta, and rho.
- ferro_ta.analysis.options.implied_volatility(price, underlying, strike, rate, time_to_expiry, *, option_type='call', model='bsm', carry=0.0, initial_guess=0.2, tolerance=1e-08, max_iterations=100)[source]
Invert option prices to implied volatility.
- Parameters:
- Return type:
float | NDArray[float64]
- ferro_ta.analysis.options.iv_percentile(iv_series, window=252)[source]
Compute rolling IV percentile in Rust while preserving the legacy API.
- Parameters:
iv_series (ArrayLike)
window (int)
- Return type:
NDArray[float64]
- ferro_ta.analysis.options.iv_rank(iv_series, window=252)[source]
Compute rolling IV rank in Rust while preserving the legacy API.
- Parameters:
iv_series (ArrayLike)
window (int)
- Return type:
NDArray[float64]
- ferro_ta.analysis.options.iv_zscore(iv_series, window=252)[source]
Compute rolling IV z-score in Rust while preserving the legacy API.
- Parameters:
iv_series (ArrayLike)
window (int)
- Return type:
NDArray[float64]
- ferro_ta.analysis.options.label_moneyness(strikes, reference_price, *, option_type='call')[source]
Label strikes as
ITM,ATM, orOTM.
- ferro_ta.analysis.options.option_price(underlying, strike, rate, time_to_expiry, volatility, *, option_type='call', model='bsm', carry=0.0)[source]
Model-dispatched option price helper.
- ferro_ta.analysis.options.parkinson_vol(high, low, window=20, trading_days_per_year=252.0)[source]
Rolling Parkinson high-low realized volatility estimator (annualised).
~5× more efficient than close-to-close for diffusion processes. Does not account for drift or overnight gaps.
- Parameters:
- Returns:
Same length as high. First
window - 1values are NaN.- Return type:
NDArray[float64]
Notes
Formula per window:
σ² = (1 / (4·ln2·window)) · Σ ln²(Hᵢ/Lᵢ) × trading_days_per_year
Reference: Parkinson, M. (1980). “The Extreme Value Method for Estimating the Variance of the Rate of Return.” Journal of Business, 53(1).
- ferro_ta.analysis.options.put_call_parity_deviation(call_price, put_price, spot, strike, rate, time_to_expiry, *, carry=0.0)[source]
Put-call parity deviation:
C − P − (S·e^{−q·T} − K·e^{−r·T}).At no-arbitrage the deviation is exactly 0. A non-zero result indicates mispricing, a data error, or a stale quote.
- Parameters:
call_price (float) – Market or model price of the call option.
put_price (float) – Market or model price of the put option.
spot (float) – Current underlying price.
strike (float) – Common strike price of the call and put.
rate (float) – Risk-free rate (annualised, decimal).
time_to_expiry (float) – Time to expiry in years.
carry (float) – Continuous dividend yield / carry rate (annualised, decimal).
- Returns:
Signed deviation. Positive → call is overpriced relative to put; negative → put is overpriced relative to call.
- Return type:
Examples
>>> from ferro_ta.analysis.options import option_price, put_call_parity_deviation >>> call = option_price(100, 100, 0.05, 1.0, 0.2, option_type="call") >>> put = option_price(100, 100, 0.05, 1.0, 0.2, option_type="put") >>> put_call_parity_deviation(call, put, 100, 100, 0.05, 1.0) # ≈ 0.0
- ferro_ta.analysis.options.rogers_satchell_vol(open, high, low, close, window=20, trading_days_per_year=252.0)[source]
Rolling Rogers-Satchell OHLC realized volatility estimator (annualised).
Drift-invariant: unbiased for assets with non-zero expected return. Does not handle overnight gaps.
- Parameters:
open (ArrayLike) – Arrays of daily OHLC prices (same length, ≥ window).
high (ArrayLike) – Arrays of daily OHLC prices (same length, ≥ window).
low (ArrayLike) – Arrays of daily OHLC prices (same length, ≥ window).
close (ArrayLike) – Arrays of daily OHLC prices (same length, ≥ window).
window (int) – Rolling look-back period in bars (default 20).
trading_days_per_year (float) – Annualisation factor (default 252).
- Returns:
Same length as close. First
window - 1values are NaN.- Return type:
NDArray[float64]
Notes
Per-bar contribution (u = ln(H/O), d = ln(L/O), c = ln(C/O)):
RS = u·(u − c) + d·(d − c)
Reference: Rogers, L.C.G. & Satchell, S.E. (1991). “Estimating Variance from High, Low and Closing Prices.” Annals of Applied Probability, 1(4).
- ferro_ta.analysis.options.select_strike(strikes, reference_price, *, option_type='call', selector='ATM', delta_target=None, volatilities=None, time_to_expiry=None, model='bsm', rate=0.0, carry=0.0)[source]
Select a strike by ATM/ITM/OTM offset or delta target.
- ferro_ta.analysis.options.smile_metrics(strikes, vols, reference_price, time_to_expiry, *, model='bsm', rate=0.0, carry=0.0)[source]
Compute ATM IV, 25-delta RR/BF, skew slope, and convexity.
- ferro_ta.analysis.options.term_structure_slope(tenors, atm_ivs)[source]
Slope of ATM IV against tenor.
- Parameters:
tenors (ArrayLike)
atm_ivs (ArrayLike)
- Return type:
- ferro_ta.analysis.options.vol_cone(close, *, windows=(21, 42, 63, 126, 252), trading_days_per_year=252.0)[source]
Historical realised vol distribution across window lengths (volatility cone).
For each window, computes the full history of rolling close-to-close realised vol, then returns the min / p25 / median / p75 / max distribution. Contextualises current implied vol: “Is 30 % IV cheap or expensive?”
- Parameters:
- Returns:
Dataclass with arrays
windows,min,p25,median,p75,max— one value per element of windows.- Return type:
Notes
Uses close-to-close vol internally. Overlay the current IV on the cone to see whether it is historically cheap or expensive for each tenor.
Examples
>>> import numpy as np >>> from ferro_ta.analysis.options import vol_cone >>> rng = np.random.default_rng(0) >>> close = 100 * np.cumprod(np.exp(rng.normal(0, 0.01, 500))) >>> cone = vol_cone(close, windows=(21, 63, 252)) >>> cone.median # annualised median realised vol per window
- ferro_ta.analysis.options.yang_zhang_vol(open, high, low, close, window=20, trading_days_per_year=252.0)[source]
Rolling Yang-Zhang OHLC realized volatility estimator (annualised).
The most efficient standard estimator (~14× vs close-to-close). Handles overnight gaps by combining overnight, intraday open-close, and Rogers-Satchell variance components with an optimal weight k.
- Parameters:
open (ArrayLike) – Arrays of daily OHLC prices (same length, ≥ window + 1).
high (ArrayLike) – Arrays of daily OHLC prices (same length, ≥ window + 1).
low (ArrayLike) – Arrays of daily OHLC prices (same length, ≥ window + 1).
close (ArrayLike) – Arrays of daily OHLC prices (same length, ≥ window + 1).
window (int) – Rolling look-back period in bars (default 20).
trading_days_per_year (float) – Annualisation factor (default 252).
- Returns:
Same length as close. First
windowvalues are NaN.- Return type:
NDArray[float64]
Notes
Mixed estimator:
σ²_YZ = σ²_overnight + k·σ²_open_close + (1−k)·σ²_RS
where k = 0.34 / (1.34 + (window+1)/(window-1)).
Reference: Yang, D. & Zhang, Q. (2000). “Drift-Independent Volatility Estimation Based on High, Low, Open, and Close Prices.” Journal of Business, 73(3).
ferro_ta.analysis.futures — Futures and forward-curve analytics.
- class ferro_ta.analysis.futures.CurveSummary(front_basis: 'float', average_basis: 'float', slope: 'float', is_contango: 'bool')[source]
Bases:
object
- ferro_ta.analysis.futures.back_adjusted_continuous_contract(front, next_contract, next_weights)[source]
- Parameters:
front (ArrayLike)
next_contract (ArrayLike)
next_weights (ArrayLike)
- Return type:
NDArray[float64]
- ferro_ta.analysis.futures.calendar_spreads(futures_prices)[source]
- Parameters:
futures_prices (ArrayLike)
- Return type:
NDArray[float64]
- ferro_ta.analysis.futures.curve_slope(tenors, futures_prices)[source]
- Parameters:
tenors (ArrayLike)
futures_prices (ArrayLike)
- Return type:
- ferro_ta.analysis.futures.curve_summary(spot, tenors, futures_prices)[source]
- Parameters:
spot (float)
tenors (ArrayLike)
futures_prices (ArrayLike)
- Return type:
- ferro_ta.analysis.futures.parity_gap(call_price, put_price, spot, strike, rate, time_to_expiry, *, carry=0.0)[source]
- ferro_ta.analysis.futures.ratio_adjusted_continuous_contract(front, next_contract, next_weights)[source]
- Parameters:
front (ArrayLike)
next_contract (ArrayLike)
next_weights (ArrayLike)
- Return type:
NDArray[float64]
- ferro_ta.analysis.futures.synthetic_forward(call_price, put_price, strike, rate, time_to_expiry)[source]
- ferro_ta.analysis.futures.synthetic_spot(call_price, put_price, strike, rate, time_to_expiry, *, carry=0.0)[source]
- ferro_ta.analysis.futures.weighted_continuous_contract(front, next_contract, next_weights)[source]
- Parameters:
front (ArrayLike)
next_contract (ArrayLike)
next_weights (ArrayLike)
- Return type:
NDArray[float64]
ferro_ta.analysis.options_strategy — Typed strategy parameter schemas.
- class ferro_ta.analysis.options_strategy.DerivativesStrategy(name: 'str', preset: 'LegPreset | str' = <LegPreset.CUSTOM: 'custom'>, legs: 'tuple[StrategyLeg, ...]'=<factory>, risk_controls: 'RiskControl' = <factory>, risk_mode: 'RiskMode | str' = <RiskMode.COMBINED_PNL: 'combined_pnl'>, commission: 'float' = 0.0, slippage: 'float' = 0.0, spread_assumption: 'float' = 0.0, limits: 'SimulationLimits' = <factory>)[source]
Bases:
object- Parameters:
name (str)
legs (tuple[StrategyLeg, ...])
risk_controls (RiskControl)
commission (float)
slippage (float)
spread_assumption (float)
limits (SimulationLimits)
- legs: tuple[StrategyLeg, ...]
- limits: SimulationLimits
- risk_controls: RiskControl
- class ferro_ta.analysis.options_strategy.ExpirySelector(kind: 'ExpirySelectorKind | str', explicit_date: 'date | None' = None)[source]
Bases:
object- Parameters:
kind (ExpirySelectorKind | str)
explicit_date (date | None)
- kind: ExpirySelectorKind | str
- class ferro_ta.analysis.options_strategy.ExpirySelectorKind(*values)[source]
-
- CURRENT_MONTH = 'current_month'
- CURRENT_WEEK = 'current_week'
- EXPLICIT_DATE = 'explicit_date'
- NEXT_MONTH = 'next_month'
- NEXT_WEEK = 'next_week'
- class ferro_ta.analysis.options_strategy.LegPreset(*values)[source]
-
- BEAR_PUT_SPREAD = 'bear_put_spread'
- BULL_CALL_SPREAD = 'bull_call_spread'
- CUSTOM = 'custom'
- IRON_CONDOR = 'iron_condor'
- STRADDLE = 'straddle'
- STRANGLE = 'strangle'
- class ferro_ta.analysis.options_strategy.RiskControl(stop_loss_type: 'str | None' = None, stop_loss_value: 'float | None' = None, target_type: 'str | None' = None, target_value: 'float | None' = None, trailstop_type: 'str | None' = None, trailstop_value: 'float | None' = None, breakeven_trigger: 'float | None' = None)[source]
Bases:
object- Parameters:
- class ferro_ta.analysis.options_strategy.RiskMode(*values)[source]
-
- COMBINED_PNL = 'combined_pnl'
- PER_LEG = 'per_leg'
- class ferro_ta.analysis.options_strategy.SimulationLimits(max_premium_outlay: 'float | None' = None, max_loss_per_trade: 'float | None' = None, daily_max_drawdown: 'float | None' = None, cooldown_bars: 'int' = 0, reentry_allowed: 'bool' = True)[source]
Bases:
object- Parameters:
- class ferro_ta.analysis.options_strategy.StrategyLeg(underlying: 'str', expiry_selector: 'ExpirySelector | None', strike_selector: 'StrikeSelector | None', option_type: 'str | None', side: 'str' = 'long', quantity: 'int' = 1, instrument: 'str' = 'option', premium_limit: 'float | None' = None)[source]
Bases:
object- Parameters:
underlying (str)
expiry_selector (ExpirySelector | None)
strike_selector (StrikeSelector | None)
option_type (str | None)
side (str)
quantity (int)
instrument (str)
premium_limit (float | None)
- expiry_selector: ExpirySelector | None
- strike_selector: StrikeSelector | None
- class ferro_ta.analysis.options_strategy.StrikeSelector(kind: 'StrikeSelectorKind | str', steps: 'int' = 0, delta: 'float | None' = None, explicit_strike: 'float | None' = None)[source]
Bases:
object- Parameters:
kind (StrikeSelectorKind | str)
steps (int)
delta (float | None)
explicit_strike (float | None)
- kind: StrikeSelectorKind | str
- class ferro_ta.analysis.options_strategy.StrikeSelectorKind(*values)[source]
-
- ATM = 'atm'
- DELTA = 'delta'
- EXPLICIT = 'explicit'
- ITM = 'itm'
- OTM = 'otm'
- ferro_ta.analysis.options_strategy.build_strategy_preset(preset, *, name, underlying, expiry_selector, base_strike_selector=None, risk_controls=None, risk_mode=RiskMode.COMBINED_PNL, commission=0.0, slippage=0.0, spread_assumption=0.0, limits=None)[source]
Build a common research preset using typed leg definitions.
- Parameters:
name (str)
underlying (str)
expiry_selector (ExpirySelector)
base_strike_selector (StrikeSelector | None)
risk_controls (RiskControl | None)
commission (float)
slippage (float)
spread_assumption (float)
limits (SimulationLimits | None)
- Return type:
ferro_ta.analysis.derivatives_payoff — Multi-leg payoff and Greeks aggregation.
- class ferro_ta.analysis.derivatives_payoff.OptionStrategy(legs)
Bases:
objectMulti-leg option strategy: exact breakevens, max profit / loss (with unlimited detection), probability of profit, greeks and scenario grids.
Build with
OptionStrategy(legs)oroption_strategy(legs)(which also accepts dicts and aDerivativesStrategy).- breakevens()
Spots where expiry P&L crosses zero, ascending.
- greeks(spot, rate=0.0, carry=0.0)
(delta, gamma, vega, theta, rho)summed over legs.
- max_loss()
(value, spot)of the worst expiry P&L;(-inf, None)if unlimited.
- max_profit()
(value, spot)of the best expiry P&L;(inf, None)if unlimited.
- payoff(spot_grid)
Expiry P&L over
spot_grid.
- probability_of_profit(spot, volatility, time_to_expiry, rate=0.0, carry=0.0)
Risk-neutral probability that expiry P&L > 0 (lognormal underlying).
- scenario_grid(spots, vol_shifts, days_forward=0.0, rate=0.0, carry=0.0)
Mark-to-model P&L, shape
(len(vol_shifts), len(spots)).
- class ferro_ta.analysis.derivatives_payoff.PayoffLeg(instrument: 'str', side: 'str', quantity: 'float' = 1.0, option_type: 'str | None' = None, strike: 'float | None' = None, premium: 'float' = 0.0, entry_price: 'float | None' = None, volatility: 'float | None' = None, time_to_expiry: 'float | None' = None, rate: 'float' = 0.0, carry: 'float' = 0.0, multiplier: 'float' = 1.0)[source]
Bases:
object- Parameters:
- ferro_ta.analysis.derivatives_payoff.aggregate_greeks(spot, *, legs=None, strategy=None)[source]
Aggregate Greeks across option and futures legs.
- Parameters:
- Return type:
- ferro_ta.analysis.derivatives_payoff.futures_leg_payoff(spot_grid, *, entry_price, side='long', quantity=1.0, multiplier=1.0)[source]
P/L profile for a futures leg.
- ferro_ta.analysis.derivatives_payoff.option_leg_payoff(spot_grid, *, strike, premium=0.0, option_type='call', side='long', quantity=1.0, multiplier=1.0)[source]
Expiry payoff for a single option leg.
- ferro_ta.analysis.derivatives_payoff.option_strategy(legs=None, *, strategy=None)[source]
Build an
OptionStrategyfrom legs (PayoffLegor dicts).The result exposes exact
breakevens(),max_profit()/max_loss()as(value, spot)with(±inf, None)when unlimited,probability_of_profit(spot, volatility, time_to_expiry),greeks(spot)andscenario_grid(spots, vol_shifts, days_forward). Option legs that should feed greeks or scenarios needvolatilityandtime_to_expiry; per-legrate/carryare ignored in favour of the method arguments.- Parameters:
- Return type:
- ferro_ta.analysis.derivatives_payoff.stock_leg_payoff(spot_grid, *, entry_price, side='long', quantity=1.0, multiplier=1.0)[source]
P/L profile for a single stock (equity) leg over a spot grid.
Payoff is linear:
P/L = sign(side) × quantity × multiplier × (spot − entry_price)
Mathematically equivalent to a futures leg — no optionality. Use this leg type when modelling strategies that hold the underlying equity: Covered Call, Protective Put, Collar, Covered Strangle, etc.
- Parameters:
spot_grid (ArrayLike) – 1-D array of spot prices at which to evaluate the P/L.
entry_price (float) – Purchase (or short-sale) price of the stock.
side (str) –
"long"(default) or"short".quantity (float) – Number of shares / contracts (default 1).
multiplier (float) – Contract multiplier (default 1.0).
- Returns:
P/L at each grid point, same shape as spot_grid.
- Return type:
NDArray[float64]
- ferro_ta.analysis.derivatives_payoff.strategy_payoff(spot_grid, *, legs=None, strategy=None)[source]
Aggregate expiry payoff across option and futures legs.
- ferro_ta.analysis.derivatives_payoff.strategy_value(spot_grid, *, legs, time_to_expiry, volatility, rate=0.0, carry=0.0)[source]
Current BSM mid-price value of a multi-leg strategy over a spot grid.
Unlike
strategy_payoff()(which computes intrinsic value at expiry), this uses live BSM pricing for option legs so the result reflects the pre-expiry value including time value.- Parameters:
spot_grid (ArrayLike) – Array of spot prices to evaluate.
legs (Sequence[PayoffLeg | Mapping[str, Any]]) – Sequence of
PayoffLeg(or dicts). Option legs must havestrikeandpremiumset; future/stock legs must haveentry_priceset.time_to_expiry (float) – Shared time-to-expiry (years) applied to all option legs.
volatility (float) – Shared implied vol applied to all option legs.
rate (float) – Risk-free rate applied to all legs.
carry (float) – Carry / dividend yield applied to all option legs.
- Return type:
NDArray[float64]