aether_quant.portfolio¶
Book construction: rank-based long/short selection, neutrality, tranches, class caps and the sleeve loss guard (formally verified in formal/AetherFormal/{BookNeutrality,BookConstruction,SleeveGuard}.lean).
Book construction: rank-based long/short selection, neutrality, tranches, class caps and the sleeve loss guard (formally verified in formal/AetherFormal/{BookNeutrality,BookConstruction,SleeveGuard}.lean).
BookAllocation
dataclass
¶
One name's place in the rank book: long or short, its multiplier, its predicted rank and why it was chosen.
ClassSleeveConfig
dataclass
¶
Settings of one asset-class sleeve: whether it runs, names per side and its weights.
min_rank_confidence_spread
class-attribute
instance-attribute
¶
min_rank_confidence_spread: float = 0.0
MarginSizingDecision
dataclass
¶
Margin-tier sibling of OptionsMultiLegPositionDecision - same legs/expiries/contracts/strategy_name/sizing_reason shape (so main.py's record-building code can treat either decision type identically via duck typing), plus margin_required/margin_utilization in place of net_debit_or_credit/net_delta/net_vega (a margin-tier position's defining risk unit is margin consumed, not vega).
MultiLegSpec
dataclass
¶
One leg of a multi-leg OptionStrategy factory result. side/ratio
are transcribed directly from OptionStrategies.cs's actual Quantity
field for that leg (e.g. a butterfly's body leg is ratio=2, side=
"short" for a long butterfly) - never inferred from the factory's
positional strike-name alone (a real bug this pass found and fixed,
see the module-level note above). is_anchor marks a leg selected
FIRST, by confidence-scaled target delta; every other leg in the same
strategy is selected as a strike/expiry offset from an anchor leg.
Iron condor/butterfly have TWO anchor legs (short_put, short_call) -
one per composed vertical selection.
OptionsMultiLegPositionDecision
dataclass
¶
N-leg sibling of OptionsSpreadPositionDecision - generalized to any leg count/ratio and to 1 OR 2 distinct expiries (the calendar family), rather than OptionsSpreadPositionDecision's fixed 2-leg/1-expiry shape. A SEPARATE dataclass, not a generalization of it - every existing caller of OptionsSpreadPositionDecision (bull_call_spread/ bear_put_spread's own dedicated sizing path) is completely untouched.
to_dict ¶
to_dict() -> dict[str, Any]
Same manual-field-access convention as every other decision dataclass in this module - never dataclasses.asdict().
OptionsPositionDecision
dataclass
¶
One option order: contracts, right, strike, expiry and the reasons for it.
to_dict ¶
to_dict() -> dict[str, Any]
Deliberately NOT dataclasses.asdict(self) - asdict() deep-copies every field, and a raw Lean Symbol (a C#/.NET interop object) isn't guaranteed deepcopy-safe. Manual field access avoids that risk entirely and stringifies contract_symbol so the result is always JSON-serializable - required for main.py's dashboard state writer (see main.py::on_data()'s "dynamic_sizing_for_state" sanitization, which calls this before signals[...] ever reaches json.dumps()).
OptionsSpreadLeg
dataclass
¶
One leg of a 2-leg vertical spread. side is "long"/"short" (never a signed quantity) - keeps this symmetric with how Lean's own OptionStrategies/Leg model a spread: a strike, a side, a contract.
OptionsSpreadPositionDecision
dataclass
¶
2-leg vertical spread sibling of OptionsPositionDecision - a SEPARATE dataclass, not a generalization of it (OptionsPositionDecision stays single-leg-shaped, every existing caller untouched).
to_dict ¶
to_dict() -> dict[str, Any]
Same manual-field-access convention as OptionsPositionDecision.to_dict() above - never dataclasses.asdict(), the legs' contract_symbol fields are real Lean Symbol objects, not deepcopy-safe.
ResearchPanel
dataclass
¶
What sleeves may read: known closes by session, each ticker's class, and FRED series as {key: dated rows}.
extras
class-attribute
instance-attribute
¶
extras: Mapping[str, DataFrame] = field(default_factory=dict)
SleeveGuardConfig
dataclass
¶
Settings of a sleeve's loss guard: on or off, the loss limit as a share of NAV and the lookback.
SleeveSpec
dataclass
¶
A sleeve to research or trade: its id, track, kind (time series or cross-sectional), family, universe, rebalance cadence and parameters.
params
class-attribute
instance-attribute
¶
params: Mapping[str, float] = field(default_factory=dict)
StrategySpec
dataclass
¶
One registry entry - everything main.py's generic order-placement layer needs to resolve and call the real OptionStrategies factory, plus everything the shape-family selectors below need to pick strikes/ expiries for it. factory_name is resolved via getattr(OptionStrategies, factory_name) at the main.py call site (never imported per-strategy here - this module must not import Lean's AlgorithmImports, matching every other pure function in this file).
TargetWeightsConfig
dataclass
¶
Settings of the target-weights path: the weights file and its pin, deadbands, minimum trades, fees, slippage and financing.
no_bar_haircut_by_class
class-attribute
instance-attribute
¶
no_bar_haircut_by_class: Mapping[str, float] = field(default_factory=lambda: dict(DEFAULT_NO_BAR_HAIRCUT))
slippage_bps_by_class
class-attribute
instance-attribute
¶
slippage_bps_by_class: Mapping[str, float] = field(default_factory=lambda: dict(DEFAULT_SLIPPAGE_BPS))
TrendSleeveConfig
dataclass
¶
Settings of the trend sleeve: on or off, its symbols, the signal and the volatility target.
history_needed
property
¶
history_needed: int
Closes a symbol needs before it can carry a signal and a volatility.
WeightSettings
dataclass
¶
How a sleeve's signal becomes weights: the volatility target, the gross cap and the per-name and per-class caps.
advance_strategy_value ¶
advance_strategy_value(state: dict[str, Any] | None, total_value: float, sweep_profit: float) -> tuple[dict, float]
The value the risk controls read: an index that compounds the strategy's own P&L (the portfolio's change minus the sweep's) over the TOTAL value the strategy is sized on, starting at the first total value seen. Subtracting the cumulative carry from the total instead (V6.2.0's first BT1) divides the strategy's dollar P&L by a smaller base than it was sized on: ~8% inflated returns and drawdown speed by mid-2024, enough to trip the kill switch on a day V6.1 did not. The offline simulator's kill switch reads the same quantity (book + legacy return as a fraction of NAV). Returns (new state, value); a non-finite or non-positive input keeps the previous value.
align_score_table ¶
align_score_table(table: DataFrame, asset_class: Mapping[str, str]) -> pd.DataFrame
A score table keyed by dataset date: a crypto or forex score dated D reads bar D, which closes after D's equity
decision, so it may only be used a row later. test_no_lookahead can't see into a precomputed table.
amihud ¶
amihud(known: DataFrame, quote_volume: DataFrame, window: int = 30) -> pd.DataFrame
Illiquidity: the trailing mean of |return| per unit of quote volume. A zero or missing volume leaves that day out; half a window is the minimum.
announcement_proximity ¶
announcement_proximity(rows: DataFrame, index: Index, columns: Index, *, window_days: int = 14) -> pd.DataFrame
1.0 during the window_days calendar days before a name's expected announcement (about ten sessions), 0.0 otherwise, NaN for a name with no earlier announcement.
The expected date is an announcement 52 weeks earlier (Frazzini and Lamont's announcement premium), so no filing at or after the session is read. An expected date beyond the last
session flags only the sessions that really lie inside its window.
announcement_return ¶
announcement_return(rows: DataFrame, known: DataFrame, *, days: int = 3, decay: int = 60, bench: str = 'SPY') -> pd.DataFrame
The abnormal return over days rows from the announcement (Brandt, Kishore, Santa-Clara and Venkatachalam), held for decay rows once it is complete.
Row start holds the close before the announcement day, row start + days the close of its last reaction day. Blank until those days have passed, and for bench itself.
annualised_vol ¶
annualised_vol(closes: Sequence[float], window: int, annualization: float = 252.0) -> float | None
Sample std of the last window daily log returns x sqrt(annualization); None if too short, flat or invalid.
apply_book_neutrality ¶
apply_book_neutrality(raw_weights_by_symbol: dict[str, float], *, sector_by_symbol: dict[str, str], dollar_neutral: bool, sector_neutral: bool, gross_exposure_cap: float, max_weight_per_name: float, sector_max_net_weight: float, reapply_sector_cap_after_dollar_neutral: bool = False) -> tuple[dict[str, float], dict]
Deterministic 4-step pipeline over an already-selected book's raw per-symbol weights (main.py's today-formula: min(max_position_weight, 0.10 + 0.15*confidence) * book_role_multiplier, one entry per book-selected symbol only - not the whole universe).
Steps, always in this order: 1. Per-name cap: clip every weight to [-max_weight_per_name, +max_weight_per_name]. 2. Sector neutrality (if sector_neutral): within each sector bucket, if |bucket net| exceeds sector_max_net_weight, SHRINK the whole bucket (every member, proportionally, sign preserved, never amplified) so its net lands exactly at the cap. A bucket already within the cap is left untouched.
Deliberately NOT "demean to exact zero" (the pre-V5.1.3 behavior -
see Problems.md #81). Demeaning only preserves information when a
bucket has genuine within-bucket weight dispersion to fall back
on. Both of this module's callers (main.py's book formula,
evaluation/rank_book_simulator.py) equal-weight every name within
a role, so a bucket that is monolithic to one role - e.g. every
Forex ticker maps to a single "Forex" bucket, and Forex can never
appear on the opposite leg - has NO within-bucket dispersion:
demeaning drove every member to exactly zero, silently erasing the
entire position instead of bounding a genuine sector tilt.
Shrink-toward-cap degrades gracefully in every case: an already-
balanced bucket is untouched, a lopsided or monolithic bucket is
shrunk (never erased) toward the cap, and a single-member bucket
(net == its own weight) needs no special case - same code path.
- Dollar neutrality (if dollar_neutral): scale the LARGER leg down so sum(long) == sum(|short|). Scale-down only, never up - this can never increase gross exposure. A one-sided book (no shorts, or no longs) has nothing to balance against; that step is skipped for that bar rather than zeroing the book out, and the skip is recorded in diagnostics.
- Gross cap: scale everything by min(1, gross_exposure_cap / gross).
Step 3 shrinks one leg only, so a sector holding both a long and a short can end above its cap (proved in
formal/AetherFormal/BookNeutrality.lean, sector_cap_counterexample). reapply_sector_cap_after_dollar_neutral
(default False, the shipped behaviour) runs step 2 again after step 3: every sector then ends within its cap, and
the book may be left slightly off dollar-neutral (recorded as "sector_cap_reapplied").
A non-positive max_weight_per_name skips step 1 like the other two caps (V6.4.3: a cap of 0 zeroed the book and a negative one turned every weight, shorts included, into a long of |cap|).
Returns (weights, diagnostics) where diagnostics is JSON-serializable (state.json's state["book_neutrality"]): {"pre_gross", "post_gross", "net_before", "net_after", "per_sector_net": {sector: net_weight, ...}, "steps_applied": [...]}
Never raises. An empty input returns ({}, diagnostics-with-zeros). Pure dict-in/dict-out - result does not depend on input dict iteration order (every step is a symmetric sum/mean over the group, not a positional operation).
apply_book_weight_floor ¶
apply_book_weight_floor(target_weight: float, base_target_weight: float, floor_fraction: float, skip_floor: bool = False) -> float
V5.5.0 (Problems.md #128) - bound how far the multiplier chain can shrink a book member below the weight the book chose for it.
The offline simulation trades the book's own weight (~8% a name); live
stacks volatility (0.35-1.25), confidence (0.5-1.0), topology
(0.5-1.0), cost (>=0.25) and liquidity (0.35) multipliers on top, which
left equity at ~3% of NAV. The floor is symmetric (|weight| only, sign
kept) so it cannot tilt the long/short balance, and is a no-op at
floor_fraction <= 0 (the pre-V5.5.0 behavior). skip_floor is for the
liquidity "reduce_size" haircut, which is a safety cut that must never be
undone. Never raises the weight above |base_target_weight|.
apply_class_weight_caps ¶
apply_class_weight_caps(weights: dict[str, float], asset_class_by_symbol: dict[str, str | None], caps: dict[str, dict]) -> dict[str, float]
V5.8.0: bounds the book weights of a class — each name to per_name_cap, then the class to class_gross_cap
(proportional scale-down) — leaving exempt names (the proven core, e.g. BTC/LTC) and every other class untouched.
Used for the names that must trade without a proven edge (the 10 crypto pairs that became eligible with the
extended window). Capping a leg of a dollar-neutral book can leave a small net exposure; the sizing chain and the
kill switch run after this, exactly as live and as-live both do. Pure; input is not mutated.
apply_direction_overlay ¶
apply_direction_overlay(ranks: dict[str, float], probabilities: dict[str, float | None], params: dict | None, group_by_symbol: dict[str, str] | None = None) -> dict[str, float]
The overlay on one bar's percentile ranks ({symbol: rank}); params from resolve_direction_overlay (None or an
empty rank dict returns ranks untouched). Symbols without a probability keep their rank (z = 0 / no filter).
apply_hrp_weights ¶
apply_hrp_weights(book_allocations: dict[str, BookAllocation], returns_by_symbol: dict[str, list[float]], gross_exposure: float = 1.0) -> dict[str, float]
V5.4.4 - HRP book SIZING over an already-selected book. Replaces the per-symbol confidence formula (min(max_position_weight, 0.10+0.15*confidence) * book_role_multiplier) with Hierarchical Risk Parity inverse-variance weights derived from the selected symbols' return correlation structure - SAME symbols, SAME roles, different sizing only. Selection (build_rank_based_book()) always runs first and is never affected; this function must be called with its output.
returns_by_symbol is {symbol_key: [close-to-close returns]} -
main.py builds it from each selected symbol's own rolling window
(self.symbol_windows, the 25-bar deque) via pct_returns_from_closes();
symbols missing from the dict (or with < 2 usable returns) are simply
excluded from the covariance estimation by build_hrp_allocation()'s
own filtering, never a KeyError.
Per-leg weight contract (mirrors portfolio/hrp_allocation.py:: build_hrp_allocation(), which this delegates to): with BOTH legs populated each leg sums to +/-gross_exposure/2 (dollar-neutral book); a single-sided book (long_flat strategy_mode, bottom_n=0) gives the present leg the FULL gross. Insufficient history (< 2 symbols with usable returns) or an HRP convergence failure degrades to equal weight within each leg - sizing always covers exactly the selected symbol set, {} only when book_allocations itself is empty.
Deliberately a lazy import of portfolio.hrp_allocation inside the function body: that module imports scipy at module scope, and this keeps book_construction.py (imported by main.py on EVERY run, HRP engaged or not) scipy-free on the default "rank" path - the same isolator-timeout/import-weight concern that keeps evaluation/ rank_ic_core.py torch-free (development/Problems.md #16/#102).
apply_legacy_sleeve_policy ¶
apply_legacy_sleeve_policy(signal_name: str, target_weight: float, is_book_member: bool, is_currently_invested: bool, book_is_active: bool, sleeve_gross_used: float, sleeve_config: dict[str, Any], is_book_owned: bool = False) -> dict[str, Any]
Decide how much of a non-book symbol's legacy signal may trade.
Returns {"signal", "target_weight", "reason", "sleeve_gross_used"}.
- A NEW entry ("buy"/"short" on a flat, non-book symbol) is admitted only
inside the gross budget and the per-name cap. The returned
sleeve_gross_used includes it, so the caller threads it through the
rest of the bar's loop and the budget is atomic across symbols.
- A HELD sleeve position (invested, not book-owned) that the legacy signal
wants to size up is capped at the per-name weight - otherwise the 2%
entry would be scaled to the signal's full ~8% on the next bar.
- Book members, positions the BOOK opened (is_book_owned - during a veto
they are non-members too, and must not be shrunk to a sleeve weight),
exits ("sell") and "hold" pass through untouched.
apply_min_change ¶
apply_min_change(previous: Mapping[str, float], target: Mapping[str, float], min_change: float) -> dict[str, float]
Keeps a held weight when the new target moves it by less than min_change (fewer small, fee-heavy trades). A
symbol whose target is missing or zero always closes; a new symbol always opens.
assignment_risk_flag ¶
assignment_risk_flag(score: float, threshold: float) -> bool
Pure threshold predicate - the sweep-trigger boundary.
assignment_risk_score ¶
assignment_risk_score(moneyness: float | None, right: str, extrinsic_value: float | None, expected_dividend_amount: float | None, days_to_next_ex_div: int | None, window_days: int) -> float
0.0-1.0 composite early-call-assignment-risk score.
Call-only by construction: a put always scores 0.0 (an upcoming dividend never makes early exercise of a long put rational - dividends only ever pull CALL exercise forward). Also 0.0 whenever: the leg isn't in-the-money (moneyness <= 1.0), there's no known upcoming ex-dividend date within window_days (including a negative days_to_next_ex_div - already past due, stale schedule), window_days itself is non-positive, or extrinsic_value/expected_dividend_amount is unknown (None) or expected_dividend_amount is non-positive.
Within the risk window: scores at the time-proximity ceiling when early_exercise_optimal_for_dividend() is already True (extrinsic value below the expected dividend); otherwise decays smoothly via this module's own _normal_cdf() as the extrinsic-value cushion over the dividend widens (reusing this codebase's existing probabilistic vocabulary rather than inventing an ad hoc formula), further scaled by how close the ex-dividend date actually is within the window. Never raises.
atm_implied_volatility ¶
atm_implied_volatility(available_chain: list[dict], expiry: str) -> float | None
Nearest-to-spot call+put average IV for one expiry - "nearest to spot" approximated here as the LOWEST |delta - 0.5| call combined with its same-strike put (a call delta near 0.50 is the standard ATM proxy when a real spot/strike distance isn't threaded through this function). Returns None when the expiry has no usable call (chain empty, IB disabled, or no "iv"/"implied_volatility" field present) - never a crash, matching this module's degrade-to-absent convention.
bond_credit_timing ¶
bond_credit_timing(panel: ResearchPanel, spec: SleeveSpec) -> pd.DataFrame
Long credit against duration while the BAA spread is narrowing over the last quarter, and the reverse.
bond_slope_timing ¶
bond_slope_timing(panel: ResearchPanel, spec: SleeveSpec) -> pd.DataFrame
Long duration against the short end while the 10y-3m slope is above its one-year mean, and the reverse.
book_history_mode_allowed ¶
book_history_mode_allowed(runtime_mode: str, enabled: bool, paper_enabled: bool = False) -> tuple[bool, bool]
(write the book history this rebalance?, bounded record). Backtests log the full record; paper_enabled (V6.4.0,
default off) also logs paper runs with the bounded record (no full universe, no feature snapshot: one small line per
rebalance); live never logs. Disabled = nothing.
box_spread_fair_value ¶
box_spread_fair_value(higher_strike: float, lower_strike: float, risk_free_rate: float, time_to_expiry_years: float) -> float
A box spread (long put + short call at the higher strike, short put + long call at the lower strike, same expiry) synthesizes a risk-free zero-coupon bond paying (higher_strike - lower_strike) at expiry - its fair value today is that payoff discounted at the risk-free rate. Standard textbook result.
breadth_timing ¶
breadth_timing(known: DataFrame, liquid: DataFrame, window: int = 50, floor: float = 0.5, core: tuple[str, ...] = CORE) -> pd.DataFrame
1.0 on the core coins while at least floor of the liquid coins trade above their own window-session average, else 0.0. NaN elsewhere and while breadth can't be measured.
build_arbitrage_position_sizing ¶
build_arbitrage_position_sizing(strategy_name: str, available_chain: list[dict], current_date: date, risk_free_rate: float, min_mispricing_bps: float, max_contracts_per_signal: int = 1, target_delta: float = 0.5) -> OptionsMultiLegPositionDecision | None
The ONLY sizing path for the 6 arbitrage strategies - deliberately
NOT vega-budget or margin-tier sized (an arbitrage structure is
delta/vega-neutral by construction, so a vega budget has nothing
meaningful to scale against). Sized instead to a small, FIXED
max_contracts_per_signal (default 1) whenever
select_arbitrage_signal() confirms a real mispricing this bar - a
deliberately conservative first-cut sizing model for a genuinely new
capability, not a risk-budgeted one; a real margin-aware arbitrage
sizer is a reasonable future follow-up once this is IB-verified.
Returns None when no mispricing is detected, selection fails, or
max_contracts_per_signal isn't positive - never raises.
build_book_history_record ¶
build_book_history_record(date_str: str, book_allocations: dict[str, BookAllocation], raw_scores_by_symbol: dict[str, float], target_weights_by_symbol: dict[str, float], sector_by_symbol: dict[str, str], rank_signal_policy: dict[str, Any], full_universe_signals: dict[str, dict] | None = None, book_member_decisions: dict[str, dict] | None = None, gate_veto_reason: str | None = None, feature_snapshot: dict[str, dict] | None = None, run_id: str | None = None, rolling_ic_state: dict[str, Any] | None = None, member_probabilities: dict[str, dict] | None = None) -> dict[str, Any]
V5.2.2 (development/Problems.md) - a diagnostic snapshot of one
rebalance bar's book, for main.py's optional (config-gated, off by
default, backtest-only) book-history log. Exists to make a real,
ground-truth reconciliation possible between what a live/backtest run
actually selected and what evaluation/rank_book_simulator.py's
offline simulator would select for the identical date - the tool
this project needed after six other hypotheses for a large live-vs-
offline Sharpe gap were tested and ruled out without finding the
cause (see Problems.md #89-#90).
Pure: no I/O, no self, never raises on well-formed inputs - every
per-symbol field degrades via .get(..., None) rather than
KeyError, since a symbol missing from raw_scores_by_symbol or
target_weights_by_symbol (e.g. target_weight is never populated
when phase_v2.portfolio_book.neutrality.enabled is False - see
BookAllocation.target_weight's own None-means-"not applicable"
convention) is expected, not exceptional.
rank_signal_policy (main.py's own self._rank_signal_policy,
resolved once in Initialize()) is embedded INLINE in every record,
not referenced externally - deliberately, so the log stays self-
contained and correctly interpretable even if config or training
metrics change between when a run produced this line and when a
later reconciliation reads it back.
full_universe_signals (V5.2.3, opt-in via
phase_v2.diagnostics.book_history.include_full_universe): None
(default) reproduces this function's exact V5.2.2 return shape - no
"universe" key at all, so every existing caller/log consumer is
unaffected. When given {symbol: {"raw_rank_score", "feature_ready",
"reason", "trading_eligible", "security_type"}} (main.py's own Phase
1a signals payload, one entry per symbol with a bar this tick,
selected or not), adds a "universe" key with the same shape -
closes the exact gap that made the V5.2.2 tool unable to see WHY a
symbol was never selected (e.g. crypto/FX consistently absent from
every live book - Problems.md #91): the V5.2.2 log only ever recorded
book_allocations, i.e. symbols that already made the cut, with no
way to see what score/readiness a NON-selected symbol had that day.
Each per-symbol sub-dict degrades the same .get(..., None) way as
every other field here - a caller-supplied dict missing a key never
raises.
book_member_decisions (V5.2.6, opt-in via
phase_v2.diagnostics.book_history.include_decisions): None
(default) reproduces this function's exact pre-V5.2.6 return shape -
no "book_member_decisions" key at all, same convention as
full_universe_signals. When given
{symbol: MarketAnalysisDecision.to_dict()} (main.py's own Pass 2,
collected per book member as each symbol's decision is computed),
adds a "book_member_decisions" key recording each book member's
final action/reasons - closes the gap where a symbol could be
logged here as "selected" via book_allocations even though it was
later diverted to simulate/reduce_risk/retrain_candidate by a
risk/execution gate book_allocations alone can never reveal (see
development/Problems.md - the crypto/FX zero-order-execution
finding).
gate_veto_reason (V5.3.5, development/Problems.md #102): None
(default) omits the key entirely, same convention as the two fields
above. When main.py calls this on a genuine rebalance bar whose
build_rank_based_book() call came back empty, it passes the
veto_reason_out["reason"] string that call populated (see
_select_book_group()'s own docstring) - without this, a vetoed
rebalance bar left ZERO trace in this log (the write was gated on
book_allocations being truthy), making it impossible to tell from
the log alone whether a disengaged stretch came from
min_rank_confidence_spread or the new rolling-IC gate, or anything
else. main.py now writes a record on every genuine rebalance bar
regardless of whether book_allocations came back empty.
feature_snapshot (V5.3.5.3 Workstream B, development/Problems.md
91/#100): None (default) omits the key entirely, same convention¶
as the three fields above. When given {symbol: {feature: value}}
(main.py's own pass1_state[symbol]["feature_payload"]["base_features"]
dict, captured ONLY for the symbols in
phase_v2.diagnostics.book_history.feature_snapshot_symbols's
allowlist and only when
phase_v2.diagnostics.book_history.include_feature_snapshot is on),
adds a "feature_snapshot" key - the raw per-feature values the live
model actually saw for those symbols that bar. Exists so
evaluation/feature_reconciliation.py::reconcile_feature_snapshot()
can diff these against the same (ticker, date) row of
ml/datasets/full_dataset.csv: XOM is the one tracked ticker whose
live-vs-offline book-selection mismatch never resolved across
91/#97/#99/#100, and until this field existed there was nothing in¶
the log to diff feature-by-feature. Allowlist-bounded by design - without it every bar would serialize ~77 symbols x dozens of floats, ballooning this log for a diagnostic only ever needed for a handful of tickers.
run_id (V5.7.0): the backtest's start stamp; None omits the key. Lets the audit and reconcile tools select a
run by id instead of guessing run boundaries from a date that goes backwards.
build_book_raw_weights ¶
build_book_raw_weights(book_allocations: Mapping[str, BookAllocation], max_position_weight: float) -> dict[str, float]
V5.5.0 (Problems.md #128) - the live book's pre-neutrality weight per symbol: min(max_position_weight, 0.10 + 0.15 * confidence) * role multiplier, confidence = min(1, |rank - 0.5| * 2). Extracted from main.py's inline loop so the offline simulator's as-live mode feeds apply_book_neutrality() the IDENTICAL raw weights instead of equal weights (parity rule, AGENTS.md). Non-finite rank -> skipped.
build_class_sleeve_weights ¶
build_class_sleeve_weights(allocations: Mapping[str, BookAllocation], gross_budget: float, max_weight_per_name: float | None = None) -> dict[str, float]
Signed target weight (fraction of NAV) per selected name: each leg gets gross_budget / 2, split evenly over its
names, optionally capped per name. A one-sided book (no shorts, no longs) gets {} - a class sleeve is dollar-neutral by
construction, never a directional bet on the class. Non-finite or non-positive budget -> {}.
build_covered_protective_position_sizing ¶
build_covered_protective_position_sizing(strategy_name: str, confidence: float, available_chain: list[dict], equity_quantity: float, contract_multiplier: float = 100.0, target_delta_at_full_confidence: float = 0.3) -> OptionsMultiLegPositionDecision | None
Sizes covered_call/covered_put/protective_call/protective_put/ protective_collar's option leg(s) purely as floor(|equity_quantity| / contract_multiplier) - the number of contracts the CURRENTLY HELD equity position actually covers, never a vega/confidence-scaled target of its own (§9.3's floor-rounding: any fractional remainder is carried as plain uncovered equity, never partially hedged).
equity_quantity must carry the correct SIGN for this strategy's covering_equity_side (positive/long for covered_call, protective_put, protective_collar; negative/short for covered_put, protective_call) - a mismatched sign (e.g. a short equity holding passed for covered_call, which needs a LONG holding) returns None rather than sizing against the wrong side of the book. Returns None for any strategy_name whose risk_tier isn't "covered_protective", for confidence <= 0, or when floor rounding or leg selection yields nothing - never raises.
build_forex_sleeve_weights ¶
build_forex_sleeve_weights(allocations: Mapping[str, BookAllocation], gross_budget: float, max_weight_per_pair: float | None = None) -> dict[str, float]
Signed target weight (fraction of NAV) per selected pair: each leg gets gross_budget / 2, split evenly over its
names, optionally capped per pair. A one-sided book (no shorts, no longs) gets {} - the sleeve is dollar-neutral by
construction, never a directional currency bet. Non-finite or non-positive budget -> {}.
build_margin_position_sizing ¶
build_margin_position_sizing(strategy_name: str, signal_direction: str, confidence: float, available_chain: list[dict], underlying_price: float, portfolio_value: float, contract_multiplier: float = 100.0, target_delta_at_full_confidence: float = 0.6, target_margin_utilization: float = 0.2, max_margin_utilization: float = 0.4, pct_of_underlying_value: float = 0.2, min_pct_of_underlying_value: float = 0.1, **selector_tuning_kwargs: Any) -> MarginSizingDecision | None
The single public entry point for every margin-tier strategy (risk_tier in {"margin_naked", "margin_uncovered_leg", "margin_bounded_backspread"}) - dispatches leg selection via options_strategy.select_strategy_legs() (the SAME registry-driven selector every vega-budget strategy uses, so a ladder's or backspread's strikes are chosen identically regardless of which sizing model ultimately budgets it), then sizes via the sub-model its risk_tier maps to. Returns None for confidence/portfolio_value <= 0, an invalid signal_direction, a non-margin-tier strategy_name (sized by options_strategy.py instead), or when selection/margin-budget rounds to zero contracts - never raises.
build_margin_position_sizing_for_legs ¶
build_margin_position_sizing_for_legs(strategy_name: str, held_rows_by_role: dict[str, dict], underlying_price: float, portfolio_value: float, contract_multiplier: float = 100.0, max_margin_utilization: float = 0.4, pct_of_underlying_value: float = 0.2, min_pct_of_underlying_value: float = 0.1) -> MarginSizingDecision | None
Resize-in-place sibling of build_margin_position_sizing() - sizes an ALREADY-HELD margin-tier position on its legs' own current chain rows, skipping selection entirely (held_rows_by_role must carry an entry for every role in MULTI_LEG_STRATEGY_REGISTRY[strategy_name].legs, same shape select_strategy_legs() returns). No confidence-scaled soft target here - the position already exists, so sizing is to the full max_margin_utilization ceiling directly, the same "size to budget on already-resolved greeks, no fresh confidence input" convention options_strategy.py's build_multi_leg_position_sizing_for_legs() and build_options_position_sizing_for_contract() already use.
build_multi_leg_position_sizing ¶
build_multi_leg_position_sizing(strategy_name: str, signal_direction: str, confidence: float, available_chain: list[dict], portfolio_value: float, target_delta_at_full_confidence: float = 0.6, max_vega_budget_pct_of_equity: float = 0.02, grouped_chain_by_expiry: dict[str, list[dict]] | None = None, **selector_tuning_kwargs: Any) -> OptionsMultiLegPositionDecision | None
The single public entry point for every NEW (V4.5) vega-budget-tier strategy - dispatches leg selection via select_strategy_legs(), then sizes via _size_multi_leg(). Returns None for any margin-tier or unreachable-arbitrage strategy_name (those are sized by portfolio/options_margin_sizing.py, or never sized at all, respectively) - a caller passing the wrong tier's strategy_name here gets a clean None, not a wrong/unsafe budget calculation.
grouped_chain_by_expiry (V4.9 Priority 3) passes straight through to
select_strategy_legs() - see its own docstring. Default None
reproduces the exact original per-call regroup behavior.
build_multi_leg_position_sizing_for_legs ¶
build_multi_leg_position_sizing_for_legs(strategy_name: str, held_rows_by_role: dict[str, dict], portfolio_value: float, max_vega_budget_pct_of_equity: float = 0.02) -> OptionsMultiLegPositionDecision | None
Resize-in-place sibling of build_multi_leg_position_sizing() - sizes an ALREADY-HELD position on its legs' own current greeks, skipping selection entirely. held_rows_by_role must carry an entry for every role in MULTI_LEG_STRATEGY_REGISTRY[strategy_name].legs (same shape select_strategy_legs() returns) - same degrade-to-None contract as build_multi_leg_position_sizing() for non-positive portfolio_value or non-positive net vega.
build_options_position_sizing ¶
build_options_position_sizing(signal_direction: str, confidence: float, available_chain: list[dict], portfolio_value: float, target_delta_at_full_confidence: float = 0.6, max_vega_budget_pct_of_equity: float = 0.02) -> OptionsPositionDecision | None
Translates the existing direction+confidence prediction into ONE single-leg options position. signal_direction == "buy" selects calls; {"sell", "short"} select puts (matches portfolio/book_construction.py's own "buy"/"short" role vocabulary and main.py::_apply_signal()'s signal_name values).
target_delta scales linearly with confidence: target_delta_at_full_confidence * confidence. contract count is capped so total position vega stays under max_vega_budget_pct_of_equity * portfolio_value / contract_vega - vega (not notional) is the risk unit an options book should budget against, since a small-notional far-OTM option can carry disproportionate vega risk relative to its price.
Returns None when: confidence is 0, signal_direction doesn't map to a right, available_chain has no usable contract for that right (e.g. IB disabled -> empty chain, or select_single_leg_contract() found no match), the selected contract's vega is non-positive, or portfolio_value is non-positive - degrade-to-absent, never a crash, matching risk/futures_risk.py::build_futures_position_sizing()'s "no active signal -> None/zero" convention.
build_options_position_sizing_for_contract ¶
build_options_position_sizing_for_contract(held_contract: dict[str, Any], portfolio_value: float, max_vega_budget_pct_of_equity: float = 0.02) -> OptionsPositionDecision | None
V4.4 - sizes the EXACT held contract on its own current greeks, skipping select_single_leg_contract() entirely (no re-selection).
Closes a real architectural gap: without this, a position whose ideal strike has drifted away from what main.py is currently holding could only ever be handled by RE-SELECTING a contract (which picks a DIFFERENT one, forcing a binary rotate-or-freeze choice) - this lets the held position keep being managed - scaled up or down - on its own delta/vega, on bars where main.py chooses not to rotate.
held_contract is a live chain row for the currently-held contract - same shape select_single_leg_contract() returns (must carry right/strike/expiry/delta/vega/symbol, e.g. found by matching the held contract_symbol against this bar's available_chain). There is no fresh confidence-scaled target here - the position already exists - so target_delta/actual_delta are both just the held contract's own current delta.
Same degrade-to-None contract as build_options_position_sizing(): non-positive portfolio_value, missing/non-positive vega, or a budget that rounds to zero contracts all return None, never raise.
build_rank_based_book ¶
build_rank_based_book(book_candidates: dict[str, dict], top_n: int, bottom_n: int, min_rank_confidence_spread: float = 0.0, per_asset_class_slots: dict[str, tuple[int, int]] | None = None, previous_allocations: dict[str, BookAllocation] | None = None, hysteresis_rank_margin: float = 0.0, spread_check_ranks: dict[str, float] | None = None, rolling_ic_gate_result: dict[str, Any] | None = None, veto_reason_out: dict[str, Any] | None = None) -> dict[str, BookAllocation]
Discrete top-N-long / bottom-N-short book construction from each symbol's predicted_rank_20d for this bar. Continuous rank-weighted sizing (rather than a hard top/bottom-N cutoff) is a documented future extension, not built here - the discrete version is simpler to reason about and test first.
book_candidates is {symbol: {"predicted_rank_20d": float | None,
"trading_eligible": bool, "asset_class": str | None, ...}} - one entry
per symbol Pass 1 of main.py::on_data() collected this bar (extra keys
like probability_up/confidence are ignored here, kept only so callers
can pass the same dict they already built for other purposes). A
symbol is eligible for book selection only if trading_eligible is
true (this is exactly how phase9.asset_quality's observation-only
assets get excluded - main.py already computes this per-symbol flag
identically for every other decision) and predicted_rank_20d is not
None (model unavailable/still warming up that bar).
bottom_n == 0 requests a deliberate long-only book (no shorts at all) -
used by main.py to honor phase5.backtest.strategy_mode == "long_flat"
while still getting rank-driven entry/rotation on the long side. Any
bottom_n > 0 still requires at least one eligible symbol on EACH side
to form a book at all - a one-sided book isn't attempted when a short
side was actually requested but nothing was available for it; returns
{} in that case, same as every other degenerate-input case below.
top_n/bottom_n (or each
asset class's own pair, see per_asset_class_slots below) exceeding
the number of eligible symbols degrades gracefully to however many are
actually available (never raises) - same "pad/truncate rather than
error on a thin universe" convention as topology/market_topology.py's
rank_correlated_peers().
min_rank_confidence_spread is a floor on (mean long-side rank - mean
short-side rank) before a book (or, with per_asset_class_slots, each
individual asset class's own book) engages at all - on a day where the
ranked pool's predicted ranks are all clustered near 0.5 (no real
dispersion), forcing a long/short split would be noise, not signal;
the book disengages entirely (returns {} for that pool) rather than
trading a meaningless split. Every symbol then falls through to
whatever non-book decision main.py's Pass 2 would have made anyway -
byte-identical to this module not existing at all, the same "missing/
degraded signal never changes trading behavior" contract this
codebase's other optional overlays already guarantee.
per_asset_class_slots (default None - pooled ranking, this function's
original and only behavior before this parameter existed) is an
optional {asset_class: (top_n, bottom_n)} map. When provided, top_n/
bottom_n are ignored and every eligible symbol is ranked within its
own asset_class group only instead of one combined-universe pool - so
e.g. equities and crypto each get their own long/short slot budget
instead of one side potentially being filled entirely by a single
dominant asset class. A symbol whose asset_class isn't a key in
per_asset_class_slots is excluded from book selection entirely (not
silently folded into some catch-all group) - same explicit-opt-in
convention risk/asset_class_router.py already uses for future/option.
min_rank_confidence_spread is applied independently per asset class
(each class's own long/short spread must individually clear the bar -
pooling the check across classes with potentially very different rank
distributions would be misleading). Results from every class are
unioned into one returned dict, same shape as the pooled path.
Returns a dict covering ONLY the symbols the book actively wants long or short (not a "flat" entry for every candidate) - callers should treat "symbol absent from the returned dict" as "book has no view on this symbol," not "book says flat."
previous_allocations/hysteresis_rank_margin (V5.1 Phase 0/1,
defaults None/0.0 - a strict no-op reproducing pre-hysteresis selection
exactly): threaded straight through to _select_book_group() (pooled
path) or applied identically within each asset class (per-asset-class
path) - see _hysteresis_adjusted_selection()'s docstring for the actual
sticky-selection behavior.
spread_check_ranks (V5.1 Phase 1, development/Problems.md #77):
optional {symbol: raw_score} override for the min_rank_confidence_spread
check only - see _select_book_group()'s docstring for why this must be
the PRE-normalization score when the caller's book_candidates
predicted_rank_20d values are already a cross-sectional percentile.
Defaults to None (falls back to book_candidates' own predicted_rank_20d,
byte-identical to before this parameter existed).
rolling_ic_gate_result/veto_reason_out (V5.3.5, development/
Problems.md #102): threaded straight through to _select_book_group() -
see that function's own docstring. Both default to None, a strict
no-op. With per_asset_class_slots, rolling_ic_gate_result is
applied identically to every class's own selection (this signal is
portfolio-wide, not asset-class-specific, unlike min_rank_confidence_spread
which already gets a note above about being checked per class); if
more than one class vetoes, veto_reason_out ends up holding
whichever class's reason was computed last - an accepted, documented
simplification, since the only thing that consumes this diagnostic
(main.py's book-history logging) only reads it when the OVERALL
allocations result comes back completely empty, at which point any
one real reason is more useful than none.
build_vertical_spread_position_sizing ¶
build_vertical_spread_position_sizing(signal_direction: str, confidence: float, available_chain: list[dict], portfolio_value: float, target_delta_at_full_confidence: float = 0.6, short_leg_delta_offset: float = 0.2, max_vega_budget_pct_of_equity: float = 0.02) -> OptionsSpreadPositionDecision | None
Vertical-spread sibling of build_options_position_sizing() above - same signal_direction -> right mapping ("buy" -> call vertical/ bull_call_spread, "sell"/"short" -> put vertical/bear_put_spread), same confidence-scaled target_delta for the long leg.
Sizes by NET vega (long leg vega minus short leg vega) rather than the long leg's vega alone - a vertical's defining risk reduction versus a single leg, and the correct risk unit to budget a spread position against. Returns None on the same degrade-to-absent conditions build_options_position_sizing() already documents, plus: no usable short leg found (select_vertical_spread_legs() returned None), or net_vega is non-positive (a spread with ~zero or negative net vega is unsizeable by this budget, not an error).
build_vertical_spread_position_sizing_for_legs ¶
build_vertical_spread_position_sizing_for_legs(held_long: dict[str, Any], held_short: dict[str, Any], portfolio_value: float, max_vega_budget_pct_of_equity: float = 0.02) -> OptionsSpreadPositionDecision | None
V4.4 - net-vega sibling of build_options_position_sizing_for_contract() for a held 2-leg vertical spread: sizes the EXACT held long/short legs on their own current greeks, skipping select_vertical_spread_legs() entirely. held_long/held_short are live chain rows for the currently- held legs (same shape select_vertical_spread_legs() returns each of its pair). strategy_name/right are derived from held_long's own "right" field rather than passed in - there is no fresh signal here, the spread already exists. Same degrade-to-None contract as build_vertical_spread_position_sizing().
calibrate_probability ¶
calibrate_probability(probability: float, calibration: dict | None) -> float
The fitted map applied to one probability; unknown or missing maps leave it unchanged.
cash_profitability ¶
cash_profitability(fundamentals: DataFrame, index: Index, columns: Index, *, max_age: int = 400) -> pd.DataFrame
Trailing operating cash flow over assets (Ball et al.), each as last filed before the session.
cash_sweep_target_weight ¶
cash_sweep_target_weight(*, long_weight: float, short_weight: float, margin_used: float, reserve: dict[str, Any], config: dict[str, Any]) -> float
Sweep weight (fraction of NAV) inside both limits and max_weight; 0.0 for any non-finite input.
classify_volatility_view ¶
classify_volatility_view(predicted_volatility_annualized: float, atm_iv: float, margin: float = 0.05) -> str
Pure, unit-agnostic classifier: "long_vol" when the model's own
annualized volatility forecast exceeds the chain's current ATM implied
vol by more than margin, "short_vol" when it's below by more than
margin, else "neutral". The annualization (predicted_volatility *
ANNUALIZATION_FACTOR_DAILY_TO_ANNUAL) MUST happen at the call site,
never inside this function - train.py's target_volatility_next_day is
a raw daily high-low-range proxy, not annualized, and comparing it
against chain IV (always annualized) unit-mismatched would produce an
almost-always-false signal. Documented approximation regardless of
units: a range-based estimator and a lognormal-return-implied vol
aren't the same statistical object even after unit-matching.
combine_sleeves ¶
combine_sleeves(sleeve_weights: Mapping[str, Mapping[str, Mapping[str, float]]], panel: ResearchPanel, *, method: str = 'inverse_vol', every: int = 21, settings: WeightSettings | None = None, start: str | None = None, end: str | None = None) -> dict[str, dict[str, float]]
A fixed-rule mix of sleeves, never fitted to their returns: each month a sleeve's budget comes from the trailing year of its own gross returns (inverse volatility, or hierarchical risk parity), and the sum is scaled to one volatility target.
combine_tranche_weights ¶
combine_tranche_weights(tranche_weights: list[dict[str, float]]) -> dict[str, float]
The live book target: the sum of the tranches' signed weights per name (a name held by two tranches nets); names that net to zero are dropped.
compute_confidence_spread ¶
compute_confidence_spread(long_symbols: list[str], short_symbols: list[str], spread_ranks: dict[str, float], eligible_ranks: dict[str, float]) -> float | None
mean(long-side score) - mean(short-side score), the exact quantity _select_book_group()'s min_rank_confidence_spread gate compares against a floor. Public (not module-private) so a calibration tool can compute the identical statistic against historical data without re-deriving the formula - the two can then never disagree on what "spread" means for a given day's selection.
spread_ranks.get(symbol, eligible_ranks[symbol]) - a symbol missing
from spread_ranks (e.g. a caller-supplied dict that doesn't cover
every eligible symbol) falls back to its own eligible_ranks value
rather than a KeyError, same defensive convention as every other
optional-override lookup in this module.
None if either list is empty - spread is undefined without both sides (mirrors the caller never reaching this check for a long-only book).
compute_hrp_weights ¶
compute_hrp_weights(returns: 'pd.DataFrame') -> 'pd.Series'
Hierarchical risk parity weights (portfolio/hrp_allocation.py; needs scipy, imported on call).
compute_rolling_ic_state ¶
compute_rolling_ic_state(event_buffer: list[dict], *, horizon_days: int, rolling_window_days: int, min_names_per_date: int = 2) -> dict[str, Any]
Resolves event_buffer (main.py's own flat, in-memory
{"ticker", "created_at", "resolved_predicted_rank_20d", "close_price"}
deque, or the offline replay/calibration tools' equivalent) via
evaluation.rank_ic_core.resolve_realized_rank_ic_observations(), then
restricts to the trailing rolling_window_days UNIQUE resolved
origin dates (not calendar days, and not raw observation count - a
date with more symbols resolved must not count for more "days" of
trailing history than a thin one) before aggregating.
min_names_per_date (V5.3.5, development/Problems.md #102) - default
2 preserves this function's original behavior exactly (see
evaluation.rank_ic_core.rank_ic_from_arrays()'s own docstring for why
this exists: a 2-name origin date is mathematically forced to a
+-1 IC reading regardless of real skill, and real historical data
confirmed this dominates the low tail of a walk-forward calibration's
distribution). Callers evaluating a live or replayed rolling-IC
reading - where thin-universe noise masquerading as "the model is
badly wrong" would cause a spurious veto - should pass a value well
above 2 (evaluation/rolling_ic_gate_calibration.py and evaluation/
rolling_ic_gate_replay.py both default to 10, sourced from
phase_v2.rolling_ic_gate.min_names_per_date).
Returns {"rolling_mean_ic": float | None, "num_resolved_dates": int,
"num_resolved_observations": int}. rolling_mean_ic is None (never
0.0) when zero observations have resolved yet - "unknown" must never
be silently treated as "IC is exactly zero" by evaluate_rolling_ic_gate()
below, the same None-vs-neutral-default distinction every other pure
signal function in this codebase already draws (e.g.
risk/kill_switch.py::_rolling_sharpe()).
compute_sleeve_gross_used ¶
compute_sleeve_gross_used(held_weights_by_symbol: dict[str, float], book_member_keys: set[str] | frozenset[str]) -> float
Gross |weight| currently held outside the book: every invested symbol that is neither in this bar's book nor in the last formed book. During a veto the book is empty, so remembered members still count as book (they are positions the book opened), not sleeve.
conversion_parity_value ¶
conversion_parity_value(call_price: float, put_price: float, spot: float, strike: float, risk_free_rate: float, time_to_expiry_years: float, pv_dividends: float = 0.0) -> float
Put-call parity: call_price - put_price should equal
spot - pv_dividends - strike * exp(-r*T). Returns the SIGNED
discrepancy (actual minus theoretical). A conversion (long stock, long
put, short call) locks in call - put - spot + PV(strike) + PV(dividends), which is this
discrepancy, so it profits when it is positive (calls rich against
puts); a reverse conversion profits when it is negative. Without
pv_dividends, a dividend payer's calls read cheap: a reverse conversion is short the
stock and owes each dividend, which is why it fired on AAPL at fair prices.
core_constant ¶
core_constant(known: DataFrame, core: tuple[str, ...] = CORE) -> pd.DataFrame
1.0 on the core coins wherever they have a price: a volatility-managed hold, whose size the weight pipeline sets.
cross_asset_trend ¶
cross_asset_trend(known: DataFrame, tickers: tuple[str, ...] | list[str], *, long: int = 252, skip: int = 21) -> pd.DataFrame
1.0 where the 12-1 month return (long rows back to skip rows back) is positive, else 0.0; NaN without that history. Only the named assets.
cross_sectional_rank ¶
cross_sectional_rank(table: DataFrame) -> pd.DataFrame
Each row's percentile ranks across the columns.
cross_sectional_rank_scores ¶
cross_sectional_rank_scores(scores_by_symbol: dict[str, float]) -> dict[str, float]
Per-bar percentile rank across whatever symbols have a raw score
this bar, ties averaged (matching pandas' .rank(pct=True) semantics -
the same transform train.py::_rank_ic_from_arrays() applies to
predictions before computing rank-IC, so a raw score's absolute scale
can never matter, only its ordering within this bar's cross-section).
Pure Python - no pandas import on the runtime hot path.
<=1 eligible symbol returns {} (no meaningful cross-section to rank within, so the book has no view this bar) - NEVER a spurious 0.5 for a lone symbol, which would silently look like "predicted median" rather than "no ranking possible.".
cross_sectional_return ¶
cross_sectional_return(panel: ResearchPanel, spec: SleeveSpec) -> pd.DataFrame
Rank on the trailing return over lookback, skipping the last skip sessions.
crypto_core_only_settings ¶
crypto_core_only_settings(class_weight_caps_config: dict[str, Any] | None) -> tuple[bool, frozenset[str]]
(core_only, core names) from phase_v2.class_weight_caps.crypto: core_only (V6.4.0, default false, lead L9) keeps the
crypto pairs that are not exempt (the proven core, BTC / LTC) out of the main book's candidates.
crypto_graduate_excluded ¶
crypto_graduate_excluded(ticker: str, asset_class: str | None, core_only: bool, core_names: frozenset[str]) -> bool
True when core_only is on and ticker is a crypto pair outside the core: it takes no book slot or neutrality weight.
One definition for main.py's live candidates and the offline simulator's candidate metadata.
crypto_tables ¶
crypto_tables(known: DataFrame, funding: DataFrame | None, liquid: DataFrame, btc: str = 'BTCUSD') -> dict[str, pd.DataFrame]
c1-c4, session x coin, masked to the liquid set. known already holds each coin's bar from before the session, and funding is
shifted the same way: a day's settlements aren't all known at that day's equity close.
crypto_trend ¶
crypto_trend(panel: ResearchPanel, spec: SleeveSpec) -> pd.DataFrame
Long while the trailing return over the lookback is positive; flat otherwise (long-only: no reliable short venue).
daily_financing_usd ¶
daily_financing_usd(*, long_usd: float, short_usd: float, nav: float, rf_annual_pct: float, fx_positions: Iterable[tuple[float, float]] = (), days: float = 1.0, margin_spread_bps: float = 150.0, borrow_bps: float = 25.0, fx_spread_bps: float = 50.0) -> float
Financing for days calendar days. long_usd and short_usd (positive) cover everything but forex, including
the T-bill sweep. The debit is what the longs need beyond NAV and the short proceeds, charged at the risk-free rate
plus the spread. Each forex position is (signed USD notional of the base currency, r_base - r_quote in percentage points):
long the base earns the differential, either way less the spread. Positive cash earns nothing, as in Lean.
days_to_next_ex_dividend ¶
days_to_next_ex_dividend(current_date: date | None, next_ex_dividend_date: date | None) -> int | None
Pure date arithmetic, mirrors options_strategy.option_auto_close_due()'s own calendar-day-diff pattern. None when either input is missing - never raises.
detect_executable_edge ¶
detect_executable_edge(edge: float, min_mispricing_bps: float, notional: float) -> bool
True when entering the structure at executable prices beats its fair value by at least the threshold. One-sided: a structure priced dear is no arbitrage for the side that would pay up, and only its opposite may fire.
detect_mispricing ¶
detect_mispricing(actual_net_cost: float, fair_value: float, min_mispricing_bps: float, notional: float) -> bool
Shared threshold predicate for all 3 families above: True when the discrepancy between actual_net_cost and fair_value exceeds min_mispricing_bps of notional - requiring a MINIMUM edge (not a bare non-zero difference) accounts for real bid/ask spread and transaction costs a genuine arbitrage must clear to be worth trading. Returns False (never trades, never raises) when notional is non-positive.
early_exercise_optimal_for_dividend ¶
early_exercise_optimal_for_dividend(extrinsic_value: float | None, expected_dividend_amount: float | None) -> bool
Classic textbook rule: early call exercise is optimal only when the option's extrinsic value is LESS than the dividend the holder would capture by exercising before ex-div (they give up less time value than they gain in dividend). Returns False (never optimal) when either input is None - an unknown value never triggers a false-positive risk flag.
equity_fee_usd ¶
equity_fee_usd(notional: float, bps: float, min_usd: float) -> float
Commission in basis points with a dollar minimum. Used instead of Lean's per-share IB model on this path: the data is split- and dividend-adjusted, so an old adjusted price of $0.49 would make a per-share fee 1% of the trade.
evaluate_rolling_ic_gate ¶
evaluate_rolling_ic_gate(rolling_ic_state: dict[str, Any], config: dict[str, Any]) -> dict[str, Any]
Pure decision function - the direct sibling of
portfolio.book_construction.compute_confidence_spread()'s consumer at
_select_book_group(). rolling_ic_state is compute_rolling_ic_state()'s
own output; config is phase_v2.rolling_ic_gate's dict.
GUARDRAIL - fail-open at two levels, matching risk/kill_switch.py::evaluate_kill_switch()'s own documented contract: 1. config["enabled"] is False -> always engaged, full no-op, regardless of any other input. 2. num_resolved_dates < min_resolved_dates_required (default: config's own rolling_window_days, matching evaluate_kill_switch()'s min_bars_for_sharpe precedent - "the trailing window size" is the value's own documented intent) -> also engaged. A cold-start buffer (the first ~40-60 live bars of any run, before enough predictions have had time to resolve against a realized 20-day forward return) must never masquerade as "IC is bad" - it must masquerade as "gate not evaluated yet".
Returns {"engaged": bool, "reason": str, "observed_rolling_mean_ic": float | None, "min_rolling_mean_ic": float, "num_resolved_dates": int}.
evaluate_sleeve_guard ¶
evaluate_sleeve_guard(pnl_increments: Sequence[float], nav: float, config: SleeveGuardConfig, *, current_bar: int, suspended_until_bar: int | None = None) -> dict[str, Any]
Decides whether the sleeve may trade on current_bar.
pnl_increments is the sleeve's per-bar P&L history (dollars, or NAV fractions with nav=1.0), oldest first.
Returns {"active": bool, "reason": str, "suspended_until_bar": int | None, "drawdown": float, "limit": float}.
A suspension started earlier holds until suspended_until_bar (exclusive); a fresh breach starts a new one. The
history is NOT reset by the caller: after the cooldown the trailing window usually no longer contains the loss, and
if it still does the sleeve stays off - a loss that has not healed is not a reason to resume.
events_to_sessions ¶
events_to_sessions(events: DataFrame, index: Index, columns: Index, *, date: str, value: str, limit: int | None) -> pd.DataFrame
The newest value per name as of each session, from rows with a date and a ticker. Visible from the first session strictly after date; a later event
at the same row wins; carried forward at most limit rows.
external_score ¶
external_score(panel: ResearchPanel, spec: SleeveSpec) -> pd.DataFrame
A score table computed elsewhere (a walk-forward model, COT positioning, earnings surprise), already as of each session.
extrinsic_value ¶
extrinsic_value(option_price: float | None, spot: float, strike: float, right: str) -> float | None
option_price minus intrinsic value. Returns None (not 0.0) when option_price is missing/non-positive - "unknown extrinsic value" must never be indistinguishable from "zero extrinsic value" (a real, meaningful early-exercise trigger condition in its own right), the same None-vs-neutral-default distinction features/bond_features.py's module docstring already establishes for this codebase.
forex_resize_below_minimum ¶
forex_resize_below_minimum(delta_units: float, price: float, min_trade_usd: float) -> bool
True when a resize of delta_units base-currency units at price is worth less than min_trade_usd in notional:
paying IB's dollar minimum to move such a small amount costs more bps than the move is worth. min_trade_usd <= 0
disables the check (False). Non-finite inputs -> True (do not trade on garbage).
forward_table ¶
forward_table(spec: Mapping[str, Any], known: DataFrame) -> pd.DataFrame
A round-4C trial as the forward feed can compute it, from the marks alone (no volume, no funding): {"builder": name, "params": {...}, "gate": "regime"} for one signal, or
{"rank_mean": [spec, ...], "minimum": n, "gate": "regime"} for a composite. The builder comes from a closed set. breadth_timing counts every coin in known as liquid, since the feed
holds only the coins the definition lists.
fred_as_known ¶
fred_as_known(series: dict[str, Any], index: Index, *, keys: tuple[str, ...] = ('treasury_10yr', 'treasury_3mo', 'credit_spread_baa10y'), lag_sessions: int = 1) -> pd.DataFrame
FRED series ({key: Series by date}) as the session knows them: carried across days with no mark, then read lag_sessions late, since FRED's daily marks post after the equity close.
fred_series_table ¶
fred_series_table(fred_rows: Mapping[str, list[dict]], keys: tuple[str, ...]) -> dict[str, pd.Series]
{key: value by date string} from load_cached_fred_series() rows.
funding_filtered_trend ¶
funding_filtered_trend(known: DataFrame, funding: DataFrame | None, window: int = 14, rank_window: int = 180, cut: float = 0.8) -> pd.DataFrame
The trend votes, set to 0 (flat) where a coin's mean funding sits above its own trailing cut quantile, since crowded longs paid to stay long. Missing funding leaves the vote alone.
fx_carry_signal ¶
fx_carry_signal(panel: ResearchPanel, spec: SleeveSpec) -> pd.DataFrame
Rank on the 3-month rate differential; momentum_weight adds a rank of the trailing 63-session return.
group_chain_by_expiry ¶
group_chain_by_expiry(available_chain: list[dict]) -> dict[str, list[dict]]
Single O(n) pass grouping chain rows by expiry - built ONCE per selector call and reused for every leg lookup within it, rather than re-scanning the full chain per leg (this is the "latency-efficient" requirement for a function that runs every bar, per candidate strategy, per symbol). Also the mechanism that makes expiry-anchoring possible: every leg after the anchor is selected ONLY from grouped[anchor_expiry], never re-scanning the other expiries.
Public (V4.9 Priority 3, renamed from the original _group_chain_by_expiry) since risk/asset_class_router.py::route_multi_leg_option_sizing() now calls this ONCE per routing call (not per selector call) when the calendar shape family is in play, avoiding a repeated regroup of the same chain across multiple candidate strategies tried in the same bar - see select_calendar_legs()'s/select_arbitrage_jelly_roll_legs()'s own grouped_chain_by_expiry parameter.
grouped_cross_sectional_rank_scores ¶
grouped_cross_sectional_rank_scores(scores_by_symbol: dict[str, float], group_by_symbol: dict[str, str] | None, sector_by_symbol: dict[str, str] | None = None, min_sector_size: int = 3) -> dict[str, float]
cross_sectional_rank_scores() applied WITHIN each rank group (V5.8.0): a model trained on grouped rank targets (bonds ranked among bonds, forex among forex) emits scores that are only comparable inside their group, so the percentile is taken per group. With no mapping (or every symbol in "main") this equals cross_sectional_rank_scores().
sector_by_symbol (V6.4.0, rank_signal.normalization: "sector_relative", default None = off) de-means each group's
scores by their sector's mean first (sector_relative_scores), so the percentile ranks a name against its sector peers.
high_proximity ¶
high_proximity(known: DataFrame, window: int = 365, min_periods: int = 180) -> pd.DataFrame
Close over its trailing high (George and Hwang): 1.0 at the high.
idio_vol ¶
idio_vol(known: DataFrame, btc: str = BTC, window: int = 60) -> pd.DataFrame
Minus the standard deviation of the return left after BTC.
is_rotation_exit_candidate ¶
is_rotation_exit_candidate(is_currently_invested: bool, sleeve_enabled: bool, is_book_owned: bool) -> bool
V5.5.0 (Problems.md #128) - may the rotation exit force-sell this position when the book is active and did not select it?
should_exit_non_selected_book_symbol() was documented as applying to a symbol "previously book-selected" but applied to EVERY invested non-member. With the legacy sleeve on, that would liquidate a sleeve entry the very next bar (and, with exits no longer blocked by the trade cooldown, re-enter and re-exit it - pure churn), so the sleeve's positions must be left to the non-model exits (max holding age, trailing stop) and their own sell signal. With the sleeve OFF this is the pre-V5.5.0 rule unchanged: every invested non-member is a candidate.
jelly_roll_fair_value ¶
jelly_roll_fair_value(strike: float, risk_free_rate: float, near_time_to_expiry_years: float, far_time_to_expiry_years: float) -> float
The interest-rate-implied fair cost of a jelly roll (rolling a synthetic position from the near expiry to the far one) - the discounted strike difference between the two expiries, the standard cost-of-carry result for extending a synthetic forward's expiry.
known_closes ¶
known_closes(dates: list[str], tickers: list[str], closes: ndarray, asset_class: Mapping[str, str], sessions: set[str]) -> pd.DataFrame
Closes known at each session's equity close. A crypto or forex bar dated D closes at the midnight after that close, so on session D the latest usable bar of those classes is the one dated before D.
live_names_per_rebalance ¶
live_names_per_rebalance(table: DataFrame, rebalance_every: int) -> dict[str, float]
How many names a signal can rank, on the days it decides: an empty book must not read as a sleeve with no skill.
load_live_weight_file ¶
load_live_weight_file(path: Path | str) -> dict[str, dict[str, float]]
A growing weight file, checked against its sidecar. Raises as load_weight_file does; the caller keeps what it has.
load_rate_series ¶
load_rate_series(path: Path | str) -> tuple[list[str], list[float]]
(ISO dates, rates in percent) of Lean's interest-rate.csv; empty when the file is missing or unreadable, so a
run without it charges a 0% base rate plus the spread rather than failing.
load_weight_file ¶
load_weight_file(path: Path | str, expected_sha256: str = '') -> dict[str, dict[str, float]]
date -> ticker -> weight. Raises ValueError on a hash mismatch, a malformed row or a non-finite weight: a run must not trade a file other than the one its config froze.
lottery_max ¶
lottery_max(known: DataFrame, window: int = 30) -> pd.DataFrame
The coin with the smaller best day ranks higher: minus the trailing maximum daily return (Bali, Cakici, Whitelaw's MAX effect).
low_beta ¶
low_beta(known: DataFrame, btc: str = BTC, window: int = 60) -> pd.DataFrame
Minus the trailing beta to BTC.
low_vol ¶
low_vol(known: DataFrame, window: int = 60) -> pd.DataFrame
The calmer coin ranks higher: minus the trailing standard deviation of daily returns.
low_volatility ¶
low_volatility(panel: ResearchPanel, spec: SleeveSpec) -> pd.DataFrame
Minus the trailing volatility of each name's returns: the low-volatility signal.
margin_fraction ¶
margin_fraction(weights: list[tuple[str, float]]) -> float
Margin Lean's default model holds for these (security type, signed weight) positions, as a fraction of NAV.
names_to_exit_without_bars ¶
names_to_exit_without_bars(missing: Mapping[str, int], config: TargetWeightsConfig) -> list[str]
Held names whose bars stopped, in a fixed order. This reacts only to bars already missing, so it reads no future: a delisted name can't be sold at any later price, and holding it at its last close forever would hide the loss.
net_debit_or_credit_for_legs ¶
net_debit_or_credit_for_legs(spec: StrategySpec, legs_by_role: dict[str, dict]) -> float | None
Signed net premium to trade every leg in spec.legs at legs_by_role's current bid/ask - long legs value at ask, short legs at bid (the conservative/executable-price convention _size_multi_leg() below already uses, and options_arbitrage_detector.py's own _actual_net_cost() also uses independently). Positive = net DEBIT (paying more for long legs than received for short legs), negative = net CREDIT - same sign convention as OptionsMultiLegPositionDecision's own net_debit_or_credit field.
Returns None (not 0.0) when any leg's role is missing a bid/ask row - "cannot compute this bar" must not be indistinguishable from "computed a genuine 0.0 premium", the same None-vs-neutral-default distinction features/bond_features.py's module docstring and features/options_greeks.py::implied_volatility() already draw. _size_multi_leg() below deliberately keeps ITS OWN reset-to-0.0-on- missing-price behavior (a documented best-effort SIZING diagnostic, unchanged) by coercing this function's None to 0.0 at its own call site - this function is the stricter sibling, extracted for a caller (main.py's per-strategy realized-outcome computation, V4.7) where "unknown" must never silently look like "breakeven".
next_unapplied_row ¶
next_unapplied_row(weights: Mapping[str, Mapping[str, float]], tick_date: str, applied_through: str | None) -> tuple[str, dict[str, float]] | None
Paper and live (live_reload): the latest row dated at or before this tick and after the last one applied. Row D is
written after D's close, so the exact-date lookup a backtest uses would miss every row.
no_bar_exit_cost_usd ¶
no_bar_exit_cost_usd(units: float, last_close_usd: float, asset_class: str, config: TargetWeightsConfig) -> float
What such an exit loses against the last close: Lean fills the order at that stale price, so this is charged on top.
normalize_per_asset_class_slots ¶
normalize_per_asset_class_slots(raw: dict[str, object] | None) -> tuple[dict[str, tuple[int, int]], list[str]]
Validates and coerces a raw phase_v2.portfolio_book.per_asset_class_slots
config dict into build_rank_based_book()'s expected shape.
build_rank_based_book() unpacks each value unconditionally
(for asset_class, (top_n, bottom_n) in per_asset_class_slots.items()),
so a malformed entry (wrong length, wrong type) would otherwise hard-
crash every bar instead of degrading gracefully like every other
optional config-driven feature in this codebase - development/
Problems.md. Each value must be a 2-element [top_n, bottom_n]
list/tuple; anything else is skipped, not fatal.
Returns (valid_slots, skipped_asset_classes) - pure and no-logging
by design (so it stays independently testable); callers with access
to a logger (main.py's self.Debug()) should report the skipped
list themselves.
null_signal ¶
null_signal(signal: DataFrame, mode: str, *, seed: int, asset_class: Mapping[str, str], block: int = 60, min_shift: int = 252) -> pd.DataFrame
A signal with the skill removed, for placebos.
asset_block: relabel assets within their class, held for block sessions; keeps turnover and persistence.
time_shift: a circular shift of at least a year; keeps each asset's long-run tilt, so a survivor or class effect that
an asset relabelling would credit as skill is matched here.
sign_block: for time-series and timing sleeves, reverse the position per asset in block-session blocks (a long/flat
signal in 0..1 swaps long and flat).
opportunistic_buying ¶
opportunistic_buying(trades: DataFrame, index: Index, columns: Index, *, routine_years: int = 3, window: int = 63) -> pd.DataFrame
Open-market purchases (code P) by owners who weren't routine buyers (Cohen, Malloy, Pomorski): an owner who bought in the same calendar month in each of the previous
routine_years years is routine and left out. The value over the last window rows, logged. Dated by filed; 0.0 means no such purchase.
option_auto_close_due ¶
option_auto_close_due(current_date: date, expiry_date: date, auto_close_days_before_expiry: int = 2) -> bool
§9.5 - the narrow expiry-day safety net: True once expiry_date is
within auto_close_days_before_expiry calendar days of current_date.
Pure date comparison, mirroring risk/futures_risk.py::rollover_due()'s
own pattern exactly (generalized from futures rollover to options
expiry) - a diagnostic/trigger signal only, main.py's own force-
liquidate call is what actually acts on it. Returns False when
expiry_date is unparseable/unknown - never raises, matching every
other pure predicate in this module's degrade-to-absent convention.
order_enabled_strategies ¶
order_enabled_strategies(enabled_strategy_names: list[str], risk_tier_preference: str = 'defined_risk_first') -> list[str]
Pure reordering (§9.2): enabled_strategy_names is a PRIORITY list, not just a membership set - the first enabled name (in the returned order) that matches the current bucket and successfully sizes wins. "defined_risk_first" (the safe default) stable-sorts every defined-risk name (iron condor/butterfly variants) ahead of every unbounded-risk name (short straddle/strangle), preserving the caller's own relative order within each group; "premium_maximizing_first" inverts that. Names outside both known sets (every other strategy) keep their original relative position, interleaved after the reordered ones - this function only ever reorders, never drops or adds a name.
partial_adjust_weights ¶
partial_adjust_weights(current: dict[str, float], target: dict[str, float], fraction: float, min_weight: float = 0.001) -> dict[str, float]
V6.4.0 (lead L6): move fraction of the way from the held weights to the new target on a rebalance,
current + fraction * (target - current) per name; weights that fall under min_weight are dropped. A fraction of 1
returns the target unchanged (the shipped book); the blend of two valid books stays inside the same gross cap.
pct_returns_from_closes ¶
pct_returns_from_closes(closes: list[Any]) -> list[float]
V5.4.4 - close-to-close simple returns from a symbol's rolling
close window (main.py's self.symbol_windows[symbol] "close" values,
oldest first). The per-symbol return history apply_hrp_weights()
consumes for its covariance estimation.
Pairs containing a missing/non-positive/non-finite close are SKIPPED
(not emitted as NaN/inf/-1.0) - a split-adjusted 0 or a None from a
partially-populated window would otherwise poison the covariance
matrix HRP estimates downstream (a 0 CURRENT close would emit a
-100% return, a variance bomb, even though its previous close is
technically usable). V5.4.5 (#117): NaN closes pass the old <= 0.0
check (nan <= 0 is False) and poisoned the covariance the same way.
Fewer than 2 usable closes returns [] (no return can exist without at
least one previous close to diff against).
pv_dividends_to_expiry ¶
pv_dividends_to_expiry(payments: Iterable[tuple[date, float]] | None, current_date: date, expiry_date_str: str, risk_free_rate: float) -> float
Present value of the dividends going ex after current_date and on or before the expiry. payments holds
(ex_date, cash_per_share) pairs. A row it can't read counts as no dividend, as the detector did before it knew of any.
rank_group_by_symbol ¶
rank_group_by_symbol(asset_class_by_symbol: dict[str, str | None], separate_rank_groups: dict[str, Any] | None) -> dict[str, str]
{symbol: rank group} from phase1.target.ranking.separate_rank_groups ({group: [asset classes]}): a symbol whose
class is listed under a group belongs to it, everything else to "main". The same mapping train.py uses to build the
grouped rank targets (V5.7.0), so live ranks each group the way the model was trained.
rank_mean ¶
rank_mean(tables: list[DataFrame], *, minimum: int) -> pd.DataFrame
The mean of each table's cross-sectional rank, where at least minimum of them have a value.
rate_asof ¶
rate_asof(dates: list[str], rates: list[float], date: str) -> float
The last rate at or before date; 0.0 before the first observation.
rebalance_deltas ¶
rebalance_deltas(held_units: Mapping[str, float], usd_per_unit: Mapping[str, float], nav: float, target: Mapping[str, float], config: TargetWeightsConfig, *, lot_size: Mapping[str, float] | None = None, class_by_ticker: Mapping[str, str] | None = None) -> list[tuple[str, float]]
(ticker, signed units to trade), reductions first: Lean checks buying power when an order is submitted and does not net sells that have not filled. A ticker with no usable price is skipped, never traded on a guess. A ticker the target omits is closed in full; otherwise a change below the deadband or the minimum trade size is left alone.
regime_gate ¶
regime_gate(table: DataFrame, known: DataFrame, btc: str = BTC, window: int = 120) -> pd.DataFrame
The table on sessions when BTC trades above its window-session average; no signal at all (a flat book) otherwise, and while BTC's average can't be measured.
remember_formed_book ¶
remember_formed_book(previous_formed: dict[str, Any] | None, new_allocations: dict[str, Any] | None) -> dict[str, Any]
V5.5.0 (Problems.md #128) - the hysteresis memory a veto must NOT wipe.
main.py stored every rebalance result into the one dict it also uses as
"the book to trade", so a gate veto (empty allocations) erased the
previous book. The next rebalance then selected with no hysteresis
anchor, and has_previous_allocation read False so the book re-formed
on every following bar until a gate opened. The positions the book had
opened were still held the whole time. This keeps the last NON-EMPTY
book as the anchor: a veto leaves it untouched, a real book replaces it.
rerank_enabled_strategies_by_score ¶
rerank_enabled_strategies_by_score(enabled_strategy_names: list[str], strategy_selector_scores: dict[str, float]) -> list[str]
V4.7 (development/Problems.md #29's own framing) - the learned strategy-selector model's live inference plug point: a drop-in ALTERNATIVE to order_enabled_strategies()'s static reordering above, not a replacement for it. Stable-sorts enabled_strategy_names descending by strategy_selector_scores.get(name, float("-inf")) - names with no score (the model hasn't seen that strategy_name yet, or strategy_selector_scores is a stale/partial version) sink to the end in their ORIGINAL relative order, never dropped - same "only ever reorders, never drops or adds a name" contract order_enabled_strategies() itself keeps.
Callers must only invoke this when strategy_selector_scores is truthy (non-empty) - risk/asset_class_router.py::route_multi_leg_option_sizing() falls back to order_enabled_strategies() itself whenever no trained model's scores are available, so this function has no fallback path of its own; it is never the byte-identical default.
residual_momentum ¶
residual_momentum(panel: ResearchPanel, spec: SleeveSpec) -> pd.DataFrame
Rank on the market-residual return from lookback to skip sessions ago.
residual_reversal ¶
residual_reversal(panel: ResearchPanel, spec: SleeveSpec) -> pd.DataFrame
Rank against the market-residual return of the last window sessions.
resize_below_minimum ¶
resize_below_minimum(delta_units: float, price: float, min_trade_usd: float) -> bool
True when a resize of delta_units at price is worth less than min_trade_usd in notional (a fixed per-order
fee would cost more bps than the move is worth). min_trade_usd <= 0 disables the check (False). Non-finite inputs ->
True (do not trade on garbage).
resolve_cash_sweep_config ¶
resolve_cash_sweep_config(raw: dict[str, Any] | None) -> dict[str, Any]
phase_v2.cash_sweep with defaults; out-of-range numbers fall back to the default rather than raise.
resolve_class_sleeve_config ¶
resolve_class_sleeve_config(asset_class: str, raw: dict | None) -> ClassSleeveConfig
Config block (phase_v2.<class>_sleeve) -> ClassSleeveConfig. Absent block = disabled.
resolve_class_weight_caps ¶
resolve_class_weight_caps(raw: dict[str, Any] | None) -> dict[str, dict]
phase_v2.class_weight_caps -> {asset_class: {"per_name_cap", "class_gross_cap", "exempt"}} for the enabled classes.
Absent/empty = no caps (the pre-V5.8.0 behaviour). A cap of 0 or less means "no cap on that dimension".
resolve_direction_overlay ¶
resolve_direction_overlay(raw: dict | None) -> dict | None
phase_v2.rank_signal.direction_overlay -> params, or None when absent / disabled / malformed (the shipped book).
resolve_enabled_strategy_names ¶
resolve_enabled_strategy_names(asset: dict[str, Any], global_default: list[str]) -> list[str]
V4.6 (development/Problems.md #59's own deferred item) - per-asset
enabled_strategy_names override: an option asset's phase1.universe.
assets entry may carry an optional
"options_strategy_override": {"enabled_strategy_names": [...]} key
to trade a DIFFERENT strategy set than the global default (e.g. one
ticker trades iron condors, another trades covered calls). Returns
the override when present and non-empty, else global_default
unchanged - byte-identical to every asset with no override key at
all, which is every asset today (V4.6 adds no default overrides).
Never raises on a malformed override (falls back to global_default).
resolve_late_bar_event_count ¶
resolve_late_bar_event_count(gate_config: dict[str, Any] | None) -> str
rolling_ic_gate.late_bar_event_count (V6.2.0): "per_tick" (default, the behaviour up to V6.1.0) records a
crypto / forex symbol's event on its own midnight tick AND again on the next equity tick, where its last bar is
re-scored, so it weighs twice per date in the gate's IC; "once" records it on its own tick only.
resolve_legacy_signal_params ¶
resolve_legacy_signal_params(config: dict[str, Any], decision_threshold: float) -> dict[str, Any]
The probability signal's settings, read the way main.py reads them (V6.1.0: one definition for main.py and the
offline legacy replay): buy / sell thresholds around the baseline model's decision_threshold
(phase5.backtest.*_threshold_offset, clamped to [0.25, 0.75]), the adaptive sell band (phase_v2.exits.adaptive_band_*)
and the position cap the signal sizes against (phase6.risk.max_position_weight).
resolve_legacy_sleeve_config ¶
resolve_legacy_sleeve_config(raw: dict[str, Any] | None) -> dict[str, Any]
Normalize phase_v2.legacy_sleeve; any malformed value falls back to
the default (sleeve disabled == the pre-V5.5.0 unbounded behavior).
resolve_rank_signal_policy ¶
resolve_rank_signal_policy(training_metrics_by_model: dict[str, dict | None], config: dict[str, Any]) -> dict[str, Any]
Resolved ONCE in main.py::Initialize() (never per-bar - the promotion-gate verdicts this reads only change when a new model is installed). Reads phase_v2.rank_signal:
heads: {"rank_20d": 1.0, "rank_5d": 0.0} - the blend weights. model_priority: ["sequence", "multitask"] - which model's value for a given head wins when both are available. demote_not_promotable_heads: when true, a head whose backtest promotion-gate verdict (backtest.{head}_ranking_quality.quality_status, checked against the FIRST model in model_priority that has a verdict for it - the same precedence select_raw_rank_score() uses at runtime) is not in min_promotion_status has its weight zeroed and redistributed proportionally across the surviving heads. A head configured at weight 0.0 is still checked and can still become a survivor - "listed with zero weight" means "tracked, currently inactive," not "not configured" (development/Problems.md); it only actually receives weight if every positively-weighted head gets demoted, in which case survivors split the freed weight equally rather than dividing by the now-zero surviving total.
Returns {"heads": {...}, "model_priority": [...], "normalization": ..., "demoted": [...], "reason": "...", "demotion_fallback": bool, "failed_heads": [...]}.
FAIL-OPEN, always: never raises. Missing/unreadable metrics for a head leave that head's configured weight untouched (a promotion verdict that cannot be checked is not evidence of a problem). If demotion would zero out EVERY head's weight, the entire operation is skipped and the originally configured policy is returned unchanged, with reason recording why - demoting every head would silently stop the book from ever engaging, a far bigger behavior change than "prefer a different head" implies.
resolve_sleeve_guard_config ¶
resolve_sleeve_guard_config(raw: dict[str, Any] | None) -> SleeveGuardConfig
Config block -> SleeveGuardConfig; absent or malformed keys fall back to the defaults (disabled).
resolve_target_weights_config ¶
resolve_target_weights_config(raw: Mapping[str, Any] | None) -> TargetWeightsConfig
A TargetWeightsConfig from phase_v2.target_weights, every number finite and non-negative, defaults where missing.
resolve_trend_sleeve_config ¶
resolve_trend_sleeve_config(raw: Mapping | None) -> TrendSleeveConfig
phase_v2.trend_sleeve -> TrendSleeveConfig. Absent block = disabled; a malformed number keeps its default.
rotation_cooldown_active ¶
rotation_cooldown_active(current_bar_index: int, last_rotation_bar: int | None, cooldown_bars: int) -> bool
V4.6 (development/Problems.md #57/#58/#59) - anti-thrashing guard
for options rotation: True while current_bar_index is within
cooldown_bars of the last bar a rotation (liquidate + re-enter)
actually executed for this symbol_key. Pure bar-index arithmetic,
mirroring option_auto_close_due()'s own pure-predicate-only pattern -
main.py's rotate_on_drift branch is what actually degrades to the
"manage nearest position in place" fallback when this returns True,
never a hard block. Returns False (cooldown never active) when
last_rotation_bar is None - no rotation has ever fired for this
symbol_key yet, never a crash on the first-ever rotation.
same_month_seasonality ¶
same_month_seasonality(known: DataFrame, *, years: int = 7, min_years: int = 5) -> pd.DataFrame
The mean return of this calendar month over the previous years years (Heston and Sadka), needing min_years of them. Only completed months of earlier years are read.
scale_and_cap ¶
scale_and_cap(raw: Series, covariance: DataFrame, asset_class: Mapping[str, str], settings: WeightSettings) -> dict[str, float]
One ex-ante volatility target, then the per-name, per-class and gross caps. Crypto is long-only.
sector_relative_scores ¶
sector_relative_scores(scores_by_symbol: dict[str, float], sector_by_symbol: dict[str, str] | None, min_sector_size: int = 3) -> dict[str, float]
Each score minus the mean of its sector's scores in this cross-section (V6.4.0, lead L4): a rank within the sector's
own peers rather than across sectors. A sector with fewer than min_sector_size scored names (or none mapped) is
de-meaned by the whole cross-section's mean instead, so its names keep their relative order.
select_arbitrage_box_legs ¶
select_arbitrage_box_legs(spec: StrategySpec, available_chain: list[dict], target_delta: float) -> dict[str, dict] | None
shape_family == "arbitrage_box" (box_spread/short_box_spread) - wired-but-unreachable (development/Problems.md): registry/selector exist and are unit-tested, but no signal source in this codebase ever calls this - a real box spread requires a mispricing detector (rich/ cheap vs. the risk-free-rate-implied fair value), a separate future project. Reuses select_vertical_legs()-style same-expiry anchoring: the "higher"-strike-role legs (long_put, short_call) anchor by delta, the "lower"-strike-role legs (short_put, long_call) share the SAME strike as one of the anchor legs by construction (a box spread is a single strike pair, not 4 independent strikes).
select_arbitrage_conversion_legs ¶
select_arbitrage_conversion_legs(spec: StrategySpec, available_chain: list[dict], target_delta: float) -> dict[str, dict] | None
shape_family == "arbitrage_conversion" (conversion/reverse_conversion) - wired-but-unreachable, same caveat as select_arbitrage_box_legs() above. Single shared strike for both legs (same-expiry, same-strike call+put pair - identical selection shape to select_straddle_legs()).
select_arbitrage_jelly_roll_legs ¶
select_arbitrage_jelly_roll_legs(spec: StrategySpec, available_chain: list[dict], target_delta: float, grouped_chain_by_expiry: dict[str, list[dict]] | None = None) -> dict[str, dict] | None
shape_family == "arbitrage_jelly_roll" (jelly_roll/short_jelly_roll) - wired-but-unreachable, same caveat as the other 2 arbitrage selectors above. Same expiry-grouping primitive as select_calendar_legs(), but requires all 4 legs (near call, far call, near put, far put) to share ONE strike across both expiries.
grouped_chain_by_expiry (V4.9 Priority 3) - same optional
precomputed-grouping parameter as select_calendar_legs(), same default-
None-reproduces-original-behavior contract. In practice never actually
passed today (this shape family is permanently unreachable via
route_multi_leg_option_sizing()'s own risk_tier routing - see its
"unreachable_arbitrage" branch), kept for interface symmetry with
select_calendar_legs() and in case a future caller reaches it directly.
select_arbitrage_signal ¶
select_arbitrage_signal(strategy_name: str, available_chain: list[dict], current_date: date, risk_free_rate: float, min_mispricing_bps: float, target_delta: float = 0.5, dividend_payments: Iterable[tuple[date, float]] | None = None) -> bool
Composes the existing V4.5 arbitrage leg selectors (select_strategy_legs(), already registry-driven) with the fair-value/ threshold functions above: True iff THIS bar's chain shows an exploitable mispricing for strategy_name, using each leg's own bid/ask and days-to-expiry. Strike/expiry ROLES (which role is "higher"/"lower"/"near"/"far") are resolved from MULTI_LEG_STRATEGY_REGISTRY, never hardcoded per strategy_name - the short_* variants invert which role holds which strike/side while keeping the same role NAMES. Returns False (never a crash) on any missing selection/pricing/date data, or for any strategy_name outside the 6 arbitrage families - degrade-to-absent, matching every other selector's convention in this module's sibling file.
select_backspread_legs ¶
select_backspread_legs(spec: StrategySpec, available_chain: list[dict], target_delta: float, backspread_delta_offset: float = 0.2) -> dict[str, dict] | None
shape_family == "backspread" - 1:2-ratio 2-leg selector shared by all 4 variants (call_backspread/short_call_backspread/put_backspread/ short_put_backspread); which strike_role ("lower"/"higher") is the anchor and which side each role maps to both come from the registry, so this one function is correct for every variant without branching on strategy_name.
select_butterfly_legs ¶
select_butterfly_legs(spec: StrategySpec, available_chain: list[dict], target_delta: float, wing_width_delta_offset: float = 0.2, max_strike_asymmetry_tolerance: float = 0.15) -> dict[str, dict] | None
shape_family == "butterfly" - middle strike anchored by delta (the 2x-ratio body leg), then wings picked as the nearest available strikes ABOVE/BELOW it within the same expiry, degrading to None (never a forced/asymmetric fill) when the two wing strikes aren't within max_strike_asymmetry_tolerance of being equidistant from the middle - real discrete strike ladders widen away from spot, so "best-effort near-symmetric" is documented here as the honest contract, not exact equidistance.
select_calendar_legs ¶
select_calendar_legs(spec: StrategySpec, available_chain: list[dict], target_delta: float, grouped_chain_by_expiry: dict[str, list[dict]] | None = None) -> dict[str, dict] | None
shape_family == "calendar" - a genuinely new primitive (nothing before this pass compares two DIFFERENT expiries): groups the chain by expiry first (group_chain_by_expiry(), O(n) once), requires at least 2 distinct expiries, picks the near leg's strike by delta within the nearest expiry, then requires the SAME strike be present in every LATER expiry group - the far leg uses the closest-dated expiry that has it. Returns None for a chain with fewer than 2 expiries (routine for a thinly configured universe, never a crash).
grouped_chain_by_expiry (V4.9 Priority 3), when given, is used
INSTEAD OF recomputing group_chain_by_expiry(available_chain) here -
lets a caller trying several calendar-family candidates against the
SAME chain in one bar (risk/asset_class_router.py::
route_multi_leg_option_sizing()) group it once and reuse it. Default
None reproduces the exact original per-call regroup behavior.
select_covered_protective_leg ¶
select_covered_protective_leg(spec: StrategySpec, available_chain: list[dict], target_delta: float) -> dict[str, dict] | None
shape_family in {"covered_protective", "collar"} - the OPTION leg(s) only (never the equity side - see main.py's corrected cross-asset design, the equity leg is read from the currently-held quantity, not selected from a chain). protective_collar has 2 anchor legs (call and put), each selected independently by delta since a collar's call/put strikes are deliberately NOT required to be equidistant from spot (call_strike > put_strike is the only real constraint, enforced upstream by OptionStrategies.ProtectiveCollar itself).
select_iron_butterfly_legs ¶
select_iron_butterfly_legs(spec: StrategySpec, available_chain: list[dict], target_delta: float, wing_delta_offset: float = 0.2, max_strike_asymmetry_tolerance: float = 0.15) -> dict[str, dict] | None
shape_family == "iron_butterfly" - the ATM strike is shared by BOTH middle legs (short_put and short_call both sit at the same strike, per OptionStrategies.cs's IronButterfly == IronCondor with its 2 middle strikes collapsed together), with the OTM wings picked equidistant (tolerance-based, same contract as select_butterfly_legs()) rather than independently re-selected by delta on each side.
select_iron_condor_legs ¶
select_iron_condor_legs(spec: StrategySpec, available_chain: list[dict], target_delta: float, short_leg_delta_offset: float = 0.2, wing_delta_offset: float = 0.1) -> dict[str, dict] | None
shape_family == "iron_condor" - composed from 2 independent credit-vertical-style selections (put side, call side), both anchored to the SAME expiry (the put side's anchor expiry is picked first and passed down to the call side, closing the same expiry-drift risk select_vertical_legs() closes for a plain 2-leg vertical). The inner legs are the registry's anchors: the shorts of an iron condor, the longs of a short iron condor, whose short legs are the outer wings. Lean's factory rejects anything but ascending strikes.
select_ladder_legs ¶
select_ladder_legs(spec: StrategySpec, available_chain: list[dict], target_delta: float, ladder_wing_delta_offset: float = 0.15) -> dict[str, dict] | None
shape_family == "ladder" - 3-strike, 1:1:1-ratio selector shared by all 4 variants (bear_call_ladder/bull_call_ladder/bear_put_ladder/ bull_put_ladder). Anchors on the spec's anchor leg by delta, then picks the other 2 legs as the nearest available strikes further from the anchor in the direction _outer_strike_predicate defines for this right - same expiry-anchoring guarantee as every other selector here.
select_naked_leg ¶
select_naked_leg(spec: StrategySpec, available_chain: list[dict], target_delta: float) -> dict[str, dict] | None
shape_family == "naked" (naked_call/naked_put) - a single short leg, nearest-|delta| selection, no expiry-anchoring needed (only 1 leg).
select_raw_rank_score ¶
select_raw_rank_score(sequence_prediction: dict[str, Any] | None, multitask_payload: dict[str, Any] | None, policy: dict[str, Any]) -> tuple[float | None, str]
Replaces main.py's previous hardcode (always prefer the sequence model's rank_20d, fall back to multitask's). Now a weighted blend across policy["heads"], each head's own value resolved via policy["model_priority"] (sequence before multitask, by default).
Returns (score, source) where source records exactly which heads/ models contributed, e.g. "sequence:0.6rank_5d+0.4rank_20d" or "sequence:1.0rank_20d,multitask:1.0rank_5d" when different heads come from different models. Returns (None, "no_rank_available") when no configured head has a value from any model this bar (e.g. every model still warming up).
select_single_leg_contract ¶
select_single_leg_contract(available_chain: list[dict], target_delta: float, right: str) -> dict[str, Any] | None
Picks the chain entry of the requested right ("call"/"put") whose
|delta| is closest to |target_delta| - nearest-strike-by-delta
selection, no ML, no spread construction. Each entry in
available_chain must already carry a computed "delta" key (see
features/options_greeks.py::compute_greeks() - this function does no
pricing itself, purely a selection over already-priced rows).
Returns None when available_chain is empty or has no entry of the requested right (e.g. IB disabled -> empty chain, or a chain with only calls when a put was requested) - degrade-to-absent, never a crash.
select_straddle_legs ¶
select_straddle_legs(spec: StrategySpec, available_chain: list[dict], target_delta: float) -> dict[str, dict] | None
shape_family == "straddle" (straddle/short_straddle) - same-strike, same-expiry call+put pair. Anchors on the call leg by delta, then requires a put at the EXACT same strike/expiry (never a nearest-delta put pick - a straddle's defining property is one shared strike).
select_strangle_legs ¶
select_strangle_legs(spec: StrategySpec, available_chain: list[dict], target_delta: float, strangle_delta_offset: float = 0.15) -> dict[str, dict] | None
shape_family == "strangle" (strangle/short_strangle) - offset-strike, same-expiry call+put pair (call strike above put strike, enforced by _outer_strike_predicate-style filtering anchored on the call leg's expiry, so both legs land on the same expiry by construction).
select_strategy_legs ¶
select_strategy_legs(strategy_name: str, available_chain: list[dict], target_delta: float, grouped_chain_by_expiry: dict[str, list[dict]] | None = None, **tuning_kwargs: Any) -> dict[str, dict] | None
Single dispatch point every caller (main.py, tests) should use instead of calling a shape-family selector directly - looks up strategy_name's StrategySpec then its shape_family's selector, a plain dict lookup (O(1)) rather than an if/elif chain over 43 names, matching this module's "registry, not 41 near-duplicate functions" design goal. "vertical" strategies use select_vertical_legs() directly (not in the dispatch dict above, since it takes the full spec + is reused by 4 strategy_names identically). tuning_kwargs passes through to whichever selector is chosen (e.g. short_leg_delta_offset, wing_width_delta_offset) - unrecognized kwargs for a given selector are simply not in its signature and would raise, exactly like any other Python call; callers should only pass kwargs relevant to the strategy family they're invoking, or none at all to accept every selector's own defaults.
grouped_chain_by_expiry (V4.9 Priority 3) is a DEDICATED top-level
parameter, not folded into tuning_kwargs - it's only meaningful for the
2 shape families in _GROUPED_CHAIN_SHAPE_FAMILIES (calendar,
arbitrage_jelly_roll) and is deliberately swallowed (never forwarded)
for every other strategy_name, since blindly passing it through
tuning_kwargs would raise a TypeError for the 12 selectors that don't
accept it. Default None reproduces the exact original per-call regroup
behavior for every strategy_name, unchanged.
select_vertical_legs ¶
select_vertical_legs(spec: StrategySpec, available_chain: list[dict], target_delta: float, short_leg_delta_offset: float = 0.2) -> dict[str, dict] | None
Shared 2-leg vertical selector for shape_family == "vertical" - covers debit (bull_call_spread/bear_put_spread) AND credit (bear_call_spread/bull_put_spread) verticals identically: the ANCHOR leg (spec's is_anchor=True role) is picked by nearest-|delta| exactly like the existing single-leg selector; the OUTER leg is the nearest-|delta - short_leg_delta_offset| match among the SAME expiry's rows on the further-OTM side (_outer_strike_predicate). Which role ends up "long" vs "short" is irrelevant to this SELECTION geometry - that's already fixed by the registry (spec.legs[*].side) - so the same function is correct for both debit and credit shapes, closing the debit/credit leg-role-inversion bug without needing 2 separate selection algorithms.
select_vertical_spread_legs ¶
select_vertical_spread_legs(available_chain: list[dict], target_delta: float, right: str, short_leg_delta_offset: float) -> tuple[dict, dict] | None
Picks two same-expiry, same-right chain rows for a vertical spread: the long leg via the EXACT same nearest-|delta| selection select_single_leg_contract() already uses (no new selection logic duplicated for it), and the short leg as the nearest-|delta| match to (target_delta - short_leg_delta_offset) among rows of the SAME expiry as the long leg, filtered to the side that caps risk (strike > long strike for a call vertical, strike < long strike for a put vertical - enforced explicitly by filtering on strike, not inferred from delta ordering, since assuming monotonic real-market delta-vs-strike ordering shouldn't be load-bearing here).
Returns None when: no usable long leg (same degrade-to-absent condition as select_single_leg_contract()), or no candidate short leg exists at the long leg's expiry on the risk-capping side - a chain with only one usable strike degrades to "no spread", never a crash.
short_reversal ¶
short_reversal(known: DataFrame, window: int = 7) -> pd.DataFrame
Minus the trailing return: last week's losers rank higher.
should_exit_non_selected_book_symbol ¶
should_exit_non_selected_book_symbol(book_is_active: bool, portfolio_book_enabled: bool, is_currently_invested: bool) -> bool
V5.2.4 (development/Problems.md #91) - whether main.py's Pass 2 should force-close a currently-invested, book-managed position because the book engaged this bar and didn't select it (the "rotation exit").
Returns False whenever book_is_active is False (this bar's
book_allocations came back empty) - matching build_rank_based_book()'s
own documented contract just above: a disengaged book
(min_rank_confidence_spread gate failure, or - pre-V5.2.4 - a thin/
off-session-tick artifact) is "byte-identical to this module not
existing at all", never a liquidation trigger. Before this function
existed, main.py's own Pass 2 violated that documented contract -
force-selling EVERY currently-invested book symbol whenever
book_allocations didn't contain it, without distinguishing "the whole
book disengaged today" from "this specific symbol got rotated out of a
REAL, active book" (the only case that should ever force an exit).
Callers pass book_is_active=bool(book_allocations) - symbol_key not
in book_allocations (i.e. "this symbol specifically wasn't selected")
is the caller's own responsibility to check separately, same as before;
this function only answers "is a force-exit ever appropriate given the
book's overall state this bar", not "for this particular symbol".
should_hold_owned_position_on_veto ¶
should_hold_owned_position_on_veto(enabled: bool, is_book_member: bool, is_book_owned: bool, is_currently_invested: bool, book_is_active: bool) -> bool
V5.5.0 (Problems.md #133) - while the book is VETOED (empty allocations:
spread / rolling-IC gate), a position the book opened must be HELD, not
handed to the legacy probability signal. Live never liquidates on a veto
(should_exit_non_selected_book_symbol needs an active book) and the
offline as-live simulator holds through one (hold_positions_on_veto),
but main.py let the legacy signal manage those positions: its next
"sell" closed them, so book positions lasted ~6 days instead of the
designed ~20+ (and shorts were re-sized to legacy weights). Only the
max-holding / trailing-stop backstops and the next ACTIVE book's rotation
exit may close an owned position. Off (False) reproduces pre-V5.5.0.
should_rebalance_this_bar ¶
should_rebalance_this_bar(bar_index: int, rebalance_every_bars: int, has_previous_allocation: bool, is_trading_day_bar: bool = True, phase_offset: int = 0) -> bool
Stage 2 of the rank-pivot roadmap (development/Problems.md#43): the book previously re-ranked and re-formed on EVERY bar, churning positions far faster than rank_20d's actual ~20-trading-day horizon can support (653 orders/~2yr, no edge left after costs). This is a pure, main.py- independent extraction of main.py::on_data()'s rebalance-gating decision so it's unit-testable without a running Lean/QCAlgorithm instance (this module has no Lean import, main.py does).
bar_index is main.py's 1-indexed self.bar_index (incremented to 1 on
the very first on_data() call, before any bar has been processed).
rebalance_every_bars is phase_v2.portfolio_book.rebalance_every_bars
(1 reproduces the previous every-bar-rebalance behavior exactly, since
(n - 1) % 1 == 0 for every n). has_previous_allocation should be
False only on a genuinely fresh run (no cached book yet) - it forces a
rebalance regardless of the modulo so the book is never left empty.
is_trading_day_bar (V5.2.4, development/Problems.md #91) - default
True, fully backward-compatible with every existing caller. False on a
tick main.py has determined isn't the equity-session tick (e.g. an
off-session crypto/forex-only Lean Slice, see main.py's own
is_equity_session_bar) - never rebalances on such a tick, full stop,
regardless of bar_index's modulo or has_previous_allocation. This
exists because main.py's bar_index only advances on equity-session
ticks post-V5.2.4 - without this explicit gate, several consecutive
off-session ticks following a real rebalance-eligible bar would keep
re-evaluating the SAME already-satisfied modulo condition and could
re-trigger a rebalance that already happened.
Returns True on bar_index 1, 1+N, 1+2N, ... (i.e. 0, N, 2N, ... in 0-indexed bar terms) - False on every bar in between, where the caller should reuse its previously cached allocation and simply hold.
phase_offset (V6.2.0, staggered tranches): shifts the cadence by that many bars (0 = the cadence above).
should_record_ic_event ¶
should_record_ic_event(security_type: str, fresh_bar: bool, mode: str) -> bool
Whether this tick's rank event for one symbol enters the rolling-IC buffer: always, except a late-bar symbol re-scored without a fresh bar under "once". Shared by main.py and the offline replica (evaluation.live_parity).
signal_lookbacks ¶
signal_lookbacks(signal: str) -> tuple[int, ...]
The lookbacks a trend signal name stands for (sign_252 is 252; sign_blend the blend's).
signal_to_weights ¶
signal_to_weights(signal: DataFrame, panel: ResearchPanel, *, kind: str, rebalance_every: int, phase_offset: int = 0, settings: WeightSettings | None = None, start: str | None = None, end: str | None = None, listed: Callable[[str, str], bool] | None = None, top_k: int | None = None) -> dict[str, dict[str, float]]
{date: {ticker: weight}} on every rebalance_every-th session from start, counted from phase_offset.
similarity_table ¶
similarity_table(rows: DataFrame, index: Index, columns: Index, *, max_age: int = 130) -> pd.DataFrame
The newest 10-K or 10-Q's similarity to the same form a year earlier (Cohen, Malloy, Nguyen): high means the filing is unchanged. Blank after max_age sessions without a newer one.
sleeve_classes_out_of_main_pool ¶
sleeve_classes_out_of_main_pool(configs: Mapping[str, ClassSleeveConfig], class_enabled: Mapping[str, bool]) -> frozenset[str]
Asset classes that leave the main book's candidate pool: those with an enabled sleeve whose class may trade.
sleeve_signal ¶
sleeve_signal(spec: SleeveSpec, panel: ResearchPanel) -> pd.DataFrame
The sleeve's raw signal, masked where a name lacks enough history for its risk estimate.
slippage_bps_for ¶
slippage_bps_for(asset_class: str, config: TargetWeightsConfig) -> float
The slippage the simulator charges one asset class, in bps.
strategies_for_volatility_view ¶
strategies_for_volatility_view(volatility_view: str) -> frozenset[str]
Pure lookup: which registered strategy_names express volatility_view
("long_vol" -> long straddle/strangle; "short_vol" -> short straddle/
strangle + iron condor/butterfly, both defined- and unbounded-risk
variants included so risk_tier_preference below can choose between
them; "neutral" -> empty, no strategy actively expresses a neutral
view here). Iron condor/butterfly are "short_vol" regardless of the
long/short spelling of their own name (short_iron_condor is itself
already the credit/short-vol side; there is no long_iron_condor
concept in this shape family).
strategy_reserve ¶
strategy_reserve(*, rebalance_next: bool, book_gross_cap: float, max_short_exposure: float, formed_long: float, formed_short: float, book_long: float, book_short: float, other_budget_remaining: float, neutral_budget: float = 0.0) -> dict[str, Any]
Gross the strategy may add by the next session, by side, and the margin it would hold. On a rebalance attempt a
whole new book can be ordered (long up to half the cap, short up to half the cap and max_short_exposure) while the
current one is still held, since sells free nothing until they fill; otherwise only the formed book's unfilled part.
other_budget_remaining (the legacy signal) is counted as long at leverage 1, the worst case for both limits;
neutral_budget (the class sleeves, dollar-neutral by construction: build_class_sleeve_weights) as half long and
half short at equity leverage.
sweep_order_needed ¶
sweep_order_needed(current_weight: float, target_weight: float, min_trade_weight: float) -> bool
Trade when the gap is at least min_trade_weight, or whenever the sweep is above its target (a cut for the
limits is never deferred).
sweep_reserve ¶
sweep_reserve(config: TargetWeightsConfig) -> dict[str, Any]
What the T-bill sweep leaves free for the next session's orders on this path: a fixed share of gross, since the next row of the file must not be read before its date.
term_credit_timing ¶
term_credit_timing(fred_table: DataFrame, known: DataFrame, *, ticker: str = 'SPY', window: int = 252) -> pd.DataFrame
1.0 on ticker while the term spread (10 years less 3 months) is positive and the Baa credit spread is at or below its own trailing median, else 0.0. NaN when a rate is missing.
time_series_momentum ¶
time_series_momentum(panel: ResearchPanel, spec: SleeveSpec) -> pd.DataFrame
Direction of the trailing return, averaged over the lookbacks.
trailing_drawdown ¶
trailing_drawdown(pnl_increments: Sequence[float], lookback_bars: int) -> float
The current drawdown at the latest bar (a positive number, same unit as the increments): the cumulative P&L's
running peak over the last lookback_bars increments minus where it ends, not the largest fall inside the window
(formal/AetherFormal/SleeveGuard.lean). Non-finite increments count as 0. A non-positive
lookback reads no window (V6.4.3: xs[-0:] is the whole history).
tranche_book_params ¶
tranche_book_params(top_n: int, bottom_n: int, gross_exposure_cap: float, tranches: int, max_active_positions: int | None = None) -> dict[str, Any]
One tranche's share of the book: ceil(top_n / K) longs and ceil(bottom_n / K) shorts (a side with 0 names stays 0), gross cap / K and, when given, ceil(position cap / K) positions. K = 1 returns the book's own values.
tranche_offsets ¶
tranche_offsets(rebalance_every_bars: int, tranches: int) -> list[int]
V6.2.0 staggered book: the phase offset of each of tranches sub-books, spread evenly over one cadence
(20 bars, 2 tranches -> [0, 10]). One tranche -> [0], the single book.
trend_signal ¶
trend_signal(closes: Sequence[float], signal: str) -> float | None
sign_N: the sign of the trailing N-session log return (-1, 0, +1); sign_blend: the mean of sign_63/126/252. None when the history is shorter than the longest lookback + 1 or holds a non-finite / non-positive close.
trend_sleeve_weights ¶
trend_sleeve_weights(closes_by_symbol: Mapping[str, Sequence[float]], config: TrendSleeveConfig, *, annualization: float = 252.0) -> dict[str, float]
Signed target weight (fraction of NAV) per symbol of config.symbols with a non-zero signal and a volatility:
w = s * (target_vol / sqrt(N_active)) / sigma, each |w| capped at max_weight_per_name, then all scaled down so that
sum |w| <= gross_cap. {} when disabled or nothing qualifies.
trend_votes ¶
trend_votes(known: DataFrame, lookbacks: tuple[int, ...] = (20, 60, 120)) -> pd.DataFrame
The installed rule's votes: the share of lookbacks over which the trailing return is positive. NaN until the longest has a price.
turn_of_month ¶
turn_of_month(panel: ResearchPanel, spec: SleeveSpec) -> pd.DataFrame
Hold SPY over the month's last session and the next three. A decision is filled at the next open, so the holding days are decided one session earlier. Calendar arithmetic only (weekdays, no holiday table: never reads the future).
update_book_owned_symbols ¶
update_book_owned_symbols(owned: set[str], symbol_key: str, is_book_member: bool, is_currently_invested: bool) -> bool
V5.5.0 (Problems.md #128) - which held positions the BOOK opened, as
opposed to the legacy-signal sleeve. Mutates owned in place and returns
whether symbol_key is book-owned after the update: invested AND a member
this bar adds it; a flat symbol is dropped; an invested non-member keeps
whatever status it already had (that is exactly the rotated-out case).
update_missing_bars ¶
update_missing_bars(missing: Mapping[str, int], held_units: Mapping[str, float], has_bar: Mapping[str, bool]) -> dict[str, int]
Consecutive bars each held name has gone without one. A name not held, or with a bar, starts again from zero.
vix_term_structure ¶
vix_term_structure(panel: ResearchPanel, spec: SleeveSpec) -> pd.DataFrame
Long SPY while VIX is below VIX3M. threshold gives an on/off rule; without it, exposure grows with the gap.
volume_trend ¶
volume_trend(quote_volume: DataFrame, short: int = 7, long: int = 28) -> pd.DataFrame
Log of the short mean volume over the long mean: rising attention ranks higher.
weights_at_tick ¶
weights_at_tick(weights: Mapping[str, Mapping[str, float]], tick_date: str) -> dict[str, float] | None
The decision taken at this date's equity close; None when the file has no row for it (hold what is held).
weights_sidecar ¶
weights_sidecar(path: Path | str) -> Path
<weights>.sha256: what a live run checks each reload against. The file grows daily, so no config can pin its hash.
wide_tables ¶
wide_tables(features: Mapping[str, DataFrame], member: DataFrame) -> dict[str, pd.DataFrame]
Every w-trial's table that its features allow. A feature all NaN (no data for this window) leaves its trial with an empty book, which the minimum-names check reports rather than reading as no skill.
write_weight_file ¶
write_weight_file(weights_by_date: Mapping[str, Mapping[str, float]], path: Path | str) -> str
Writes the file (LF, rows sorted, so one set of weights has one hash) and returns its sha256. A date with no
positions gets a __FLAT__ row with weight 0: without it, "go flat" would read as "no decision today".
write_weights_sidecar ¶
write_weights_sidecar(path: Path | str) -> str
Records the file's current hash, atomically, so a reload never reads a half-written one.