What Optimize() Actually Does
Optimize() is one line of AFL with an unusual property: what it does depends entirely on
which button you pressed. Press Backtest and it is an elaborate way of writing a constant.
Press Optimize and the same line turns your formula into several hundred formulas. Most of
the trouble people get into with optimization starts with not knowing which of those two
things is happening.
By the end of this lesson you will be able to write the function correctly, say what it returns in every Analysis mode, work out how many backtests a set of ranges will produce before you start one, run an optimization on a complete portfolio system, and read what comes back without being misled by the way AmiBroker chooses to sort it.
One function, two behaviours
Section titled “One function, two behaviours”The signature
Section titled “The signature”The official reference gives one form, with five positional arguments and no optional ones:
Fragment — not a complete formula
FastPeriod = Optimize( "Fast MA", 20, 5, 60, 5 );Read as Optimize( "description", default, min, max, step ):
| Argument | What it is |
|---|---|
"description" |
A string identifying this variable. It becomes the column heading for the parameter in the optimization result list. |
default |
The value the function returns in exploration, indicator, commentary, scan and normal backtest modes. |
min |
The lowest value the optimizer will try. |
max |
The highest value the optimizer will try. |
step |
The increment used to walk from min to max. |
The function returns a NUMBER — a scalar, not an array — which is why it can be used anywhere a plain number can, including as an argument to another function.
What it returns when you are not optimizing
Section titled “What it returns when you are not optimizing”This is the fact that catches people, and it is stated plainly on the reference page: in
normal backtesting, scanning, exploration and commentary modes, Optimize() returns the
default value. Only in Optimization mode does it return successive values from min to
max, inclusively, stepping by step.
| What you ran | What Optimize( "Fast MA", 20, 5, 60, 5 ) gives you |
|---|---|
| Indicator / chart pane | 20 |
| Commentary | 20 |
| Scan | 20 |
| Exploration | 20 |
| Backtest | 20 |
| Optimization | 5, then 10, then 15 … up to 60 |
So the line above, on every run that is not an optimization, is exactly equivalent to
FastPeriod = 20;. That design is deliberate and useful: one file charts, scans, backtests
and optimizes without being edited. It also produces the single most common optimization
mistake in AmiBroker.
That manual step reads like an omission and is closer to a favour. The moment a program starts silently rewriting your formula with the best of several hundred fitted values, you lose the record of what you chose and why. Typing it in is a small tax that keeps the decision visible, and the lab at the end of this part turns it into a documented act rather than an afterthought.
Optimize() is not Param()
Section titled “Optimize() is not Param()”The two functions look almost identical and do opposite things.
Param( "name", defaultval, min, max, step, sincr )puts a slider in the Parameters dialog. Drag it and the chart redraws immediately. It does not create an optimization sweep.Optimize( "description", default, min, max, step )creates an optimization sweep. It puts nothing in the Parameters dialog, so an indicator built onOptimize()looks frozen on a chart with no way to explore it interactively.
AmiBroker’s author published a helper in a comment on the official Optimize page that
combines the two, so a single declaration both slides and sweeps:
Fragment — not a complete formula
function ParamOptimize( pname, defaultval, minv, maxv, step ){ return Optimize( pname, Param( pname, defaultval, minv, maxv, step ), minv, maxv, step );}Ranges, steps and how many backtests you just asked for
Section titled “Ranges, steps and how many backtests you just asked for”Counting the steps
Section titled “Counting the steps”The values run from min to max inclusively. The number of values one Optimize()
call produces is therefore:
Pseudocode — not valid AFL
number of values = (max - min) / step, plus oneThe plus one matters and the User’s Guide is loose about it: the optimization chapter says
each call generates (max - min)/step loops, while the same page says the values run from
min to max inclusively. Those two statements disagree by one. The inclusive count is right,
and AmiBroker’s own Knowledge Base confirms it — the article on the Exclude statement
describes two parameters each running 1 to 100 in steps of 1 as “all 10000 backtest runs”,
which is 100 × 100, and 100 is (100 − 1)/1 + 1.
| Declaration | Values tried | Count |
|---|---|---|
Optimize( "p", 20, 5, 60, 5 ) |
5, 10, 15 … 60 | 12 |
Optimize( "p", 20, 10, 20, 1 ) |
10, 11, 12 … 20 | 11 |
Optimize( "p", 2, 1, 3, 0.5 ) |
1, 1.5, 2, 2.5, 3 | 5 |
Multiplication, not addition
Section titled “Multiplication, not addition”Two parameters do not cost you the sum of their step counts. They cost the product, because every value of one is tried against every value of the other. The guide works the arithmetic through with parameters ranging 1 to 100 in steps of 1:
| Parameters | Combinations |
|---|---|
| 1 | 100 |
| 2 | 10,000 |
| 3 | 1,000,000 |
| 4 | 100,000,000 |
| 5 | 10,000,000,000 |
Each of those combinations is a complete backtest over your whole universe and date range. If one backtest takes a second, the three-parameter case takes about eleven days of machine time. This is the honest reason to limit exhaustive optimization to a small number of parameters — the guide’s own advice is to keep it “to just a few” — and it is also, quietly, a research argument rather than a computing one. A system with five tunable parameters has five degrees of freedom with which to fit the past, and Part 30’s lesson on curve fitting explains why that is a liability rather than a feature.
The documented limits
Section titled “The documented limits”The User’s Guide states two hard limits: up to 64 calls to Optimize(), and a maximum
search space of 264 combinations.
Rules that keep the search space intact
Section titled “Rules that keep the search space intact”Three requirements come from the documentation, and breaking any of them corrupts the run rather than producing an error message.
- Call each
Optimize()exactly once, near the top of the formula. The guide states it directly: each call generates new optimization loops. Putting one inside anifbranch, a loop, or a function that runs conditionally does not give you a conditional parameter; it gives you a search space that is not the one you think you declared. - Give every call a unique description string. A comment published on the official page
reports that copy-pasting an
Optimize()line without changing the description produced all-zero results. Duplicate descriptions are a bug with no diagnostic. - The description is the column heading. Name it so that you can still tell what it
meant when you read the result list a month later.
"Fast MA"and"Slow MA"beat"p1"and"p2"for exactly the reasonMaPeriodbeatspeverywhere else in this course.
Pruning combinations that are not systems
Section titled “Pruning combinations that are not systems”Half the grid of a two-average system is nonsense: every combination where the “fast” average is slower than the “slow” one. Those runs still consume time and still print rows.
The reserved variable Exclude is the documented tool. It is a variable, not a function —
there is no Exclude() page in the function reference — and setting it true removes the
current run from scan, exploration and backtest statistics, and from buy-and-hold
calculations:
Fragment — not a complete formula
FastPeriod = Optimize( "Fast MA", 20, 5, 60, 5 );SlowPeriod = Optimize( "Slow MA", 120, 60, 260, 10 );
Exclude = FastPeriod >= SlowPeriod;The formula this part will optimize
Section titled “The formula this part will optimize”We need a system with a parameter space worth looking at: two parameters, so the result is a surface rather than a line; a sensible portfolio setup, so the numbers are not fantasy; and rules simple enough that nothing hides behind them. A two-moving-average crossover is the obvious candidate — not because it is a good system, but because everybody already understands what its parameters mean, which lets the lesson be about the search rather than about the rules.
Complete formula
Section titled “Complete formula”Complete runnable AFL
// optimizable-trend-system.afl// Part 31 - Optimization//// A deliberately plain two-parameter trend system, written so that the SAME// file runs as a chart, a scan, a backtest and an optimization without being// edited. Its job in this part is not to be a good system. Its job is to give// us a parameter space whose shape we can look at.//// ASSUMPTIONS - every number this file produces inherits all of them:// - Daily bars. Portfolio backtest: one account, cash only, no margin.// - Signals are computed from the close of the signal bar. Every entry and// exit is delayed one bar and filled at the NEXT bar's open, so nothing is// ever filled at a price that was not yet known when the signal formed.// - Costs: commission is charged as a percentage of trade value, per side,// and is set from the formula so that it is recorded with the run. The// figure below is a stand-in for YOUR broker's commission plus an// allowance for the spread you would have crossed. Raise it, do not// lower it.// - Liquidity: a symbol may not be bought unless its average turnover over// the past 50 bars clears the floor below. Without this filter the// optimizer finds its best cells in symbols you could never have// transacted in, and the surface you read is largely fiction.// - No survivorship correction is applied. If the database contains only// companies that still exist, every result is optimistic by an amount// this file cannot measure.//// Both optimized variables are declared exactly ONCE, at the top, with unique// description strings. The User's Guide requires both: each call generates its// own optimization loops, and duplicated description strings produce garbage.
_SECTION_BEGIN( "Optimizable Trend System" );
// --- The parameter space -------------------------------------------------// Optimize( "description", default, min, max, step ) hands back "default" in// every mode except Optimization. The two numbers below are therefore also the// parameters this file trades with when you press Backtest, Scan or Explore.FastPeriod = Optimize( "Fast MA", 20, 5, 60, 5 );SlowPeriod = Optimize( "Slow MA", 120, 60, 260, 10 );
// Combinations where the "fast" average is not actually faster are not a// system. Exclude suppresses their statistics so they do not clutter the// result list or waste report time. It does not reduce the number of// combinations the optimizer enumerates.Exclude = FastPeriod >= SlowPeriod;
// --- Fixed assumptions ---------------------------------------------------StartingEquity = 100000;MaxPositions = 10;CommissionPct = 0.30; // percent of trade value, charged per sideMinTurnover = 2000000; // in the currency of the databaseTurnoverPeriod = 50;
SetOption( "InitialEquity", StartingEquity );SetOption( "MaxOpenPositions", MaxPositions );SetOption( "CommissionMode", 1 ); // 1 = percent of trade valueSetOption( "CommissionAmount", CommissionPct );SetOption( "AllowPositionShrinking", True );
// Ten slots, so each entry asks for a tenth of current portfolio equity.SetPositionSize( 100 / MaxPositions, spsPercentOfEquity );
// Signals form on the close; trades happen on the following open.SetTradeDelays( 1, 1, 1, 1 );BuyPrice = Open;SellPrice = Open;
// --- Liquidity filter ----------------------------------------------------// MA() is Null until it has enough bars, and any comparison with Null is Null,// so the filter is written to be explicitly false during warm-up rather than// silently undefined.Turnover = Close * Volume;AvgTurnover = MA( Turnover, TurnoverPeriod );LiquidEnough = NOT IsNull( AvgTurnover ) AND AvgTurnover >= MinTurnover;
// --- The rules -----------------------------------------------------------FastAvg = MA( Close, FastPeriod );SlowAvg = MA( Close, SlowPeriod );
Buy = Cross( FastAvg, SlowAvg ) AND LiquidEnough;Sell = Cross( SlowAvg, FastAvg );
// When more symbols signal on one bar than there is capital for, the// backtester needs a rule for choosing between them. Ranking by how far price// sits above its slow average is one choice among many, and it is part of the// system being optimized - not a neutral detail.PositionScore = 100 * ( Close - SlowAvg ) / SlowAvg;
// --- Chart output, so the same file can also be read visually -------------Plot( Close, "Close", colorDefault, styleCandle );Plot( FastAvg, "Fast MA(" + FastPeriod + ")", colorBlue, styleLine );Plot( SlowAvg, "Slow MA(" + SlowPeriod + ")", colorRed, styleLine );
_SECTION_END();How it works
Section titled “How it works”The file falls into five sections. The parameter space comes first, in the two
Optimize() calls and the Exclude line, exactly as the guide requires. The fixed
assumptions follow: starting equity, ten position slots, commission as a percentage of
trade value set through SetOption( "CommissionMode", 1 ) and
SetOption( "CommissionAmount", … ), and one-bar trade delays with fills at the next open.
Setting those from the formula rather than the Settings dialog means they travel with the
file, so a run is reproducible from the file alone.
The liquidity filter computes average turnover — close times volume, averaged — and
compares it to a floor. It is written as NOT IsNull( AvgTurnover ) AND AvgTurnover >= …
rather than the shorter comparison alone, because MA() returns Null during warm-up and any
comparison against Null is Null rather than false.
The rules are two crossovers. The ranking line sets PositionScore to the
percentage distance of price above its slow average, which decides who gets a slot when more
symbols signal on one bar than there is cash for. That line is part of the system, not
plumbing: change it and the optimization surface changes.
Key functions
Section titled “Key functions”Optimize( "description", default, min, max, step )— declares one swept variable; returnsdefaultoutside optimization.Exclude— a reserved variable. True suppresses statistics for the current run.SetOption( "CommissionMode", 1 )— commission expressed as a percentage of trade value; mode 2 is a fixed amount per trade and mode 3 is per share or contract.SetTradeDelays( buydelay, selldelay, shortdelay, coverdelay )— shifts the four signal arrays inside the backtester. It shifts nothing else: price arrays,PositionSizeandPositionScoreare untouched.SetPositionSize( size, method )withspsPercentOfEquity— size as a percentage of portfolio-level equity.
Expected result
Section titled “Expected result”Applied to a chart, you see candles with two moving averages using the default periods —
20 and 120 — because that is what Optimize() returns outside an optimization. Sent to the
Analysis window and backtested, you get one report for the 20/120 pair. Optimized, you get a
result list with one row per surviving combination and two extra columns at the far right
headed Fast MA and Slow MA.
Test it
Section titled “Test it”Run a Backtest first and write down two numbers from the report: Annual Return % and the
number of trades. Then edit the two defaults to some other pair inside the ranges, apply,
and backtest again. Both numbers should change. If they do not, the formula is not reading
your edits — check that you applied the formula rather than just saving it, and that you
edited the second argument of Optimize() and not the third.
Common errors
Section titled “Common errors”- Editing
mininstead ofdefault. The backtest keeps using the old default and you conclude the parameters do not matter. - Two
Optimize()calls with the same description string. No error; garbage results. - Forgetting
Exclude. The result list fills with combinations in which the fast average is slower than the slow one, and some of them will float to the top, because a system that trades almost never can post a striking-looking ratio. - Leaving costs at zero. The optimizer will then reliably prefer the shortest holding periods available in your grid, since their only disadvantage has been switched off.
Extension
Section titled “Extension”Add a third Optimize() call for the turnover floor and watch the run time. Before you
start it, work out the new combination count on paper. That arithmetic is the point of the
exercise; running it is optional.
Running the optimization
Section titled “Running the optimization”One optimization run, end to end
- Declare the spaceOne Optimize() call per parameter, at the top, unique descriptions
- Count it(max − min)/step + 1 per parameter, then multiply
- Set Apply to and RangeWhich symbols, which dates — recorded as part of the result
- Press OptimizeOne complete backtest per combination
- Read the whole listNot just the top row
The New Analysis window is where this happens. Apply to chooses All symbols, Current
symbol or Filter; Range chooses All quotations, N recent bars, N recent days, or From-To
dates. Both belong in your notes, because a result without a universe and a period attached
is not a result.
The drop-down arrow on the Optimize button holds four commands: Optimize, Walk-Forward, 3D Optimization chart and Individual Optimize. Only the first is in scope here; the 3D chart appears in the next lesson and walk-forward is Part 32.
Two practical notes before you start a long run:
- The warning threshold. Settings → Report tab has a checkbox, Warn before running time-consuming optimizations, which triggers above 300 steps. If you dismiss that dialog by reflex you have removed the one thing standing between you and an overnight run you did not intend.
- Threads. AmiBroker’s threading model is one operation on one symbol equals one thread, so an optimization over many symbols parallelises well while a single-symbol optimization is single-threaded. Individual Optimize, added in 5.70, is the documented exception: it spreads a single-symbol optimization across cores, at the cost of not supporting the custom backtester and not supporting the smart search engines. The Standard edition allows 2 threads per Analysis window and the Professional edition up to 32, so the same run can differ by more than an order of magnitude in wall-clock time between editions. Neither edition changes the results.
Reading the results table
Section titled “Reading the results table”One row per combination. The parameter values occupy the last columns by default — the guide
says so explicitly — which on a wide result list means scrolling right past every metric to
find out which run you are even looking at. SetOption( "ExtraColumnsLocation", 1 ) moves
them to the front. The guide notes this changes the visual order only, not the export or
copy-paste order, so what you copy out keeps its original arrangement.
Then the fact that changes how you should read the whole thing:
Sorting by any column is supported, and the guide names the obvious ones: lowest drawdown, lowest number of trades, largest profit factor, lowest market exposure, highest risk-adjusted annual return. Two habits are worth forming now. First, always sort by the number of trades once, just to see how thin the extremes are. Second, look at the rows adjacent in parameter space to whichever row you liked — which is the whole subject of the next two lessons.
One more default worth knowing: backtest reports are disabled during optimization. The
SetOption reference states it directly under GenerateReport. You get the result list, not
several hundred HTML reports, which is almost always what you want; forcing full reports with
SetOption( "GenerateReport", 1 ) is possible and will cost you disk and time in proportion
to the grid.
Optimize() takes five required arguments and returns a number. Outside Optimization mode
it returns its default, so the same file charts, scans, backtests and optimizes unchanged —
and so a backtest run straight after an optimization silently uses the default rather than
the winner. Nothing is written back into your formula; you type the values in.
Values run inclusively from min to max, so one call produces (max − min)/step + 1
tests, and multiple calls multiply. The documented ceilings are 64 variables and a 264
search space, and the practical ceiling is far lower. Exclude prunes meaningless
combinations from the statistics without shrinking the declared space.
And the result list arrives sorted by Net % profit, which is a presentation choice rather than a judgement about your system. What to do with the rest of that list is the rest of this part.
Check your understanding
Sources for this lesson
9 verified · checked 2026-08-31
- 01AmiBroker AFL Function Reference — Optimizeamibroker.com/guide/afl/optimize.html2026-08-31
- 02AmiBroker User's Guide — How to optimize a trading systemamibroker.com/guide/h_optimization.html2026-08-31
- 03AmiBroker User's Guide — Using New Analysis windowamibroker.com/guide/h_newanalysis.html2026-08-31
- 04AmiBroker AFL Function Reference — SetOptionamibroker.com/guide/afl/setoption.html2026-08-31
- 05AmiBroker AFL Function Reference — Paramamibroker.com/guide/afl/param.html2026-08-31
- 06AmiBroker Knowledge Base — Using optimum parameter values in backtestingamibroker.com/kb/2015/01/02/using-optimum-parameter-values-in-backtesting2026-08-31
- 07AmiBroker Knowledge Base — Using Exclude statement to skip unwanted optimization stepsamibroker.com/kb/2015/02/05/using-exclude-statement-to-skip-unwanted-optimization-steps2026-08-31
- 08AmiBroker User's Guide — Efficient use of multi-threadingamibroker.com/guide/h_multithreading.html2026-08-31
- 09AmiBroker User's Guide — System test settings window§ Report tabamibroker.com/guide/w_settings.html2026-08-31
Every technical claim on this page was checked against the official AmiBroker documentation on the date shown. Where the course disagrees with folklore, the source is how you can tell which one to trust.