Skip to content

Backtest: run and optimize

View Markdown

Use Backtest to run a Strategy subclass against one asset's historical data. Import both from openbacktesting. Start with the complete first backtest if you do not have a strategy yet.

Backtest · DefaultOrderSize

Run a strategy

from openbacktesting import Backtest, Strategy
from openbacktesting.test import GOOG

class BuyAndHold(Strategy):
    def init(self):
        pass

    def next(self):
        if self.bar_index == 0:
            self.buy(qty=1)

bt = Backtest(GOOG, BuyAndHold, cash=10_000, commission=0.002,
              end_policy="close_at_last_close")
result = bt.run()
print(result[["Return [%]", "# Trades", "Max. Drawdown [%]"]])
print(result["_trades"])

bt.run() returns a pandas Series. Each call starts a new account and strategy instance. commission=0.002 means 0.2% per execution. The order placed on the first bar normally fills at the next bar's open. The end policy in this example closes remaining holdings at the final close.

Constructor parameters

Parameter What to pass
data pandas DataFrame with Open, High, Low, Close; Volume is optional unless an indicator needs it. See data requirements.
strategy Your Strategy subclass, without constructing an instance.
cash Positive initial account balance; default 10,000.
commission Fraction of execution notional, (fixed_fee, fraction), a Commission, or a callable (signed_qty, price) -> fee. Charged on entries and exits.
spread Fractional price adjustment on every execution; 0.001 means 0.1%.
slippage Slippage("bps", 5) or Slippage("fixed", 0.01); see costs.
margin Required margin as a fraction of notional, in (0, 1]; 1 is unleveraged.
execution_timing "next_open" (default), or "same_close" for unconditional market orders submitted in next().
end_policy "mark_to_market" keeps final open holdings; "close_at_last_close" liquidates actual holdings and charges costs.
instrument InstrumentSpec for quantity steps, minimum size, and optional price ticks.
default_order_size DefaultOrderSize("fixed", 1) or DefaultOrderSize("equity_fraction", 0.2) used when buy()/sell() omit size. Default None requires explicit sizing.
allow_short Whether new short positions may be opened; default True.
max_gross_exposure Optional gross-notional/equity cap; valid exits remain allowed.
hedging False nets opposite orders against existing trades; True allows long and short lots together.
exclusive_orders A new ordinary entry cancels pending entries and submits exits for existing lots. Default False.
context Optional DataContext for timeframe/timezone. Requires a timezone-aware index.
risk_free_rate Annualized fraction used for statistics only; 0.03 means 3%.
plotting, plotting_limits Collect visual declarations and optionally set resource limits. See plotting.
indicator_cache_bytes Retained indicator-cache budget; default 64 MiB, 0 disables reuse. Does not cap total process memory.

Backtest uses the arguments you pass; it does not apply an @backtest decorator's settings. Use runtime.run when you want that declaration applied.

Pass strategy parameters

Declare a class attribute (length = 20) or an input, then call bt.run(length=30). Keys are Python attribute names. For a title-based input request, use inputs.request_for(MyStrategy, {"length": 30}) and bt.run_inputs(request).

Find a parameter combination

from openbacktesting import Backtest, Strategy
from openbacktesting.test import GOOG

class MovingAverage(Strategy):
    length = 20

    def init(self):
        self.average = self.ta.sma(length=self.length)

    def next(self):
        if not self.average.valid[-1]:
            return
        if self.close[-1] > self.average[-1] and not self.position:
            self.buy(qty=1)
        elif self.close[-1] < self.average[-1] and self.position:
            self.position.close()

if __name__ == "__main__":
    bt = Backtest(GOOG, MovingAverage, end_policy="close_at_last_close")
    best, scores = bt.optimize(length=[10, 20, 40], maximize="Return [%]",
                               return_heatmap=True)
    print(best["_strategy"])
    print(scores)

Install the optimize extra before using optimization. Additional keyword arguments are candidate sequences for strategy parameters. maximize is a result key or a function receiving a result and returning a score; higher wins. constraint=lambda p: p.fast < p.slow filters combinations before running.

Option Meaning
method="grid" Evaluate the candidate product; input declarations support this method.
method="sambo" Model-based search for ordinary class parameters; unavailable for declared inputs.
max_tries Integer evaluation budget, or a fraction (0, 1] of the grid. Omit for an exhaustive grid; SAMBO defaults to 200.
random_state Fixed integer for repeatable search sampling.
return_heatmap=True Return (best_result, scores); scores has a parameter MultiIndex.
return_optimization=True SAMBO only: append the optimizer's raw result to the returned tuple.

Use saved experiments when you need complete reports for every candidate, filtering, or resume. Backtest.optimize returns the best run and optional scores.

Read results and logs

result["_equity_curve"] contains the equity time series; result["_trades"] contains closed trade fragments. With the default end policy, open holdings appear in result["_open_trades"] and may contribute to return without contributing to # Trades. See results for units and tables, and Portfolio for JSON reports.

Call bt.get_logs() after success or failure to read messages written through self.log.info/warning/error. A subsequent run clears the previous run's logs.

Backtest

Backtest(
    data,
    strategy,
    *,
    cash=10000,
    spread=0.0,
    commission=0.0,
    risk_free_rate=0.0,
    default_order_size=None,
    margin=1.0,
    execution_timing='next_open',
    instrument=None,
    slippage=None,
    allow_short=True,
    max_gross_exposure=None,
    hedging=False,
    exclusive_orders=False,
    end_policy='mark_to_market',
    indicator_cache_bytes=67108864,
    context=None,
    plotting=True,
    plotting_limits=None,
)

Store single-asset historical market data, strategy, and linear margin-account configuration.

Backtest.optimize

Backtest.optimize(
    *,
    maximize='SQN',
    method='grid',
    max_tries=None,
    constraint=None,
    return_heatmap=False,
    return_optimization=False,
    random_state=None,
    **kwargs,
)

Search strategy-parameter combinations and select results by a specified statistic or scoring function.

Backtest.run

Backtest.run(**kwargs)

Backtest strategy parameters and return a statistics Series, plain-value ledger tables, and an optional diagnostic strategy.

Backtest.run_inputs

Backtest.run_inputs(request)

Submit an input request with schemaVersion/schemaHash/values keyed by unique title.

Backtest.get_logs

Backtest.get_logs()

Return an independent copy of all strategy logs for this run; empty before any run.

DefaultOrderSize

DefaultOrderSize(mode, value)

Request budget when ordinary orders omit qty; does not represent a target position or filled notional.

Attribute Type Meaning
mode str fixed asset quantity or equity_fraction.
value float Positive quantity, or an equity fraction in (0, 1].

DefaultOrderSize.snapshot

DefaultOrderSize.snapshot()

Export independent mode and value.

DefaultOrderSize.from_snapshot

DefaultOrderSize.from_snapshot(value)

Strictly restore complete configuration without filling missing fields.

Strategy · Orders and positions · User manual