Optimisation with seek

Optimisation with seek

The seek command finds optimal strategy input parameters by running multiple backtests and maximising a chosen fitness metric.

Basic usage

algo seek --strategy macrossover --symbols @ES --fitness-metric sharpe

This runs every combination of LenFast and LenSlow values defined in the strategy’s algolang: struct tags and reports the combination that maximises the Sharpe ratio.

Fitness metrics

The most commonly used single-variable metrics:

VariableDescription
npddNet Profit / Max Drawdown (default)
sharpeSharpe ratio
sortinoSortino ratio
calmarCalmar ratio
marMAR ratio
rinaRINA index
netprofitNet profit
profitfactorProfit factor
r2R-squared (equity curve linearity)
carCompound annual return
avgtradeAverage trade net profit
pctpmPercent of months profitable

Any of these can be used directly:

algo seek --strategy macrossover --symbols @ES --fitness-metric sharpe

Custom fitness expressions

The --fitness-metric flag accepts any mathematical expression built from the variables listed below. The seek command evaluates the expression for every parameter combination and selects the combination that produces the highest value.

Expressions are evaluated by the ExprTK mathematical expression library.

Expression variables

All 24 variables listed below are available in fitness expressions. Variable names are case-sensitive and must be used exactly as shown.

Profitability

VariableDescription
netprofitTotal net profit after slippage and commission
profitfactorGross Profit / Gross Loss (values > 1.0 are profitable)
avgtradeAverage net profit per trade
nbrtradesTotal number of closed trades

Risk-Adjusted Returns

VariableDescription
sharpeAnnualised Sharpe ratio
sortinoSortino ratio (uses only downside volatility)
calmarCalmar ratio (Compound Annual Return / Max Drawdown)
marMAR ratio
mar12roll12-month rolling MAR ratio
rinaRINA index (risk-adjusted consistency)
tsindexTrading System index
ulcerUlcer index (measures depth and duration of drawdowns)

Drawdown and Risk

VariableDescription
npddNet Profit / Maximum Intra-Day Drawdown
npmarginNet Profit / Initial Margin requirement
pctinddPercent of time spent in drawdown

Equity Curve Quality

VariableDescription
r2R-squared fit of the equity curve (1.0 = perfectly linear growth)
pearsonPearson correlation coefficient of the equity curve

Returns

VariableDescription
carCompound annual return (strategy), expressed as a decimal (e.g. 0.15 = 15%)
carbhCompound annual return (buy-and-hold benchmark)

Consistency

VariableDescription
pctpmPercent of months profitable (e.g. 65.0 = 65%)
pctpqPercent of quarters profitable
pctpyPercent of years profitable
profsegs10Number of profitable segments out of 10 (integer 0–10)

Notes:

  • npmargin equals netprofit when no margin requirement is configured via --margin; otherwise it equals netprofit / initial_margin.
  • nbrdd (number of drawdowns) is registered as a variable but is not yet populated with real data; avoid using it in expressions.
  • All variables default to 0.0 when the underlying metric cannot be computed (e.g. zero trades).

Expression syntax

Operators

CategoryOperators
Arithmetic+ - * / % (modulo) ^ (power)
Comparison== != < <= > >=
Logicaland or not nand nor xor

Standard operator precedence applies. Use parentheses to control evaluation order.

Functions

FunctionDescription
abs(x)Absolute value
sqrt(x)Square root
log(x)Natural logarithm
log10(x)Base-10 logarithm
exp(x)e raised to the power x
min(x, y, ...)Smallest value
max(x, y, ...)Largest value
clamp(lo, x, hi)Clamp x between lo and hi
floor(x)Round down to nearest integer
ceil(x)Round up to nearest integer
round(x)Round to nearest integer
if(cond, a, b)Returns a when cond is true (non-zero), otherwise b

For the complete ExprTK expression language reference including additional functions, see https://www.partow.net/programming/exprtk/.

Expression examples

Single metric — use the variable name directly:

algo seek --strategy macrossover --symbols @ES --fitness-metric sharpe

Weighted combination — multiply two desirable properties:

algo seek --strategy kbt --symbols @ES --fitness-metric "sharpe * r2"

A strategy with Sharpe 2.0 and R-squared 0.95 scores higher (1.90) than one with Sharpe 2.5 and R-squared 0.50 (1.25).

Penalise low trade counts — use if() to reject sparse results:

algo seek --strategy kbt --symbols @ES --fitness-metric "if(nbrtrades < 30, -1, npdd)"

Assigns fitness -1 to any combination producing fewer than 30 trades; otherwise uses NP/DD.

Multi-factor with guard — combine several objectives:

algo seek --strategy kbt --symbols @ES \
  --fitness-metric "if(nbrtrades < 20, -1, sharpe * sqrt(nbrtrades) * r2)"

Requires at least 20 trades, then scores by Sharpe scaled by the square root of trade count and equity curve quality.

Clamp outliers — prevent overfitting to a few lucky trades:

algo seek --strategy kbt --symbols @ES \
  --fitness-metric "clamp(0, profitfactor, 5) * pctpm / 100"

Caps profit factor at 5.0 and multiplies by the fraction of profitable months.

Complex multi-objective — combine drawdown, time-in-drawdown, and consistency:

algo seek --strategy kbt --symbols @ES \
  --fitness-metric "npdd * (1 - pctindd / 100) * if(profsegs10 >= 7, 1, 0.5)"

NP/DD penalised by time spent in drawdown, with a 50% penalty if fewer than 7 out of 10 trade segments are profitable.

Expression tips

  • The seek command always maximises the expression result. To minimise a metric (e.g. ulcer index), negate it: --fitness-metric "-ulcer".
  • Division by zero in ExprTK returns infinity. Use if() guards to avoid unexpected results: if(nbrtrades > 0, netprofit / nbrtrades, 0).
  • Wrap expressions containing spaces or special characters in double quotes on the command line.

Controlling inputs

Include specific inputs

# Only optimise LenFast
algo seek --strategy macrossover --symbols @ES --inputs-include LenFast

Exclude specific inputs

# Optimise everything except LenSlow
algo seek --strategy macrossover --symbols @ES --inputs-exclude LenSlow

Set base input values

Strategies without defaults, or where you want to override defaults before optimisation, can use --inputs:

algo seek --strategy mystrategy --symbols @ES --inputs Period=20,Mult=1.5

Fields set via --inputs are automatically excluded from optimisation — the field is frozen at the provided value. This means you do not need to also specify --inputs-exclude for the same field.

To override this and still optimise a field that has a base value set via --inputs, add it to --inputs-include:

# Period is frozen at 20; all other fields are optimised normally
algo seek --strategy mystrategy --symbols @ES --inputs Period=20

# Period starts at 20 but IS still optimised (because of --inputs-include)
algo seek --strategy mystrategy --symbols @ES --inputs Period=20 --inputs-include Period

Optimisation modes

ModeDescription
exhaustiveTests every combination (default). Guaranteed to find the global optimum.
geneticUses a genetic algorithm to search the parameter space. Faster for large spaces.

Exhaustive mode

The default mode generates every combination of parameter values (cartesian product) and evaluates each one. This guarantees finding the global optimum but becomes impractical when the total number of permutations is large.

Genetic mode

Genetic mode uses an evolutionary algorithm to intelligently search the parameter space without evaluating every combination. It maintains a population of candidate solutions that evolves over generations through selection, crossover, and mutation.

# Use genetic optimisation
algo seek --strategy meanrevert --symbols @ES --opt-mode genetic

# Customise GA parameters
algo seek --strategy meanrevert --symbols @ES --opt-mode genetic \
  --ga-population 100 --ga-generations 30 --ga-mutation-rate 0.15

# Use a fixed seed for reproducible results
algo seek --strategy meanrevert --symbols @ES --opt-mode genetic --ga-seed 42

How it works:

  1. An initial population of random parameter combinations is generated
  2. Each candidate is evaluated using the fitness metric (same as exhaustive mode)
  3. The best candidates are selected as parents for the next generation
  4. New candidates are created through crossover (combining two parents) and mutation (randomly changing individual parameters)
  5. The top individuals (elites) survive unchanged into the next generation
  6. The process repeats until convergence or the maximum generation count is reached
  7. Results from the final generation are displayed in the same format as exhaustive mode

When to use genetic mode: Use it when the total number of permutations exceeds a few hundred. For small parameter spaces, the seek command will automatically fall back to exhaustive mode even if --opt-mode genetic is specified.

Genetic algorithm flags:

FlagDefaultDescription
--ga-populationautoPopulation size per generation (default: 10 x number of fields, min 20, max 200)
--ga-generations50Maximum number of generations
--ga-mutation-rate0.1Probability of mutating each parameter (0.0-1.0)
--ga-crossover-rate0.8Probability of crossover between parents (0.0-1.0)
--ga-tournament-size3Number of candidates in tournament selection
--ga-elite-count2Number of top individuals preserved across generations
--ga-stagnation5Stop early after N generations without fitness improvement
--ga-seed0Random seed (0 = time-based; set non-zero for reproducibility)

Progress output is written to stderr during genetic optimisation:

Genetic optimisation: 9 fields, 38880 total permutations, population=90, max_generations=50
[GA] Gen  1/50 | Best: 3.4521 | Avg: 1.2340 | Evaluated: 90 total
[GA] Gen  2/50 | Best: 3.6102 | Avg: 1.8921 | Evaluated: 168 total
...
[GA] Gen 15/50 | Best: 4.1230 | Avg: 3.2100 | Evaluated: 1180 total (converged)

Multi-symbol genetic mode

When multiple symbols (or bar intervals) are passed to seek with --opt-mode genetic, all symbol/interval combinations are combined into a single optimization run. Each candidate parameter set is evaluated against every symbol/interval spec, and the fitness is the minimum across all specs. This finds parameters that are robust across all instruments — optimizing for the worst-case symbol rather than overfitting to one.

# Optimize across two symbols — finds parameters that work for both
algo seek --strategy meanrevert --symbols "@ES,@NQ" --opt-mode genetic

# Combine symbols and bar intervals — 4 specs in one run
algo seek --strategy meanrevert --symbols "@ES,@NQ" --bar-interval "1d,4h" --opt-mode genetic

# Customise GA parameters for multi-symbol
algo seek --strategy meanrevert --symbols "@ES,@NQ,@YM" --opt-mode genetic \
  --ga-population 100 --ga-generations 40

The results table shows the aggregate (minimum) fitness for each candidate, along with the statistics from the worst-performing spec. This ensures the displayed metrics reflect the actual fitness that was optimized.

Progress output for multi-symbol mode includes the spec count:

Genetic optimisation (multi-symbol): 3 specs [@ES @ 1d, @NQ @ 1d, @YM @ 1d], 9 fields, 38880 total permutations, population=90, max_generations=50
[GA] Gen  1/50 | Best: 2.1340 | Avg: 0.8920 | Evaluated: 90 (90 candidates x 3 specs)

Note that multi-symbol mode is only available with genetic optimisation. When using exhaustive mode (--opt-mode exhaustive) with multiple symbols, each symbol is optimized independently (sequentially or in parallel).

Multi-threaded optimisation

# Use 16 threads
algo seek --strategy kbt --symbols @ES --nbr-threads 16

The default is 24 threads. Algolang uses test deduplication to avoid running identical parameter combinations.

Avoiding overfitting

Use --use-first and --use-last to split data for in-sample optimisation and out-of-sample validation:

# Optimise on first 60% of data
algo seek --strategy kbt --symbols @ES --use-first 60 --fitness-metric sharpe

# Then validate the best parameters on the last 40%
algo run --strategy kbt --symbols @ES --use-last 40 \
    --inputs LenFast=10,LenSlow=50 --output-mode summary