bagelquant-bt evaluates research outputs. It does not retrieve data and it does not build factor signals. Inputs are numeric long-form Polars DataFrames.

Install

uv add bagelquant-bt

Weight Backtest

Use kind="weights" when the signal frame already contains portfolio weights. Weights use time, asset_id, and weight; prices use time, asset_id, and price.

import polars as pl

from bagelquant_bt import BacktestConfig, run_backtest

prices = pl.DataFrame(
    {
        "time": ["2024-01-02", "2024-01-03"],
        "asset_id": ["AAA", "AAA"],
        "price": [100.0, 102.0],
    }
)
weights = pl.DataFrame(
    {"time": ["2024-01-02"], "asset_id": ["AAA"], "weight": [1.0]}
)

result = run_backtest(
    weights,
    prices,
    kind="weights",
    config=BacktestConfig(initial_capital=1_000_000),
)

result.summary
result.net_cumulative_returns

Factor Evaluation

Use kind="factor" when the first frame contains cross-sectional factor scores with time, asset_id, and factor columns. The package computes forward returns, information coefficients, quantile returns, and a top-N backtest.

from bagelquant_bt import BacktestConfig, run_backtest

factor = pl.DataFrame(
    {"time": ["2024-01-02"], "asset_id": ["AAA"], "factor": [1.5]}
)

result = run_backtest(
    factor,
    prices,
    kind="factor",
    config=BacktestConfig(
        initial_capital=1_000_000,
        quantiles=5,
        top_n=50,
    ),
)

result.ic_mean
result.spread_returns

Transaction Costs

from bagelquant_bt import BacktestConfig, TransactionCostConfig

config = BacktestConfig(
    initial_capital=1_000_000,
    transaction_cost=TransactionCostConfig(rate=0.00015, min_fee=5.0),
)

Minimum fees require initial_capital so the engine can translate weight turnover into traded notional.