Lightweight backtesting library built on polars
Project description
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
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file polarbt-0.2.1.tar.gz.
File metadata
- Download URL: polarbt-0.2.1.tar.gz
- Upload date:
- Size: 187.1 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
a923d911d4b3f1f8edfb72987eec62ba42e6186ef0661ff6f9ba99fcc1a7e2b8
|
|
| MD5 |
2ccb4a156829bcae34d3ab2badd9e0de
|
|
| BLAKE2b-256 |
c666e55b9ee7701544b77f3fee2a69a37512e90f1bdedc0e58631a9628e03ec6
|
Provenance
The following attestation bundles were made for polarbt-0.2.1.tar.gz:
Publisher:
publish.yml on nikkisora/PolarBT
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
polarbt-0.2.1.tar.gz -
Subject digest:
a923d911d4b3f1f8edfb72987eec62ba42e6186ef0661ff6f9ba99fcc1a7e2b8 - Sigstore transparency entry: 1102326168
- Sigstore integration time:
-
Permalink:
nikkisora/PolarBT@c6d89bbb8cb685085e07c053625f5962903fc1b7 -
Branch / Tag:
refs/heads/main - Owner: https://github.com/nikkisora
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@c6d89bbb8cb685085e07c053625f5962903fc1b7 -
Trigger Event:
push
-
Statement type:
File details
Details for the file polarbt-0.2.1-py3-none-any.whl.
File metadata
- Download URL: polarbt-0.2.1-py3-none-any.whl
- Upload date:
- Size: 109.4 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
1e45e3cf1d37fa28dcf7ed41357b83dee74950c15360a426d22b5ef119732624
|
|
| MD5 |
22d8060c9802a2678d3517cf900b461d
|
|
| BLAKE2b-256 |
70392e412f00ceafd443791887db300f53bcdb16e5b0833b24de53c6b08026fb
|
Provenance
The following attestation bundles were made for polarbt-0.2.1-py3-none-any.whl:
Publisher:
publish.yml on nikkisora/PolarBT
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
polarbt-0.2.1-py3-none-any.whl -
Subject digest:
1e45e3cf1d37fa28dcf7ed41357b83dee74950c15360a426d22b5ef119732624 - Sigstore transparency entry: 1102326206
- Sigstore integration time:
-
Permalink:
nikkisora/PolarBT@c6d89bbb8cb685085e07c053625f5962903fc1b7 -
Branch / Tag:
refs/heads/main - Owner: https://github.com/nikkisora
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@c6d89bbb8cb685085e07c053625f5962903fc1b7 -
Trigger Event:
push
-
Statement type: