Cleaning Signals: ExRem, ExRemSpan and Flip
The previous lesson ended with a rule that produced fourteen hundred buy signals where it should have produced forty-six. The standard response, reached for within about thirty seconds by most people who hit it, is:
Fragment — not a complete formula
Buy = ExRem( Buy, Sell );Sell = ExRem( Sell, Buy );Those two lines are the official example on the ExRem page, and they usually
work. This lesson is about understanding them precisely enough to know when they
are the right answer, and honest enough to recognise the cases where applying them
hides a defect instead of fixing one.
What ExRem actually does
Section titled “What ExRem actually does”Fragment — not a complete formula
exrem( ARRAY1, ARRAY2 )The documented behaviour, stated exactly: it returns 1 on the first occurrence
of a true signal in ARRAY1, then returns 0 for every subsequent true signal in
ARRAY1 until ARRAY2 becomes true.
Three things follow from that definition, and all three matter.
It keeps the first signal of each run — not the best one, not the strongest, not a confirmed one. First.
It needs ARRAY2 to reset it. Without a true value in the second array, the
first signal is the only signal that will ever pass, for the rest of the data.
It discards information. After ExRem, the array no longer records whether
the underlying condition was still true on bar 900. If you need that later, keep
the raw condition in a separate variable.
ExRem thinning a pair of state-shaped rules, and Flip putting the state back
| Bar | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
|---|---|---|---|---|---|---|---|---|---|---|
RawBuy (a state)5 true bars | 0 | 1 | 1 | 1 | 0 | 0 | 0 | 1 | 1 | 0 |
RawSell (a state)5 true bars | 1 | 0 | 0 | 0 | 1 | 1 | 1 | 0 | 0 | 1 |
CleanBuy = ExRem( RawBuy, RawSell )2 true bars | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 |
CleanSell = ExRem( RawSell, CleanBuy )3 true bars | 1 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 1 |
Flip( CleanBuy, CleanSell )identical to RawBuy here | 0 | 1 | 1 | 1 | 0 | 0 | 0 | 1 | 1 | 0 |
The order of the two lines is part of the idiom
Section titled “The order of the two lines is part of the idiom”Look carefully at the second line of the official example:
Fragment — not a complete formula
buy = ExRem( buy, sell );sell = ExRem( sell, buy );By the time the second line runs, buy has already been overwritten by the
thinned version. The sell cleaning therefore resets against the cleaned buy
array, not the raw one. That is deliberate, and it is why the two lines produce a
neatly alternating buy-sell-buy-sell sequence.
Reversing the two lines, or computing both from copies of the originals, gives different arrays. Neither is illegal; both are different from what the documentation’s idiom produces. If you write it any way other than the documented one, write a comment saying why.
Flip is the documented inverse
Section titled “Flip is the documented inverse”Fragment — not a complete formula
flip( ARRAY1, ARRAY2 )A latch: 1 from the first true in ARRAY1 until a true occurs in ARRAY2, which
resets it to 0 until the next true in ARRAY1. The Flip page’s own example is
the round trip:
Fragment — not a complete formula
buy = ExRem( buy, sell );buy = Flip( buy, sell ); // multiple signals are back againThat pairing is worth holding on to, because it tells you exactly what the two
functions are: ExRem collapses a state into its leading edges, Flip expands
leading edges back into a state. Neither adds information. They convert between
two representations of the same thing, and the conversion is lossy in the ExRem
direction.
Flip is the tool for anything that needs to know “am I notionally in a
position?” — shading a chart, computing exposure, gating a second rule, or
counting bars in trade. Two properties deserve attention:
- It is sticky. If the reset array never becomes true, the state stays on to the end of the data. That is correct, and it is the argument for putting a fallback exit — a stop, a time limit — into the reset array rather than relying on the primary exit rule alone.
- Simultaneous set and reset is undocumented. The official page does not say which wins when both arrays are true on the same bar. If your two conditions can coincide, test it rather than assuming.
ExRemSpan, and what the documentation says about it
Section titled “ExRemSpan, and what the documentation says about it”Fragment — not a complete formula
exremspan( ARRAY1, numbars )The first non-zero bar passes through; all subsequent non-zero bars are suppressed
until numbars bars have elapsed since the initial signal, after which a new
signal may pass. There is no second array — nothing resets it except the passage
of time.
The official page then says something a course has an obligation to repeat:
This function is marked as obsolete. To implement N-bar stop you should use ApplyStop function instead.
There is also no worked example on the official page: the EXAMPLE field is empty. That is worth knowing before you go looking for one.
What the backtester does with unclean signals
Section titled “What the backtester does with unclean signals”This is the part that changes how most people think about ExRem, and it is
documented rather than folklore.
AmiBroker’s default backtest mode already removes redundant entry signals. The
portfolio-backtesting chapter of the User’s Guide describes it directly: buy and
sell signals are matched into trades, and an entry signal arriving after an entry
but before the matching exit is ignored. The chapter then states that this process
of removing excess signals is the same as the ExRem() function provides.
The Equity() page says the same thing from the other side: Equity( 1 ) updates
the buy, sell, short and cover arrays so that all redundant signals are removed
“exactly as it is done internally by the backtester”.
So for a plain, default-mode backtest, applying ExRem yourself typically changes
nothing at all. Where it does matter is everywhere else:
- Chart arrows.
PlotShapes()marks every true bar. Without thinning you get a band instead of a marker. - Scans and Explorations. Each true bar becomes a report row when the range covers more than one quotation.
- Alerts. One notification per true bar, for the duration of the state.
- Non-default backtest modes. And here the relationship reverses — see below.
When ExRem is the wrong fix
Section titled “When ExRem is the wrong fix”The honest version of this lesson is that ExRem is a presentation fix that is
often mistaken for a logic fix. Five situations where reaching for it makes
things worse:
The rule is a state and should have been an event. If you wrote
Buy = Close > MA( Close, 50 ) and meant “when price moves above the average”,
ExRem will tidy the output and leave the rule saying the wrong thing. The two
are not equivalent: Cross() fires when the relationship changes, while
ExRem-of-a-state fires on the first bar of a run that follows a true sell
signal — which, if the sell rule is also wrong or rare, can be a completely
different bar. Fix the rule.
You wanted a different signal than the first. ExRem keeps the earliest true
bar in each run. If your intention was “the first bar with confirming volume”, or
“the strongest setup this week”, ExRem silently substitutes a different
selection rule for yours. Express the selection you actually want, then thin.
The reset never fires. ExRem( Buy, Sell ) where Sell is rare, or where the
exit is handled entirely by ApplyStop() and so never appears in the Sell
array, produces exactly one entry signal in the whole history. The symptom — one
trade per symbol — looks like a data problem and is not.
The underlying rate is the real problem. A condition true on forty per cent of bars is not a trigger, and thinning it produces a tidy-looking array built on a meaningless rule. The occurrence-rate measurement from earlier in this part is the check that catches this; it takes a minute and it asks the right question.
Duplicate alerts have a different cause. Repeated alerts during a live session
are often produced by repeat scanning re-evaluating the same bar many times, not
by a state-shaped array at all. Part 25 deals with alert suppression properly, and
ExRem is not the tool for it.
Verifying that a signal array is clean
Section titled “Verifying that a signal array is clean”Replace “I applied ExRem, so it should be fine” with a number. The formula counts
raw signals against cleaned signals, rebuilds the state with Flip, and reports
how far the rebuild disagrees with the original — so you can see what was removed
and confirm that nothing else changed.
The complete formula
Section titled “The complete formula”Complete runnable AFL
// signal-cleaning-workbench.afl// Part 9 - Cleaning Signals: ExRem, ExRemSpan and Flip//// Counts raw signals against cleaned signals, so that what ExRem() removed is a// number on the screen rather than a belief. It also rebuilds the state with// Flip() and reports how far the rebuilt state differs from the original - the// cheapest available check that the cleaning did what you expected.//// One row per symbol. Run it over a watch list to find the instruments where a// rule is firing hundreds of times instead of a handful.//// Assumptions:// - The rules below are state-shaped on purpose. They are the kind of rule// that produces signal storms and sends people reaching for ExRem.// - The second ExRem line consumes the ALREADY THINNED first array. That// ordering is the documented idiom; swapping the two lines changes the// result, so do not reorder them casually.// - Counts cover the bars delivered to this run.// - Cleaning changes what a chart and a scan report. It does not by itself// change what the portfolio backtester does in its default mode, which// already discards redundant same-direction signals.
SetBarsRequired( sbrAll );
MaPeriod = 50;Average = MA( Close, MaPeriod );
// ---------------------------------------------------------------------------// Deliberately state-shaped rules: true on EVERY bar the condition holds// ---------------------------------------------------------------------------
RawBuy = Close > Average;RawSell = Close < Average;
// ---------------------------------------------------------------------------// Cleaning// ---------------------------------------------------------------------------
// ExRem( a, b ): keep the first true in a, then suppress further trues in a// until b is true. Note the second line uses CleanBuy, not RawBuy.CleanBuy = ExRem( RawBuy, RawSell );CleanSell = ExRem( RawSell, CleanBuy );
// Flip( set, reset ): latch on from the first clean entry until a clean exit.// This puts back the state that ExRem discarded.Holding = Flip( CleanBuy, CleanSell );
// ---------------------------------------------------------------------------// Counting// ---------------------------------------------------------------------------
BarsInRange = Cum( 1 );RawBuyCount = Cum( IsTrue( RawBuy ) );CleanBuyCount = Cum( IsTrue( CleanBuy ) );RawSellCount = Cum( IsTrue( RawSell ) );CleanSellCount = Cum( IsTrue( CleanSell ) );HoldingBars = Cum( IsTrue( Holding ) );
// Where the rebuilt state and the original state disagree. Expect a small// number, concentrated at the left edge before the first signal has occurred.RebuildGap = Cum( IsTrue( Holding ) != IsTrue( RawBuy ) );
Filter = Status( "lastbarinrange" );
AddColumn( BarsInRange, "Bars in range", 1.0 );AddColumn( RawBuyCount, "Raw buy trues (= bars the state held)", 1.0 );AddColumn( CleanBuyCount, "Buy signals after ExRem", 1.0 );AddColumn( RawSellCount, "Raw sell trues", 1.0 );AddColumn( CleanSellCount, "Sell signals after ExRem", 1.0 );AddColumn( HoldingBars, "Bars inside the Flip state", 1.0 );AddColumn( RebuildGap, "Bars where the rebuild disagrees", 1.0 );AddColumn( 100 * HoldingBars / BarsInRange, "Time in state, % of bars", 1.2 );How it works
Section titled “How it works”The rules at the top are deliberately state-shaped: price above and below a fifty-bar average. That guarantees the signal storm the lesson is about, and it makes the counts easy to interpret — for a state-shaped rule, the number of “raw signals” and the number of bars the state was true are the same number, which is itself the diagnosis.
The cleaning block follows the documented idiom exactly, including the ordering
that makes the second call consume the already-thinned first array. The Flip
call rebuilds the state, and RebuildGap counts the bars where the rebuild and
the original state disagree — the direct check that the ExRem and Flip pair
round-tripped.
Filter = Status( "lastbarinrange" ) gives one row per symbol, and
SetBarsRequired( sbrAll ) makes the cumulative counts cover the whole delivered
history rather than a QuickAFL slice.
Key functions
Section titled “Key functions”ExRem( ARRAY1, ARRAY2 )— keeps the first true of each run, resets on the second array.Flip( ARRAY1, ARRAY2 )— the latch that reverses the process.IsTrue( ARRAY )— mapsNullto 0 before counting, so warm-up bars cannot poison a total.Cum( ARRAY )— running totals, read at the last bar via the filter.Status( "lastbarinrange" )— one row per symbol.
Expected result
Section titled “Expected result”Test it
Section titled “Test it”Two checks, both quick.
First, the round trip. If RebuildGap is large — hundreds of bars rather than a
handful — the ExRem and Flip pair did not reverse each other, which usually
means the two rules can be true on the same bar. Add a column for
Cum( IsTrue( RawBuy ) AND IsTrue( RawSell ) ) and see whether it is non-zero.
Second, the reset. Temporarily replace RawSell with Close < 0, which is never
true on a price series. The cleaned buy count should collapse to 1 for every
symbol — a vivid demonstration of what happens when the resetting array never
fires, and the shape of a bug that produces exactly one trade per symbol.
Common errors
Section titled “Common errors”- Cleaning arrays that were already events. The counts will be almost identical before and after, which is the signal that the thinning was unnecessary.
- Reversing the two
ExRemlines. The output changes. If you need a different ordering, comment the reason. - Reading the “time in state” figure as an exposure estimate. It is the fraction of bars the condition held, not the fraction of capital deployed. Real exposure comes from the backtest report, in Part 29.
Extension
Section titled “Extension”Add a column comparing ExRem( RawBuy, RawSell ) with ExRemSpan( RawBuy, 20 )
to see how differently a reset-based filter and a time-based filter thin the same
array. Then read the note on the ExRemSpan page again and decide, for a rule of
your own, whether what you want is really an entry filter or really an exit.
ExRem( a, b ) keeps the first true value of each run in a and suppresses the
rest until b fires. Flip( a, b ) is the documented inverse: it turns a pair of
edges back into a continuous state. ExRemSpan() thins by elapsed bars instead of
by a reset array, and its own page marks it obsolete and points to ApplyStop()
for time-based exits.
The backtester’s default mode already discards redundant entry signals, in the same
way ExRem does. Cleaning therefore matters most for charts, scans, explorations
and alerts — and it actively works against the raw backtest modes, which exist to
use the signals ExRem throws away.
Above all, ExRem tidies output. It does not correct a rule that says the wrong
thing. Measure the array first; if the longest run of true bars is long and the
rule was meant to fire on a moment, the fix belongs in the rule.
Check your understanding
Sources for this lesson
6 verified · checked 2026-08-31
- 01AFL Function Reference — ExRemamibroker.com/guide/afl/exrem.html2026-08-31
- 02AFL Function Reference — ExRemSpanamibroker.com/guide/afl/exremspan.html2026-08-31
- 03AFL Function Reference — Flipamibroker.com/guide/afl/flip.html2026-08-31
- 04AFL Function Reference — Equityamibroker.com/guide/afl/equity.html2026-08-31
- 05AmiBroker User's Guide — Portfolio-level backtesting§ Backtest modesamibroker.com/guide/h_portfolio.html2026-08-31
- 06AmiBroker User's Guide — Pyramidingamibroker.com/guide/h_pyramid.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.