Challenge: State or Event?
Six formulas follow. Every one of them compiles. Every one of them runs without a warning. Every one of them was written by somebody who understood what they wanted and expressed it incorrectly, and every one produces a symptom that people routinely misdiagnose as a problem with AmiBroker, with their data, or with their computer.
Your job is to say what each one actually computes, why the reported symptom follows from that, and what the correct formula is. The point is not to spot typos. There are none. The point is to practise the only skill that reliably finds this class of bug: measuring what an array contains instead of reasoning about what it ought to contain.
How to use this page
Section titled “How to use this page”Work in this order, and resist the temptation to scroll.
- Read all six problems. Do not look at the hints.
- For each one, write down — on paper, in a comment, anywhere — what you think the array actually contains and why the symptom follows.
- Run the diagnosis procedure below on the ones you are unsure about, using the audit tool supplied here on real data.
- Only then read the hints, which are staged: the first is a nudge, the second names the area, the third is close to the answer.
- Read the worked solutions last, including the ones you got right — the root cause is often not the thing that first catches the eye.
Give yourself the full thirty-five minutes. Four of the six can be identified without running anything, if you read carefully.
The diagnosis procedure
Section titled “The diagnosis procedure”This is the procedure, in the order that finds problems fastest.
Step 1 — Say the rule out loud
Section titled “Step 1 — Say the rule out loud”Turn the formula back into an English sentence, then compare that sentence with what the author says they wanted. Words like when, crosses, breaks and the moment describe events. Words like while, during, as long as and is above describe states. A mismatch between the sentence and the code is found here more often than anywhere else, and it costs nothing.
Step 2 — Measure the arrays
Section titled “Step 2 — Measure the arrays”For every Boolean array in the formula, get two numbers: how many bars it is true, and the length of its longest unbroken run of true bars.
- Longest run of 1 → the array is event-shaped.
- Longest run in the tens or hundreds → the array is a state.
- True on zero bars → the condition is impossible, or the warm-up swallowed it.
Compare those numbers with what the rule was supposed to do. A trigger that is true on a third of all bars is not a trigger. A market filter that is true on 0.2% of bars is not a filter.
Step 3 — Check the argument order
Section titled “Step 3 — Check the argument order”ValueWhen, HighestSince, LowestSince, HighestSinceBars, LowestSinceBars
and SumSince all take the condition first. Every one of them compiles with
the arguments reversed. The tell is that the output is in the wrong units: ones
and zeros where you expected prices, or prices where you expected a count.
Cross( a, b ) and Cross( b, a ) are different questions, not a question and
its negation.
Step 4 — Check every window and every sign
Section titled “Step 4 — Check every window and every sign”For each Ref(): is the period negative? A positive period reads the future.
For each HHV, LLV or Sum: does the window include the current bar? It does —
that is documented — so a comparison of the current bar against its own window is
either impossible or trivial.
For each anchored measure: is the anchor an event, or is it a rolling window pretending to be one?
Step 5 — Ask what will consume the array
Section titled “Step 5 — Ask what will consume the array”The same defective array behaves differently in different parts of AmiBroker. A
state-shaped Buy floods a scan, a chart and an alert, while the default
backtest mode quietly discards the redundant entries and produces a report that
looks reasonable. So “the backtest looks fine” is not evidence that the array is
right, and “the scan is broken” is often evidence that it is not.
The instrument
Section titled “The instrument”Answer Step 2 of the procedure without writing a new formula each time: paste in any pair of rules and get back their true-bar counts, their longest unbroken runs and the size of their warm-up gaps.
The complete formula
Section titled “The complete formula”Complete runnable AFL
// signal-audit-tool.afl// Part 9 - Challenge: State or Event?//// A diagnosis instrument, not a trading system. Paste the rules under test into// the marked block and it answers the questions that separate a state bug from// an event bug://// How many bars is each array true?// What is the longest unbroken run of true bars?// Does the array ever become true at all?// How many bars at the left edge are empty rather than false?//// The decisive column is the longest unbroken run. A Buy array whose longest run// is 1 is event-shaped. A Buy array whose longest run is 60 is state-shaped. Two// of the six broken formulas in this challenge are identified by that column// alone, and two more by the "ever true" column.//// Assumptions:// - Replace only the marked block. Everything below it is measurement.// - Counts cover the bars delivered to this run; SetBarsRequired( sbrAll )// asks for the whole history.// - Run length is measured as "bars since the array was last false". On a// symbol whose Buy array is true from the very first delivered bar, that// count has never been reset, so treat a run length equal to the bar count// as "always true" rather than as a precise measurement.// - The tool describes the arrays. It has no opinion on whether the rule is// a good idea.
SetBarsRequired( sbrAll );
// ===========================================================================// The formula under test - replace this block// ===========================================================================
Average = MA( Close, 50 );
Buy = Close > Average;Sell = Close < Average;
// ===========================================================================// Measurement - leave this alone// ===========================================================================
// IsTrue() maps Null to 0 and any non-zero value to 1, so a warm-up Null cannot// poison the counts the way a raw AND would.BuyTrue = IsTrue( Buy );SellTrue = IsTrue( Sell );
// Bars since the array was last false. On a false bar this is zero, so the// running maximum of it is the longest unbroken true run so far.BuyRun = BarsSince( NOT BuyTrue );SellRun = BarsSince( NOT SellTrue );
BarsInRange = Cum( 1 );BuyBars = Cum( BuyTrue );SellBars = Cum( SellTrue );
LongestBuyRun = Highest( BuyRun );LongestSellRun = Highest( SellRun );
// How much of the left edge is empty rather than false? A long leading Null run// means a warm-up dependency you may not have noticed.BuyNulls = NullCount( Buy, 1 );SellNulls = NullCount( Sell, 1 );
Filter = Status( "lastbarinrange" );
AddColumn( BarsInRange, "Bars in range", 1.0 );AddColumn( BuyBars, "Bars where Buy is true", 1.0 );AddColumn( LongestBuyRun, "Longest unbroken Buy run", 1.0 );AddColumn( SellBars, "Bars where Sell is true", 1.0 );AddColumn( LongestSellRun, "Longest unbroken Sell run", 1.0 );AddColumn( BuyNulls, "Leading Nulls in Buy", 1.0 );AddColumn( SellNulls, "Leading Nulls in Sell", 1.0 );AddColumn( 100 * BuyBars / BarsInRange, "Buy true, % of bars", 1.2 );How it works
Section titled “How it works”The block at the top is the only part you edit. Everything below it measures.
IsTrue() converts each array to strict ones and zeros so that warm-up empties
count as false instead of propagating. Cum() of that gives the total number of
true bars. The run-length trick is the interesting part: BarsSince( NOT BuyTrue )
is the number of bars since the array was last false, which is zero on every false
bar and climbs through each true run — so the running maximum of it,
Highest( BuyRun ), is the longest unbroken run so far. NullCount( Buy, 1 )
reports how much of the left edge is empty rather than false, which is how a long
warm-up dependency announces itself.
Filter = Status( "lastbarinrange" ) reduces the report to one row per symbol,
and SetBarsRequired( sbrAll ) asks for the whole history so the totals mean what
they say.
Key functions
Section titled “Key functions”IsTrue( ARRAY )— mapsNullto 0, non-zero to 1.BarsSince( ARRAY )— used here on the negation of a condition, to measure run length rather than event age.Highest( ARRAY )— the running all-history maximum, with no window; exactly right for “the longest run so far”.NullCount( array, mode )— the length of the leading empty run.Status( "lastbarinrange" )— one row per symbol.
Expected result
Section titled “Expected result”Test it
Section titled “Test it”Point it at two arrays whose answers you already know. Replace the block with
Buy = Close > 0; and Sell = Close < 0;. The buy count should equal the bar
count, the longest buy run should equal the bar count, the sell count should be
zero — and the longest sell run should be zero too, which incidentally tells you
what BarsSince returns when its condition is true on every bar.
Common errors
Section titled “Common errors”- Leaving
Filter = 1. One row per bar per symbol, which is unreadable. - Reading the run length on an array that is true from the very first delivered bar. The count has never been reset, so treat a run equal to the bar count as “always true” rather than as a measurement.
- Forgetting that the numbers describe the delivered range, not the database.
Extension
Section titled “Extension”Add a column for the number of bars on which Buy and Sell are both true.
Two rules that are supposed to be opposites should never overlap, and an overlap
is a fast explanation for ExRem and Flip behaving oddly.
The six problems
Section titled “The six problems”Problem 1 — The wall of arrows
Section titled “Problem 1 — The wall of arrows”What the author wanted. “Mark the chart where the fifty-bar average moves above the two-hundred-bar average, and where it moves back below. Then run it as a daily scan to see which symbols crossed today.”
What they wrote.
Fragment — not a complete formula
Trend = MA( Close, 50 ) > MA( Close, 200 );
Buy = Trend;Sell = NOT Trend;
PlotShapes( Buy * shapeUpArrow, colorGreen, 0, Low, -20 );PlotShapes( Sell * shapeDownArrow, colorRed, 0, High, 20 );Symptom. The chart shows a continuous green band under roughly half the bars and a continuous red band above the rest; individual arrows are indistinguishable. The daily scan reports almost every symbol in the watch list, every single day. The author has checked the scan settings three times.
Problem 2 — The system with no trades
Section titled “Problem 2 — The system with no trades”What the author wanted. “Enter when price crosses above its twenty-bar average, but only in a rising market and only with above-average volume.”
What they wrote.
Fragment — not a complete formula
Buy = Cross( Close, MA( Close, 20 ) ) AND Cross( MA( Close, 50 ), MA( Close, 200 ) ) AND Cross( Volume, MA( Volume, 50 ) );
Sell = Cross( MA( Close, 20 ), Close );Symptom. The backtest report is empty. Not “few trades” — zero, across five hundred symbols and twenty years. The author has widened the date range, changed the universe and reinstalled AmiBroker.
Problem 3 — The breakout that never breaks out
Section titled “Problem 3 — The breakout that never breaks out”What the author wanted. “Buy when the close breaks above the highest high of the previous twenty bars, on at least twice the average volume.”
What they wrote.
Fragment — not a complete formula
Buy = Close > HHV( Close, 20 ) AND Volume > 2 * MA( Volume, 50 );
Sell = Close < LLV( Close, 20 );Symptom. Zero signals. The author concluded that the volume filter was too
strict, relaxed it to 1.2 *, then removed it entirely. Still zero signals. They
now believe their data has no breakouts in it.
Problem 4 — The trailing stop that moves on its own
Section titled “Problem 4 — The trailing stop that moves on its own”What the author wanted. “Exit when price falls two average true ranges below the highest high reached since the entry.”
What they wrote.
Fragment — not a complete formula
Buy = Cross( Close, MA( Close, 50 ) );
StopLevel = HHV( High, 20 ) - 2 * ATR( 14 );Sell = Close < StopLevel;Symptom. The backtest produces trades, so nothing looks obviously wrong. But
plotting StopLevel shows it moving up and down before any trade exists, and on
several trades the level falls while the position is open. Two trades exit on a
bar where price made a new high.
Problem 5 — The entry price in the thousands of per cent
Section titled “Problem 5 — The entry price in the thousands of per cent”What the author wanted. “Record the close at each entry signal and show the open profit as a percentage.”
What they wrote.
Fragment — not a complete formula
Buy = Cross( Close, MA( Close, 50 ) );
EntryPrice = ValueWhen( Close, Buy );ProfitPct = 100 * ( Close - EntryPrice ) / EntryPrice;
Filter = Buy;AddColumn( EntryPrice, "Entry price", 1.2 );AddColumn( ProfitPct, "Open profit %", 1.2 );Symptom. The entry-price column contains only 0.00 and 1.00. The profit column contains enormous numbers and empty cells. The author suspects a currency or decimal-places setting.
Problem 6 — The extraordinary backtest
Section titled “Problem 6 — The extraordinary backtest”What the author wanted. “Buy when twenty-bar momentum is positive and price is above its fifty-bar average; sell when momentum turns negative.”
What they wrote.
Fragment — not a complete formula
Momentum = Close - Ref( Close, 20 );
Buy = Momentum > 0 AND Close > MA( Close, 50 );Sell = Momentum < 0;Symptom. The equity curve is almost a straight line. The maximum drawdown is under two per cent. The win rate is above eighty per cent. The author is delighted and is about to trade it.
Each problem gets three hints of increasing directness. Take the smallest number you need.
Hints for problem 1
Section titled “Hints for problem 1”- How many bars is
Trendtrue on a ten-year chart? Roughly, not exactly. - The author’s sentence contains the word “moves”. What kind of array does that word describe?
Trendis a state. The chart and the scan are both reporting it faithfully.
Hints for problem 2
Section titled “Hints for problem 2”- Estimate how often each of the three
Cross()calls is true, per year. - Now estimate how often all three are true on the same bar.
- Two of the three conditions were meant as context, not as triggers.
Hints for problem 3
Section titled “Hints for problem 3”- Re-read the documented definition of
HHV’speriodsargument. There is a parenthesis in it that decides this problem. - Ask whether
Close > HHV( Close, 20 )can be true for any value of any close, on any symbol, ever. - The
Sellrule has the same defect, in the other direction — which is why removing the volume filter changed nothing.
Hints for problem 4
Section titled “Hints for problem 4”- On bar 3 of a trade, which bars does
HHV( High, 20 )look at? - How many of those bars happened before the entry?
- The author’s sentence says “since the entry”. Which function anchors at a condition rather than sliding a window?
Hints for problem 5
Section titled “Hints for problem 5”- What are the units of the entry-price column? What should they be?
ValueWhenhas a documented argument order. Check it.Closeis non-zero on every bar, so as a condition it is true on every bar.
Hints for problem 6
Section titled “Hints for problem 6”- Which functions in this formula can read a bar that had not printed yet?
- There is exactly one, and it appears once.
- Read the sign of the second argument to
Ref(), then read the documented meaning of a positive period.
Worked solutions
Section titled “Worked solutions”Read these even for the problems you solved. The root cause is frequently one level below the thing that first looks wrong.
Solution 1 — The wall of arrows
Section titled “Solution 1 — The wall of arrows”Root cause. Trend is a state. Buy = Trend assigns that state to a signal
array, so Buy is true on every bar of every stretch where the fifty-bar average
is above the two-hundred-bar average — on a ten-year daily chart, plausibly a
thousand bars or more.
Why the symptom follows. PlotShapes() draws a shape on every true bar, so a
thousand true bars produce a thousand arrows, which read as a band. A Scan reports
every bar that passes, so every symbol currently in an uptrend appears every day.
Nothing is misconfigured; both tools are reporting the array accurately.
The fix.
Fragment — not a complete formula
Buy = Cross( MA( Close, 50 ), MA( Close, 200 ) );Sell = Cross( MA( Close, 200 ), MA( Close, 50 ) );Why it works. Cross() is true only on the bar the relationship changed, which
is what “moves above” means. Note that the Sell line swaps the arguments rather
than negating anything — Cross( b, a ) is a different array, not NOT Cross( a, b ).
What else to check. If the author genuinely wants the state as well — for
shading, or as a filter on some other rule — keep it in its own variable:
TrendUp = MA( Close, 50 ) > MA( Close, 200 ); and use it where a state belongs.
ExRem( Trend, NOT Trend ) would also produce one signal per run, but it is the
wrong tool here: the rule was always meant to be an event, and expressing it as
one is clearer than thinning a state after the fact.
Solution 2 — The system with no trades
Section titled “Solution 2 — The system with no trades”Root cause. Three events ANDed together. A twenty-bar crossing might happen ten times a year; a fifty-over-two-hundred crossing perhaps twice a decade; a volume crossing dozens of times a year. Requiring all three on the same bar makes the rule almost impossible to satisfy, and across the tested universe it was satisfied zero times.
Why the symptom follows. An empty Buy array produces no trades, so the
backtest report is genuinely empty rather than broken. Widening the date range
cannot help, because the problem is a conjunction of rare moments rather than a
shortage of data.
The fix.
Fragment — not a complete formula
TrendUp = MA( Close, 50 ) > MA( Close, 200 ); // STATE: market contextVolumeOK = Volume > MA( Volume, 50 ); // STATE: liquidity contextTrigger = Cross( Close, MA( Close, 20 ) ); // EVENT: the moment
Buy = Trigger AND TrendUp AND VolumeOK;Sell = Cross( MA( Close, 20 ), Close );Why it works. One event supplies the timing; the two states supply the context. Read as a sentence: at the moment price crosses its twenty-bar average, while the longer averages are in the right order and while volume is above average.
What else to check. Run the audit tool on TrendUp and VolumeOK before
trusting them. If either is true on almost every bar it is not filtering anything;
if either is true on almost no bars, it will suppress the trigger almost entirely.
The occurrence-rate measurement from earlier in this part is the right instrument.
Solution 3 — The breakout that never breaks out
Section titled “Solution 3 — The breakout that never breaks out”Root cause. The periods argument of HHV is documented to include the
current day. HHV( Close, 20 ) on any bar is therefore the maximum of twenty
values, one of which is that bar’s own close. A number cannot be strictly greater
than the maximum of a set containing it, so Close > HHV( Close, 20 ) is false on
every bar of every symbol.
Why the symptom follows. The volume filter was never the constraint. Buy was
already an array of zeros before the AND, so relaxing or removing the second
condition could not change anything. The Sell rule has the mirror-image defect —
Close < LLV( Close, 20 ) is likewise never true — which is why the system had no
exits either.
The fix.
Fragment — not a complete formula
PriorHigh = Ref( HHV( High, 20 ), -1 ); // the window that ended last barPriorLow = Ref( LLV( Low, 20 ), -1 );
Buy = Cross( Close, PriorHigh ) AND Volume > 2 * MA( Volume, 50 );Sell = Cross( PriorLow, Close );Why it works. Shifting the window back one bar gives a level that was already
fixed before the current bar opened, so the comparison is between the current close
and a set that does not contain it. Cross() then makes the breakout an event
rather than a state, which stops the rule reporting every bar of an extended run
above the old high.
What else to check. Decide explicitly whether you want a closing breakout
(Cross( Close, PriorHigh )) or an intrabar one (High > PriorHigh), and write
the choice down. They are different rules, and the intrabar version carries a fill
assumption the closing version does not.
Solution 4 — The trailing stop that moves on its own
Section titled “Solution 4 — The trailing stop that moves on its own”Root cause. HHV( High, 20 ) is a rolling window, not an anchor. On bar 3 of a
trade it covers the entry bar, the two bars after it, and seventeen bars from
before the entry — bars that have nothing to do with the position. As the trade
progresses, old bars drop out of the window, so the “highest high” can fall, and
with it the stop level.
Why the symptom follows. The level moves before any trade exists because the window exists on every bar regardless of positions. It falls during a trade whenever a high from before the entry rolls out of the twenty-bar window. And a trade can exit on a bar that made a new high, because the stop is computed from a window whose composition changed on that bar.
The fix.
Fragment — not a complete formula
Buy = Cross( Close, MA( Close, 50 ) );
PeakSinceEntry = HighestSince( Buy, High ); // condition firstStopLevel = PeakSinceEntry - 2 * ATR( 14 );Sell = Close < StopLevel;Why it works. HighestSince( EXPRESSION, ARRAY ) measures from the most recent
bar on which the condition was true, so the anchor is the entry and the peak can
only rise while the trade is open. Note the condition-first argument order, which
is Step 3 of the diagnosis procedure.
What else to check. Before the first Buy has ever occurred, the return value
of HighestSince is not stated in the official documentation — guard it with
Cum( Buy ) > 0 if the level feeds anything other than a plot. And for a real
system, AmiBroker’s own ApplyStop() is the proper mechanism for trailing stops:
it is evaluated by the backtester with the correct intrabar semantics, which a
hand-built Sell rule computed on closes cannot reproduce. Part 28 covers it.
Note also that Sell here is state-shaped — true on every bar below the stop. In
the default backtest mode that is harmless, because the first matching exit closes
the trade and the rest are ignored. In a scan or on a chart it would produce the
same wall of markers as Problem 1.
Solution 5 — The entry price in the thousands of per cent
Section titled “Solution 5 — The entry price in the thousands of per cent”Root cause. The arguments to ValueWhen are reversed. The documented signature
is ValueWhen( EXPRESSION, ARRAY, n = 1 ) — condition first, array to sample
second. The formula passes Close as the condition and Buy as the array, so it
computes “the value of Buy at the most recent bar on which Close was non-zero”.
Since the close is non-zero on every bar, that is simply Buy itself.
Why the symptom follows. EntryPrice therefore contains ones and zeros, which
is what the column shows. The profit calculation divides by it, giving a division
by zero on the zero bars — hence the empty cells — and a percentage computed
against a denominator of 1 on the rest, which is why the numbers are enormous.
Nothing to do with currency or decimal settings.
The fix.
Fragment — not a complete formula
Buy = Cross( Close, MA( Close, 50 ) );
EntryPrice = ValueWhen( Buy, Close );ProfitPct = 100 * ( Close - EntryPrice ) / EntryPrice;Why it works. With the condition first, ValueWhen samples the close on each
signal bar and holds it until the next signal — the sample-and-hold shelf from the
ValueWhen lesson.
What else to check. Two things. First, Filter = Buy means the report shows
only the signal bars, where the open profit is by definition zero; if the author
wanted to watch the profit evolve they need a different filter. Second, if the
system enters at the next bar’s open rather than at the signal bar’s close, the
honest entry price is:
Fragment — not a complete formula
TradeBar = Ref( Buy, -1 ); // the bar after the signalEntryPrice = ValueWhen( TradeBar, Open );And for anything that feeds a real evaluation, the backtester’s own trade prices — not a hand-built array — are the authority.
Solution 6 — The extraordinary backtest
Section titled “Solution 6 — The extraordinary backtest”Root cause. Ref( Close, 20 ) has a positive period. The official
documentation states that a positive period references periods in the future, and
its own example annotates the positive case as looking up the future. So
Momentum = Close - Ref( Close, 20 ) is the current close minus the close twenty
bars later — a quantity nobody could have known.
Why the symptom follows. Momentum > 0 means “price is lower now than it will
be in twenty bars”, so the rule buys only when it already knows price rises.
Momentum < 0 exits only when it already knows price falls. A near-straight
equity curve, a tiny drawdown and a very high win rate are the expected signature
of look-ahead bias, not evidence of a good idea.
The fix.
Fragment — not a complete formula
Momentum = Close - Ref( Close, -20 ); // minus: twenty bars AGO
Buy = Momentum > 0 AND Close > MA( Close, 50 );Sell = Momentum < 0;Why it works. A negative period reads backwards, so every input to the rule was available at the time the decision had to be made.
What else to check. The corrected rule is still state-shaped in both directions, which is fine for a default-mode backtest and wrong for a scan, a chart or an alert. And a plausible result from the corrected version deserves the same scepticism the implausible one should have received: Part 30 lists the other routes to look-ahead bias, several of which leave no plus sign to find.
What the six have in common
Section titled “What the six have in common”Five of the six defects are one of two things: an array of the wrong shape — a
state where an event was needed, or the reverse — or an array of the right shape
computed from the wrong bars. The sixth, the reversed ValueWhen, is an array of
the wrong units, which the audit tool exposes as clearly as the others.
None of them is a subtle language feature. All of them are visible in about ninety seconds of measurement:
- Count the true bars and the longest run. Wrong shape shows up here.
- Look at the units of every derived column. Reversed arguments show up here.
- Read the sign of every
Ref()and the window of everyHHV,LLVandSum. Wrong bars show up here.
That is the whole method, and it generalises far beyond this part. Part 12’s challenge on empty scans and Part 30’s collection of broken backtests are the same skill applied to different symptoms.
A formula that compiles has told you nothing about whether it is correct. AFL has no way to distinguish a state from an event, an anchored measure from a rolling one, or a backward shift from a forward one — all of them are arrays of numbers, and all of them are legal in every position.
What replaces the compiler is a habit: describe the rule in words, measure every Boolean array, check the argument order of the condition-first functions, check every sign and every window, and ask what part of AmiBroker is going to read the result. Six formulas that defeated their authors gave way to that procedure in this page, and the procedure is worth more than any of the six individual answers.
Check your understanding
Sources for this lesson
7 verified · checked 2026-08-31
- 01AFL Function Reference — Crossamibroker.com/guide/afl/cross.html2026-08-31
- 02AFL Function Reference — Refamibroker.com/guide/afl/ref.html2026-08-31
- 03AFL Function Reference — HHVamibroker.com/guide/afl/hhv.html2026-08-31
- 04AFL Function Reference — ValueWhenamibroker.com/guide/afl/valuewhen.html2026-08-31
- 05AFL Function Reference — HighestSinceamibroker.com/guide/afl/highestsince.html2026-08-31
- 06AFL Function Reference — ExRemamibroker.com/guide/afl/exrem.html2026-08-31
- 07AmiBroker User's Guide — Portfolio-level backtesting§ Backtest modesamibroker.com/guide/h_portfolio.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.