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".
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]]
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").
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.
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).