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 sharpeThis 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:
| Variable | Description |
|---|---|
npdd | Net Profit / Max Drawdown (default) |
sharpe | Sharpe ratio |
sortino | Sortino ratio |
calmar | Calmar ratio |
mar | MAR ratio |
rina | RINA index |
netprofit | Net profit |
profitfactor | Profit factor |
r2 | R-squared (equity curve linearity) |
car | Compound annual return |
avgtrade | Average trade net profit |
pctpm | Percent of months profitable |
Any of these can be used directly:
algo seek --strategy macrossover --symbols @ES --fitness-metric sharpeCustom 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
| Variable | Description |
|---|---|
netprofit | Total net profit after slippage and commission |
profitfactor | Gross Profit / Gross Loss (values > 1.0 are profitable) |
avgtrade | Average net profit per trade |
nbrtrades | Total number of closed trades |
Risk-Adjusted Returns
| Variable | Description |
|---|---|
sharpe | Annualised Sharpe ratio |
sortino | Sortino ratio (uses only downside volatility) |
calmar | Calmar ratio (Compound Annual Return / Max Drawdown) |
mar | MAR ratio |
mar12roll | 12-month rolling MAR ratio |
rina | RINA index (risk-adjusted consistency) |
tsindex | Trading System index |
ulcer | Ulcer index (measures depth and duration of drawdowns) |
Drawdown and Risk
| Variable | Description |
|---|---|
npdd | Net Profit / Maximum Intra-Day Drawdown |
npmargin | Net Profit / Initial Margin requirement |
pctindd | Percent of time spent in drawdown |
Equity Curve Quality
| Variable | Description |
|---|---|
r2 | R-squared fit of the equity curve (1.0 = perfectly linear growth) |
pearson | Pearson correlation coefficient of the equity curve |
Returns
| Variable | Description |
|---|---|
car | Compound annual return (strategy), expressed as a decimal (e.g. 0.15 = 15%) |
carbh | Compound annual return (buy-and-hold benchmark) |
Consistency
| Variable | Description |
|---|---|
pctpm | Percent of months profitable (e.g. 65.0 = 65%) |
pctpq | Percent of quarters profitable |
pctpy | Percent of years profitable |
profsegs10 | Number of profitable segments out of 10 (integer 0–10) |
Notes:
npmarginequalsnetprofitwhen no margin requirement is configured via--margin; otherwise it equalsnetprofit / 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
| Category | Operators |
|---|---|
| Arithmetic | + - * / % (modulo) ^ (power) |
| Comparison | == != < <= > >= |
| Logical | and or not nand nor xor |
Standard operator precedence applies. Use parentheses to control evaluation order.
Functions
| Function | Description |
|---|---|
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 sharpeWeighted 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 LenFastExclude specific inputs
# Optimise everything except LenSlow
algo seek --strategy macrossover --symbols @ES --inputs-exclude LenSlowSet 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.5Fields 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 PeriodOptimisation modes
| Mode | Description |
|---|---|
exhaustive | Tests every combination (default). Guaranteed to find the global optimum. |
genetic | Uses 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 42How it works:
- An initial population of random parameter combinations is generated
- Each candidate is evaluated using the fitness metric (same as exhaustive mode)
- The best candidates are selected as parents for the next generation
- New candidates are created through crossover (combining two parents) and mutation (randomly changing individual parameters)
- The top individuals (elites) survive unchanged into the next generation
- The process repeats until convergence or the maximum generation count is reached
- 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:
| Flag | Default | Description |
|---|---|---|
--ga-population | auto | Population size per generation (default: 10 x number of fields, min 20, max 200) |
--ga-generations | 50 | Maximum number of generations |
--ga-mutation-rate | 0.1 | Probability of mutating each parameter (0.0-1.0) |
--ga-crossover-rate | 0.8 | Probability of crossover between parents (0.0-1.0) |
--ga-tournament-size | 3 | Number of candidates in tournament selection |
--ga-elite-count | 2 | Number of top individuals preserved across generations |
--ga-stagnation | 5 | Stop early after N generations without fitness improvement |
--ga-seed | 0 | Random 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 40The 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 16The 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