| name | polars-backtest |
| description | Help users backtest trading strategies with polars-backtest library. Use when user asks about backtesting, portfolio simulation, trading strategy analysis, or working with polars-backtest. |
Polars Backtest Usage Skill
Help users use polars-backtest to backtest their trading strategies efficiently.
Installation
pip install polars-backtest
# or
uv add polars-backtest
Data Format
Long format: one row per (date, symbol) pair.
import polars as pl
import polars_backtest as pl_bt
df = pl.DataFrame({
"date": ["2024-01-01", "2024-01-01", "2024-01-02", "2024-01-02"],
"symbol": ["2330", "2317", "2330", "2317"],
"close": [100.0, 50.0, 102.0, 51.0],
"weight": [0.6, 0.4, 0.6, 0.4],
})
Basic Usage
# DataFrame namespace
result = df.bt.backtest(trade_at_price="close", position="weight")
# Function API
result = pl_bt.backtest(df, trade_at_price="close", position="weight")
# With report (includes trades, stats)
report = df.bt.backtest_with_report(position="weight", resample="M")
Complete Parameter Reference
Column Mapping Parameters
| Parameter | Default | Type | Description |
|---|---|---|---|
trade_at_price |
"close" |
str | Expr | Price column for trade execution. Use adjusted close for accurate returns. |
position |
"weight" |
str | Expr | Position weight column. Can be float weights or boolean signals (True=buy). Null values are filled with 0. |
date |
"date" |
str | Expr | Date column. Must be sortable. |
symbol |
"symbol" |
str | Expr | Stock symbol/ticker column. |
open |
"open" |
str | Expr | Open price column. Required when touched_exit=True. |
high |
"high" |
str | Expr | High price column. Required when touched_exit=True. |
low |
"low" |
str | Expr | Low price column. Required when touched_exit=True. |
factor |
"factor" |
str | Adjustment factor column. Used to calculate raw price: raw_price = adj_price / factor. If column doesn't exist, defaults to 1.0. Useful for dividend/split adjusted prices. |
Rebalancing Parameters
| Parameter | Default | Type | Description |
|---|---|---|---|
resample |
"D" |
str | None | Rebalance frequency. Options: "D" (daily), "W" (weekly), "W-FRI" (weekly on Friday), "M" (monthly), "Q" (quarterly), "Y" (yearly), None (only when position changes). |
resample_offset |
None |
str | None | Delay rebalance execution. Examples: "1d" (1 day delay), "2d", "1W". Useful for simulating delayed signal execution. |
Cost Parameters
| Parameter | Default | Type | Description |
|---|---|---|---|
fee_ratio |
0.001425 |
float | Transaction fee rate (both buy and sell). Taiwan stock default is 0.1425%. |
tax_ratio |
0.003 |
float | Transaction tax rate (sell only). Taiwan stock default is 0.3%. |
Risk Management Parameters
| Parameter | Default | Type | Description |
|---|---|---|---|
stop_loss |
1.0 |
float | Stop loss threshold. Exit when loss reaches this ratio. 1.0 = disabled (100% loss). Example: -0.1 exits at 10% loss. |
take_profit |
inf |
float | Take profit threshold. Exit when profit reaches this ratio. inf = disabled. Example: 0.2 exits at 20% profit. |
trail_stop |
inf |
float | Trailing stop threshold. Exit when price drops this much from peak. inf = disabled. Example: 0.05 exits when 5% below peak. |
position_limit |
1.0 |
float | Maximum weight per single stock. 1.0 = no limit. Example: 0.1 caps each stock at 10% of portfolio. |
touched_exit |
False |
bool | Use intraday OHLC for stop detection. When True, checks if stop/take profit was touched during the day using high/low prices, not just close. Requires open/high/low columns. |
stop_trading_next_period |
True |
bool | When stop is triggered, skip trading in the next period. Prevents immediate re-entry after stop. |
Calculation Mode Parameters
| Parameter | Default | Description |
|---|---|---|
finlab_mode |
False (backtest) / True (backtest_with_report) |
Use Finlab-compatible calculation. When True, boolean signals are converted to equal weights. Affects weight normalization behavior. |
retain_cost_when_rebalance |
False |
When rebalancing, retain the cost basis instead of resetting. Affects return calculation for partially sold positions. |
Benchmark Parameters (backtest_with_report only)
| Parameter | Default | Type | Description |
|---|---|---|---|
benchmark |
None |
str | DataFrame | None | Benchmark for alpha/beta calculation. str: symbol value in your data (e.g., "0050"), uses that symbol's price. DataFrame: must have date and creturn columns (cumulative return starting at 1.0). |
Liquidity Metrics Parameters (backtest_with_report only)
| Parameter | Default | Type | Description |
|---|---|---|---|
limit_up |
"limit_up" |
str | Column name for limit-up price. Used to calculate buyHigh metric (ratio of entries at limit-up). |
limit_down |
"limit_down" |
str | Column name for limit-down price. Used to calculate sellLow metric (ratio of exits at limit-down). |
trading_value |
"trading_value" |
str | Column for trading value (e.g., close * volume). Used to calculate capacity metric. |
BacktestReport Object
report = df.bt.backtest_with_report(position="weight")
# Properties
report.creturn # DataFrame with date, creturn columns
report.trades # DataFrame with trade records (entry/exit dates, prices, returns, MAE/MFE)
report.stats # Statistics DataFrame (shortcut for get_stats())
report.fee_ratio # Fee ratio used
report.tax_ratio # Tax ratio used
report.stop_loss # Stop loss threshold (None if disabled)
report.take_profit # Take profit threshold (None if disabled)
report.trail_stop # Trail stop threshold (None if disabled)
report.trade_at # Trade timing (e.g., 'close')
report.resample # Resample frequency
report.benchmark # Benchmark DataFrame (can be set after creation)
# Set benchmark after creation
report.benchmark = benchmark_df
BacktestReport Methods
get_stats(riskfree_rate=0.02)
Get basic statistics as single-row DataFrame.
report.get_stats() # or report.stats
Columns: start, end, rf, total_return, cagr, max_drawdown, avg_drawdown, daily_mean, daily_vol, daily_sharpe, daily_sortino, best_day, worst_day, calmar, win_ratio
get_monthly_stats(riskfree_rate=0.02)
Get monthly statistics.
report.get_monthly_stats()
Columns: monthly_mean, monthly_vol, monthly_sharpe, monthly_sortino, best_month, worst_month
get_return_table()
Get monthly return table pivoted by year x month.
report.get_return_table()
Returns DataFrame with year as rows and months (1-12) as columns.
get_metrics(sections=None, riskfree_rate=0.02)
Get structured metrics as single-row DataFrame.
metrics = report.get_metrics() # All sections
metrics = report.get_metrics(sections=["profitability", "risk"])
current_trades()
Get active trades (positions without exit or exiting on last date).
report.current_trades()
actions()
Get trade actions for current positions.
report.actions()
# Returns: stock_id, action ('enter', 'exit', 'hold')
is_stop_triggered()
Check if any trade was triggered by stop loss or take profit.
if report.is_stop_triggered():
print("Stop was triggered")
daily_creturn()
Get daily resampled cumulative return DataFrame.
report.daily_creturn()
get_metrics Sections
| Section | Metrics | Description |
|---|---|---|
| backtest | startDate, endDate, feeRatio, taxRatio, freq, tradeAt, stopLoss, takeProfit, trailStop |
Backtest configuration |
| profitability | annualReturn, avgNStock, maxNStock, alpha, beta |
Returns and benchmark comparison |
| risk | maxDrawdown, avgDrawdown, avgDrawdownDays, valueAtRisk, cvalueAtRisk |
Risk metrics |
| ratio | sharpeRatio, sortinoRatio, calmarRatio, volatility, profitFactor, tailRatio |
Risk-adjusted ratios |
| winrate | winRate, expectancy, mae, mfe, m12WinRate |
Win rate and trade analysis |
| liquidity | buyHigh, sellLow, capacity |
Liquidity metrics (requires columns) |
Note: alpha, beta, m12WinRate require benchmark to be set.
Statistics Expressions
Use these expressions for custom calculations:
from polars_backtest import daily_returns, cumulative_returns, sharpe_ratio, max_drawdown
df.with_columns(
ret=daily_returns("close"),
creturn=cumulative_returns("ret"),
)
df.select(
sharpe=sharpe_ratio("ret"),
mdd=max_drawdown("creturn"),
)
Usage Examples
Momentum Strategy with Monthly Rebalancing
df = df.with_columns(
pl.when(pl.col("close") >= pl.col("close").rolling_max(60).over("symbol"))
.then(1.0)
.otherwise(0.0)
.alias("weight")
)
report = df.bt.backtest_with_report(position="weight", resample="M")
With Risk Management
report = df.bt.backtest_with_report(
position="weight",
stop_loss=-0.1, # -10% stop loss
take_profit=0.2, # +20% take profit
trail_stop=0.05, # 5% trailing stop
touched_exit=True, # Use intraday OHLC for detection
)
With Benchmark Comparison
# Using a symbol in your data as benchmark
report = df.bt.backtest_with_report(
position="weight",
benchmark="0050", # ETF ticker
)
# Or set after creation
report.benchmark = benchmark_df
# Get metrics with alpha, beta, m12WinRate
metrics = report.get_metrics(sections=["profitability", "winrate"])
With Factor Column (Adjusted Prices)
# When using adjusted prices, factor converts back to raw price
# raw_price = adj_close / factor
df = df.with_columns(
(pl.col("close_raw") / pl.col("close_adj")).alias("factor")
)
report = df.bt.backtest_with_report(
trade_at_price="close_adj",
factor="factor",
)
With Liquidity Metrics
df = df.with_columns([
pl.col("limit_up_price").alias("limit_up"),
pl.col("limit_down_price").alias("limit_down"),
(pl.col("close") * pl.col("volume")).alias("trading_value"),
])
report = df.bt.backtest_with_report(position="weight")
metrics = report.get_metrics(sections=["liquidity"])
# Returns: buyHigh, sellLow, capacity
Using Polars Expressions
# Pass expressions directly instead of column names
result = df.bt.backtest(
trade_at_price=pl.col("adj_close"),
position=pl.col("signal").cast(pl.Float64),
resample="M",
)
Delayed Rebalancing
# Rebalance monthly, but execute 2 days after signal
report = df.bt.backtest_with_report(
position="weight",
resample="M",
resample_offset="2d",
)
Analyzing Current Positions
report = df.bt.backtest_with_report(position="weight")
# Get current active trades
current = report.current_trades()
# Get recommended actions
actions = report.actions() # enter/exit/hold per stock
Resample Options
| Value | Description |
|---|---|
None |
Only rebalance when position changes |
'D' |
Daily |
'W' |
Weekly (Sunday) |
'W-FRI' |
Weekly (Friday) |
'M' |
Monthly |
'Q' |
Quarterly |
'Y' |
Yearly |
Tips
- Weight normalization: Weights are automatically normalized to sum to 1.0 per date
- T+1 execution: Trades execute at next day's price (realistic simulation)
- Boolean signals: Pass boolean column as position, library converts True to equal weights
- Null handling: Null values in position column are filled with 0.0
- resample=None: Only rebalance when position changes (reduces turnover)
- Position limit: Use
position_limit=0.1to cap each stock at 10%