Skip to content

quantex.backtest

Backtest orchestration utilities.

This module exposes
  • BacktestResult – a dataclass aggregating NAV, orders, fills and metrics.
  • BacktestRunner – a convenience wrapper that wires together a Strategy, one or more DataSource objects, an execution simulator and the internal EventBus. End-users typically instantiate BacktestRunner once per test and call run() to obtain a BacktestResult.

BacktestResult dataclass

Contains the results of a backtest.

Attributes:

Name Type Description
nav Series

A pandas Series representing the Net Asset Value (NAV) over time.

orders list[Order]

A list of all orders generated during the backtest.

fills list[Fill]

A list of all fills executed during the backtest.

metrics dict

A dictionary of performance metrics.

Source code in src/quantex/backtest.py
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
@dataclass
class BacktestResult:
    """Contains the results of a backtest.

    Attributes:
        nav: A pandas Series representing the Net Asset Value (NAV) over time.
        orders: A list of all orders generated during the backtest.
        fills: A list of all fills executed during the backtest.
        metrics: A dictionary of performance metrics.
    """

    nav: pd.Series
    orders: list[Order]
    fills: list[Fill]
    metrics: dict

BacktestRunner

User-facing helper that wires Strategy, EventBus, and Simulator.

Source code in src/quantex/backtest.py
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
class BacktestRunner:
    """User-facing helper that wires Strategy, EventBus, and Simulator."""

    def __init__(
        self,
        strategy: Strategy,
        data_sources: Mapping[str, BacktestingDataSource],
        risk_free_rate: float = 0.043,
        periods_per_year: int = 98_280,
    ):
        """Initializes the BacktestRunner.

        Args:
            strategy: The trading strategy to be backtested.
            data_sources: A dictionary of data sources for the backtest.
            risk_free_rate: The risk-free rate to use for the Sharpe ratio.
            periods_per_year: The number of periods per year to use for the Sharpe ratio.
        """
        self.strategy = strategy
        self.data_sources = data_sources
        self.simulator = ImmediateFillSimulator(self.strategy.portfolio)
        self.event_bus = EventBus(strategy, data_sources, self.simulator)
        self.risk_free_rate = risk_free_rate
        self.periods_per_year = periods_per_year

    def run(self) -> BacktestResult:
        """Runs the back-test.

        Returns:
            A `BacktestResult` object containing the results.
        """
        self.event_bus.run()
        nav_series = pd.Series(
            self.event_bus.nav, index=self.event_bus.timestamps, name="NAV"
        )

        metrics: dict = {}

        if not nav_series.empty and nav_series.iloc[0] != 0:
            metrics["total_return"] = nav_series.iloc[-1] / nav_series.iloc[0] - 1

            # Maximum drawdown ---------------------------------------------
            metrics["max_drawdown"] = _max_drawdown(nav_series)

            # Sharpe ratio -------------------------------------------------
            if len(nav_series) > 1:
                metrics["sharpe_ratio"] = _annualised_sharpe(
                    nav_series, self.risk_free_rate, self.periods_per_year
                )

        return BacktestResult(
            nav_series, self.event_bus.orders, self.event_bus.fills, metrics
        )

__init__(strategy, data_sources, risk_free_rate=0.043, periods_per_year=98280)

Initializes the BacktestRunner.

Parameters:

Name Type Description Default
strategy Strategy

The trading strategy to be backtested.

required
data_sources Mapping[str, BacktestingDataSource]

A dictionary of data sources for the backtest.

required
risk_free_rate float

The risk-free rate to use for the Sharpe ratio.

0.043
periods_per_year int

The number of periods per year to use for the Sharpe ratio.

98280
Source code in src/quantex/backtest.py
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
def __init__(
    self,
    strategy: Strategy,
    data_sources: Mapping[str, BacktestingDataSource],
    risk_free_rate: float = 0.043,
    periods_per_year: int = 98_280,
):
    """Initializes the BacktestRunner.

    Args:
        strategy: The trading strategy to be backtested.
        data_sources: A dictionary of data sources for the backtest.
        risk_free_rate: The risk-free rate to use for the Sharpe ratio.
        periods_per_year: The number of periods per year to use for the Sharpe ratio.
    """
    self.strategy = strategy
    self.data_sources = data_sources
    self.simulator = ImmediateFillSimulator(self.strategy.portfolio)
    self.event_bus = EventBus(strategy, data_sources, self.simulator)
    self.risk_free_rate = risk_free_rate
    self.periods_per_year = periods_per_year

run()

Runs the back-test.

Returns:

Type Description
BacktestResult

A BacktestResult object containing the results.

Source code in src/quantex/backtest.py
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
def run(self) -> BacktestResult:
    """Runs the back-test.

    Returns:
        A `BacktestResult` object containing the results.
    """
    self.event_bus.run()
    nav_series = pd.Series(
        self.event_bus.nav, index=self.event_bus.timestamps, name="NAV"
    )

    metrics: dict = {}

    if not nav_series.empty and nav_series.iloc[0] != 0:
        metrics["total_return"] = nav_series.iloc[-1] / nav_series.iloc[0] - 1

        # Maximum drawdown ---------------------------------------------
        metrics["max_drawdown"] = _max_drawdown(nav_series)

        # Sharpe ratio -------------------------------------------------
        if len(nav_series) > 1:
            metrics["sharpe_ratio"] = _annualised_sharpe(
                nav_series, self.risk_free_rate, self.periods_per_year
            )

    return BacktestResult(
        nav_series, self.event_bus.orders, self.event_bus.fills, metrics
    )