Skip to content

Run an experiment

This example runs two built-in strategies over the same historical data, compares their summary metrics, inspects the best run's records, and plots normalized profit and loss. Missing market data is downloaded automatically, and the completed experiment is persisted to local storage.

import pandas as pd

from backtide.analysis import plot_pnl
from backtide import (
    DataExpConfig,
    Experiment,
    ExperimentConfig,
    GeneralExpConfig,
    StrategyExpConfig,
)
from backtide.strategies import BuyAndHold, SmaNaive

config = ExperimentConfig(
    general=GeneralExpConfig(name="Compare Apple strategies"),
    data=DataExpConfig(
        symbols=["AAPL"],
        interval="1d",
        start_date="2022-01-01",
        end_date="2024-12-31",
        full_history=False,
    ),
    strategy=StrategyExpConfig(benchmark="SPY"),
    metrics=["total_return", "sharpe", "pnl", "max_dd"],
)
result = Experiment(
    config,
    strategies=[BuyAndHold(), SmaNaive()],
).run()

print(f"Experiment {result.experiment_id}: {result.status}")
for warning in result.warnings:
    print(f"Warning: {warning}")

successful = [run for run in result.strategies if run.error is None]
if not successful:
    errors = [run.error for run in result.strategies]
    raise RuntimeError(f"Every strategy failed: {errors}")

summary = pd.DataFrame(
    {
        "strategy": run.strategy_name,
        "benchmark": run.is_benchmark,
        "total_return": run.metrics.get("total_return"),
        "sharpe": run.metrics.get("sharpe"),
        "pnl": run.metrics.get("pnl"),
        "max_dd": run.metrics.get("max_dd"),
        "trades": len(run.trades),
        "orders": len(run.orders),
    }
    for run in successful
).sort_values("sharpe", ascending=False)
summary.style.format(precision=2)

strategy_runs = [run for run in successful if not run.is_benchmark]
best = max(strategy_runs, key=lambda run: run.metrics.get("sharpe", float("-inf")))
print(
    f"Inspecting {best.strategy_name}: "
    f"{len(best.equity_curve)} equity samples and {len(best.trades)} closed trades"
)

trades = pd.DataFrame(
    {
        "symbol": trade.symbol,
        "quantity": trade.quantity,
        "entry": trade.entry_ts,
        "exit": trade.exit_ts,
        "pnl": trade.pnl,
    }
    for trade in best.trades
)
trades.head()

plot_pnl(successful, normalize=True, title="Strategy comparison")

Always inspect result.status, result.warnings, and each run.error before ranking runs. The returned RunResult objects also expose the complete equity_curve, trades, and orders collections. The same experiment can be reopened later with query_experiments and query_strategy_runs.