pybroker.strategy module
Contains implementation for backtesting trading strategies.
- class BacktestMixin[source]
Bases:
objectMixin implementing backtesting functionality.
- backtest_executions(config: StrategyConfig, executions: set[Execution], before_exec_fn: Callable[[Mapping[str, ExecContext]], None] | None, after_exec_fn: Callable[[Mapping[str, ExecContext]], None] | None, sessions: Mapping[str, MutableMapping], models: Mapping[ModelSymbol, TrainedModel], indicator_data: Mapping[IndicatorSymbol, Series], test_data: DataFrame, portfolio: Portfolio, exit_dates: Mapping[str, datetime64], backtest_settings: BacktestSettings = BacktestSettings(max_long_positions=None, max_short_positions=None, worst_rank_held=None), rotation_sizer: Callable[[RotationContext], None] | None = None, train_only: bool = False, slippage_model: SlippageModel | None = None, enable_fractional_shares: bool = False, round_fill_price: bool = True, warmup: int | None = None, interval_data: IntervalData = IntervalData(compressed={}), history_col_scope: ColumnScope | None = None, test_col_scope: ColumnScope | None = None, run_hyperparams: dict[str, Any] | None = None, pending_order_scope: PendingOrderScope | None = None, master_col_scope: ColumnScope | None = None) dict[str, DataFrame][source]
Backtests a
setofExecutions that implement trading logic.- Parameters:
config –
pybroker.config.StrategyConfig.executions –
Executions to run.sessions –
Mappingof symbols toMappingof custom data that persists for every bar during theExecution.models –
Mappingofpybroker.common.ModelSymbolpairs topybroker.common.TrainedModels.indicator_data –
Mappingofpybroker.common.IndicatorSymbolpairs topandas.Seriesofpybroker.indicator.Indicatorvalues.test_data –
pandas.DataFrameof test data.portfolio –
pybroker.portfolio.Portfolio.exit_dates –
Mappingof symbols to exit dates.train_only – Whether the backtest is run with trading rules or only trains models.
slippage_model –
Optionalpybroker.slippage.SlippageModelapplied to order fills, stop exits, and position exits.enable_fractional_shares – Whether to enable trading fractional shares.
round_fill_price – Whether to round fill prices to the nearest cent.
warmup – Number of bars that need to pass before running the executions.
- Returns:
Dictionary of
pandas.DataFrames containing bar data, indicator data, and model predictions for each symbol whenpybroker.config.StrategyConfig.return_signalsisTrue. Signals contain base-timeframe values only; an interval-bound indicator or model appears only when'base'is included in its binding.
- class BacktestSettings(max_long_positions: int | None = None, max_short_positions: int | None = None, worst_rank_held: int | None = None)[source]
Bases:
object
- class Execution(id: int, symbols: frozenset[str] | Callable[[DataFrame], Sequence[str]], fn: Callable[[ExecContext], None] | None, model_names: frozenset[str], indicator_names: frozenset[str], intervals: frozenset[int | Literal['daily', 'weekly', 'monthly', 'quarterly', 'yearly'] | str] = frozenset({}), hyperparam_names: frozenset[str] = frozenset({}), args: tuple[Any, ...] = (), kwargs: tuple[tuple[str, Any], ...] = ())[source]
Bases:
NamedTupleRepresents an execution of a
Strategy. Holds a reference to aCallablethat implements trading logic.- symbols
Ticker symbols used for execution of
fn.- Type:
frozenset[str] | Callable[[pandas.DataFrame], Sequence[str]]
- fn
Implements trading logic.
- Type:
Callable[[pybroker.context.ExecContext], None] | None
- model_names
Names of
pybroker.model.ModelSources used for execution offn, including the suffixed per-interval names of interval-bound models.
- indicator_names
Names of
pybroker.indicator.Indicators used for execution offn, including the suffixed per-interval names of interval-bound indicators.
- intervals
Compression intervals available to
fnthroughpybroker.context.ExecContext.interval(): the union of theintervalsdeclared onpybroker.strategy.Strategy.add_execution()and the intervals bound to the execution’s models and indicators.
- class Strategy(data_source: DataSource | DataFrame, start_date: str | datetime, end_date: str | datetime, config: StrategyConfig | None = None)[source]
Bases:
BacktestMixin,EvaluateMixin,IndicatorsMixin,ModelsMixin,WalkforwardMixin,OptimizeMixinClass representing a trading strategy to backtest.
- Parameters:
data_source –
pybroker.data.DataSourceorpandas.DataFrameof backtesting data.start_date – Starting date of the data to fetch from
data_source(inclusive).end_date – Ending date of the data to fetch from
data_source(inclusive).config –
Optionalpybroker.config.StrategyConfig.
- add_execution(fn: ~typing.Callable[[~typing.Concatenate[~pybroker.context.ExecContext, ~P]], None] | None, symbols: str | ~typing.Iterable[str] | ~typing.Callable[[~pandas.DataFrame], ~typing.Sequence[str]], models: ~pybroker.model.ModelSource | ~pybroker.model.IntervalBoundModel | ~typing.Iterable[~pybroker.model.ModelSource | ~pybroker.model.IntervalBoundModel] | None = None, indicators: ~pybroker.indicator.Indicator | ~pybroker.indicator.IntervalBoundIndicator | ~typing.Iterable[~pybroker.indicator.Indicator | ~pybroker.indicator.IntervalBoundIndicator] | None = None, hyperparams: ~typing.Iterable[~pybroker.optimize.Hyperparam] | None = None, intervals: int | ~typing.Literal['daily', 'weekly', 'monthly', 'quarterly', 'yearly'] | str | ~typing.Iterable[int | ~typing.Literal['daily', 'weekly', 'monthly', 'quarterly', 'yearly'] | str] | None = None, *args: ~typing.~P, **kwargs: ~typing.~P)[source]
Adds an execution to backtest.
A
TimeframeIntervalpassed tointervalsis one of the following:Every-n-bars (
int): Compress everynbase bars into one bar, wheren > 1. On 1-minute data,5yields 5-bar bins (approximately 5-minute bars).Duration (
str): Fixed time span as digits plus one unit letter —"1m","5m","1h","30s", or"1d".Calendar (
str): Calendar buckets —"daily","weekly","monthly","quarterly", or"yearly". Weeks start on Monday, months on the first of the month, quarters in January, April, July, and October, and years on January 1.
For example, to read 5-bar bins and 1-hour duration bars on a 1-minute feed, and additionally compute
smaon weekly bars:strategy.add_execution( fn, "SPY", indicators=sma.intervals("weekly"), intervals=[5, "1h"], ) strategy.walkforward(windows=1, timeframe="1m")
- Parameters:
fn –
Callableinvoked on every bar of data during the backtest and passed anpybroker.context.ExecContextfor each ticker symbol insymbols.symbols – Ticker symbols used to run
fn, wherefnis called separately for each symbol. Can also be apybroker.common.SymbolSelector— aCallable(df) -> Sequence[str]that picks the symbols to trade once per walkforward window, so the universe changes over the backtest. It receives the window’s training data, never test data, and therefore requires a training window:backtest()andtrain_size=0raiseValueError. The candidate universe must be supplied as apandas.DataFramerather than apybroker.data.DataSource, since the symbols to query are unknown until a window is split. A position in a symbol that a later window drops is closed at the first bar of that window; if the symbol has no bars left, it is closed at its final bar. Note thatshuffle=Truerandomizes the training frame’s row order, so avoid it with a selector that depends on bar order.models –
Iterableofpybroker.model.ModelSources to train/load for backtesting. A model passed directly is trained on the base timeframe. Bind a model to compression intervals withpybroker.model.ModelSource.intervals()to instead train it on exactly the listed intervals’ compressed bars, together with the indicators registered on it; include the literal'base'in the binding to also train on the base timeframe. Per-interval predictions are read withpybroker.context.IntervalContext.preds(). An interval-bound model honors thelookaheadpassed tobacktest(),walkforward(), oroptimize()in the interval’s units:lookaheadcompressed bars are held out between its train and test rows.indicators –
Iterableofpybroker.indicator.Indicators to compute for backtesting. An indicator passed directly is computed on the base timeframe. Bind an indicator to compression intervals withpybroker.indicator.Indicator.intervals()to instead compute it on exactly the listed intervals’ compressed bars, read withpybroker.context.IntervalContext.indicator(); include the literal'base'in the binding to also compute it on the base timeframe.hyperparams –
Iterableofpybroker.optimize.Hyperparams thatfncan read withpybroker.context.ExecContext.hyperparam().intervals – One or more compression intervals whose bars are made available to
fnthroughpybroker.context.ExecContext.interval(). Declaring an interval here provides compressed bars only. It does not compute indicators or train models on that interval. Bind those per source withpybroker.indicator.Indicator.intervals()andpybroker.model.ModelSource.intervals()instead; bound intervals are automatically made available throughinterval()without repeating them here. Each interval must be strictly coarser than the base bar spacing passed astimeframetobacktest()orwalkforward(); invalid combinations raiseValueErrorwhen the backtest runs. Intervals are scoped to this execution, so another execution’spybroker.context.ExecContextcannot read them — including inside aset_before_exec()orset_after_exec()callback, which receives contexts from every execution.args – Positional arguments passed to
fn.kwargs – Keyword arguments passed to
fn.
- backtest(start_date: str | datetime | None = None, end_date: str | datetime | None = None, timeframe: str = '', between_time: tuple[str, str] | None = None, days: str | Day | Iterable[str | Day] | None = None, lookahead: int = 1, train_size: float = 0, shuffle: bool = False, calc_bootstrap: bool = False, parallel_indicators: bool = False, parallel_models: bool = False, warmup: int | None = None, portfolio: Portfolio | None = None, adjust: Any | None = None, seed: int | None = 42, params: dict[str, Any] | None = None) TestResult[source]
Backtests the trading strategy by running executions that were added with
add_execution().- Parameters:
start_date – Starting date of the backtest (inclusive). Must be within
start_dateandend_daterange that was passed toStrategy.end_date – Ending date of the backtest (inclusive). Must be within
start_dateandend_daterange that was passed toStrategy.timeframe –
Formatted string that specifies the timeframe resolution of the backtesting data. The timeframe string supports the following units:
"s"/"sec": seconds"m"/"min": minutes"h"/"hour": hours"d"/"day": days"w"/"week": weeks
An example timeframe string is
1h 30m. Required when any execution declaresintervals, since it defines the base bar spacing that compression intervals are validated and aligned against.between_time –
tuple[str, str]of times of day e.g. (‘9:30’, ‘16:00’) used to filter the backtesting data (inclusive).days – Days (e.g.
"mon","tues"etc.) used to filter the backtesting data.lookahead – Number of bars in the future of the target prediction. For example, predicting returns for the next bar would have a
lookaheadof1. This quantity is needed to prevent training data from leaking into the test boundary. It is expressed in the bars of the timeframe each model is fitted on: a model bound to an interval withpybroker.model.ModelSource.intervals()holds outlookaheadbars of that interval, not of the base timeframe. No bar in a window’s test set is ever used to fit that window’s models.train_size – Amount of
pybroker.data.DataSourcedata to use for training, where the maxtrain_sizeis1. For example, atrain_sizeof0.9would result in 90% of data being used for training and the remaining 10% of data being used for testing.shuffle – Whether to randomly shuffle the data used for training. Defaults to
False. Disabled when model caching is enabled viapybroker.cache.enable_model_cache().calc_bootstrap – Whether to compute randomized bootstrap evaluation metrics. Defaults to
False.parallel_indicators – If
True,pybroker.indicator.Indicatordata is computed in parallel using multiple processes. Defaults toFalse.parallel_models – If
True,pybroker.model.ModelTrainermodels are trained in parallel using multiple processes. Defaults toFalse.warmup – Number of bars that need to pass before running the executions.
portfolio – Custom
pybroker.portfolio.Portfolioto use for backtests.adjust – The type of adjustment to make to the
pybroker.data.DataSource.seed – Random seed used for reproducibility. Defaults to
42.
- Returns:
TestResultcontaining portfolio balances, order history, and evaluation metrics.
- clear_executions()[source]
Clears executions that were added with
add_execution().
- enable_rotation(worst_rank_held: int | Hyperparam | None, sizer: Callable[[RotationContext], None] | None = None) None[source]
Enables rotational hold-band logic and optional custom sizing.
Each bar, held positions ranked worse than
worst_rank_heldare liquidated, and the top-ranked symbols are entered to fill the position slots that remain free. Without asizer, entries are equal-weighted acrossset_max_long_positions()plusset_max_short_positions()slots.Rotation is exclusive: trading is driven entirely by
pybroker.context.ExecContext.long_scoreandpybroker.context.ExecContext.short_score, and orders placed by anExecutionare ignored. Fill prices and stops set during an execution are kept and applied to the orders rotation places.Ranking spans the whole portfolio, so a held position without a rankable score is liquidated even when another
Executionopened it.- Parameters:
worst_rank_held – Worst score rank at which a held position is kept, a searchable
pybroker.optimize.Hyperparam, orNoneto disable rotation. Must be greater than or equal to the maximum long and short position counts.sizer – Optional
Callablethat takes apybroker.context.RotationContextto override equal-weight entry sizing after rotation decisions are made. Do not override sell or cover signals set by rotation.
- set_after_exec(fn: Callable[[Mapping[str, ExecContext]], None] | None)[source]
Callable[[Mapping[str, ExecContext]], None]that runs after all execution functions.- Parameters:
fn –
Callablethat takes aMappingof all ticker symbols toExecContexts.
- set_before_exec(fn: Callable[[Mapping[str, ExecContext]], None] | None)[source]
Callable[[Mapping[str, ExecContext]], None]that runs before all execution functions.- Parameters:
fn –
Callablethat takes aMappingof all ticker symbols toExecContexts.
- set_max_long_positions(max_long: int | Hyperparam | None) None[source]
Sets the maximum number of long positions held at any time.
- Parameters:
max_long – Maximum long positions, a searchable
pybroker.optimize.Hyperparam, orNonefor unlimited.
- set_max_short_positions(max_short: int | Hyperparam | None) None[source]
Sets the maximum number of short positions held at any time.
- Parameters:
max_short – Maximum short positions, a searchable
pybroker.optimize.Hyperparam, orNonefor unlimited.
- set_slippage_model(slippage_model: SlippageModel | None)[source]
Sets
pybroker.slippage.SlippageModel.Built-in models are
pybroker.slippage.FixedSlippageModel(fixed basis points),pybroker.slippage.VolatilitySlippageModel(ATR-scaled), andpybroker.slippage.VolumeSlippageModel(participation cap and square-law price impact). PassNoneto disable slippage.Fill-time slippage applies to scheduled orders, stop exits, and position exits. Stop and position exits use the adjusted fill price only; share adjustments are ignored on those paths because they exit an entry in full.
- walkforward(windows: int, lookahead: int = 1, start_date: str | datetime | None = None, end_date: str | datetime | None = None, timeframe: str = '', between_time: tuple[str, str] | None = None, days: str | Day | Iterable[str | Day] | None = None, train_size: float = 0.5, shuffle: bool = False, calc_bootstrap: bool = False, parallel_indicators: bool = False, parallel_models: bool = False, warmup: int | None = None, portfolio: Portfolio | None = None, adjust: Any | None = None, seed: int | None = 42, params: dict[str, Any] | None = None) TestResult[source]
Backtests the trading strategy using Walkforward Analysis. Backtesting data supplied by the
pybroker.data.DataSourceis divided intowindowsnumber of equal sized time windows, with each window split into train and test data as specified bytrain_size. The backtest “walks forward” in time through each window, running executions that were added withadd_execution().- Parameters:
windows – Number of walkforward time windows.
start_date – Starting date of the Walkforward Analysis (inclusive). Must be within
start_dateandend_daterange that was passed toStrategy.end_date – Ending date of the Walkforward Analysis (inclusive). Must be within
start_dateandend_daterange that was passed toStrategy.timeframe –
Formatted string that specifies the timeframe resolution of the backtesting data. The timeframe string supports the following units:
"s"/"sec": seconds"m"/"min": minutes"h"/"hour": hours"d"/"day": days"w"/"week": weeks
An example timeframe string is
1h 30m. Required when any execution declaresintervals, since it defines the base bar spacing that compression intervals are validated and aligned against.between_time –
tuple[str, str]of times of day e.g. (‘9:30’, ‘16:00’) used to filter the backtesting data (inclusive).days – Days (e.g.
"mon","tues"etc.) used to filter the backtesting data.lookahead – Number of bars in the future of the target prediction. For example, predicting returns for the next bar would have a
lookaheadof1. This quantity is needed to prevent training data from leaking into the test boundary. It is expressed in the bars of the timeframe each model is fitted on: a model bound to an interval withpybroker.model.ModelSource.intervals()holds outlookaheadbars of that interval, not of the base timeframe. No bar in a window’s test set is ever used to fit that window’s models.train_size – Amount of
pybroker.data.DataSourcedata to use for training, where the maxtrain_sizeis1. For example, atrain_sizeof0.9would result in 90% of data being used for training and the remaining 10% of data being used for testing.shuffle – Whether to randomly shuffle the data used for training. Defaults to
False. Disabled when model caching is enabled viapybroker.cache.enable_model_cache().calc_bootstrap – Whether to compute randomized bootstrap evaluation metrics. Defaults to
False.parallel_indicators – If
True,pybroker.indicator.Indicatordata is computed in parallel using multiple processes. Defaults toFalse.parallel_models – If
True,pybroker.model.ModelTrainermodels are trained in parallel using multiple processes. Defaults toFalse.warmup – Number of bars that need to pass before running the executions.
portfolio – Custom
pybroker.portfolio.Portfolioto use for backtests.adjust – The type of adjustment to make to the
pybroker.data.DataSource.seed – Random seed used for reproducibility. Defaults to
42.
- Returns:
TestResultcontaining portfolio balances, order history, and evaluation metrics.
- class TestResult(start_date: datetime, end_date: datetime, portfolio: DataFrame, positions: DataFrame, orders: DataFrame, trades: DataFrame, metrics: EvalMetrics, metrics_df: DataFrame, bootstrap: BootstrapResult | None, signals: dict[str, DataFrame] | None, stops: DataFrame | None, symbols: frozenset[str] = frozenset({}))[source]
Bases:
objectContains the results of backtesting a
Strategy.- start_date
Starting date of backtest.
- Type:
- end_date
Ending date of backtest.
- Type:
- portfolio
pandas.DataFrameofpybroker.portfolio.Portfoliobalances for every bar.- Type:
- positions
pandas.DataFrameofpybroker.portfolio.Positionbalances for every bar.- Type:
- orders
pandas.DataFrameof all orders that were placed.- Type:
- trades
pandas.DataFrameof all trades that were made.- Type:
- metrics
Evaluation metrics.
- metrics_df
pandas.DataFrameof evaluation metrics.- Type:
- bootstrap
Randomized bootstrap evaluation metrics.
- Type:
- signals
Dictionary of
pandas.DataFrames containing bar data, indicator data, and model predictions for each symbol whenpybroker.config.StrategyConfig.return_signalsisTrue. Signals contain base-timeframe values only; an interval-bound indicator or model appears only when'base'is included in its binding.- Type:
dict[str, pandas.DataFrame] | None
- stops
pandas.DataFramecontaining stop data per-bar whenpybroker.config.StrategyConfig.return_stopsisTrue.- Type:
pandas.DataFrame | None
- to_json(*, include: frozenset[str] = frozenset({'bootstrap', 'metrics', 'orders', 'trades'}), max_rows: int | None = 100, symbols: frozenset[str] | None = None) dict[str, Any][source]
Returns JSON-serializable backtest results.
By default includes
start_date,end_date,metrics,trades,orders, andbootstrap(when present). Large time series such asportfolio,positions,signals, andstopsare opt-in viainclude. Dates serialize as naive UTC, NaN asnull, and infinite metric values as the string sentinels"Infinity"/"-Infinity".- Parameters:
include – Names of optional result sections to include. Valid names are
metrics,metrics_df,trades,orders,portfolio,positions,bootstrap,signals, andstops. Note thatpositionshas rows only whenpybroker.config.StrategyConfig.record_position_barsisTrue, andsignals/stopsonly with their matching config flags.max_rows – Maximum rows per tabular section.
Nonefor no limit.symbols – When set, filter symbol-specific sections to these tickers. Must be a non-empty subset of
symbols.
- class WalkforwardMixin[source]
Bases:
objectMixin implementing logic for Walkforward Analysis.
- walkforward_split(df: DataFrame, windows: int, lookahead: int, train_size: float = 0.9, shuffle: bool = False) Iterator[WalkforwardWindow][source]
Splits a
pandas.DataFramecontaining data for multiple ticker symbols into anIteratorof train/test time windows for Walkforward Analysis.- Parameters:
df –
pandas.DataFrameof data to split into train/test windows for Walkforward Analysis.windows – Number of walkforward time windows.
lookahead – Number of bars in the future of the target prediction. For example, predicting returns for the next bar would have a
lookaheadof1. This quantity is needed to prevent training data from leaking into the test boundary. It is expressed in the bars of the timeframe each model is fitted on: a model bound to an interval withpybroker.model.ModelSource.intervals()holds outlookaheadbars of that interval, not of the base timeframe. No bar in a window’s test set is ever used to fit that window’s models.train_size – Amount of data in
dfto use for training, where the maxtrain_sizeis1. For example, atrain_sizeof0.9would result in 90% of data indfbeing used for training and the remaining 10% of data being used for testing.shuffle – Whether to randomly shuffle the data used for training. Defaults to
False.
- Returns:
IteratorofWalkforwardWindows containing train and test data.
- class WalkforwardWindow(train_data: ndarray[tuple[Any, ...], dtype[int64]], test_data: ndarray[tuple[Any, ...], dtype[int64]])[source]
Bases:
NamedTupleContains train/test row indices for a walkforward window.
- train_data
Integer row indices into the master frame for training.
- Type:
numpy.ndarray[tuple[Any, …], numpy.dtype[numpy.int64]]
- test_data
Integer row indices into the master frame for testing.
- Type:
numpy.ndarray[tuple[Any, …], numpy.dtype[numpy.int64]]