Skip to content

Run a study

This example evaluates nine constructor combinations on the full sample, excludes candidates with too few trades or excessive drawdown, and validates the training-window winner on consecutive one-year test windows.

The study contains one experiment for each parameter combination, plus temporary training and test experiments when walk-forward validation is enabled. The strategy can be a saved Library name or a runtime instance. A runtime custom strategy must keep its constructor values on same-named attributes so Backtide can create an isolated instance for every candidate and fold.

from backtide import DataExpConfig, ExperimentConfig, GeneralExpConfig, Study, WalkForwardConfig
from backtide.strategies import BaseStrategy


class CustomCrossover(BaseStrategy):
    def __init__(self, fast: int = 20, slow: int = 100):
        self.fast = fast
        self.slow = slow

    def evaluate(self, data, portfolio, state, indicators):
        # Replace this with the strategy's order logic.
        return []


config = ExperimentConfig(
    general=GeneralExpConfig(name="Custom crossover study"),
    data=DataExpConfig(
        symbols=["SPY"],
        interval="1d",
        start_date="2012-01-01",
        end_date="2025-12-31",
        full_history=False,
    ),
    metrics=["sharpe", "total_return", "max_dd", "n_trades"],
)

# The first metric is both the experiment headline and the study objective.

study = Study(
    config,
    strategy=CustomCrossover(),
    parameter_space={
        "fast": [10, 20, 30],
        "slow": [75, 100, 150],
    },
    min_trades=20,
    max_drawdown=0.25,
    walk_forward=WalkForwardConfig(
        training_days=3 * 365,
        test_days=365,
        step_days=365,
        anchored=False,
    ),
)
result = study.run()  

print(result.study_id, result.best_candidate.parameters)  
for candidate in sorted(result.candidates, key=lambda item: item.rank or 10_000):  
    print(candidate.rank, candidate.parameters, candidate.metrics.get("sharpe"))  

for fold in result.folds:  
    print(  
        fold.fold,  
        fold.parameters,  
        fold.training_objective,  
        fold.test_objective,  
    )  

max_drawdown=0.25 means a 25% drawdown magnitude. Temporary training and test experiments are deleted after each fold; result.study_id identifies the persisted study. Reopen its summary later with query_study.