Balaena Quant's LogoBalaena Quant
Search

Search

Grid and random search over alpha parameter spaces.

Overview

ADRS provides two search strategies for exploring an alpha's parameter space — the set of all possible combinations of hyper-parameters (e.g. rolling window size, entry threshold).

Both strategies implement the same Search interface, making them interchangeable.


ParameterGrid

A parameter grid is simply a dict where each key is a parameter name and each value is an array of candidate values:

grid: ParameterGrid = {
    "window": [20, 40, 60, 100],
    "long_entry_threshold": [0.5, 0.75, 1.0, 1.25],
    "fees": [0.02, 0.035],
}

With the grid above, the total Cartesian product contains 4 × 4 × 2 = 32 permutations.


GridSearch

GridSearch exhaustively enumerates every combination in the parameter grid.

from adrs.search import GridSearch

search = GridSearch()
permutations = search.search(grid)
# [{"window": 20, "long_entry_threshold": 0.5, "fees": 0.02}, ...]

Use GridSearch when the parameter space is small enough to evaluate fully, or as a baseline for comparing other search strategies.


RandomSearch

RandomSearch draws a random sample from the parameter space, optionally weighted by a statistical distribution.

from adrs.search import RandomSearch, Distribution

search = RandomSearch(
    samples=0.3,              # evaluate 30 % of all permutations
    seed=42,                  # reproducibility
    dist=Distribution.UNIFORM,
)
permutations = search.search(grid)

Constructor

RandomSearch(
    samples: int | float,
    seed: int,
    dist: Distribution,
    **dist_kwargs,
)
ParameterTypeDescription
samplesintAbsolute number of permutations to sample
samplesfloatFraction of total permutations to sample (e.g. 0.3 = 30 %)
seedintRandom seed for reproducibility
distDistributionProbability distribution to weight the sampling
**dist_kwargsAdditional keyword arguments forwarded to the distribution

Distribution

from adrs.search import Distribution

Distribution.UNIFORM     # equal probability for every value (default)
Distribution.NORMAL      # bell-curve weighting around the centre of each range
Distribution.LOGNORMAL   # log-normal weighting (positive skew)
Distribution.EXPONENTIAL # exponential decay from the start of each range
Distribution.BETA        # beta distribution (requires alpha, beta kwargs)
Distribution.GAMMA       # gamma distribution (requires shape kwargs)

Search interface

Both classes implement the Search base interface:

class Search:
    def search(self, grid: ParameterGrid) -> Sequence[Permutation]:
        """Generate a sequence of parameter dicts to evaluate."""
        ...

    def filter(self, permutations: Sequence[T]) -> Sequence[T]:
        """Post-filter the generated permutations if needed."""
        ...

You can subclass Search to implement your own strategy (e.g. Bayesian optimisation):

from adrs.search import Search, ParameterGrid, Permutation
from typing import Sequence

class MySearch(Search):
    @staticmethod
    def id() -> str:
        return "my_search"

    def search(self, grid: ParameterGrid) -> Sequence[Permutation]:
        # your custom logic here
        ...

    def filter(self, permutations):
        return permutations  # no filtering

Usage with Sensitivity

Search strategies are passed directly to Sensitivity to control how parameter variations are generated during robustness testing:

from adrs.tests import Sensitivity, SensitivityParameter
from adrs.search import RandomSearch, Distribution

sensitivity = Sensitivity(
    alpha=alpha,
    parameters={
        "window": SensitivityParameter(min_val=10, min_gap=5),
        "long_entry_threshold": SensitivityParameter(min_val=0.1),
    },
    gap_percent=0.15,
    num_steps=3,
    search=RandomSearch(samples=0.5, seed=0, dist=Distribution.UNIFORM),
)

See Sensitivity for a complete walkthrough.

On this page