Skip to content

aether_quant.backtest

Backtests, grouped by the job (V6.6.5): backtest() for any weights on any prices, the project's own rank-book and target-weights simulators, Lean-exact statistics and Monte Carlo.

Backtests, grouped by the job (V6.6.5): backtest() for any weights on any prices, the project's own rank-book and target-weights simulators, Lean-exact statistics and Monte Carlo.

BacktestResult dataclass

Daily series of one path. returns are net of costs, gross_returns before them; costs and turnover are fractions of the previous NAV; weights are the end-of-day holdings.

costs instance-attribute

costs: Series

gross_returns instance-attribute

gross_returns: Series

nav instance-attribute

nav: Series

periods_per_year class-attribute instance-attribute

periods_per_year: int = 252

returns instance-attribute

returns: Series

turnover instance-attribute

turnover: Series

weights instance-attribute

weights: DataFrame

report

report(*, title: str = 'Backtest', risk_free: float = 0.0) -> str

A plain-text summary table.

stats

stats(*, risk_free: float = 0.0) -> dict[str, float]

summary_statistics of the net returns.

to_frame

to_frame() -> pd.DataFrame

The daily series side by side.

MarketPanel dataclass

Bars by calendar date (a NaN where the ticker has no bar that day). sessions are the equity sessions.

asset_class instance-attribute

asset_class: dict[str, str]

closes instance-attribute

closes: ndarray

dates instance-attribute

dates: list[str]

opens instance-attribute

opens: ndarray

sessions class-attribute instance-attribute

sessions: set[str] = field(default_factory=set)

tickers instance-attribute

tickers: list[str]

index

index() -> dict[str, int]

backtest

backtest(weights: DataFrame, prices: DataFrame, *, opens: DataFrame | None = None, cost_model: CostModel | None = None, fill: Literal['next_open', 'close'] = 'next_open', initial_nav: float = 100000.0, rebalance_every: int = 1, periods_per_year: int = 252, adv_usd: Mapping[str, float] | None = None, daily_vol: Mapping[str, float] | None = None, min_weight_change: float = 0.0) -> BacktestResult

Run weights (dates x tickers, a row of all-NaN = no decision that day, NaN cell = 0) over prices (closes).

rebalance_every=k acts on every k-th decision row. With fill="next_open", opens is required; a name without an open that day fills at the previous close. A name without a usable close is never sized (its units are left alone). adv_usd and daily_vol feed a cost model that needs them (square-root impact). min_weight_change is the live path's deadband (portfolio.target_weights.rebalance_deltas): a smaller change is not traded, but a zero target always closes the name.

lean_statistics

lean_statistics(performance: Sequence[float], equity_dates: Iterable[date], *, trading_days_per_year: int, rate_changes: Sequence[tuple[date, float]]) -> dict[str, Any]

Sharpe, Sortino, PSR and their inputs exactly as Lean computes them; non-finite inputs give 0 like Lean's guards.

run_monte_carlo

run_monte_carlo(per_date_net_returns: ArrayLike, *, n_runs: int = 1000, block_size: int = 20, seed: int = 42, method: str = 'block', n_plot_points: int = 500, periods_per_year: float = TRADING_DAYS_PER_YEAR) -> dict[str, Any]

Runs the bootstrap and returns a self-describing result dict:

{"config": {...}, "status": "OK" | "SKIPPED: ", "avg_curve": [...], # downsampled, red line "pct_band": {"p5": [...], "p25": [...], "p75": [...], "p95": [...]}, "runs_curves": np.ndarray (n_runs x points), # downsampled, for PNG "final_return_pct": {...percentiles}, "sharpe": {...}, "max_drawdown": {...}, "prob_negative_return": float, "best_run_total_return": ..., "worst_run_total_return": ...}

Equity curves are compounded from resampled daily returns and start at 1.0. The average curve is the element-wise MEAN over all runs (the red line), NOT the mean of final values.

simulate_rank_book

simulate_rank_book(frame: DataFrame, **kwargs: Any) -> RankBookSimulationResult

Public entry point - see _simulate_rank_book_core()'s docstring for the full algorithm. Discards held_days_by_ticker (only capacity_curve() needs it).

simulate_target_weights

simulate_target_weights(weights_by_date: Mapping[str, Mapping[str, float]], panel: MarketPanel, config: TargetWeightsConfig, *, start: str, end: str, nav: float = 100000.0, sweep_config: dict[str, Any] | None = None, sweep_ticker: str = 'BIL', rf_dates: list[str] | None = None, rf_rates: list[float] | None = None, carry_fn: Callable[[str, str], float] | None = None, max_total_drawdown: float | None = None, max_drawdown_velocity: float | None = None, fee_fn: Callable[[str, float, str, TargetWeightsConfig], float] = default_fee_usd, lot_size: Mapping[str, float] | None = None) -> dict[str, Any]

One path over [start, end]. Returns {"nav", "strategy" (ex-sweep daily return), "strategy_excess" (that, less the risk-free return on the net capital invested), "total" (daily return), "gross", "fees", "financing", "turnover", "fills", "locked_on"}, the series indexed by calendar date.

"strategy" is what Lean reports. Ex-sweep, a long book pays nothing for the cash it ties up, which the sweep would have earned, so a long sleeve's Sharpe is raw and not excess unless it is scored on "strategy_excess". A forex position is self-financed and has no such cost.

A drawdown lock (on the strategy, ex sweep, as main.py reads it) closes everything at the next fills and stops trading.

summary_statistics

summary_statistics(returns: Series, *, periods_per_year: int = 252, risk_free: float = 0.0, turnover: Series | None = None, costs: Series | None = None, nav: Series | None = None) -> dict[str, float]

CAGR, volatility, Sharpe, Sortino, PSR (Lean's formulas, at a constant annual risk_free), max drawdown, Calmar, annual turnover and annual cost drag in bps. Non-finite returns are dropped; an empty series gives zeros.