Skip to main content

Lightweight backtesting library built on polars

Project description

PolarBT

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() or WeightStrategy, rebalance scheduling, stop-loss/take-profit, next-actions output
  • Multi-asset — pass a dict of DataFrames or a long-format DataFrame with symbol column; all OHLC data preserved
  • Dynamic universeUniverseProvider protocol filters tradeable symbols per bar; built-in AgeFilter, VolumeFilter, TopN, CompositeFilter; token lifecycle tracking via ctx.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 SlippageModel with FlatSlippage and AMMSlippage (constant-product formula)
  • Exchange rate supportEngine(exchange_rate=...) converts equity curve to USD; reports dual quote/USD metrics
  • DeFi indicatorsbuy_sell_ratio, net_flow, trade_intensity, pump_detector, rug_pull_detector, AMM price_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

PolarBT

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

MIT

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

polarbt-0.2.1.tar.gz (187.1 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

polarbt-0.2.1-py3-none-any.whl (109.4 kB view details)

Uploaded Python 3

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

Hashes for polarbt-0.2.1.tar.gz
Algorithm Hash digest
SHA256 a923d911d4b3f1f8edfb72987eec62ba42e6186ef0661ff6f9ba99fcc1a7e2b8
MD5 2ccb4a156829bcae34d3ab2badd9e0de
BLAKE2b-256 c666e55b9ee7701544b77f3fee2a69a37512e90f1bdedc0e58631a9628e03ec6

See more details on using hashes here.

Provenance

The following attestation bundles were made for polarbt-0.2.1.tar.gz:

Publisher: publish.yml on nikkisora/PolarBT

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

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

Hashes for polarbt-0.2.1-py3-none-any.whl
Algorithm Hash digest
SHA256 1e45e3cf1d37fa28dcf7ed41357b83dee74950c15360a426d22b5ef119732624
MD5 22d8060c9802a2678d3517cf900b461d
BLAKE2b-256 70392e412f00ceafd443791887db300f53bcdb16e5b0833b24de53c6b08026fb

See more details on using hashes here.

Provenance

The following attestation bundles were made for polarbt-0.2.1-py3-none-any.whl:

Publisher: publish.yml on nikkisora/PolarBT

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page