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.
Execute trading strategy backtests using polars-backtest library. Use when users ask about backtesting, portfolio simulation, or trading strategy analysis. Triggers on requests for strategy performance evaluation with position weights and rebalancing schedules.
/plugin marketplace add Yvictor/polars_backtest_extension/plugin install polars-backtest@polars-backtest-marketplaceThis skill inherits all available tools. When active, it can use any tool Claude has access to.
Help users use polars-backtest to backtest their trading strategies efficiently.
pip install polars-backtest
# or
uv add polars-backtest
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],
})
# 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")
| 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. |
| 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. |
| 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%. |
| 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. |
| 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. |
| 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). |
| 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. |
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
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 statistics.
report.get_monthly_stats()
Columns: monthly_mean, monthly_vol, monthly_sharpe, monthly_sortino, best_month, worst_month
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 structured metrics as single-row DataFrame.
metrics = report.get_metrics() # All sections
metrics = report.get_metrics(sections=["profitability", "risk"])
Get active trades (positions without exit or exiting on last date).
report.current_trades()
Get trade actions for current positions.
report.actions()
# Returns: stock_id, action ('enter', 'exit', 'hold')
Check if any trade was triggered by stop loss or take profit.
if report.is_stop_triggered():
print("Stop was triggered")
Get daily resampled cumulative return DataFrame.
report.daily_creturn()
| 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.
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"),
)
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")
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
)
# 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"])
# 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",
)
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
# 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",
)
# Rebalance monthly, but execute 2 days after signal
report = df.bt.backtest_with_report(
position="weight",
resample="M",
resample_offset="2d",
)
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
| Value | Description |
|---|---|
None | Only rebalance when position changes |
'D' | Daily |
'W' | Weekly (Sunday) |
'W-FRI' | Weekly (Friday) |
'M' | Monthly |
'Q' | Quarterly |
'Y' | Yearly |
position_limit=0.1 to cap each stock at 10%Create distinctive, production-grade frontend interfaces with high design quality. Use this skill when the user asks to build web components, pages, or applications. Generates creative, polished code that avoids generic AI aesthetics.
This skill should be used when the user asks to "create a hookify rule", "write a hook rule", "configure hookify", "add a hookify rule", or needs guidance on hookify rule syntax and patterns.
This skill should be used when the user asks to "create an agent", "add an agent", "write a subagent", "agent frontmatter", "when to use description", "agent examples", "agent tools", "agent colors", "autonomous agent", or needs guidance on agent structure, system prompts, triggering conditions, or agent development best practices for Claude Code plugins.