Skip to content
Level 1 · Chart ReaderLessonPart 02 · page 1 of 622 min
22Minutes
3AFL functions
4Sources
StandardRequires
AFL functions taught here3

OHLCV and What a Bar Hides

A candlestick looks like a picture of a trading day. It is not. It is a four-number summary of a trading day, drawn so that it resembles one. By the end of this lesson you will be able to say exactly which questions that summary can answer, which it cannot, and why no amount of cleverness in a formula recovers the difference.

AmiBroker keeps six arrays for every symbol in a database: one for the opening price, one for the high, one for the low, one for the close, one for volume and one for open interest. Every other series you will ever plot — moving averages, oscillators, bands, your own inventions — is computed from those six and is not stored at all.

In AFL they are named Open, High, Low, Close, Volume and OpenInt, and each has a one-or-two-letter abbreviation: O, H, L, C, V and OI. The names are not case-sensitive, so close and Close are the same array.

For a time-based bar, the four prices have a mechanical definition. Take every trade that printed inside the bar’s time window. The open is the price of the first one. The close is the price of the last one. The high and the low are the largest and smallest prices among all of them.

That is the whole definition. It contains no judgement, no smoothing and no opinion — which is exactly why two vendors can disagree about the same bar. They disagree about which prints belong in the window: whether off-exchange trades count, whether the opening and closing auctions count, whether cancelled or late-reported trades are removed, and where the window starts. Your vendor’s documentation, not a general rule, decides what your Open means.

Volume is the total size of the trades inside the window. It is a single unsigned total. There is no such thing as “buying volume” in this data: every trade has a buyer and a seller, and the field records the quantity, not the initiator.

Volume is also the field most often shipped in the wrong units. AmiBroker’s ASCII importer offers VOLUME, VOL1000 (thousands of shares), VOLMIL (millions) and a VOLFACTOR multiplier precisely because vendors do not agree. A liquidity filter that rejects anything under a million shares will reject an entire market if the file was in thousands, and it will do it silently.

OpenInt is not a price and not a volume. For futures and options it is the number of contracts currently outstanding — positions opened and not yet closed. It is a stock, in the accounting sense, where volume is a flow: volume resets every session, open interest carries over.

For equity data the field is usually zero or absent, and that is the correct answer rather than a defect. It is worth knowing the field exists for a second reason: because it is unused for stocks, it is sometimes borrowed as a place to park another number. The ASCII importer’s $RAWCLOSE2OI command does exactly that — it writes the unadjusted close, multiplied by 100, into the open-interest field. We will use that in the lesson on adjusted data.

There is a seventh price identifier in AFL, Avg, and it is different in kind. It is not read from the database — AmiBroker computes it as ( High + Low + Close ) / 3, the quantity usually called the typical price.

Here is a single synthetic bar, shown as the fields AmiBroker holds and the quantities people usually mean when they talk about the bar’s “shape”. The numbers are invented for the illustration; nothing here is a real instrument.

One bar, decomposed (synthetic values)

Range, body and close position are derived on demand. Only the first six rows exist in the database.
BarDay 1Day 2Day 3
Open100.00104.00103.20
High104.50104.20106.00
Low99.20101.00102.90
Close104.00101.40105.80
Volume1,200,000900,0002,400,000
OpenInt000
Avg = (H+L+C)/3102.57102.20104.90
Range = H − L5.303.203.10
Body = |C − O|4.002.602.60
Close position0.910.130.94
Range, body and close position are derived on demand. Only the first six rows exist in the database. Prices in this diagram are invented for the illustration. They are not market data and nothing should be inferred from them.

“Close position” is the close expressed as a fraction of the range: zero means the bar closed at its low, one means it closed at its high. It is a useful readout, and it is also the first thing in this lesson that is genuinely inferred rather than recorded.

How a bar is assembled, and what is dropped on the way

Section titled “How a bar is assembled, and what is dropped on the way”

Consider a bar built from ten trades. The bar keeps four of the ten prices — first, last, largest, smallest — and the sum of the sizes. Six prices, ten timestamps, ten sizes and the entire sequence are discarded.

The consequence is easiest to see by constructing two completely different trading days that compress to an identical bar.

Two intrabar paths, one identical bar (synthetic)

Both paths open at 100.0, close at 104.0, touch 104.5 and 99.2, and produce exactly the same OHLC bar.
Bart1t2t3t4t5t6t7t8
Path A price100.099.299.4101.0102.5104.5104.2104.0
Path B price100.0104.5103.8101.299.299.9102.6104.0
A: new extreme?lowhigh
B: new extreme?highlow
Both paths open at 100.0, close at 104.0, touch 104.5 and 99.2, and produce exactly the same OHLC bar.

Drawn, the two days are nothing alike:

Two days that could not look less similar

High 104.50Low 99.20
  • Path A
  • Path B
Show the numbers behind this chart
BarOpenHighLowClose
t1100100100100
t299999999
t399999999
t4101101101101
t5103103103103
t6105105105105
t7104104104104
t8104104104104
Path A drifted down, found a floor and rallied all afternoon. Path B gapped up, collapsed through the morning low, and recovered late. The two dashed lines are the only two intrabar facts that survive. The data in this chart is invented for the illustration. It is not market data and nothing should be inferred from it.

And this is everything the database keeps of either of them:

The one bar both days produce

  • Close above open
Show the numbers behind this chart
BarOpenHighLowClose
The bar10010599104
One bar, four numbers. A trader who lived through those two days would call them opposite; from here they are the same day. The data in this chart is invented for the illustration. It is not market data and nothing should be inferred from it.

Path A drifted down, found a floor and rallied all afternoon. Path B gapped up, collapsed through the morning low, and recovered late. A trader who lived through them would call them opposite days. The database cannot tell them apart, because the bar records the extremes but not when they happened.

This is not a philosophical point. It has a direct price, and Part 30 returns to it in detail. Three examples of rules that a daily bar cannot evaluate honestly:

  1. A stop and a target that were both touched. If your stop sits at 99.5 and your target at 104.5, both paths above hit both levels. Whether you finished the day with a small loss or a full profit depends entirely on the order — which the bar does not record. A backtester must therefore assume something, and every assumption available to it is sometimes wrong.
  2. “Buy the break of the high, stop at the low.” On path B, the high came first, so the entry would have filled and the stop would then have been hit. On path A the entry came near the close and was never threatened. Same bar, opposite outcome.
  3. Anything conditioned on the close relative to an intraday level. The close is one print. On a thin instrument it can be an outlier relative to everything that traded during the day, and the bar gives you no way to know that.

The honest response is not to abandon daily data. It is to prefer rules whose evaluation does not depend on intrabar sequence, and to state explicitly, in writing, what your backtest assumed whenever a rule does depend on it.

Confirm, on an instrument you care about, that the six stored fields are what this lesson claims they are — including that Avg really is ( High + Low + Close ) / 3 — and get a readable table of the derived quantities that a bar genuinely supports.

Complete runnable AFL

bar-anatomy.afl
// bar-anatomy.afl
// Part 2 - OHLCV and What a Bar Hides
//
// An Exploration that prints, for every bar in the selected range, the six
// fields AmiBroker stores for a symbol, plus the three quantities people
// usually mean when they say "the bar": its range, its body, and where the
// close finished inside the range.
//
// Assumptions:
// - Run from the Analysis window in Exploration mode, one symbol at a time.
// A range of a few hundred bars is enough; "All quotations" on a long
// history produces a table too large to read.
// - OpenInt is only meaningful for instruments that have open interest
// (futures, options). For most stock data it is zero or absent, and that
// is the correct answer rather than a defect.
// - Nothing here is a trading rule. It is a readout of stored data.
_SECTION_BEGIN("Bar anatomy");
Filter = 1; // report every bar in the range
// The six fields AmiBroker keeps per symbol.
AddColumn( Open, "Open", 1.4 );
AddColumn( High, "High", 1.4 );
AddColumn( Low, "Low", 1.4 );
AddColumn( Close, "Close", 1.4 );
AddColumn( Volume, "Volume", 1.0 );
AddColumn( OpenInt, "OpenInt", 1.0 );
// Avg is not stored. AmiBroker computes it as ( High + Low + Close ) / 3.
// The next two columns are here so you can confirm that for yourself rather
// than take it on trust.
AddColumn( Avg, "Avg", 1.4 );
AddColumn( ( High + Low + Close ) / 3, "(H+L+C)/3", 1.4 );
// Three derived quantities a bar genuinely can answer.
BarRange = High - Low;
BarBody = abs( Close - Open );
// Where the close sits inside the range: 0 means at the low, 1 means at the
// high. A bar with no range at all would divide by zero, so it is excluded
// explicitly rather than left to produce an infinity.
SafeRange = IIf( BarRange > 0, BarRange, 1 );
ClosePosition = IIf( BarRange > 0, ( Close - Low ) / SafeRange, Null );
AddColumn( BarRange, "Range", 1.4 );
AddColumn( BarBody, "Body", 1.4 );
AddColumn( ClosePosition, "Close position", 1.2 );
_SECTION_END();

Download bar-anatomy.afl50 lines

The formula has three sections. The first sets Filter = 1, which in an Exploration means “report every bar in the range” rather than only the bars matching some condition. The second prints the six stored fields unchanged. The third prints Avg next to a hand-written ( High + Low + Close ) / 3 so the two columns can be compared, then computes range, body and close position.

The only subtlety is the guard around close position. A bar where high equals low has zero range, and dividing by it would produce an infinity that then propagates into everything downstream. SafeRange substitutes 1 for the divisor on those bars and the second IIf discards the meaningless result, so the column reports Null rather than a number that looks real.

  • AddColumn( array, name, format ) adds one column to the Exploration output. The format 1.4 means four decimal places; 1.0 means none.
  • IIf( condition, valueIfTrue, valueIfFalse ) chooses per bar. It is not an if statement — it produces a whole array, one selection for every bar.
  • abs() returns absolute value, used here so the body is a size rather than a direction.
  • _SECTION_BEGIN() and _SECTION_END() mark a named block, which is how AmiBroker’s own formulas are organised.

One row per bar, with Avg and (H+L+C)/3 agreeing to the last decimal place on every row. OpenInt will be zero for ordinary stock data. Close position will sit between 0 and 1 on every bar that has any range at all, and be blank on bars that do not.

Pick any single row and check it by hand: subtract the low from the high and compare with the Range column; take the three prices, add them, divide by three, and compare with Avg. If they disagree, the disagreement is the interesting thing, not the formula.

  • Running it over “All quotations” on twenty years of history produces a table with thousands of rows and no insight. Restrict the range to a few hundred bars.
  • Running it in Scan mode instead of Exploration mode produces no table at all — AddColumn() only does anything in an Exploration.
  • Seeing Close position blank on many bars is a finding, not a bug: it means those bars have zero range, which is itself covered later in this part.

Add a column showing the body as a percentage of the range, 100 * Body / SafeRange, and sort by it. Bars where the body is a tiny fraction of the range are the ones where the intrabar path mattered most and is least knowable.

The bar records The bar does not record
The first and last traded price in the window Any price in between, or how long it stayed there
The extreme prices reached When they were reached, or in which order
Total quantity traded How that quantity was distributed across trades or prices
The number of trades
Bid, ask or spread at any moment
Whether trades were initiated by buyers or sellers
Anything about orders that were placed and not filled

Everything on the right-hand side exists at the exchange. Some of it is purchasable as separate data. None of it is in a bar, and none of it can be inferred from one.

A bar is a lossy summary. It keeps four prices out of however many traded, one total out of every individual size, and none of the sequence. Avg is computed, not stored, and is not an average traded price. Open interest is a real field with a real meaning for derivatives and is usually empty for stocks. The single most consequential omission is intrabar order, because rules involving stops, targets and intraday levels all depend on it and none of them can be evaluated honestly without it.

The next lesson goes one level down: how the trades became a bar in the first place, and what changes at each level of compression.

Check your understanding

Question 1. A daily bar has Open 100, High 104.5, Low 99.2, Close 104. Which of the following can you determine from it?
Show the answer and why

Answer: The size of the bar’s range, which is 5.3

Range is a straight subtraction of two stored fields. The other three all require information the bar discards: sequence, trade count and trade direction are simply not in the six stored arrays.

Question 2. What does the AFL identifier `Avg` contain?
x = Avg;
Show the answer and why

Answer: ( High + Low + Close ) / 3, computed on demand

Avg is the typical price, computed from three of the four stored prices. It is not read from the database and it is not volume-weighted, so it is not a substitute for VWAP.

Question 3. Which of these are recorded in AmiBroker’s six stored fields? Select all that apply.
Show the answer and why

Answer: Open interest, Total volume

Open interest and volume are both stored fields. Spread is quote data, not trade data, and is absent entirely. The time of the high is discarded when the bar is built.

Question 4. Your data file reports volume in thousands of shares, but you import it as plain volume. What is the most likely symptom?
Show the answer and why

Answer: A liquidity filter silently rejects almost every symbol

Nothing about the file is malformed, so no error is raised. The prices are untouched. The damage lands downstream: any rule with an absolute volume threshold now compares a number that is a thousand times too small against an unchanged threshold.

Sources for this lesson

4 verified · checked 2026-08-31

  1. 01AmiBroker User's Guide — Understanding how AFL works§ What is an Array?amibroker.com/guide/h_understandafl.html2026-08-31
  2. 02AmiBroker User's Guide — AFL language reference§ Identifiersamibroker.com/guide/a_language.html2026-08-31
  3. 03AmiBroker User's Guide — ASCII importer§ $FORMAT field listamibroker.com/guide/d_ascii.html2026-08-31
  4. 04AFL Function Reference — Foreignamibroker.com/guide/afl/foreign.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.