KDB knowledge

Technical indicators

Moving averages (SMA, EMA, WMA, DEMA, TEMA, TRIMA, KAMA, HMA, VWMA), RSI and ATR on candles, per symbol, with the same results as TA-Lib.

A technical indicator is a number made from past prices. Traders draw it on the chart next to the candles to see the trend (is the price going up?), the momentum (how strong is the move?) and the volatility (how big are the moves?). The price data is usually candles: open, high, low, close, volume per day or per minute.

The indicators are series functions (window and series functions). They give a value for every candle, and they follow these rules:

FunctionFirst value on candleNULL candles at the start
sma(x, n)nn - 1
ema(x, n)nn - 1
wma(x, n)nn - 1
trima(x, n)nn - 1
vwma(price, volume, n)nn - 1
kama(x, n)n + 1n
hma(x, n)n + √n - 1 (√n rounded down)n + √n - 2
dema(x, n)2n - 12n - 2
tema(x, n)3n - 23n - 3
rsi(x, n)n + 1n
atr(high, low, close, n)n + 1n

For the volume-weighted average price of a day or a candle, use the aggregate vwap; with OVER (... ROWS n PRECEDING) it becomes a moving VWAP.

More indicators (they follow the same rules):

Example: All four indicators on COAST's daily candles (sample data: sample)

SELECT time, close,
       round(sma(close, 5), 2) AS sma5,
       round(ema(close, 5), 2) AS ema5,
       round(rsi(close, 5), 1) AS rsi5,
       round(atr(high, low, close, 5), 2) AS atr5
FROM ochl_day
WHERE symbol = 'COAST' AND time >= '2026-08-23'
ORDER BY time;

Result:

time (TIMESTAMP)close (DOUBLE)sma5 (DOUBLE)ema5 (DOUBLE)rsi5 (DOUBLE)atr5 (DOUBLE)
2026-08-23T00:00:00.000Z391NULLNULLNULLNULL
2026-08-24T00:00:00.000Z384.9NULLNULLNULLNULL
2026-08-25T00:00:00.000Z379.8NULLNULLNULLNULL
2026-08-26T00:00:00.000Z373NULLNULLNULLNULL
2026-08-27T00:00:00.000Z367.1379.16379.16NULLNULL
2026-08-30T00:00:00.000Z372.4375.44376.9118.28.92
2026-08-31T00:00:00.000Z368.2372.137415.49.04
2026-09-01T00:00:00.000Z367.4369.62371.814.87.87
2026-09-02T00:00:00.000Z369.2368.86370.9422.57.08
2026-09-03T00:00:00.000Z373.2370.08371.6937.97.56
2026-09-06T00:00:00.000Z375.4370.68372.9345.47.25
2026-09-07T00:00:00.000Z379.2372.88375.0256.77.38
2026-09-08T00:00:00.000Z377.2374.84375.7549.97
2026-09-09T00:00:00.000Z380.1377.02377.258.89.92
2026-09-10T00:00:00.000Z376.1377.6376.83459.7

(15 rows)

Example: An indicator on a raw column with SAMPLE BY: an error (sample data: sample; shows an error)

SELECT time, last(close) AS close, sma(close, 4) AS sma4
FROM ochl_minutes
WHERE symbol = 'COAST'
SAMPLE BY 15m;

Result:

Error: line 1, column 40: column 'close' is used by a window function in a query with aggregates, so it must be a key of the groups: add it to the SELECT list or to GROUP BY

Example: Right: 15-minute candles inside, indicators outside (sample data: sample)

SELECT time, close, round(sma(close, 4), 2) AS sma4, round(rsi(close, 4), 1) AS rsi4
FROM (SELECT time, last(close) AS close
      FROM ochl_minutes
      WHERE symbol = 'COAST' AND time IN '2026-09-10'
      SAMPLE BY 15m) c
ORDER BY time
LIMIT 8;

Result:

time (TIMESTAMP)close (DOUBLE)sma4 (DOUBLE)rsi4 (DOUBLE)
2026-09-10T05:15:00.000Z381.7NULLNULL
2026-09-10T05:30:00.000Z380.2NULLNULL
2026-09-10T05:45:00.000Z379.7NULLNULL
2026-09-10T06:00:00.000Z381.3380.73NULL
2026-09-10T06:15:00.000Z379.6380.230.2
2026-09-10T06:30:00.000Z379.9380.1335.1
2026-09-10T06:45:00.000Z379.238028.8
2026-09-10T07:00:00.000Z378.4379.2822.6

(8 rows)

sma

Syntax:

sma(x, n)
sma(x, n) OVER ([PARTITION BY ...] [ORDER BY ...])

The simple moving average: the average of the last n values (this candle and the n - 1 before it). A 20-day SMA smooths out the daily noise and shows the trend. When the price is above its SMA, the trend is usually up; below, down. When a short SMA crosses above a long SMA, traders call it a buy signal (a "golden cross").

ArgumentTypeMeaning
xnumberthe price, usually close
nwhole number, 1 or morehow many candles to average

Example: Is VOLT above its 5-day average? (sample data: sample)

SELECT time, close, round(sma(close, 5), 2) AS sma5,
       CASE WHEN close > sma(close, 5) THEN 'above'
            WHEN close < sma(close, 5) THEN 'below' END AS trend
FROM ochl_day
WHERE symbol = 'VOLT' AND time >= '2026-08-23'
ORDER BY time;

Result:

time (TIMESTAMP)close (DOUBLE)sma5 (DOUBLE)trend (STRING)
2026-08-23T00:00:00.000Z181NULLNULL
2026-08-24T00:00:00.000Z178.5NULLNULL
2026-08-25T00:00:00.000Z176.8NULLNULL
2026-08-26T00:00:00.000Z174.2NULLNULL
2026-08-27T00:00:00.000Z173.3176.76below
2026-08-30T00:00:00.000Z173175.16below
2026-08-31T00:00:00.000Z172.6173.98below
2026-09-01T00:00:00.000Z169172.42below
2026-09-02T00:00:00.000Z171.5171.88below
2026-09-03T00:00:00.000Z176172.42above
2026-09-06T00:00:00.000Z180.1173.84above
2026-09-07T00:00:00.000Z176.9174.7above
2026-09-08T00:00:00.000Z182.4177.38above
2026-09-09T00:00:00.000Z187.3180.54above
2026-09-10T00:00:00.000Z191.2183.58above

(15 rows)

Example: 3-day SMA of every symbol (PARTITION BY symbol) (sample data: sample)

SELECT time, symbol, close,
       round(sma(close, 3) OVER (PARTITION BY symbol ORDER BY time), 2) AS sma3
FROM ochl_day
WHERE time >= '2026-09-06'
ORDER BY symbol, time;

Result:

time (TIMESTAMP)symbol (SYMBOL)close (DOUBLE)sma3 (DOUBLE)
2026-09-06T00:00:00.000ZCITY499NULL
2026-09-07T00:00:00.000ZCITY498.7NULL
2026-09-08T00:00:00.000ZCITY497.5498.4
2026-09-09T00:00:00.000ZCITY504.6500.27
2026-09-10T00:00:00.000ZCITY504.9502.33
2026-09-06T00:00:00.000ZCOAST375.4NULL
2026-09-07T00:00:00.000ZCOAST379.2NULL
2026-09-08T00:00:00.000ZCOAST377.2377.27
2026-09-09T00:00:00.000ZCOAST380.1378.83
2026-09-10T00:00:00.000ZCOAST376.1377.8
2026-09-06T00:00:00.000ZVOLT180.1NULL
2026-09-07T00:00:00.000ZVOLT176.9NULL
2026-09-08T00:00:00.000ZVOLT182.4179.8
2026-09-09T00:00:00.000ZVOLT187.3182.2
2026-09-10T00:00:00.000ZVOLT191.2186.97

(15 rows)

Example: Moving average crossover: buy and sell signals (3-day against 5-day) (sample data: sample)

SELECT time, symbol, close, fast, slow,
       CASE WHEN fast > slow THEN 'BUY' ELSE 'SELL' END AS signal
FROM (SELECT time, symbol, close, fast, slow,
             lag(fast) OVER (PARTITION BY symbol ORDER BY time) AS prev_fast,
             lag(slow) OVER (PARTITION BY symbol ORDER BY time) AS prev_slow
      FROM (SELECT time, symbol, close,
                   round(sma(close, 3) OVER (PARTITION BY symbol ORDER BY time), 2) AS fast,
                   round(sma(close, 5) OVER (PARTITION BY symbol ORDER BY time), 2) AS slow
            FROM ochl_day
            WHERE time >= '2026-08-23') a) b
WHERE (fast > slow AND prev_fast <= prev_slow) OR (fast < slow AND prev_fast >= prev_slow)
ORDER BY time, symbol;

Result:

time (TIMESTAMP)symbol (SYMBOL)close (DOUBLE)fast (DOUBLE)slow (DOUBLE)signal (STRING)
2026-08-30T00:00:00.000ZCITY503.1507.4509.16SELL
2026-09-06T00:00:00.000ZCOAST375.4372.6370.68BUY
2026-09-06T00:00:00.000ZVOLT180.1175.87173.84BUY
2026-09-08T00:00:00.000ZCITY497.5498.4497.9BUY

(4 rows)

Example: WHERE runs first: compute on all history, then keep the last 3 days (sample data: sample)

SELECT time, close, round(sma(close, 3), 2) AS sma3_only_3_rows
FROM ochl_day
WHERE symbol = 'CITY' AND time >= '2026-09-08'
ORDER BY time;

SELECT time, close, sma3
FROM (SELECT time, close, round(sma(close, 3), 2) AS sma3 FROM ochl_day WHERE symbol = 'CITY') t
WHERE time >= '2026-09-08'
ORDER BY time;

Result:

SELECT time, close, round(sma(close, 3), 2) AS sma3_only_3_rows FROM ochl_day WHERE symbol = 'CITY'

time (TIMESTAMP)close (DOUBLE)sma3_only_3_rows (DOUBLE)
2026-09-08T00:00:00.000Z497.5NULL
2026-09-09T00:00:00.000Z504.6NULL
2026-09-10T00:00:00.000Z504.9502.33

(3 rows)

SELECT time, close, sma3 FROM (SELECT time, close, round(sma(close, 3), 2) AS sma3 FROM ochl_day WHE

time (TIMESTAMP)close (DOUBLE)sma3 (DOUBLE)
2026-09-08T00:00:00.000Z497.5498.4
2026-09-09T00:00:00.000Z504.6500.27
2026-09-10T00:00:00.000Z504.9502.33

(3 rows)

Example: The period must be 1 or more (sample data: sample; shows an error)

SELECT sma(close, 0) FROM ochl_day;

Result:

Error: line 1, column 8: sma: the period must be a whole number of rows (1 or more)

ema

Syntax:

ema(x, n)
ema(x, n) OVER ([PARTITION BY ...] [ORDER BY ...])

The exponential moving average. Like the SMA, it smooths prices, but new prices count more than old ones, so it follows the price faster. Each new EMA is previous EMA + k × (price - previous EMA) with k = 2 / (n + 1). The first EMA (on candle n) is the SMA of the first n prices, like TA-Lib.

ArgumentTypeMeaning
xnumberthe price, usually close
nwhole number, 1 or morethe period; bigger is smoother and slower

Example: EMA follows the price faster than SMA (sample data: sample)

SELECT time, close,
       round(sma(close, 5), 2) AS sma5,
       round(ema(close, 5), 2) AS ema5
FROM ochl_day
WHERE symbol = 'VOLT' AND time >= '2026-08-23'
ORDER BY time;

Result:

time (TIMESTAMP)close (DOUBLE)sma5 (DOUBLE)ema5 (DOUBLE)
2026-08-23T00:00:00.000Z181NULLNULL
2026-08-24T00:00:00.000Z178.5NULLNULL
2026-08-25T00:00:00.000Z176.8NULLNULL
2026-08-26T00:00:00.000Z174.2NULLNULL
2026-08-27T00:00:00.000Z173.3176.76176.76
2026-08-30T00:00:00.000Z173175.16175.51
2026-08-31T00:00:00.000Z172.6173.98174.54
2026-09-01T00:00:00.000Z169172.42172.69
2026-09-02T00:00:00.000Z171.5171.88172.29
2026-09-03T00:00:00.000Z176172.42173.53
2026-09-06T00:00:00.000Z180.1173.84175.72
2026-09-07T00:00:00.000Z176.9174.7176.11
2026-09-08T00:00:00.000Z182.4177.38178.21
2026-09-09T00:00:00.000Z187.3180.54181.24
2026-09-10T00:00:00.000Z191.2183.58184.56

(15 rows)

Example: 12- and 26-candle EMA of CITY's one-minute candles (the last 5) (sample data: sample)

SELECT time, close, ema12, ema26, round(ema12 - ema26, 3) AS difference
FROM (SELECT time, close,
             round(ema(close, 12), 2) AS ema12,
             round(ema(close, 26), 2) AS ema26
      FROM ochl_minutes
      WHERE symbol = 'CITY' AND time IN '2026-09-10') t
ORDER BY time DESC
LIMIT 5;

Result:

time (TIMESTAMP)close (DOUBLE)ema12 (DOUBLE)ema26 (DOUBLE)difference (DOUBLE)
2026-09-10T09:14:00.000Z504.9504.99505.08-0.09
2026-09-10T09:13:00.000Z505.1505505.09-0.09
2026-09-10T09:12:00.000Z504.8504.98505.09-0.11
2026-09-10T09:11:00.000Z504.5505.02505.12-0.1
2026-09-10T09:10:00.000Z504.6505.11505.16-0.05

(5 rows)

Example: A NULL price: SMA comes back, EMA stays NULL (sample data: sample)

Setup:

CREATE TABLE prices (time TIMESTAMP, close DOUBLE) TIMESERIES(time) PARTITION BY DAY;
INSERT INTO prices VALUES ('2026-09-01', 10), ('2026-09-02', 11), ('2026-09-03', NULL),
  ('2026-09-04', 13), ('2026-09-05', 12), ('2026-09-06', 14), ('2026-09-07', 15);
SELECT time, close, sma(close, 2) AS sma2, ema(close, 2) AS ema2, rsi(close, 2) AS rsi2
FROM prices
ORDER BY time;

Result:

time (TIMESTAMP)close (DOUBLE)sma2 (DOUBLE)ema2 (DOUBLE)rsi2 (DOUBLE)
2026-09-01T00:00:00.000Z10NULLNULLNULL
2026-09-02T00:00:00.000Z1110.510.5NULL
2026-09-03T00:00:00.000ZNULLNULLNULL100
2026-09-04T00:00:00.000Z13NULLNULL100
2026-09-05T00:00:00.000Z1212.5NULL20
2026-09-06T00:00:00.000Z1413NULL80.9523809523809
2026-09-07T00:00:00.000Z1514.5NULL89.1891891891892

(7 rows)

wma

Syntax:

wma(x, n)
wma(x, n) OVER ([PARTITION BY ...] [ORDER BY ...])

The weighted moving average. Like the SMA it averages the last n values, but newer prices count more: the newest price has weight n, the one before n - 1, and the oldest weight 1. So it has less lag than the SMA. Lag means how many candles an average is late when the price turns.

ArgumentTypeMeaning
xnumberthe price, usually close
nwhole number, 1 or morehow many candles to average

Example: SMA and WMA of VOLT: the WMA turns sooner (sample data: sample)

SELECT time, close, round(sma(close, 5), 2) AS sma5, round(wma(close, 5), 2) AS wma5
FROM ochl_day
WHERE symbol = 'VOLT' AND time >= '2026-08-23'
ORDER BY time;

Result:

time (TIMESTAMP)close (DOUBLE)sma5 (DOUBLE)wma5 (DOUBLE)
2026-08-23T00:00:00.000Z181NULLNULL
2026-08-24T00:00:00.000Z178.5NULLNULL
2026-08-25T00:00:00.000Z176.8NULLNULL
2026-08-26T00:00:00.000Z174.2NULLNULL
2026-08-27T00:00:00.000Z173.3176.76175.45
2026-08-30T00:00:00.000Z173175.16174.19
2026-08-31T00:00:00.000Z172.6173.98173.34
2026-09-01T00:00:00.000Z169172.42171.68
2026-09-02T00:00:00.000Z171.5171.88171.37
2026-09-03T00:00:00.000Z176172.42172.75
2026-09-06T00:00:00.000Z180.1173.84175.31
2026-09-07T00:00:00.000Z176.9174.7176.33
2026-09-08T00:00:00.000Z182.4177.38178.89
2026-09-09T00:00:00.000Z187.3180.54182.2
2026-09-10T00:00:00.000Z191.2183.58185.75

(15 rows)

Example: Close against its 5-day WMA, every symbol on the last day (sample data: sample)

SELECT symbol, close, wma5,
       CASE WHEN close > wma5 THEN 'above' ELSE 'below' END AS position
FROM (SELECT time, symbol, close,
             round(wma(close, 5) OVER (PARTITION BY symbol ORDER BY time), 2) AS wma5
      FROM ochl_day) t
WHERE time IN '2026-09-10'
ORDER BY symbol;

Result:

symbol (SYMBOL)close (DOUBLE)wma5 (DOUBLE)position (STRING)
CITY504.9502.12above
COAST376.1377.75below
VOLT191.2185.75above

(3 rows)

dema

Syntax:

dema(x, n)
dema(x, n) OVER ([PARTITION BY ...] [ORDER BY ...])

The double exponential moving average: 2 × EMA - EMA of the EMA. Taking away the "EMA of the EMA" removes much of the EMA's lag, so the DEMA stays closer to the price. It also jumps around a little more.

ArgumentTypeMeaning
xnumberthe price, usually close
nwhole number, 1 or morethe period of both EMAs

Example: EMA, DEMA and TEMA of CITY's one-minute candles (the last 5) (sample data: sample)

SELECT time, close, ema10, dema10, tema10
FROM (SELECT time, close,
             round(ema(close, 10), 2) AS ema10,
             round(dema(close, 10), 2) AS dema10,
             round(tema(close, 10), 2) AS tema10
      FROM ochl_minutes
      WHERE symbol = 'CITY' AND time IN '2026-09-10') t
ORDER BY time DESC
LIMIT 5;

Result:

time (TIMESTAMP)close (DOUBLE)ema10 (DOUBLE)dema10 (DOUBLE)tema10 (DOUBLE)
2026-09-10T09:14:00.000Z504.9504.96504.86504.84
2026-09-10T09:13:00.000Z505.1504.98504.87504.84
2026-09-10T09:12:00.000Z504.8504.95504.79504.7
2026-09-10T09:11:00.000Z504.5504.98504.82504.71
2026-09-10T09:10:00.000Z504.6505.09505504.93

(5 rows)

tema

Syntax:

tema(x, n)
tema(x, n) OVER ([PARTITION BY ...] [ORDER BY ...])

The triple exponential moving average: 3 × EMA - 3 × EMA of the EMA + EMA of the EMA of the EMA. It has even less lag than the DEMA, and it is the fastest of the EMA family.

ArgumentTypeMeaning
xnumberthe price, usually close
nwhole number, 1 or morethe period of the three EMAs

Example: Warm-up: EMA on candle n, DEMA on candle 2n - 1, TEMA on candle 3n - 2 (sample data: sample)

SELECT time, close,
       round(ema(close, 3), 2) AS ema3,
       round(dema(close, 3), 2) AS dema3,
       round(tema(close, 3), 2) AS tema3
FROM ochl_day
WHERE symbol = 'COAST' AND time >= '2026-09-01'
ORDER BY time;

Result:

time (TIMESTAMP)close (DOUBLE)ema3 (DOUBLE)dema3 (DOUBLE)tema3 (DOUBLE)
2026-09-01T00:00:00.000Z367.4NULLNULLNULL
2026-09-02T00:00:00.000Z369.2NULLNULLNULL
2026-09-03T00:00:00.000Z373.2369.93NULLNULL
2026-09-06T00:00:00.000Z375.4372.67NULLNULL
2026-09-07T00:00:00.000Z379.2375.93379.02NULL
2026-09-08T00:00:00.000Z377.2376.57378.43NULL
2026-09-09T00:00:00.000Z380.1378.33380.15380.13
2026-09-10T00:00:00.000Z376.1377.22377.57376.82

(8 rows)

trima

Syntax:

trima(x, n)
trima(x, n) OVER ([PARTITION BY ...] [ORDER BY ...])

The triangular moving average. The prices in the middle of the window count most, and the oldest and newest count least (the weights look like a triangle: 1, 2, 3, 2, 1). It is very smooth, but it has more lag than the SMA.

ArgumentTypeMeaning
xnumberthe price, usually close
nwhole number, 1 or morehow many candles to average

Example: TRIMA is smoother and slower than SMA (sample data: sample)

SELECT time, close, round(sma(close, 5), 2) AS sma5, round(trima(close, 5), 2) AS trima5
FROM ochl_day
WHERE symbol = 'CITY' AND time >= '2026-08-23'
ORDER BY time;

Result:

time (TIMESTAMP)close (DOUBLE)sma5 (DOUBLE)trima5 (DOUBLE)
2026-08-23T00:00:00.000Z507.9NULLNULL
2026-08-24T00:00:00.000Z510.3NULLNULL
2026-08-25T00:00:00.000Z513.3NULLNULL
2026-08-26T00:00:00.000Z513.2NULLNULL
2026-08-27T00:00:00.000Z505.9510.12511.19
2026-08-30T00:00:00.000Z503.1509.16510.16
2026-08-31T00:00:00.000Z505.7508.24507.7
2026-09-01T00:00:00.000Z502.4506.06505.34
2026-09-02T00:00:00.000Z496.4502.7503.38
2026-09-03T00:00:00.000Z497.9501.1501.38
2026-09-06T00:00:00.000Z499500.28499.39
2026-09-07T00:00:00.000Z498.7498.88498.4
2026-09-08T00:00:00.000Z497.5497.9498.23
2026-09-09T00:00:00.000Z504.6499.54499.07
2026-09-10T00:00:00.000Z504.9500.94500.33

(15 rows)

kama

Syntax:

kama(x, n)
kama(x, n) OVER ([PARTITION BY ...] [ORDER BY ...])

Kaufman's adaptive moving average. It changes its own speed. It looks at the efficiency ratio of the last n candles: the price change from start to end, divided by the sum of all the small up and down moves. When the price goes straight in one direction (ratio near 1), KAMA follows it fast. When the price only goes up and down (ratio near 0), KAMA hardly moves, so it gives fewer false signals in a sideways market.

ArgumentTypeMeaning
xnumberthe price, usually close
nwhole number, 1 or morehow many candles the efficiency ratio looks back

Example: KAMA follows a straight move, and stays calm when the price goes up and down (sample data: sample)

Setup:

CREATE TABLE moves (time TIMESTAMP, straight DOUBLE, zigzag DOUBLE) TIMESERIES(time) PARTITION BY DAY;
INSERT INTO moves VALUES ('2026-09-01', 100, 100), ('2026-09-02', 101, 104), ('2026-09-03', 102, 100),
  ('2026-09-04', 103, 104), ('2026-09-05', 104, 100), ('2026-09-06', 105, 104), ('2026-09-07', 106, 100),
  ('2026-09-08', 107, 104);
SELECT time, straight, round(kama(straight, 3), 2) AS kama_straight,
       zigzag, round(kama(zigzag, 3), 2) AS kama_zigzag, round(ema(zigzag, 3), 2) AS ema_zigzag
FROM moves
ORDER BY time;

Result:

time (TIMESTAMP)straight (DOUBLE)kama_straight (DOUBLE)zigzag (DOUBLE)kama_zigzag (DOUBLE)ema_zigzag (DOUBLE)
2026-09-01T00:00:00.000Z100NULL100NULLNULL
2026-09-02T00:00:00.000Z101NULL104NULLNULL
2026-09-03T00:00:00.000Z102NULL100NULL101.33
2026-09-04T00:00:00.000Z103102.44104100.28102.67
2026-09-05T00:00:00.000Z104103.14100100.26101.33
2026-09-06T00:00:00.000Z105103.96104100.52102.67
2026-09-07T00:00:00.000Z106104.87100100.49101.33
2026-09-08T00:00:00.000Z107105.82104100.73102.67

(8 rows)

Example: KAMA and EMA of COAST's one-minute candles (the last 5) (sample data: sample)

SELECT time, close, kama10, ema10
FROM (SELECT time, close, round(kama(close, 10), 2) AS kama10, round(ema(close, 10), 2) AS ema10
      FROM ochl_minutes
      WHERE symbol = 'COAST' AND time IN '2026-09-10') t
ORDER BY time DESC
LIMIT 5;

Result:

time (TIMESTAMP)close (DOUBLE)kama10 (DOUBLE)ema10 (DOUBLE)
2026-09-10T09:14:00.000Z376.1375.38375.59
2026-09-10T09:13:00.000Z376.2375.27375.47
2026-09-10T09:12:00.000Z376375.09375.31
2026-09-10T09:11:00.000Z375.5375.02375.16
2026-09-10T09:10:00.000Z375.6375.01375.09

(5 rows)

hma

Syntax:

hma(x, n)
hma(x, n) OVER ([PARTITION BY ...] [ORDER BY ...])

The Hull moving average (by Alan Hull). It is both smooth and fast: it takes 2 × WMA of n/2 candles - WMA of n candles, which runs a little ahead of the price, and then smooths that with a WMA of √n candles.

ArgumentTypeMeaning
xnumberthe price, usually close
nwhole number, 2 or morethe period; n/2 and √n are rounded down

Example: HMA follows VOLT with little lag (sample data: sample)

SELECT time, close, round(sma(close, 4), 2) AS sma4, round(hma(close, 4), 2) AS hma4
FROM ochl_day
WHERE symbol = 'VOLT' AND time >= '2026-08-23'
ORDER BY time;

Result:

time (TIMESTAMP)close (DOUBLE)sma4 (DOUBLE)hma4 (DOUBLE)
2026-08-23T00:00:00.000Z181NULLNULL
2026-08-24T00:00:00.000Z178.5NULLNULL
2026-08-25T00:00:00.000Z176.8NULLNULL
2026-08-26T00:00:00.000Z174.2177.63NULL
2026-08-27T00:00:00.000Z173.3175.7172.81
2026-08-30T00:00:00.000Z173174.33172.46
2026-08-31T00:00:00.000Z172.6173.28172.46
2026-09-01T00:00:00.000Z169171.98170.21
2026-09-02T00:00:00.000Z171.5171.53169.84
2026-09-03T00:00:00.000Z176172.28174.13
2026-09-06T00:00:00.000Z180.1174.15179.65
2026-09-07T00:00:00.000Z176.9176.13179.67
2026-09-08T00:00:00.000Z182.4178.85180.59
2026-09-09T00:00:00.000Z187.3181.68186.03
2026-09-10T00:00:00.000Z191.2184.45191.41

(15 rows)

Example: The period must be 2 or more (sample data: sample; shows an error)

SELECT hma(close, 1) FROM ochl_day;

Result:

Error: line 1, column 8: hma: the period must be a whole number of rows (2 or more)

vwma

Syntax:

vwma(price, volume, n)
vwma(price, volume, n) OVER ([PARTITION BY ...] [ORDER BY ...])

The volume-weighted moving average: sum(price × volume) / sum(volume) of the last n candles. Candles with a big volume count more. When the VWMA is above the SMA, the higher prices came with more volume, which traders read as strong buying.

ArgumentTypeMeaning
pricenumberthe price, usually close
volumenumberthe volume of the candle
nwhole number, 1 or morehow many candles

Example: VWMA and SMA of CITY's one-minute candles (the last 5) (sample data: sample)

SELECT time, close, volume, vwma20, sma20
FROM (SELECT time, close, volume,
             round(vwma(close, volume, 20), 2) AS vwma20,
             round(sma(close, 20), 2) AS sma20
      FROM ochl_minutes
      WHERE symbol = 'CITY' AND time IN '2026-09-10') t
ORDER BY time DESC
LIMIT 5;

Result:

time (TIMESTAMP)close (DOUBLE)volume (DOUBLE)vwma20 (DOUBLE)sma20 (DOUBLE)
2026-09-10T09:14:00.000Z504.91000504.91504.97
2026-09-10T09:13:00.000Z505.1460504.88504.95
2026-09-10T09:12:00.000Z504.8390504.88504.94
2026-09-10T09:11:00.000Z504.51350504.87504.94
2026-09-10T09:10:00.000Z504.61340504.91504.98

(5 rows)

Example: VWMA is a moving VWAP that waits for n candles (sample data: sample)

SELECT time, close, volume,
       round(vwma(close, volume, 3), 2) AS vwma3,
       round(vwap(close, volume) OVER (ORDER BY time ROWS 2 PRECEDING), 2) AS moving_vwap
FROM ochl_day
WHERE symbol = 'VOLT' AND time >= '2026-09-03'
ORDER BY time;

Result:

time (TIMESTAMP)close (DOUBLE)volume (DOUBLE)vwma3 (DOUBLE)moving_vwap (DOUBLE)
2026-09-03T00:00:00.000Z176145930NULL176
2026-09-06T00:00:00.000Z180.178550NULL177.43
2026-09-07T00:00:00.000Z176.9135560177.23177.23
2026-09-08T00:00:00.000Z182.4136770179.76179.76
2026-09-09T00:00:00.000Z187.394410181.63181.63
2026-09-10T00:00:00.000Z191.241470185.44185.44

(6 rows)

rsi

Syntax:

rsi(x, n)
rsi(x, n) OVER ([PARTITION BY ...] [ORDER BY ...])

The relative strength index, a momentum number from 0 to 100. It compares the size of the recent rises with the size of the recent falls. Above 70, traders call a share "overbought" (it rose a lot, it may fall back); below 30, "oversold". The usual period is 14.

It is Wilder's RSI, like TA-Lib: the average gain and average loss of the first n changes, then average = (previous average × (n - 1) + today's gain or loss) / n, and RSI = 100 - 100 / (1 + average gain / average loss).

ArgumentTypeMeaning
xnumberthe price, usually close
nwhole number, 1 or morethe period, usually 14

Example: RSI 14 on CITY's one-minute candles (the last 5 minutes) (sample data: sample)

SELECT time, close, rsi14
FROM (SELECT time, close, round(rsi(close, 14), 1) AS rsi14
      FROM ochl_minutes
      WHERE symbol = 'CITY' AND time IN '2026-09-10') t
ORDER BY time DESC
LIMIT 5;

Result:

time (TIMESTAMP)close (DOUBLE)rsi14 (DOUBLE)
2026-09-10T09:14:00.000Z504.947.6
2026-09-10T09:13:00.000Z505.150.1
2026-09-10T09:12:00.000Z504.846.2
2026-09-10T09:11:00.000Z504.542
2026-09-10T09:10:00.000Z504.643

(5 rows)

Example: Overbought or oversold? Daily RSI 5 of every symbol (sample data: sample)

SELECT time, symbol, close, rsi5,
       CASE WHEN rsi5 >= 70 THEN 'overbought'
            WHEN rsi5 <= 30 THEN 'oversold'
            ELSE 'normal' END AS zone
FROM (SELECT time, symbol, close,
             round(rsi(close, 5) OVER (PARTITION BY symbol ORDER BY time), 1) AS rsi5
      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)rsi5 (DOUBLE)zone (STRING)
2026-09-08T00:00:00.000ZCITY497.531.6normal
2026-09-09T00:00:00.000ZCITY504.663.1normal
2026-09-10T00:00:00.000ZCITY504.964normal
2026-09-08T00:00:00.000ZCOAST377.249.9normal
2026-09-09T00:00:00.000ZCOAST380.158.8normal
2026-09-10T00:00:00.000ZCOAST376.145normal
2026-09-08T00:00:00.000ZVOLT182.467.5normal
2026-09-09T00:00:00.000ZVOLT187.376.1overbought
2026-09-10T00:00:00.000ZVOLT191.281.1overbought

(9 rows)

Example: Only rises: 100. Only falls: 0. Flat: 50 (sample data: sample)

SELECT time, rising, falling, flat,
       rsi(rising, 3) AS rsi_rising, rsi(falling, 3) AS rsi_falling, rsi(flat, 3) AS rsi_flat
FROM (SELECT time,
             100 + row_number() AS rising,
             100 - row_number() AS falling,
             100 AS flat
      FROM ochl_day
      WHERE symbol = 'VOLT' AND time >= '2026-09-06') t
ORDER BY time;

Result:

time (TIMESTAMP)rising (LONG)falling (LONG)flat (LONG)rsi_rising (DOUBLE)rsi_falling (DOUBLE)rsi_flat (DOUBLE)
2026-09-06T00:00:00.000Z10199100NULLNULLNULL
2026-09-07T00:00:00.000Z10298100NULLNULLNULL
2026-09-08T00:00:00.000Z10397100NULLNULLNULL
2026-09-09T00:00:00.000Z10496100100050
2026-09-10T00:00:00.000Z10595100100050

(5 rows)

atr

Syntax:

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

The average true range: how much the price moves in one candle, on average, in the unit of the price (for example dollars). It measures volatility, not direction. Traders use it to place a stop-loss (for example 2 × ATR below the price) and to choose a position size.

The true range of a candle is the biggest of: high - low, |high - previous close| and |low - previous close|. So a gap from yesterday's close counts too. ATR is Wilder's average of the true range, like TA-Lib: the plain average of the first n true ranges, then (previous ATR × (n - 1) + true range) / n.

ArgumentTypeMeaning
highnumberthe candle's high
lownumberthe candle's low
closenumberthe candle's close
nwhole number, 1 or morethe period, usually 14

Example: ATR in dollars and in percent of the price (sample data: sample)

SELECT time, symbol, close, atr5, round(atr5 / close * 100, 2) AS atr_pct
FROM (SELECT time, symbol, close,
             round(atr(high, low, close, 5) OVER (PARTITION BY symbol ORDER BY time), 2) AS atr5
      FROM ochl_day
      WHERE time >= '2026-08-23') t
WHERE time IN '2026-09-10'
ORDER BY atr_pct DESC;

Result:

time (TIMESTAMP)symbol (SYMBOL)close (DOUBLE)atr5 (DOUBLE)atr_pct (DOUBLE)
2026-09-10T00:00:00.000ZVOLT191.27.33.82
2026-09-10T00:00:00.000ZCOAST376.19.72.58
2026-09-10T00:00:00.000ZCITY504.97.331.45

(3 rows)

Example: A stop-loss 2 × ATR below today's close (sample data: sample)

SELECT symbol, close, atr5, round(close - 2 * atr5, 1) AS stop_loss
FROM (SELECT time, symbol, close,
             round(atr(high, low, close, 5) OVER (PARTITION BY symbol ORDER BY time), 2) AS atr5
      FROM ochl_day) t
WHERE time IN '2026-09-10'
ORDER BY symbol;

Result:

symbol (SYMBOL)close (DOUBLE)atr5 (DOUBLE)stop_loss (DOUBLE)
CITY504.97.65489.6
COAST376.19.99356.1
VOLT191.27.43176.3

(3 rows)

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

SELECT atr(close, 14) FROM ochl_day;

Result:

Error: line 1, column 8: function 'atr' takes 4 argument(s), got 2