Polar BackTest
A lightweight, high-performance backtesting library for trading strategy development and optimization. Built on Polars for fast vectorized data processing with an event-driven execution loop for flexible strategy logic.
Features
- Hybrid architecture — vectorized preprocessing (Polars) + event-driven execution loop
- 25+ built-in indicators — SMA, EMA, RSI, MACD, Bollinger Bands, ATR, SuperTrend, ADX, and more
- Complete order system — market, limit, stop, stop-limit, bracket orders, day/GTC orders
- Risk management — stop-loss, take-profit, trailing stops, position size limits, drawdown stops
- Short selling — negative positions, borrow costs, position reversals
- Margin & leverage — configurable leverage, margin tracking, margin calls
- Commission models — percentage, fixed, maker/taker, volume-tiered, custom
- Position sizing — fixed, percent, fixed-risk, Kelly, volatility-based
- Weight-based backtesting — declarative portfolio allocation with
backtest_weights()orWeightStrategy, rebalance scheduling, stop-loss/take-profit, next-actions output - Multi-asset — pass a dict of DataFrames or a long-format DataFrame with
symbolcolumn; all OHLC data preserved - Dynamic universe —
UniverseProviderprotocol filters tradeable symbols per bar; built-inAgeFilter,VolumeFilter,TopN,CompositeFilter; token lifecycle tracking viactx.first_seen_bar/ctx.bar_count - Parallel optimization — grid search, multi-objective Pareto, Bayesian optimization
- Walk-forward analysis — rolling and anchored train/test splits
- Advanced analysis — Monte Carlo simulation, look-ahead bias detection, permutation testing
- Visualization — interactive Plotly charts (price, equity, drawdown, trade markers, heatmaps)
- AMM-aware execution — pluggable
SlippageModelwithFlatSlippageandAMMSlippage(constant-product formula) - Exchange rate support —
Engine(exchange_rate=...)converts equity curve to USD; reports dual quote/USD metrics - DeFi indicators —
buy_sell_ratio,net_flow,trade_intensity,pump_detector,rug_pull_detector, AMMprice_impact_estimate,liquidity_ratio, and more - Trade data pipeline — validate and aggregate raw DEX/AMM trades into OHLCV bars (time-based or trade-count), with buy/sell volume split, VWAP, and optional USD conversion
- Data utilities — validation, cleaning, OHLCV resampling
- Optional TA-Lib integration — wrap any TA-Lib function into Polars expressions
Installation
pip install polarbt
Or with optional extras:
pip install polarbt[plotting] # Plotly charts
pip install polarbt[talib] # TA-Lib integration
Install from source:
git clone git@github.com:nikkisora/PolarBT.git
cd PolarBT
pip install -e .
Quick Start
import polars as pl
import yfinance as yf
from polarbt import Engine, Strategy
from polarbt import indicators as ind
from polarbt.core import BacktestContext
from polarbt.plotting import plot_backtest
class SMACross(Strategy):
def preprocess(self, df: pl.DataFrame) -> pl.DataFrame:
return df.with_columns(
ind.sma("close", 10).alias("sma_fast"),
ind.sma("close", 30).alias("sma_slow"),
).with_columns(
ind.crossover("sma_fast", "sma_slow").alias("buy"),
ind.crossunder("sma_fast", "sma_slow").alias("sell"),
)
def next(self, ctx: BacktestContext) -> None:
if ctx.row.get("buy"):
ctx.portfolio.order_target_percent("asset", 1.0)
elif ctx.row.get("sell"):
ctx.portfolio.close_position("asset")
# Download data from Yahoo Finance
ticker = yf.download("AAPL", start="2016-01-01", end="2026-01-01", auto_adjust=True)
ticker = ticker.droplevel("Ticker", axis=1).reset_index()
data = pl.from_pandas(ticker)
# Run backtest
engine = Engine(SMACross(), data, commission=.005, initial_cash=100_000)
results = engine.run()
print(results)
# Interactive chart saved to HTML
fig = plot_backtest(engine, title="SMA Crossover — AAPL", indicators=["sma_fast", "sma_slow"])
fig.write_html("backtest.html")
Equity Final [$] 366,236.83
Equity Peak [$] 433,930.72
Return [%] 266.24
Buy & Hold Return [%] 1044.52
Return (Ann.) [%] 14.08
CAGR [%] 14.08
Volatility (Ann.) [%] 19.78
Sharpe Ratio 0.76
Sortino Ratio 0.92
Calmar Ratio 0.44
Max. Drawdown [%] -32.16
Avg. Drawdown Duration [bars] 38
Max. Drawdown Duration [bars] 730
# Trades 42
Win Rate [%] 47.62
Best Trade [%] 57.11
Worst Trade [%] -13.43
Avg. Trade [%] 3.94
Max. Trade Duration [bars] 128
Avg. Trade Duration [bars] 39
Avg. Trade MDD [%] -8.78
Profit Factor 1.79
Expectancy [$] 6338.97
SQN 1.27
Kelly Criterion 0.2098
Weight-Based Backtesting
For portfolio allocation strategies, skip the event loop entirely — just supply target weights per (date, symbol):
import polars as pl
from polarbt import backtest_weights
# data: long-format DataFrame with columns date, symbol, close, weight
result = backtest_weights(
data,
resample="M", # rebalance monthly
resample_offset="2d", # delay 2 trading days after month boundary
fee_ratio=0.001,
stop_loss=0.10, # 10% per-position stop-loss
position_limit=0.5, # max 50% in any single name
initial_capital=100_000,
)
print(result.metrics) # standard BacktestMetrics
print(result.trades.head()) # per-trade log
print(result.next_actions) # forward-looking rebalance actions
Trade Data & DeFi Backtesting
PolarBT can ingest raw DEX/AMM trade data (e.g. Pump.fun on Solana), aggregate it into OHLCV bars, apply DeFi-specific indicators, and backtest with AMM-aware slippage — all in a single pipeline.
import polars as pl
from polarbt import Engine, Strategy, indicators_defi as defi
from polarbt.core import BacktestContext
from polarbt.data.trades import aggregate_trades, validate_trades
from polarbt.slippage import AMMSlippage
from polarbt.universe import AgeFilter, CompositeFilter, VolumeFilter
# 1. Load and validate raw trades
trades = pl.read_parquet("trades.parquet")
assert validate_trades(trades.sort("symbol", "timestamp")).valid
# 2. Aggregate to 5-minute OHLCV bars
bars = aggregate_trades(trades.sort("symbol", "timestamp"), "5m", min_trades=3)
# 3. Define a strategy using DeFi indicators
class PumpMomentum(Strategy):
def preprocess(self, df: pl.DataFrame) -> pl.DataFrame:
return df.with_columns(
defi.buy_sell_ratio().over("symbol").alias("bs_ratio"),
defi.trade_intensity(window=10).over("symbol").alias("intensity"),
)
def next(self, ctx: BacktestContext) -> None:
for sym in ctx.symbols:
row = ctx.row(sym)
if row.get("bs_ratio", 0) > 0.7 and row.get("intensity", 0) > 2.0:
ctx.portfolio.order_target_percent(sym, 0.1)
elif row.get("bs_ratio", 0) < 0.3:
ctx.portfolio.close_position(sym)
# 4. Run with AMM slippage and universe filtering
engine = Engine(
PumpMomentum(),
bars,
initial_cash=100.0, # in SOL
commission=0.01,
slippage=AMMSlippage(), # uses pool_reserve_last from bar data
universe_provider=CompositeFilter(AgeFilter(min_bars=5), VolumeFilter(min_volume=1.0)),
)
results = engine.run()
print(results)
Examples
| Example | Description |
|---|---|
example.py |
Basic SMA crossover |
example_sma_crossover_stoploss.py |
SMA crossover with ATR stop-loss and trailing stop |
example_rsi_bracket_orders.py |
RSI mean reversion with bracket orders |
example_momentum_rotation.py |
Multi-asset momentum rotation |
example_ml_strategy.py |
ML model integration |
example_walk_forward.py |
Walk-forward analysis workflow |
example_advanced_analysis.py |
Full workflow: optimization, heatmaps, Monte Carlo, permutation test |
example_limit_orders.py |
Limit orders and stop-loss |
example_trade_analysis.py |
Trade-level analysis |
example_plotting.py |
Interactive chart generation |
example_commission.py |
Commission model comparison |
example_multi_asset.py |
Multi-asset dict input |
example_weight_backtest.py |
Weight-based portfolio backtest |
example_defi_trades.py |
DeFi trade data pipeline with AMM slippage |
Documentation
License
Metadata
Release files for polarbt 0.2.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| polarbt-0.2.1.tar.gz | 187.1 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| polarbt-0.2.1-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 296.5 kB
Release files / polarbt-0.2.1.tar.gz
| Download URL | polarbt-0.2.1.tar.gz |
|---|---|
| Size | 187.1 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
a923d911d4b3f1f8edfb72987eec62ba42e6186ef0661ff6f9ba99fcc1a7e2b8
|
|
BLAKE2b-256 checksum How to use checksums |
c666e55b9ee7701544b77f3fee2a69a37512e90f1bdedc0e58631a9628e03ec6
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.7
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Mar 14, 2026.
Transparency logRelease files / polarbt-0.2.1-py3-none-any.whl
| Download URL | polarbt-0.2.1-py3-none-any.whl |
|---|---|
| Size | 109.4 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
1e45e3cf1d37fa28dcf7ed41357b83dee74950c15360a426d22b5ef119732624
|
|
BLAKE2b-256 checksum How to use checksums |
70392e412f00ceafd443791887db300f53bcdb16e5b0833b24de53c6b08026fb
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.7
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Mar 14, 2026.
Transparency log