Skip to content

04 Workflows Backtesting

Łukasz Rafał Czarnacki edited this page Mar 9, 2026 · 2 revisions

Backtesting

This page shows the standard workflow using BacktestEngine.

Step 1: Build a strategy

from trade_lab.indicators import EMA
from trade_lab.strategies import StandardStrategy

strategy = StandardStrategy(
    indicators=[(EMA(period=20), 1.0), (EMA(period=50), -1.0)],
    allow_long=True,
    allow_short=True,
    entry_threshold=0.2,
    exit_threshold=0.05,
)

Step 2: Configure risk, sizing, and the engine

from trade_lab.backtesting import BacktestEngine
from trade_lab.risk_management import FixedSL, FixedTP, SignalStrengthTS
from trade_lab.sizing import RiskBasedPositionSizer

strategy.take_profit = FixedTP(base_points=8.0)
strategy.stop_loss = FixedSL(base_points=5.0)
strategy.trailing_stop = SignalStrengthTS(base_points=6.0, step_points=1.0)
strategy.position_sizer = RiskBasedPositionSizer(max_fraction=0.02, risk_multiplier=2.0)

engine = BacktestEngine(
    strategy=strategy,
    ticker="SPY",
    start="2021-01-01",
    end="2026-01-01",
    initial_capital=100_000,
    leverage=1.0,
    commission=0.001,
    slippage=0.0005,
)

Step 3: Run and inspect outputs

result = engine.run()
print(result.metrics)
print(result.trade_log.tail())

result (BacktestResult) contains:

  • df: input data plus generated features and signal_strength
  • equity_curve: equity per bar
  • trade_log: closed trades with direction, entry/exit fields, pnl, commission, bars_held, and exit_reason
  • metrics: summary statistics

Step 4: Generate HTML report

from trade_lab.backtesting import generate_report

path = generate_report(result, output_path="outputs/backtest_report.html")
print(path)

Report includes:

  • equity curve chart
  • drawdown chart
  • price with entry/exit markers
  • metrics tables with long/short breakdown

Running on preloaded data

Use run_on(df) when data already exists, for example in optimization or Monte Carlo:

result = engine.run_on(df)

Use fetch_data() when you only want the OHLCV DataFrame for another workflow:

df = engine.fetch_data()

Execution model details

  • TP, SL, and trailing-stop exits are checked before threshold-based signal exits.
  • leverage affects cash and PnL accounting.
  • If position_sizer is set, the engine passes signal_strength, equity, price, and ATR-like volatility into it.
  • If position_sizer is not set, the engine falls back to an internal full-equity style size rule.

Common checks

  • No trades: thresholds too strict or strategy never crosses them.
  • NaN values at the beginning: normal for rolling indicators and lagged features.
  • Unrealistic performance: review slippage, commission, leverage, and sizing assumptions.

Clone this wiki locally