KDB knowledge

Volume indicators

Is money flowing in or out? On-balance volume, the A/D line, Chaikin money flow and oscillator, MFI, force index and ease of movement.

Volume is how many shares were traded in a candle. A price move on big volume means many people agree with it; a move on small volume is weak. Volume indicators mix the price and the volume to show if money is flowing into a share (buying pressure, "accumulation") or out of it (selling pressure, "distribution").

A common use is a divergence: the price goes sideways or down, but the volume indicator goes up. That can mean big buyers are collecting the share before the price rises.

These functions are series functions, like sma and atr. The same rules apply:

Function (default periods)First value on candleNULL candles at the start
obv(close, volume)10
ad(high, low, close, volume)10
cmf(high, low, close, volume, n) (n = 20)nn - 1
adosc(high, low, close, volume, fast, slow) (3, 10)the bigger of fast and slowone less
mfi(high, low, close, volume, n) (n = 14)n + 1n
efi(close, volume, n) (n = 13)n + 1n
eom(high, low, volume, n, divisor) (14, 100000000)n + 1n

Example: Volume indicators of CITY's one-minute candles (the last 5 minutes) (sample data: sample)

SELECT time, close, volume, obv, ad, cmf20, adosc, mfi14, efi13
FROM (SELECT time, close, volume,
             obv(close, volume) AS obv,
             round(ad(high, low, close, volume)) AS ad,
             round(cmf(high, low, close, volume), 3) AS cmf20,
             round(adosc(high, low, close, volume)) AS adosc,
             round(mfi(high, low, close, volume), 1) AS mfi14,
             round(efi(close, volume)) AS efi13
      FROM ochl_minutes
      WHERE symbol = 'CITY') t
ORDER BY time DESC
LIMIT 5;

Result:

time (TIMESTAMP)close (DOUBLE)volume (DOUBLE)obv (DOUBLE)ad (DOUBLE)cmf20 (DOUBLE)adosc (DOUBLE)mfi14 (DOUBLE)efi13 (DOUBLE)
2026-09-10T09:14:00.000Z504.9100013660-17532-0.031-51334.3-46
2026-09-10T09:13:00.000Z505.146014660-17389-0.06-56047.3-20
2026-09-10T09:12:00.000Z504.839014200-175420.02-66250.5-47
2026-09-10T09:11:00.000Z504.5135013810-17932-0.041-64647.7-74
2026-09-10T09:10:00.000Z504.6134015160-165820.013-15959.3-64

(5 rows)

Example: A NULL volume: cmf comes back, obv stays NULL (sample data: sample)

Setup:

CREATE TABLE candles (time TIMESTAMP, high DOUBLE, low DOUBLE, close DOUBLE, volume DOUBLE) TIMESERIES(time) PARTITION BY DAY;
INSERT INTO candles VALUES ('2026-09-01', 11, 9, 10, 100), ('2026-09-02', 12, 10, 11, 200), ('2026-09-03', 13, 11, 12, NULL),
  ('2026-09-04', 14, 12, 13, 400), ('2026-09-05', 13, 11, 12, 500), ('2026-09-06', 15, 12, 14, 600);
SELECT time, close, volume, obv(close, volume) AS obv, round(cmf(high, low, close, volume, 2), 3) AS cmf2
FROM candles
ORDER BY time;

Result:

time (TIMESTAMP)close (DOUBLE)volume (DOUBLE)obv (DOUBLE)cmf2 (DOUBLE)
2026-09-01T00:00:00.000Z10100100NULL
2026-09-02T00:00:00.000Z112003000
2026-09-03T00:00:00.000Z12NULLNULLNULL
2026-09-04T00:00:00.000Z13400NULLNULL
2026-09-05T00:00:00.000Z12500NULL0
2026-09-06T00:00:00.000Z14600NULL0.182

(6 rows)

obv

Syntax:

obv(close, volume)
obv(close, volume) OVER ([PARTITION BY ...] [ORDER BY ...])

On-balance volume: a running total of the volume. When the close is higher than the previous close, the candle's volume is added; when it is lower, the volume is subtracted; when it is the same, nothing changes. When OBV rises, the volume is bigger on up days than on down days: buyers are stronger. Only the direction of OBV matters, not the number itself.

ArgumentTypeMeaning
closenumberthe candle's close
volumenumberthe candle's volume

Example: Up day: add the volume. Down day: subtract it (sample data: sample)

SELECT time, close, volume, obv(close, volume) AS obv
FROM ochl_day
WHERE symbol = 'VOLT' AND time >= '2026-09-01'
ORDER BY time;

Result:

time (TIMESTAMP)close (DOUBLE)volume (DOUBLE)obv (DOUBLE)
2026-09-01T00:00:00.000Z1695885058850
2026-09-02T00:00:00.000Z171.52177080620
2026-09-03T00:00:00.000Z176145930226550
2026-09-06T00:00:00.000Z180.178550305100
2026-09-07T00:00:00.000Z176.9135560169540
2026-09-08T00:00:00.000Z182.4136770306310
2026-09-09T00:00:00.000Z187.394410400720
2026-09-10T00:00:00.000Z191.241470442190

(8 rows)

Example: Divergence: price change and OBV change since 23 August (sample data: sample)

SELECT symbol, count(*) AS days,
       round(last(close) - first(close), 1) AS price_change,
       last(obv) - first(obv) AS obv_change
FROM (SELECT time, symbol, close,
             obv(close, volume) OVER (PARTITION BY symbol ORDER BY time) AS obv
      FROM ochl_day
      WHERE time >= '2026-08-23') t
GROUP BY symbol
ORDER BY symbol;

Result:

symbol (SYMBOL)days (LONG)price_change (DOUBLE)obv_change (DOUBLE)
CITY15-3226090
COAST15-14.965740
VOLT1510.2-246280

(3 rows)

Syntax:

ad(high, low, close, volume)
ad_line(high, low, close, volume)
ad(high, low, close, volume) OVER ([PARTITION BY ...] [ORDER BY ...])

The accumulation/distribution line (A/D line). For every candle it finds where the close is inside the candle, with the money flow multiplier ((close - low) - (high - close)) / (high - low): 1 when the close is at the high, -1 when it is at the low, 0 in the middle. The A/D line is the running total of multiplier × volume. A rising line means the closes are near the highs on big volume: accumulation. ad_line is the same function.

ArgumentTypeMeaning
highnumberthe candle's high
lownumberthe candle's low
closenumberthe candle's close
volumenumberthe candle's volume

Example: The multiplier of each day and the A/D line of CITY (sample data: sample)

SELECT time, high, low, close, volume,
       round(((close - low) - (high - close)) / (high - low), 3) AS multiplier,
       round(ad(high, low, close, volume)) AS ad_line
FROM ochl_day
WHERE symbol = 'CITY' AND time >= '2026-09-03'
ORDER BY time;

Result:

time (TIMESTAMP)high (DOUBLE)low (DOUBLE)close (DOUBLE)volume (DOUBLE)multiplier (DOUBLE)ad_line (DOUBLE)
2026-09-03T00:00:00.000Z500.9495.5497.9101000-0.111-11222
2026-09-06T00:00:00.000Z500.1496499749500.46323511
2026-09-07T00:00:00.000Z501495498.71170800.23350829
2026-09-08T00:00:00.000Z501.1497.5497.534400-116429
2026-09-09T00:00:00.000Z512.4504504.6182390-0.857-139905
2026-09-10T00:00:00.000Z507500.7504.91830400.333-78892

(6 rows)

Example: A/D line of both symbols at the end of each day (one-minute candles) (sample data: sample)

SELECT time, symbol, close, ad_line
FROM (SELECT time, symbol, close,
             round(ad_line(high, low, close, volume) OVER (PARTITION BY symbol ORDER BY time)) AS ad_line
      FROM ochl_minutes) t
WHERE time IN ('2026-09-09T09:14:00Z', '2026-09-10T09:14:00Z')
ORDER BY symbol, time;

Result:

time (TIMESTAMP)symbol (SYMBOL)close (DOUBLE)ad_line (DOUBLE)
2026-09-09T09:14:00.000ZCITY504.6-14867
2026-09-10T09:14:00.000ZCITY504.9-17532
2026-09-09T09:14:00.000ZCOAST380.1-19693
2026-09-10T09:14:00.000ZCOAST376.1-9254

(4 rows)

cmf

Syntax:

cmf(high, low, close, volume)
cmf(high, low, close, volume, n)
cmf(high, low, close, volume, n) OVER ([PARTITION BY ...] [ORDER BY ...])

Chaikin money flow: over the last n candles, the sum of multiplier × volume divided by the sum of the volume (the multiplier is the one of ad). It is between -1 and 1. Above 0 means buying pressure (closes near the highs); below 0 means selling pressure. Many traders look for values above 0.1 or below -0.1.

ArgumentTypeMeaning
highnumberthe candle's high
lownumberthe candle's low
closenumberthe candle's close
volumenumberthe candle's volume
nwhole number, 1 or morehow many candles; default 20

Example: Buying or selling pressure? 5-day CMF of every symbol (sample data: sample)

SELECT time, symbol, close, cmf5,
       CASE WHEN cmf5 > 0 THEN 'buying' WHEN cmf5 < 0 THEN 'selling' END AS pressure
FROM (SELECT time, symbol, close,
             round(cmf(high, low, close, volume, 5) OVER (PARTITION BY symbol ORDER BY time), 3) AS cmf5
      FROM ochl_day
      WHERE time >= '2026-08-23') t
WHERE time >= '2026-09-08'
ORDER BY symbol, time;

Result:

time (TIMESTAMP)symbol (SYMBOL)close (DOUBLE)cmf5 (DOUBLE)pressure (STRING)
2026-09-08T00:00:00.000ZCITY497.5-0.072selling
2026-09-09T00:00:00.000ZCITY504.6-0.274selling
2026-09-10T00:00:00.000ZCITY504.9-0.114selling
2026-09-08T00:00:00.000ZCOAST377.20.337buying
2026-09-09T00:00:00.000ZCOAST380.1-0.077selling
2026-09-10T00:00:00.000ZCOAST376.1-0.267selling
2026-09-08T00:00:00.000ZVOLT182.40.238buying
2026-09-09T00:00:00.000ZVOLT187.30.283buying
2026-09-10T00:00:00.000ZVOLT191.20.254buying

(9 rows)

Example: 20-minute CMF at the last minute of the day (sample data: sample)

SELECT symbol, close, cmf20
FROM (SELECT time, symbol, close,
             round(cmf(high, low, close, volume) OVER (PARTITION BY symbol ORDER BY time), 3) AS cmf20
      FROM ochl_minutes) t
WHERE time = '2026-09-10T09:14:00Z'
ORDER BY symbol;

Result:

symbol (SYMBOL)close (DOUBLE)cmf20 (DOUBLE)
CITY504.9-0.031
COAST376.1-0.015

(2 rows)

adosc, chaikin_osc

Syntax:

adosc(high, low, close, volume)
adosc(high, low, close, volume, fast, slow)
chaikin_osc(high, low, close, volume, fast, slow)
adosc(high, low, close, volume, fast, slow) OVER ([PARTITION BY ...] [ORDER BY ...])

The Chaikin oscillator (Chaikin A/D oscillator): the fast EMA of the A/D line minus its slow EMA. It shows if the A/D line speeds up or slows down, like MACD does for the price. When it crosses above 0, buying pressure is growing (a buy signal); below 0, selling pressure is growing. chaikin_osc is the same function.

ArgumentTypeMeaning
highnumberthe candle's high
lownumberthe candle's low
closenumberthe candle's close
volumenumberthe candle's volume
fastwhole number, 1 or morethe fast EMA period; default 3
slowwhole number, 1 or morethe slow EMA period; default 10

Example: Chaikin oscillator of CITY (the last 5 minutes) (sample data: sample)

SELECT time, close, osc
FROM (SELECT time, close, round(adosc(high, low, close, volume)) AS osc
      FROM ochl_minutes
      WHERE symbol = 'CITY') t
ORDER BY time DESC
LIMIT 5;

Result:

time (TIMESTAMP)close (DOUBLE)osc (DOUBLE)
2026-09-10T09:14:00.000Z504.9-513
2026-09-10T09:13:00.000Z505.1-560
2026-09-10T09:12:00.000Z504.8-662
2026-09-10T09:11:00.000Z504.5-646
2026-09-10T09:10:00.000Z504.6-159

(5 rows)

Example: Signals: the oscillator crosses 0 (sample data: sample)

SELECT time, close, round(osc) AS osc, CASE WHEN osc > 0 THEN 'BUY' ELSE 'SELL' END AS signal
FROM (SELECT time, close, osc, lag(osc) AS prev_osc
      FROM (SELECT time, close, chaikin_osc(high, low, close, volume, 3, 10) AS osc
            FROM ochl_minutes
            WHERE symbol = 'CITY' AND time IN '2026-09-10') a) b
WHERE (osc > 0 AND prev_osc <= 0) OR (osc < 0 AND prev_osc >= 0)
ORDER BY time
LIMIT 6;

Result:

time (TIMESTAMP)close (DOUBLE)osc (DOUBLE)signal (STRING)
2026-09-10T05:39:00.000Z505.2420BUY
2026-09-10T05:52:00.000Z506.3-310SELL
2026-09-10T06:10:00.000Z505.9211BUY
2026-09-10T06:13:00.000Z505.4-162SELL
2026-09-10T06:16:00.000Z505.6311BUY
2026-09-10T06:21:00.000Z505.2-189SELL

(6 rows)

mfi

Syntax:

mfi(high, low, close, volume)
mfi(high, low, close, volume, n)
mfi(high, low, close, volume, n) OVER ([PARTITION BY ...] [ORDER BY ...])

The money flow index, a number from 0 to 100. It is often called the "volume RSI": it works like rsi, but with money instead of price. Each candle has a typical price (high + low + close) / 3 and a money flow typical price × volume. The money flow counts as positive when the typical price is higher than the previous one, negative when it is lower. MFI is 100 × positive / (positive + negative) over the last n candles. Above 80 traders call the share overbought; below 20, oversold.

ArgumentTypeMeaning
highnumberthe candle's high
lownumberthe candle's low
closenumberthe candle's close
volumenumberthe candle's volume
nwhole number, 1 or morehow many candles; default 14

Example: Overbought (80 or more) and oversold (20 or less) days, 5-day MFI (sample data: sample)

SELECT time, symbol, close, mfi5,
       CASE WHEN mfi5 >= 80 THEN 'overbought' ELSE 'oversold' END AS zone
FROM (SELECT time, symbol, close,
             round(mfi(high, low, close, volume, 5) OVER (PARTITION BY symbol ORDER BY time), 1) AS mfi5
      FROM ochl_day
      WHERE time >= '2026-08-23') t
WHERE mfi5 >= 80 OR mfi5 <= 20
ORDER BY time, symbol;

Result:

time (TIMESTAMP)symbol (SYMBOL)close (DOUBLE)mfi5 (DOUBLE)zone (STRING)
2026-08-30T00:00:00.000ZVOLT1730oversold
2026-08-31T00:00:00.000ZCITY505.714.3oversold
2026-08-31T00:00:00.000ZVOLT172.610.9oversold
2026-09-01T00:00:00.000ZCITY502.40oversold
2026-09-01T00:00:00.000ZVOLT16912.2oversold
2026-09-02T00:00:00.000ZCITY496.40oversold
2026-09-03T00:00:00.000ZCITY497.918.6oversold
2026-09-06T00:00:00.000ZVOLT180.183.8overbought
2026-09-07T00:00:00.000ZCOAST379.286.3overbought
2026-09-08T00:00:00.000ZCOAST377.287overbought
2026-09-09T00:00:00.000ZCOAST380.189.6overbought

(11 rows)

Example: MFI 14 against RSI 14 of COAST (the last 5 minutes) (sample data: sample)

SELECT time, close, mfi14, rsi14
FROM (SELECT time, close,
             round(mfi(high, low, close, volume), 1) AS mfi14,
             round(rsi(close, 14), 1) AS rsi14
      FROM ochl_minutes
      WHERE symbol = 'COAST') t
ORDER BY time DESC
LIMIT 5;

Result:

time (TIMESTAMP)close (DOUBLE)mfi14 (DOUBLE)rsi14 (DOUBLE)
2026-09-10T09:14:00.000Z376.168.959.4
2026-09-10T09:13:00.000Z376.258.860.9
2026-09-10T09:12:00.000Z3765459
2026-09-10T09:11:00.000Z375.543.153.9
2026-09-10T09:10:00.000Z375.636.455.2

(5 rows)

Example: high, low, close and volume are all needed (sample data: sample; shows an error)

SELECT mfi(high, low, close) FROM ochl_day;

Result:

Error: line 1, column 8: function 'mfi' takes 4 to 5 argument(s), got 3

efi, force_index

Syntax:

efi(close, volume)
efi(close, volume, n)
force_index(close, volume, n)
efi(close, volume, n) OVER ([PARTITION BY ...] [ORDER BY ...])

Elder's force index: the force of one candle is (close - previous close) × volume, so a big move on big volume is a big force. efi is the ema of the force over n candles. Above 0, buyers are in control; below 0, sellers. force_index is the same function.

ArgumentTypeMeaning
closenumberthe candle's close
volumenumberthe candle's volume
nwhole number, 1 or morethe EMA period; default 13 (traders also use 2 for short signals)

Example: The force of each day and its 3-day EMA (VOLT) (sample data: sample)

SELECT time, close, volume,
       round((close - lag(close)) * volume) AS force,
       round(efi(close, volume, 3)) AS efi3
FROM ochl_day
WHERE symbol = 'VOLT' AND time >= '2026-08-30'
ORDER BY time;

Result:

time (TIMESTAMP)close (DOUBLE)volume (DOUBLE)force (DOUBLE)efi3 (DOUBLE)
2026-08-30T00:00:00.000Z17393050NULLNULL
2026-08-31T00:00:00.000Z172.648550-19420NULL
2026-09-01T00:00:00.000Z16958850-211860NULL
2026-09-02T00:00:00.000Z171.52177054425-58952
2026-09-03T00:00:00.000Z176145930656685298867
2026-09-06T00:00:00.000Z180.178550322055310461
2026-09-07T00:00:00.000Z176.9135560-433792-61666
2026-09-08T00:00:00.000Z182.4136770752235345285
2026-09-09T00:00:00.000Z187.394410462609403947
2026-09-10T00:00:00.000Z191.241470161733282840

(10 rows)

Example: 13-minute force index of COAST (the last 5 minutes) (sample data: sample)

SELECT time, close, volume, force13
FROM (SELECT time, close, volume, round(force_index(close, volume)) AS force13
      FROM ochl_minutes
      WHERE symbol = 'COAST') t
ORDER BY time DESC
LIMIT 5;

Result:

time (TIMESTAMP)close (DOUBLE)volume (DOUBLE)force13 (DOUBLE)
2026-09-10T09:14:00.000Z376.11060104
2026-09-10T09:13:00.000Z376.21320140
2026-09-10T09:12:00.000Z3761150119
2026-09-10T09:11:00.000Z375.568043
2026-09-10T09:10:00.000Z375.6120061

(5 rows)

eom, ease_of_movement

Syntax:

eom(high, low, volume)
eom(high, low, volume, n)
eom(high, low, volume, n, divisor)
ease_of_movement(high, low, volume, n, divisor)
eom(high, low, volume, n, divisor) OVER ([PARTITION BY ...] [ORDER BY ...])

Ease of movement (Richard Arms): how easily the price moves. A big positive value means the price went up easily, on little volume; a big negative value means it went down easily. Values near 0 mean the price needed a lot of volume to move. ease_of_movement is the same function.

For each candle: the distance is (high + low) / 2 - (previous high + previous low) / 2, the box ratio is (volume / divisor) / (high - low), and the candle's value is distance / box ratio. eom is the sma of these values over n candles.

ArgumentTypeMeaning
highnumberthe candle's high
lownumberthe candle's low
volumenumberthe candle's volume
nwhole number, 1 or morehow many candles; default 14
divisornumber, not 0scales the volume; default 100000000

Example: EOM 14 of COAST with the default divisor and with 10000 (the last 5 minutes) (sample data: sample)

SELECT time, close, eom_default, eom_10000
FROM (SELECT time, close,
             round(eom(high, low, volume), 1) AS eom_default,
             round(eom(high, low, volume, 14, 10000), 3) AS eom_10000
      FROM ochl_minutes
      WHERE symbol = 'COAST') t
ORDER BY time DESC
LIMIT 5;

Result:

time (TIMESTAMP)close (DOUBLE)eom_default (DOUBLE)eom_10000 (DOUBLE)
2026-09-10T09:14:00.000Z376.1460.90.046
2026-09-10T09:13:00.000Z376.2-634.5-0.063
2026-09-10T09:12:00.000Z3763122.50.312
2026-09-10T09:11:00.000Z375.5395.90.04
2026-09-10T09:10:00.000Z375.6-1109.3-0.111

(5 rows)

Example: Did VOLT rise easily? 3-day EOM (sample data: sample)

SELECT time, close, volume, eom3,
       CASE WHEN eom3 > 0 THEN 'up easily' WHEN eom3 < 0 THEN 'down easily' END AS reading
FROM (SELECT time, close, volume, round(ease_of_movement(high, low, volume, 3, 10000), 2) AS eom3
      FROM ochl_day
      WHERE symbol = 'VOLT' AND time >= '2026-08-23') t
WHERE time >= '2026-09-01'
ORDER BY time;

Result:

time (TIMESTAMP)close (DOUBLE)volume (DOUBLE)eom3 (DOUBLE)reading (STRING)
2026-09-01T00:00:00.000Z16958850-0.97down easily
2026-09-02T00:00:00.000Z171.5217701.09up easily
2026-09-03T00:00:00.000Z1761459301.13up easily
2026-09-06T00:00:00.000Z180.1785503.88up easily
2026-09-07T00:00:00.000Z176.91355602.21up easily
2026-09-08T00:00:00.000Z182.41367702.14up easily
2026-09-09T00:00:00.000Z187.3944101.71up easily
2026-09-10T00:00:00.000Z191.2414703.2up easily

(8 rows)

Example: The divisor must not be 0 (sample data: sample; shows an error)

SELECT eom(high, low, volume, 14, 0) FROM ochl_day;

Result:

Error: line 1, column 8: eom: the divisor must not be 0