Skip to content

aether_quant.features

Pure feature functions: technical indicators, sensitivity features, per-date cross-sectional ranks and the as-of (lookahead-free) beta labels.

Pure feature functions: technical indicators, sensitivity features, per-date cross-sectional ranks and the as-of (lookahead-free) beta labels.

CrossAssetTimeline

Raw (pre-dropna) close history per ticker, queryable as "what did the live windows hold at this moment".

closes_before

closes_before(availability: float) -> dict[str, list[float]]

from_frames classmethod

from_frames(frames: Mapping[str, Any], security_types: Mapping[str, str] | None = None, forex_holidays: frozenset[int] = frozenset()) -> CrossAssetTimeline

frames: ticker -> DataFrame with date (Timestamp) and close, sorted by date. Forex history gets Lean's fill-forward bars (see fill_forward_open_forex_days), so its windows are what live holds.

inputs_before

inputs_before(availability: float) -> tuple[dict[str, list[float]], dict[str, float]]

momentum_before

momentum_before(availability: float) -> dict[str, float]

row_availability

row_availability(ticker: str, date: date | datetime | str) -> float

LateBarCrossAssetCache

The cross-asset inputs (topology node, momentum map) a crypto/forex symbol had at ITS OWN tick, for re-use on the equity tick that scores the same bar again (V6.0.0, todo lead 5).

The dataset row of a crypto/forex bar of day D sees the bars that closed before the bar did (key D + 1.0: equity through D, crypto/forex through D - 1). Live builds exactly that payload at the symbol's own midnight tick; the next equity tick (key D + 1.5) holds one more crypto bar, so re-scoring the symbol there with fresh payloads fed the model a correlation window the dataset never has. Re-using the own-tick payloads makes the equity-tick score the dataset's row D exactly (phase1.features.late_bar_cross_asset: "own_tick").

lookup

lookup(symbol_key: str) -> tuple[dict, Mapping[str, float]] | None

record

record(symbol_key: str, topology_node: dict[str, Any] | None, momentum_by_symbol: Mapping[str, float]) -> None

WideBars dataclass

Session x ticker. close, open, high and low are adjusted (the data a run trades on); raw_close is split-adjusted without dividends; volume is split-adjusted.

close instance-attribute

close: DataFrame

high instance-attribute

high: DataFrame

low instance-attribute

low: DataFrame

open instance-attribute

open: DataFrame

raw_close instance-attribute

raw_close: DataFrame

volume instance-attribute

volume: DataFrame

analytic_convexity

analytic_convexity(yield_to_maturity: float | None, coupon_rate: float | None, years_to_maturity: float | None, payments_per_year: int = 2) -> float | None

Standard closed-form convexity (the second derivative of bond price with respect to yield, annualized) from the same plain-vanilla cash-flow schedule analytic_modified_duration() uses - same ETF-level-approximation caveat and degrade-to-None convention.

analytic_modified_duration

analytic_modified_duration(yield_to_maturity: float | None, coupon_rate: float | None, years_to_maturity: float | None, payments_per_year: int = 2) -> float | None

Standard closed-form modified duration from a plain-vanilla, fixed-coupon cash-flow schedule: Macaulay duration (the price-weighted average time to each cash flow, in years) divided by (1 + yield_to_maturity/payments_per_year).

A genuinely DIFFERENT estimate from empirical_duration_beta() above, not a replacement for it: empirical_duration_beta() measures how much an ETF's OWN observed price actually moved per unit of yield change (a market-realized sensitivity); this function computes what a HYPOTHETICAL plain-vanilla bond with the given yield/coupon/maturity would theoretically be sensitive to, from cash-flow math alone - see this module's own docstring for the ETF-level-approximation caveat on what yield_to_maturity/coupon_rate/years_to_maturity should be sourced from in practice.

Returns None (never raises) when any input is missing, years_to_maturity is non-positive, or the computed price is non-positive (a degenerate/ extreme-negative-yield edge case) - an unknown/undefined duration must never be indistinguishable from a genuinely-zero one, the same convention empirical_duration_beta() already established.

apply_cross_sectional_inputs

apply_cross_sectional_inputs(frame: DataFrame, config: Mapping[str, Any] | None, names: Sequence[str], *, ticker_column: str = 'ticker', date_column: str = 'date') -> pd.DataFrame

The trainer's / offline scorer's one call: frame unchanged when the setting is off, else ranked per (date, class) with each row's class from the config universe (the same mapping main.py uses).

asof_beta_by_row

asof_beta_by_row(frame: DataFrame, *, market_ticker: str, return_column: str = 'close_to_close_return_1d', window: int = DEFAULT_WINDOW, min_periods: int = DEFAULT_MIN_PERIODS) -> pd.Series

The as-of beta of every row of a long (date, ticker) frame, aligned to frame.index. The market's daily return is matched by date, so each asset pairs only on the dates both traded.

asof_residual_forward_return

asof_residual_forward_return(fwd_asset: ArrayLike, fwd_market: ArrayLike, beta_t: ArrayLike) -> np.ndarray

fwd_asset - beta_t * fwd_market; a missing beta counts as 0 (train.py's fallback for a short history).

asof_rolling_beta

asof_rolling_beta(asset_returns: ArrayLike, market_returns: ArrayLike, *, window: int = DEFAULT_WINDOW, min_periods: int = DEFAULT_MIN_PERIODS) -> np.ndarray

Trailing OLS slope of asset_returns on market_returns (aligned arrays, NaN = missing) over the last window paired observations up to and including each position; NaN until min_periods pairs exist.

asset_class_by_ticker

asset_class_by_ticker(config: Mapping[str, Any] | None) -> dict[str, str]

{ticker: asset class} for every asset in the config's universe.

asset_class_of

asset_class_of(asset: Mapping[str, Any] | None) -> str

The class main.py's _class_raw_scores uses: asset_class, else security_type (bond ETFs are equity / bond).

average_true_range_pct

average_true_range_pct(highs: list[float], lows: list[float], closes: list[float], period: int = 14) -> float

ATR (Wilder's simple-average variant) as a % of the latest close - a gap-aware volatility measure distinct from rolling_volatility_20d's close-to-close-only stddev (a true range captures overnight gaps/intraday extremes a close-only measure misses entirely). Needs period+1 bars (period true-range values, each needing a previous close). Returns 0.0 when insufficient history or the latest close is zero.

bar_available_at

bar_available_at(ordinal: int, security_type: str) -> float

When a bar dated ordinal becomes known: the following midnight for crypto and forex, the equity close otherwise.

baw_american_price

baw_american_price(spot: float, strike: float, time_to_expiry_years: float, risk_free_rate: float, dividend_yield: float, volatility: float, right: str, max_iterations: int = 100, tolerance: float = 1e-06) -> float

Barone-Adesi & Whaley (1987) quadratic approximation for American option prices - "Efficient Analytic Approximation of American Option Values," Journal of Finance 42(2). Built entirely from this module's existing bs_price()/_d1_d2()/_normal_cdf() - the European price IS the approximation's own building block, not a separately-derived formula. Verified numerically against a 3000-step CRR binomial tree across several call/put/ITM/OTM/dividend combinations during development (differences of a few cents on option prices in the $6-14 range, consistent with BAW's known ~1-2% approximation error).

Never raises. Degrades to the European bs_price() value whenever: the dividend yield is non-positive for a call (a call should never be exercised early with zero dividend drag - the standard textbook shortcut, not an approximation gap), any input is degenerate (time/vol/spot/strike non-positive - returns intrinsic value in that case instead), or the critical-price search fails to find a bracketing sign change / doesn't converge within max_iterations. This is a SAFE direction to degrade in: the American price is always >= the European price, so under-pricing by falling back to the European value never overstates risk in the direction that matters for a short option holder (the whole reason this function exists).

Uses a safeguarded bisection search for the critical price (rather than implied_volatility()'s Newton-primary approach) - the BAW matching condition has no simple closed-form derivative the way vega is for the IV solve, so bisection (guaranteed convergence given a verified sign-changing bracket, checked explicitly before iterating) is the more directly verifiable choice for a per-bar risk signal that must never silently mis-converge.

bollinger_pctb

bollinger_pctb(closes: list[float], period: int = 20, num_std: float = 2.0) -> float

%B: where the latest close sits within its trailing period-bar Bollinger Band, normalized so 0.0 = lower band, 1.0 = upper band, 0.5 = the middle (SMA) - a bounded, self-normalizing measure of price extension, distinct from the raw high_low_range_pct/rolling_volatility features already in this codebase. Returns BOLLINGER_PCTB_NEUTRAL (0.5) when fewer than period closes exist yet, or the band has zero width (constant price - degenerate either way, not a real signal).

bond_dv01

bond_dv01(price: float | None, modified_duration: float | None, notional: float = 1.0) -> float | None

Dollar duration per 1bp yield move (DV01): price * modified_duration * 0.0001 * notional - a directly useful risk-sizing number (expected dollar P&L for a 1 basis point yield change). Returns None (never raises) when price/modified_duration is missing, or price/notional is non-positive.

bs_price

bs_price(spot: float, strike: float, time_to_expiry_years: float, risk_free_rate: float, volatility: float, dividend_yield: float, right: str) -> float

European option price under Black-Scholes-Merton with a continuous dividend yield. Callers must ensure spot/strike/time_to_expiry_years/ volatility are all strictly positive - this function does not neutral-default (unlike features/bond_features.py's pure functions): an option price is meaningless for a degenerate input, so callers (options_greeks.implied_volatility(), portfolio/options_strategy.py) are responsible for filtering those cases before calling in.

build_asof_rank_labels

build_asof_rank_labels(frame: DataFrame, *, horizons: Sequence[int] = (5, 20), market_ticker: str = 'SPY', window: int = DEFAULT_WINDOW, min_periods: int = DEFAULT_MIN_PERIODS, min_universe_size: int = 20, sector_by_ticker: dict[str, str] | None = None, size_column: str | None = 'liquidity_log_dollar_volume', use_market: bool = True, use_sector: bool = True, use_size: bool = True) -> pd.DataFrame

target_beta_neutral_rank_{h}d_asof and target_residual_rank_{h}d_asof for a long frame holding date, ticker, close_to_close_return_1d, target_return_{h}d (and size_column). Same index as frame.

compute_greeks

compute_greeks(spot: float, strike: float, time_to_expiry_years: float, risk_free_rate: float, volatility: float, dividend_yield: float, right: str) -> dict[str, Any]

Single call site wrapping the 5 functions above, for portfolio/options_strategy.py/main.py call-site brevity.

credit_spread_level

credit_spread_level(baa10y_spread: float | None) -> float

Raw BAA10Y (Moody's Baa corporate bond yield relative to the 10yr Treasury) level - widens when credit risk appetite deteriorates. Real-data replacement for macro_features.credit_spread_proxy()'s momentum-based proxy. Returns CREDIT_SPREAD_LEVEL_NEUTRAL (0.0) when unavailable.

credit_spread_proxy

credit_spread_proxy(high_yield_momentum_20d: float | None, investment_grade_momentum_20d: float | None) -> float

Relative 20-day momentum spread between a high-yield corporate bond ETF (e.g. HYG) and an investment-grade one (e.g. LQD) - widens (negative: high-yield underperforming investment-grade) when credit risk appetite deteriorates, a standard risk-off signal. Returns CREDIT_SPREAD_NEUTRAL (0.0) when either input is unavailable.

cross_asset_inputs

cross_asset_inputs(closes_by_symbol: Mapping[str, Sequence[float]]) -> tuple[dict[str, list[float]], dict[str, float]]

(returns_by_symbol, momentum_by_symbol) from each symbol's trailing close window.

cross_sectional_momentum_rank

cross_sectional_momentum_rank(momentum_by_symbol: dict[str, float], symbol: str) -> float

Per-date percentile rank ([0, 1], ties averaged - matches pandas' .rank(pct=True) convention, same as build_cross_sectional_rank_targets()'s target-side ranking) of symbol's own momentum_20d among every symbol present in momentum_by_symbol - the best-documented daily cross-sectional anomaly (12-1 month momentum), reusing the exact percentile-rank convention already established for the ranking targets. Returns CROSS_SECTIONAL_MOMENTUM_RANK_NEUTRAL (0.5, the exact middle) when symbol has fewer than 2 total entries to rank against (undefined otherwise, e.g. a single-asset universe or a thin trading day).

cross_sectional_rank

cross_sectional_rank(frame: DataFrame, columns: Sequence[str], *, date_column: str = 'date', group_column: str | None = None) -> pd.DataFrame

Percentile rank of each of columns within its date (and group_column, e.g. the asset class, when given), centred to [-0.5, 0.5]. NaN stays NaN; ties get the average rank. Same index as frame.

cross_sectional_residualize

cross_sectional_residualize(returns: ndarray, design_matrix: ndarray) -> np.ndarray

Residuals of one date's returns ~ design_matrix least-squares fit; a degenerate fit (fewer rows than columns, a LinAlgError, rank 0, a non-finite residual) returns returns unchanged. Never raises. train.py uses this one.

crypto_risk_appetite_proxy

crypto_risk_appetite_proxy(crypto_momentum_20d: float | None) -> float

20-day momentum of a liquid crypto reference asset (e.g. BTCUSD) - a fast-moving risk-on/risk-off signal distinct from the slower-moving bond-based proxies above, given crypto's comparatively higher volatility and shorter mean-reversion cycles. Returns CRYPTO_RISK_APPETITE_NEUTRAL (0.0) when the reference asset's momentum is unavailable.

day_ordinal

day_ordinal(stamp: date | datetime | str) -> int

Proleptic ordinal of a pandas Timestamp / datetime / date / ISO date string.

days_since_filing

days_since_filing(rows: DataFrame, sessions: list[str], columns: list[str]) -> pd.DataFrame

Calendar days since the latest filing that was already public, a session late as every other field is.

delta

delta(spot: float, strike: float, time_to_expiry_years: float, risk_free_rate: float, volatility: float, dividend_yield: float, right: str) -> float

dPrice/dSpot. Calls: [0, 1]. Puts: [-1, 0].

distance_from_52w_high

distance_from_52w_high(closes: list[float], window: int = 252) -> float

close / trailing-window-bar max close - 1: how far the latest close sits below its own trailing high (0.0 = at the high, negative otherwise) - a genuine anchoring-effect measure distinct from everything else in this codebase (every existing return/momentum feature is relative to a bar N periods ago, not to a rolling extreme). Returns DIST_52W_HIGH_NEUTRAL (0.0) when fewer than 2 closes exist yet, or the trailing high is zero.

duration_beta_from_arrays

duration_beta_from_arrays(ys: ndarray, xs: ndarray, min_observations: int) -> float | None

The OLS slope of ys on xs for float64 arrays in which NaN stands for a missing value (V6.0.0, latency path of the per-tick sensitivity betas, where the series are built as arrays and never carry a genuine NaN). Positional pairing, the minimum-observation floor and the variance < 1e-12 -> None guard are empirical_duration_beta's; the value differs from it only at rounding level.

empirical_duration_beta

empirical_duration_beta(etf_daily_returns: list[float | None], delta_10yr_yield_daily: list[float | None], min_observations: int = 60) -> float | None

Rolling OLS slope of ETF daily returns regressed on same-day Delta-10yr-yield: an empirical "how much does this ETF move per unit of yield change" duration proxy, since these are ETFs with no single stated maturity, not individual bonds with a textbook duration figure.

Pairs the two series positionally (index i of each list must already correspond to the same trading date - callers are responsible for alignment), drops any pair where either side is None, and requires at least min_observations valid pairs. Returns None (not 0.0) below that floor - a missing/insufficient-history duration estimate must be distinguishable from "genuinely zero duration sensitivity".

expand_input_names

expand_input_names(names: Sequence[str], settings: Mapping[str, Any] | None) -> list[str]

Model input names after the transform: unchanged for "replace", <name>_csr appended for "append".

extended_info_features

extended_info_features(sessions: list[str], columns: list[str], *, extended: DataFrame, fundamentals: DataFrame) -> dict[str, pd.DataFrame]

V6.6.1: accruals on trailing twelve months, net operating assets (Hirshleifer et al. 2004) and capex growth. A filing shows a session after its date. Missing short-term debt counts as none, since many filers report no such line; missing cash leaves the ratio unknown.

fill_forward_open_forex_days

fill_forward_open_forex_days(ordinals: Sequence[int], closes: Sequence[float], holidays: frozenset[int] | set[int] = frozenset()) -> tuple[list[int], list[float]]

Lean fills forward a forex daily bar on every day the market is open without data. The forex market is open Sunday evening through Friday, so any Sunday-Friday day that falls between two bars becomes a flat bar at the previous close (BT1 probe, Problems.md #135: every pair delivered a Sunday bar live equal to the preceding Friday close where the local zip has no Sunday row; the 2019-05-22 weekday missing from the zips moved the live correlations for the next 24 bars). Saturdays and Lean's market holidays (holidays, lean_market_holidays) stay closed; only gaps strictly between two bars are filled; ordinals must be ascending.

fx_carry_3m

fx_carry_3m(ticker: str, asset_class: str | None, current_date: date, fred_series: dict[str, list[dict]], publication_lag_days: dict[str, int] | None = None) -> float

r_base - r_quote at current_date (percentage points), 0.0 for a non-forex asset, an unparseable pair, or when either rate has no published observation yet (a missing rate must read as "no carry", never as a guessed rate).

fx_sleeve_scores

fx_sleeve_scores(signal: str, *, model_scores: Mapping[str, float] | None = None, closes_by_pair: Mapping[str, Sequence[float]] | None = None, carry_by_pair: Mapping[str, float] | None = None, lookbacks: Sequence[int] = DEFAULT_MOMENTUM_LOOKBACKS, vol_window: int = 60) -> dict[str, float]

{pair: score} for the requested signal over the pairs that have the inputs it needs (a pair without inputs is left out, never scored 0). An unknown signal raises ValueError (config typo must not trade silently).

gamma

gamma(spot: float, strike: float, time_to_expiry_years: float, risk_free_rate: float, volatility: float, dividend_yield: float) -> float

d^2Price/dSpot^2 - identical for calls and puts (no right param).

growth_rows

growth_rows(rows: DataFrame, field: str, name: str, splits: DataFrame | None = None) -> pd.DataFrame

A new field: each fiscal year's value against the year before it, published with the newer filing. Share counts compare on one basis: each is restated by the splits after its own filing, so a split isn't read as issuance.

implied_volatility

implied_volatility(option_price: float, spot: float, strike: float, time_to_expiry_years: float, risk_free_rate: float, dividend_yield: float, right: str, max_iterations: int = 100, tolerance: float = 1e-06) -> float | None

Safeguarded Newton-Raphson (Newton step when it stays inside the current bracket, bisection step otherwise) - vega can be ~0 deep ITM/OTM or near expiry, which would blow up pure Newton-Raphson. bs_price() is monotonically increasing in volatility for T>0, so the [low, high] bracket only ever narrows and bisection alone is already guaranteed to converge; Newton just speeds that up when it's safe to use.

Returns None (never raises, never NaN) when: option_price is non-positive, any of spot/strike/time_to_expiry_years is non-positive, option_price violates the no-arbitrage intrinsic-value bound, the market price falls outside the achievable range for the search bracket [1e-6, 5.0] (500% vol), or the solver fails to converge within max_iterations - same "None = failed to compute, never a crash" convention as features/bond_features.py::empirical_duration_beta().

info_features

info_features(sessions: list[str], columns: list[str], *, fundamentals: DataFrame, splits: DataFrame, raw_close: DataFrame, eps: DataFrame | None = None, insider_trades: DataFrame | None = None, short_ratios: DataFrame | None = None) -> dict[str, pd.DataFrame]

Fundamental, earnings, insider and short-volume tables. A filing shows from the session after its date.

is_late_bar_asset

is_late_bar_asset(security_type: str | None) -> bool

Crypto and forex daily bars close at midnight, after the equity session: the equity tick still holds the previous day's bar for them.

lean_market_holidays cached

lean_market_holidays(market_hours_path: str, entry: str = 'Forex-oanda-[*]') -> frozenset[int]

Day ordinals of the full-day holidays Lean's market-hours database lists for entry. On such a day Lean delivers no bar for the market: the zip's own bar is dropped and the day is not filled forward (V6.1.0: GBPJPY's live momentum on 2024-01-22 equals the calendar without the 2023-12-24 / 12-31 Sunday fills). A missing file or entry returns an empty set (the pre-V6.1.0 calendar).

load_sector_mapping

load_sector_mapping(config: dict[str, Any], *, default_path: Path = DEFAULT_SECTOR_MAPPING_PATH) -> dict[str, str]

Ticker -> sector bucket, from the checked-in data/reference/sector_mapping.json (or an override path via config phase1.target.ranking.sector_neutral.mapping_path).

Same defensive posture as every other reference-file loader in this codebase: returns {} (never raises) when the file is missing or unparseable, so a caller degrades to every ticker resolving to UNKNOWN_SECTOR_LABEL rather than crashing over a missing reference file. _-prefixed keys are comments and are stripped.

long_frame

long_frame(features: Mapping[str, DataFrame], label: DataFrame, sector: Mapping[str, str], member: DataFrame, *, label_name: str = 'target_wide_20d') -> pd.DataFrame

One row per (member session, ticker) with a known sector, float32, for the model track.

macd_histogram_normalized

macd_histogram_normalized(closes: list[float], fast: int = 12, slow: int = 26, signal: int = 9) -> float

MACD histogram (MACD line - its own signal line), normalized by the latest close so its scale is comparable across assets at very different price levels (a raw MACD histogram is in the asset's own raw price units). Both EMAs are recomputed fresh from the trailing window passed in each call (an approximation of a true "since inception" EMA, which never fully forgets old data) - a deliberate simplification so this is exactly reproducible from a bounded window on both the offline (full history slice) and runtime (self.symbol_long_windows, maxlen=260) sides, with no persistent EMA state to keep in sync between them. Needs at least slow + signal closes; returns MACD_HISTOGRAM_NEUTRAL (0.0) otherwise, or when the latest close is zero.

momentum_from_closes

momentum_from_closes(closes: Sequence[float]) -> float | None

closes[-1] / close 20 bars back - 1 over the window; None below two closes or on a zero base.

nearest_yield_curve_point

nearest_yield_curve_point(years_to_maturity: float | None, curve_points: dict[str, Any]) -> float | None

Picks the yield-curve observation whose maturity (in years, the dict's keys) is CLOSEST to years_to_maturity - the at-par-pricing proxy coupon/yield fallback analytic_modified_duration()/ analytic_convexity() need when bond_metadata.assumed_coupon_rate isn't explicitly configured for an ETF. A coarse approximation by construction (this codebase's own Treasury curve only has 4 points - 3mo/2yr/5yr/10yr - so a long-duration ETF's true maturity is necessarily approximated by the longest available point); callers already document that limitation, this function just picks the nearest one rather than guessing further. Skips any None-valued point (a FRED series with no observation as-of the current date) - returns None (never raises) when no curve point has a value at all, or years_to_maturity is None.

price_features

price_features(bars: WideBars, market: str = MARKET) -> dict[str, pd.DataFrame]

The wide panel's price features (returns, residual returns, volatility, momentum and more) as date x ticker tables.

rank_input_frame

rank_input_frame(frame: DataFrame, names: Sequence[str], groups: Sequence[Any] | Series, settings: Mapping[str, Any], *, date_column: str = 'date') -> pd.DataFrame

Offline form, same values as rank_input_matrix per (date, group): returns a copy of frame with the scaled inputs replaced (or <name>_csr columns added). groups is aligned to frame (a Series or a sequence).

rank_input_matrix

rank_input_matrix(rows: Sequence[Sequence[float]], names: Sequence[str], groups: Sequence[str], settings: Mapping[str, Any]) -> tuple[list[list[float]], list[str]]

Live form: rows are the model input vectors (in names order) of the symbols of ONE bar, groups their asset classes. Returns (new rows, new names) - new lists, the inputs are never mutated.

ranked_input_names

ranked_input_names(names: Sequence[str]) -> list[str]

The inputs that get ranked: the scaled features (categorical / class context inputs stay as they are).

relative_strength_index

relative_strength_index(closes: list[float], period: int = 14) -> float

Wilder's RSI over the trailing period closes (needs period+1 closes for period price changes) - a bounded [0, 100] mean-reversion state, a genuinely different signal shape from any existing return/volatility feature (bounded and non-linear, vs. an unbounded return). Returns RSI_NEUTRAL (50.0, "neither overbought nor oversold") when fewer than period+1 closes are available.

residual_daily

residual_daily(close: DataFrame, market: str = MARKET, window: int = RESIDUAL_WINDOW) -> pd.DataFrame

Daily return less a trailing beta (known a row earlier) times the market's.

resolve_bond_duration_beta_mode

resolve_bond_duration_beta_mode(features_config: dict[str, Any] | None) -> tuple[str, int]

(mode, lookback) from phase1.features.bond_duration_beta. Absent = ("full_history", 252): train.py's single whole-history slope and main.py's lock-in-once cache, the pre-V5.8.0 behaviour. "rolling" = a point-in-time trailing slope on both sides (V5.8.0: the whole-history slope leaked validation/backtest data into every training row and was constant per ticker, i.e. a ticker ID).

resolve_cross_sectional_inputs

resolve_cross_sectional_inputs(raw: Mapping[str, Any] | None) -> dict[str, Any] | None

phase1.features.cross_sectional_inputs -> settings, or None when absent / disabled (today's behaviour).

resolve_forex_lean_holidays

resolve_forex_lean_holidays(config: dict[str, Any], root: Path | None = None) -> frozenset[int]

Lean's forex market holidays when phase1.features.forex_lean_holidays is on (V6.1.0), else an empty set (the V6.0.0 calendar: every Sunday-Friday gap filled, every zip bar kept).

returns_from_closes

returns_from_closes(closes: Sequence[float]) -> list[float]

Simple returns of a close series, skipping a step from a zero close.

rho

rho(spot: float, strike: float, time_to_expiry_years: float, risk_free_rate: float, volatility: float, dividend_yield: float, right: str) -> float

dPrice/dRiskFreeRate, per 1.0 (100 percentage points) of rate.

rolling_duration_beta

rolling_duration_beta(closes: list[float | None], treasury_levels: list[float | None], lookback: int = BOND_DURATION_BETA_LOOKBACK, min_observations: int = 60) -> float | None

Point-in-time duration beta at the LAST element: empirical_duration_beta() over the trailing lookback daily returns (built from the last lookback + 1 closes and 10-year yield levels, index-aligned per bar). main.py calls this on its per-symbol windows; train.py's rolling_duration_beta_series() produces the same value for every row.

rolling_duration_beta_series

rolling_duration_beta_series(closes: list[float | None], treasury_levels: list[float | None], lookback: int = BOND_DURATION_BETA_LOOKBACK, min_observations: int = 60) -> list[float | None]

rolling_duration_beta() evaluated at every position (row i sees elements <= i only), vectorised with pandas so a 2,700-row bond history costs milliseconds. Pairs with either side missing are dropped exactly as the scalar function drops them (both series masked together), and the same min-observation / degenerate-variance guards apply.

rolling_sensitivity

rolling_sensitivity(asset_returns: list[float | None], macro_changes: list[float | None], *, lookback: int, min_observations: int) -> float | None

Trailing-window OLS slope of asset_returns on macro_changes, restricted to the last lookback positions of each series (positionally paired - callers are responsible for date alignment, the same contract empirical_duration_beta() itself already documents). A thin wrapper - REUSES empirical_duration_beta() rather than reimplementing OLS, exactly as bond_empirical_duration_beta already does for a whole-history (non-rolling) beta.

lookback <= 0 disables windowing (uses the full series - a whole-history beta, matching bond_empirical_duration_beta's convention). Returns None (not 0.0) when fewer than min_observations valid pairs exist in the window - a missing/insufficient-history sensitivity must stay distinguishable from a genuinely-zero one, same as empirical_duration_beta()'s own return contract.

sensitivity_features_vector

sensitivity_features_vector(closes: list[float], levels_by_driver: dict[str, list[float | None]], *, lookback: int, min_observations: int) -> dict[str, float]

All 8 sensitivity features of one symbol from its trailing closes and each driver's aligned level history, built as float64 arrays (V6.0.0, latency: the list-and-loop version was ~55% of main.py::_build_model_input).

The same definition as the list path: returns[i] = closes[i] / closes[i-1] - 1 (missing when the previous close is 0), deltas[i] = level[i] - level[i-1] (missing when either level is), the last lookback of each paired positionally, beta = OLS slope, interaction = beta x the latest delta, neutral 0.0 where a value is missing. Equal to the list path to rounding level (tests/test_v600_sensitivity_vector.py).

sensitivity_interaction

sensitivity_interaction(sensitivity: float | None, macro_change: float | None) -> float

beta_i * delta_macro_t - the interaction term that turns a per-date constant macro move into a per-asset-varying signal a cross-sectional ranker can actually use. Neutral 0.0 on any missing input, the same "None input -> 0.0 neutral output" convention every features/*.py interaction/composite function already follows (e.g. features/alt_data_features.py's own neutral defaults).

settings_from_config

settings_from_config(config: Mapping[str, Any] | None) -> dict[str, Any] | None

The resolved cross-sectional input settings from phase1.features.cross_sectional_inputs, or None when off.

split_forex_ticker

split_forex_ticker(ticker: str) -> tuple[str, str] | None

('EUR', 'USD') for 'EURUSD'; None unless the ticker is two known 3-letter currency codes.

theta

theta(spot: float, strike: float, time_to_expiry_years: float, risk_free_rate: float, volatility: float, dividend_yield: float, right: str) -> float

dPrice/dTime, annualized (divide by 365 for a per-calendar-day figure).

ts_momentum_score

ts_momentum_score(closes: Sequence[float], lookbacks: Sequence[int] = DEFAULT_MOMENTUM_LOOKBACKS, vol_window: int = 60) -> float | None

Mean over lookbacks of the trailing log return divided by (daily log-return std x sqrt(lookback)) - a t-like, vol-comparable momentum across pairs with very different volatility (EURCHF vs AUDJPY). None when the window is shorter than the longest lookback + 1 or the volatility is zero/non-finite.

ts_momentum_series

ts_momentum_series(closes: Series, lookbacks: Sequence[int] = DEFAULT_MOMENTUM_LOOKBACKS, vol_window: int = 60) -> pd.Series

ts_momentum_score() evaluated at every position of a close series (a pandas Series), vectorised for research over thousands of rows: position i equals ts_momentum_score(closes[:i + 1]). NaN where the scalar returns None. pandas is imported here so the live path never pays for it.

vega

vega(spot: float, strike: float, time_to_expiry_years: float, risk_free_rate: float, volatility: float, dividend_yield: float) -> float

dPrice/dVolatility, per 1.0 (100 percentage points) of volatility - identical for calls and puts (no right param). Divide by 100 for the conventional "per 1 vol point" quoting convention if needed.

volume_zscore

volume_zscore(volumes: list[float], period: int = 20) -> float

Latest volume's z-score against its own trailing period-bar mean/std - a self-normalizing participation measure. Robust to the exact class of raw-volume-unit discontinuity that broke volume_change_1d before its clamp fix (see development/Problems.md): a z-score compresses a single extreme outlier's effect on the bar it occurs on relative to a raw ratio, though a persisting outlier would still distort the trailing window's own mean/std for subsequent bars, same as any rolling statistic. Returns VOLUME_ZSCORE_NEUTRAL (0.0) when fewer than period volumes exist yet, or the trailing window has zero variance (a flat volume series).

wide_label

wide_label(bars: WideBars, sector: Mapping[str, str], *, horizon: int = 21, market: str = MARKET) -> pd.DataFrame

Return from the next open to the close horizon sessions on, less the market's over the same span, then a percentile rank per (date, sector) centred at zero, so knowing a name's group earns nothing (Problems.md #259). NaN where the span runs past the data.

yield_curve_curvature

yield_curve_curvature(treasury_2yr_yield: float | None, treasury_5yr_yield: float | None, treasury_10yr_yield: float | None) -> float

2*5yr - 2yr - 10yr - the standard butterfly/curvature measure of curve shape independent of its overall level or slope. Returns YIELD_CURVE_CURVATURE_NEUTRAL (0.0) when any input is missing.

yield_curve_level

yield_curve_level(treasury_10yr_yield: float | None) -> float

Raw level of the 10yr Treasury yield (e.g. 0.045 for 4.5%). Returns YIELD_CURVE_LEVEL_NEUTRAL (0.0) when unavailable.

yield_curve_slope

yield_curve_slope(treasury_10yr_yield: float | None, treasury_3mo_yield: float | None) -> float

10yr - 3mo - the standard recession-signal curve slope (negative = inverted curve). Real-data replacement for macro_features.yield_curve_slope_proxy()'s price-momentum proxy. Returns YIELD_CURVE_SLOPE_NEUTRAL (0.0) when either input is missing.

yield_curve_slope_proxy

yield_curve_slope_proxy(long_duration_momentum_20d: float | None, short_duration_momentum_20d: float | None) -> float

Relative 20-day momentum spread between a long-duration Treasury ETF (e.g. TLT) and a short-duration one (e.g. SHY) - a proxy for curve-steepening (positive: long duration outperforming, typically falling long yields relative to short) vs. flattening/inverting (negative) sentiment, computable from price data alone with no separate yield-level data feed. Returns YIELD_CURVE_SLOPE_NEUTRAL (0.0) when either input is unavailable (reference ticker missing or still warming up).