Interfaces#
Abstract interfaces and protocol classes that define the contracts implemented by backtesters and cost models. Use these as base classes when building custom implementations.
Backtester Interface#
Base backtest interface module.
This module provides the abstract base class for implementing backtesting strategies.
Key components: - BaseBacktesterInterface: Abstract base class for backtesters - BacktestResultDict: TypedDict for backtester output
- class BacktestResultDict#
Bases:
TypedDictTypedDict for backtest result dictionaries.
Defines the structure of the dictionary returned by PyArrowBacktester.run() and related methods.
- class BaseBacktesterInterface(backtest_config: BacktestConfiguration)#
Bases:
objectAbstract base class for implementing backtesting strategies.
This class provides the fundamental structure and common methods for backtesting trading strategies. It handles configuration management and defines the interface for concrete implementations.
All concrete backtester implementations must inherit from this class and implement the abstract run() method.
- Variables:
backtest_config (BacktestConfiguration) – Configuration object containing all backtest parameters. Accessed via the _backtest_config private attribute.
initial_capital (float) – Get the initial capital amount from configuration.
cash_reserve_pct (float) – Get the cash reserve percentage from configuration.
Examples
Creating a custom backtester:
>>> class MyBacktester(BaseBacktesterInterface): ... def __init__(self, config: BacktestConfiguration): ... super().__init__(backtest_config=config) ... self._data = None ... ... def run(self) -> BacktestResultDict: ... # Implementation here ... return {"results": ...}
Using the validation method:
>>> result = BaseBacktesterInterface.validate_cash_position( ... cash=1000.0, ... validation_date=pd.Timestamp("2024-01-15"), ... logger=logger, ... ) >>> result.is_valid True
Notes
This is an abstract base class and cannot be instantiated directly. Subclasses must implement the run() method.
The class uses metaclass=abc.ABCMeta to enforce the abstract method contract at class definition time.
See also
PyArrowBacktesterConcrete PyArrow-based implementation
BacktestConfigurationConfiguration dataclass
- calculate_years(start_date: date | str, end_date: date | str) float#
Calculate duration in years using trading days.
Computes the number of years between two dates based on business days, using the configured trading days per year. This is used for annualizing performance metrics.
- Parameters:
start_date (datetime.date | str) – Start date of the period. Can be datetime.date object or ISO format string (YYYY-MM-DD).
end_date (datetime.date | str) – End date of the period. Can be datetime.date object or ISO format string.
- Returns:
Duration in years calculated as: business_days / trading_days_per_year Returns 1.0 if calculation fails (fallback).
- Return type:
float
Examples
Using date objects:
>>> from datetime import date >>> years = backtester.calculate_years( ... start_date=date(2022, 1, 1), ... end_date=date(2024, 1, 1), ... ) >>> years 2.0 # Approximately 2 years
Using string dates:
>>> years = backtester.calculate_years( ... start_date="2022-01-01", ... end_date="2022-12-31", ... ) >>> years 0.996 # Approximately 1 year
Notes
Uses numpy’s busday_count for accurate business day calculation. The trading_days_per_year default (252) is standard in US finance.
The method returns 1.0 as a fallback if date parsing fails, ensuring calculations don’t fail due to date format issues.
- property cash_reserve_pct: float#
Get the cash reserve percentage.
- Returns:
Percentage of total value to maintain as cash (0.0 to 1.0).
- Return type:
float
Examples
>>> backtester.cash_reserve_pct 0.02
- property initial_capital: float#
Get the initial capital amount.
- Returns:
Starting capital for the backtest in currency units.
- Return type:
float
Examples
>>> backtester.initial_capital 100000.0
- abstractmethod run() BacktestResultDict#
Execute the backtesting strategy.
This abstract method must be implemented by subclasses to define the specific logic for running the backtest. It should contain the main loop that processes market data and makes trading decisions.
- Returns:
Dictionary containing backtest results including: - Register_df: Time series of portfolio values - Total_commissions: Total commissions paid - final_total_portfolio_value: Final portfolio value - Other metrics and DataFrames
- Return type:
- Raises:
NotImplementedError – If not implemented by a subclass.
RuntimeError – If backtest execution fails.
Examples
>>> results = backtester.run() >>> results['final_total_portfolio_value'] 125000.0 >>> results['Register_df'].head()
- static validate_cash_position(cash: float, validation_date: DateLike, logger: Logger) CashValidationResult#
Validate that the cash position is non-negative.
This static method checks if the current cash position is valid (non-negative) and returns a structured result. Logs an error message if the cash position is negative.
- Parameters:
cash (float) – The cash amount to validate. Can be a PyArrow scalar or regular float. PyArrow scalars are converted via .as_py() method.
validation_date (DateLike) – The date associated with this cash position. Used for error reporting and debugging. Can be datetime.date, pd.Timestamp, or ISO format string.
logger (logging.Logger) – Logger instance for recording error messages.
- Returns:
Structured result containing: - is_valid: True if cash >= 0 - cash_amount: The validated amount as float - validation_date: The date provided - error_message: Error description or empty string
- Return type:
CashValidationResult
Examples
Valid cash position:
>>> import logging >>> logger = logging.getLogger('test') >>> result = BaseBacktesterInterface.validate_cash_position( ... cash=1000.0, ... validation_date="2024-01-15", ... logger=logger, ... ) >>> result.is_valid True >>> result.error_message ''
Invalid (negative) cash position:
>>> result = BaseBacktesterInterface.validate_cash_position( ... cash=-500.0, ... validation_date="2024-01-15", ... logger=logger, ... ) >>> result.is_valid False >>> 'Cash error' in result.error_message True
Notes
This method uses PyArrow’s compute functions for comparison, allowing it to work with both regular Python floats and PyArrow scalars.
A negative cash position typically indicates: - Insufficient initial capital for the strategy - Commission costs higher than expected - Problems with the trading logic - Strategy too aggressive for available capital
- class PerformanceStatsDict#
Bases:
TypedDictTypedDict for portfolio/benchmark performance statistics.
All fields are optional (total=False) because not all metrics are computed in every context.
Cost Model Interfaces#
Cost model interfaces module.
This module defines abstract base classes for commission and slippage models used in backtesting. These interfaces establish contracts that all cost model implementations must follow.
The interfaces are designed for the backtesting context where: - Commission models calculate fees based on share count - Slippage models adjust execution prices based on market impact
Implementing custom cost models allows for realistic simulation of different broker fee structures and market conditions.
- class CommissionModelInterface#
Bases:
ABCAbstract interface for commission calculation models.
This interface defines the contract for all commission models used in the backtesting engine. The default signature calculates commission based on share count, matching the original backtester behavior.
All implementations must be stateless with respect to calculation - the same inputs should always produce the same outputs.
Examples
Creating a custom commission model:
>>> class MyCommission(CommissionModelInterface): ... def __init__(self, rate: float): ... self._rate = rate ... ... def calculate(self, shares: float) -> float: ... return abs(shares) * self._rate
Using the model:
>>> model = MyCommission(rate=0.01) >>> model.calculate(shares=100) 1.0
Notes
Implementations should always use the absolute value of shares since commission is charged regardless of buy/sell direction. The calculate() method should never raise exceptions for valid numeric inputs.
See also
PerShareCommissionDefault per-share rate model
ZeroCommissionNo-fee model for testing
PercentageCommissionNotional-based percentage model
- abstractmethod calculate(shares: float) float#
Calculate commission for a trade.
This method computes the commission amount for a trade based on the number of shares. Implementations should use the absolute value of shares to ensure consistent results for buys and sells.
- Parameters:
shares (float) – Number of shares being traded. Can be positive (buy) or negative (sell). The absolute value should be used for calculation.
- Returns:
Commission amount in currency units. Must be non-negative (>= 0.0).
- Return type:
float
Examples
>>> model.calculate(shares=100) 1.0 >>> model.calculate(shares=-100) # Sell order 1.0 >>> model.calculate(shares=0) 0.0
- class SlippageModelInterface#
Bases:
ABCAbstract interface for price slippage models.
Slippage represents the difference between expected and actual execution prices due to market impact, liquidity constraints, and other trading frictions.
All slippage models must work against the trader: - Buy orders execute at prices >= original - Sell orders execute at prices <= original
Examples
Creating a custom slippage model:
>>> class MySlippage(SlippageModelInterface): ... def __init__(self, basis_points: float): ... self._bp = basis_points / 10000 ... ... def apply(self, price: float, shares: float, is_buy: bool) -> float: ... if is_buy: ... return price * (1 + self._bp) ... return price * (1 - self._bp)
Using the model:
>>> model = MySlippage(basis_points=10) >>> model.apply(price=100.0, shares=1000, is_buy=True) 100.1 # 10bp higher for buy >>> model.apply(price=100.0, shares=1000, is_buy=False) 99.9 # 10bp lower for sell
Notes
Slippage should always work against the trader to model realistic market conditions. The apply() method should never raise exceptions for valid numeric inputs.
Volume-dependent models can use the shares parameter to scale slippage with order size (larger orders have more market impact).
See also
NoSlippageZero slippage for baseline testing
FixedSlippageConstant percentage slippage
VolumeBasedSlippageOrder-size dependent slippage
SquareRootSlippageAcademic market impact model
- abstractmethod apply(price: float, shares: float, is_buy: bool) float#
Apply slippage to an execution price.
Adjusts the quoted price to simulate realistic execution conditions where trades don’t fill at the exact quoted price.
- Parameters:
price (float) – Original quoted price before slippage. Must be positive.
shares (float) – Number of shares in the order. Used by volume-dependent slippage models. The absolute value should be used for calculation.
is_buy (bool) – True for buy orders, False for sell orders. Determines direction of price adjustment: - True: price increases (unfavorable for buyer) - False: price decreases (unfavorable for seller)
- Returns:
Adjusted execution price with slippage applied. For buys: result >= price For sells: result <= price
- Return type:
float
Examples
>>> model.apply(price=100.0, shares=1000, is_buy=True) 100.1 # Price increased for buy >>> model.apply(price=100.0, shares=1000, is_buy=False) 99.9 # Price decreased for sell