Moving Average Slope and Crossovers
A moving average by itself is a picture. To do anything with it — scan for it, count it, test it — you have to convert it into one of two things: a state, which is true across a span of bars, or an event, which is true on exactly one bar. Almost every mistake people make with crossovers comes from confusing the two, and the distinction is important enough that Part 9 devotes a whole lesson to it. This is the preview, using the tools you already have.
Slope: how you turn a curve into a number
Section titled “Slope: how you turn a curve into a number”“The average is rising” needs a definition before a computer can evaluate it. There are two sensible ones, and they behave very differently.
The one-bar difference. Compare the average to its own value on the previous bar:
Fragment — not a complete formula
Trend = MA( Close, 50 );Rising = Trend > Ref( Trend, -1 );Ref( array, -1 ) shifts the array one bar into the past; the negative sign means backwards,
which is the opposite of the convention you might expect from array indexing. This is exact
and cheap, and it is extremely twitchy: a 50-bar average that is climbing steadily can still
tick down for a single bar, and Rising flips to false for that bar.
The regression slope. Fit a least-squares straight line through the last n values and
take its gradient. AmiBroker provides this directly:
Fragment — not a complete formula
Slope = LinRegSlope( Close, 20 ); // gradient of the fitted lineEndPoint = LinearReg( Close, 20 ); // where that fitted line ends todayLinRegSlope( ARRAY, periods ) requires both arguments — there is no default period — and it
is a rolling fit: the value at each bar is the gradient of a line fitted to the last
periods bars ending at that bar. Using twenty bars to decide the direction rather than two
makes it far steadier than the one-bar difference.
Two more documented details. LinearReg returns the end point of the fitted line, not the
whole line and not its midpoint — it is frequently mistaken for a regression channel, which
AmiBroker has no single built-in function for. And to draw one static regression line across
a chart rather than a rolling one, the official example freezes the coefficients with
LastValue() first, because the rolling version gives you a different line at every bar.
The crossover as an event
Section titled “The crossover as an event”Cross is the operator that converts a comparison into an event. AmiBroker’s definition is
short and worth quoting exactly: Cross( ARRAY1, ARRAY2 ) gives 1 on the bar where ARRAY1
crosses above ARRAY2, and 0 otherwise. For the other direction you swap the arguments:
Cross( ARRAY2, ARRAY1 ). There is no third argument and no “cross either way” version.
State versus event on the same data
| Bar | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
|---|---|---|---|---|---|---|---|
Fast | 9.8 | 9.9 | 10.1 | 10.4 | 10.6 | 10.2 | 9.9 |
Slow | 10.0 | 10.0 | 10.0 | 10.1 | 10.2 | 10.3 | 10.3 |
Fast > Slow (state) | 0 | 0 | 1 | 1 | 1 | 0 | 0 |
Cross(Fast, Slow) (event) | 0 | 0 | 1 | 0 | 0 | 0 | 0 |
Cross(Slow, Fast) (event) | 0 | 0 | 0 | 0 | 0 | 1 | 0 |
That table is the whole idea. Fast > Slow describes a condition that persists. Cross( Fast, Slow )
marks the single bar on which the condition began. You need both, for different jobs:
- Filtering a universe for “instruments currently in an uptrend” wants the state.
- Generating an entry signal wants the event, or you buy the same instrument on every bar for the next four months.
- Measuring “what happened after the crossover” wants the event, because otherwise you are measuring the middle of the move as well as its start and calling both the same thing.
Fast and slow pairs
Section titled “Fast and slow pairs”A crossover of two averages is algebraically the same thing as one line crossing zero:
Fragment — not a complete formula
Fast = EMA( Close, 12 );Slow = EMA( Close, 26 );Difference = Fast - Slow;// These two are the same event, written two ways.CrossA = Cross( Fast, Slow );CrossB = Cross( Difference, 0 );Hold on to that. The difference between a fast and a slow exponential average, treated as an oscillator in its own right, is exactly the construction of MACD — which is the next lesson. A “moving average crossover system” and a “MACD zero-line system” with matching periods are the same rule wearing different clothes.
Choosing the pair introduces two free parameters where the single average had one. That is not a small thing. Every additional parameter is another axis you can slide until the history looks agreeable, and Part 31 shows how quickly that becomes self-deception. Note also that the pair is roughly scale-invariant: 10 and 20 produce a similar-looking chart to 20 and 40, just slower. If you find yourself tuning both numbers independently, you are usually tuning one thing — the ratio — plus one thing you did not mean to tune.
Whipsaws are not a defect of the indicator
Section titled “Whipsaws are not a defect of the indicator”In a sideways range the fast and slow averages interleave, and every interleaving is a crossover. This is not the indicator failing. It is the indicator faithfully reporting that the last twenty bars and the last fifty bars have nearly the same mean, which is what a range is.
You can count them rather than eyeball them:
Fragment — not a complete formula
Fast = EMA( Close, 20 );Slow = EMA( Close, 50 );AnyCross = Cross( Fast, Slow ) OR Cross( Slow, Fast );Crossings = Cum( AnyCross );Title = "Crossings so far: " + NumToStr( LastValue( Crossings ), 1.0 );Cum() runs a cumulative total from the first bar loaded, so Crossings at the last bar is
the number of crossings in the whole loaded history. Run that on a trending instrument and on
a range-bound one and compare. The number you get is the honest cost of the method.
Marking crossovers on the chart
Section titled “Marking crossovers on the chart”Fragment — not a complete formula
Fast = EMA( Close, 20 );Slow = EMA( Close, 50 );
Plot( Close, "Close", colorDefault, styleCandle );Plot( Fast, "EMA 20", colorBlue, styleLine | styleThick );Plot( Slow, "EMA 50", colorRed, styleLine | styleThick );
Up = Cross( Fast, Slow );Down = Cross( Slow, Fast );
// Odd shape values are drawn below the anchor, even ones above. The anchor here// is the Low array; the default offset of -12 is in screen pixels, not price.PlotShapes( Up * shapeUpArrow + Down * shapeDownArrow, IIf( Up, colorGreen, colorRed ), 0, IIf( Up, Low, High ) );
GraphXSpace = 5;Two documented details are doing work there. PlotShapes positions shapes by the
yposition argument, which defaults to the first plotted line; passing Low or High
anchors them to the bar instead. Its offset argument is in screen pixels, not price, and
negative values move shapes down — the default is -12. And GraphXSpace adds vertical
head- and foot-room to the pane as a percentage; without it, arrows near the top or bottom get
clipped. Despite the “X” in its name it controls vertical space.
The Up * shapeUpArrow + Down * shapeDownArrow idiom works because shapeNone is zero, so a
bar with no event contributes nothing. It is safe here because Fast cannot cross above and
below Slow on the same bar. In general, if two shape conditions can be true together, their
constants add to a third, unintended shape — use nested IIf in that case.
Turning this into a testable question
Section titled “Turning this into a testable question”Here is a question about crossovers you can actually answer, and it is not “do they work”.
On this instrument, over this history, what fraction of fast/slow crossovers were reversed by an opposite crossover within ten bars?
That is a reversal rate. It needs no forward-return assumption, no cost model and no trading rule — it is a property of the indicator on that data, and it tells you directly how much of what you are seeing is the range-market interleaving described above. You could compute it by counting crossings, counting those followed by an opposite crossing within ten bars, and dividing.
Notice what the question does not ask. It does not ask whether crossovers are profitable. That question needs entry prices, exit rules, costs, position sizing and a portfolio, all of which arrive in Part 28. Asking a smaller question that your current tools can answer honestly is better practice than asking the big one badly.
Slope is how you turn a curve into a number, and LinRegSlope gives you a steadier answer
than a one-bar difference — in price units per bar, so normalise before comparing symbols.
Cross( A, B ) marks the single bar on which A moved above B; swap the arguments for the
other direction. A state persists, an event does not, and using the wrong one is the most
common structural bug in a first trading rule. A crossover of two averages is the same event
as their difference crossing zero, which is the bridge to MACD. Whipsaws in a range are the
method working, not failing, and you can count them instead of arguing about them.
Check your understanding
Sources for this lesson
6 verified · checked 2026-08-31
- 01AFL Function Reference — LinRegSlopeamibroker.com/guide/afl/linregslope.html2026-08-31
- 02AFL Function Reference — LinearRegamibroker.com/guide/afl/linearreg.html2026-08-31
- 03AFL Function Reference — Crossamibroker.com/guide/afl/cross.html2026-08-31
- 04AFL Function Reference — Refamibroker.com/guide/afl/ref.html2026-08-31
- 05AFL Function Reference — PlotShapesamibroker.com/guide/afl/plotshapes.html2026-08-31
- 06AmiBroker User's Guide — Understanding how AFL language worksamibroker.com/guide/h_understandafl.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.