# Trend indicators
> Is the price going up, down or sideways, and how strong is the move? ADX and DI, Aroon, parabolic SAR, Ichimoku, SuperTrend, linear regression and Heikin-Ashi candles.
- Category: Functions — technical indicators
- Keywords: cross\_above, cross\_below, crossover, crossunder, golden cross, death cross, moving average crossover, trend, trend strength, direction, up trend, down trend, sideways, directional movement, DMI, +DI, -DI, DX, average directional index, ADX, ADXR, Aroon, Aroon oscillator, parabolic SAR, SAR, stop and reverse, trailing stop, Ichimoku, Ichimoku cloud, kumo, tenkan-sen, conversion line, kijun-sen, base line, senkou span, chikou span, lagging span, SuperTrend, linear regression, LINEARREG, time series forecast, TSF, Heikin-Ashi, Heiken Ashi, HA candles, TA-Lib, TradingView, stock market
- See also: [Technical indicators](functions-indicators.md), [Momentum indicators](functions-indicators-momentum.md), [Volatility indicators and bands](functions-indicators-volatility.md), [Volume indicators](functions-indicators-volume.md), [Window and series functions](functions-window.md), [Correlation and regression (regr_slope)](functions-correlation.md#regr_slope), [Candles cookbook](candles-cookbook.md)
A **trend** is the main direction of the price over some time: up (higher highs and higher lows), down, or sideways (no clear direction). **Trend indicators** answer two questions for a trader: *which way* is the price going, and *how strong* is the move? Many traders only buy in an up trend and use a trend line as a **trailing stop**: a stop-loss price that moves up with the price, so you sell when the trend ends.
These indicators are **series functions**, like [sma and ema](functions-indicators.md). They give a value for every candle and follow the same rules:
- **Order:** without `OVER`, all result rows are one series in time order. With **many symbols**, always write `OVER (PARTITION BY symbol ORDER BY time)`.
- **Period:** `n` is a number of candles, a whole number. Every argument after the price columns has a default, so `adx(high, low, close)` is the usual 14-candle ADX.
- **WHERE runs first**, and candles from `SAMPLE BY` must be made in a subquery, with the indicator outside (see [technical indicators](functions-indicators.md)).
- **Warm-up:** the first candles have no value (`NULL`). With the default periods:
| Function | Default periods | First value on candle | NULL candles at the start |
|---|---|---|---|
| `plus_di`, `minus_di`, `dx` | n = 14 | n + 1 = 15 | 14 |
| `adx` | n = 14 | 2n = 28 | 27 |
| `adxr` | n = 14 | 3n - 1 = 41 | 40 |
| `aroon_up`, `aroon_down`, `aroon_osc` | n = 14 | n + 1 = 15 | 14 |
| `sar` | 0.02, 0.2 | 2 | 1 |
| `ichimoku_tenkan` | 9 | 9 | 8 |
| `ichimoku_kijun` | 26 | 26 | 25 |
| `ichimoku_senkou_a` | 9, 26, shift 26 | 26 + 26 = 52 | 51 |
| `ichimoku_senkou_b` | 52, shift 26 | 52 + 26 = 78 | 77 |
| `ichimoku_chikou` | shift 26 | 1 (but the **last** 26 candles are NULL) | 0 |
| `supertrend`, `supertrend_direction` | n = 10, 3 | n = 10 | 9 |
| `linearreg`, `linearreg_slope`, `linearreg_intercept`, `linearreg_angle`, `tsf` | n = 14 | n = 14 | 13 |
| `ha_open`, `ha_high`, `ha_low`, `ha_close` | none | 1 | 0 |
- **Same as TA-Lib:** DI, DX, ADX, ADXR, Aroon, SAR and the linear regression functions give the same numbers as TA-Lib. SuperTrend follows TradingView (`ta.supertrend`). Ichimoku and Heikin-Ashi use the usual textbook formulas.
- **NULL prices:** the step-by-step indicators (`plus_di`, `minus_di`, `dx`, `adx`, `adxr`, `sar`, `supertrend`, `supertrend_direction`, the `ha_` functions) **stop** at the first NULL: that candle and every later one is `NULL`. The window indicators (Aroon, Ichimoku, linear regression) come back once the NULL is out of their window. Leave NULL candles out with `WHERE`.
- **Returns** `DOUBLE`, except `supertrend_direction` (`LONG`). Use `round(x, 2)` for prices.
**Example: The trend of CITY in its last 5 minutes: ADX, DI, SuperTrend and SAR** (sample data: sample)
```sql
SELECT time, close, adx14, plus_di14, minus_di14, st_dir, sar
FROM (SELECT time, close,
round(adx(high, low, close), 1) AS adx14,
round(plus_di(high, low, close), 1) AS plus_di14,
round(minus_di(high, low, close), 1) AS minus_di14,
supertrend_direction(high, low, close) AS st_dir,
round(sar(high, low), 2) AS sar
FROM ochl_minutes
WHERE symbol = 'CITY') t
ORDER BY time DESC
LIMIT 5;
```
Result:
| time (TIMESTAMP) | close (DOUBLE) | adx14 (DOUBLE) | plus_di14 (DOUBLE) | minus_di14 (DOUBLE) | st_dir (LONG) | sar (DOUBLE) |
|---|---|---|---|---|---|---|
| 2026-09-10T09:14:00.000Z | 504.9 | 16.7 | 17.3 | 17.9 | -1 | 505.78 |
| 2026-09-10T09:13:00.000Z | 505.1 | 17.9 | 18.9 | 18.2 | -1 | 505.84 |
| 2026-09-10T09:12:00.000Z | 504.8 | 19.1 | 14 | 19.6 | -1 | 505.9 |
| 2026-09-10T09:11:00.000Z | 504.5 | 19.3 | 14.9 | 20.8 | -1 | 505.97 |
| 2026-09-10T09:10:00.000Z | 504.6 | 19.5 | 15 | 21.1 | -1 | 506.04 |
(5 rows)
**Example: A NULL high: the step-by-step indicators stop, the window ones come back** (sample data: sample)
Setup:
```sql
CREATE TABLE candles (time TIMESTAMP, open DOUBLE, high DOUBLE, low DOUBLE, close DOUBLE) TIMESERIES(time) PARTITION BY DAY;
INSERT INTO candles VALUES
('2026-09-01', 10, 11, 9, 10.5), ('2026-09-02', 10.5, 12, 10, 11.5), ('2026-09-03', 11.5, 12.5, 11, 12),
('2026-09-04', 12, NULL, 11.5, 12.2), ('2026-09-05', 12.2, 13, 12, 12.8), ('2026-09-06', 12.8, 13.5, 12.5, 13.2),
('2026-09-07', 13.2, 14, 13, 13.9), ('2026-09-08', 13.9, 14.2, 13.1, 13.3);
```
```sql
SELECT time, high,
round(sar(high, low), 2) AS sar,
round(ha_close(open, high, low, close), 2) AS ha_close,
round(aroon_up(high, 2), 1) AS aroon_up2,
round(linearreg_slope(high, 2), 2) AS slope2
FROM candles
ORDER BY time;
```
Result:
| time (TIMESTAMP) | high (DOUBLE) | sar (DOUBLE) | ha_close (DOUBLE) | aroon_up2 (DOUBLE) | slope2 (DOUBLE) |
|---|---|---|---|---|---|
| 2026-09-01T00:00:00.000Z | 11 | NULL | 10.13 | NULL | NULL |
| 2026-09-02T00:00:00.000Z | 12 | 9 | 11 | NULL | 1 |
| 2026-09-03T00:00:00.000Z | 12.5 | 9.06 | 11.75 | 100 | 0.5 |
| 2026-09-04T00:00:00.000Z | NULL | NULL | NULL | NULL | NULL |
| 2026-09-05T00:00:00.000Z | 13 | NULL | NULL | NULL | NULL |
| 2026-09-06T00:00:00.000Z | 13.5 | NULL | NULL | NULL | 0.5 |
| 2026-09-07T00:00:00.000Z | 14 | NULL | NULL | 100 | 0.5 |
| 2026-09-08T00:00:00.000Z | 14.2 | NULL | NULL | 100 | 0.2 |
(8 rows)
## cross_above, cross_below
Syntax:
```sql
cross_above(a, b) OVER ([PARTITION BY ...] [ORDER BY ...])
cross_below(a, b) OVER ([PARTITION BY ...] [ORDER BY ...])
```
**Crossings**: `cross_above` is `true` on the candle where `a` goes from at or below `b` to above it, `cross_below` where it goes from at or above `b` to below it. With two moving averages that is the **golden cross** (fast above slow) and the **death cross**. `crossover` and `crossunder` are other names for them (TradingView's).
| Argument | Type | Meaning |
|---|---|---|
| `a`, `b` | number | two series, or a series and a fixed level (`cross_above(rsi(close, 14), 30)`) |
- **Returns:** `BOOL`: `true` on the crossing candle, `false` on the others.
- **NULL:** on the first candle, and where `a` or `b` is `NULL` on this candle or the one before.
- **Notes:** `a` and `b` may themselves be indicators, as in `cross_above(sma(close, 20), sma(close, 50)) OVER (...)`: an indicator written inside another one without its own `OVER` takes the `PARTITION BY` and `ORDER BY` of the one it is in ([indicators inside indicators](candles-cookbook.md#moving-averages)).
**Example: Golden and death crosses of CITY: 3-day average against 8-day** (sample data: sample)
```sql
SELECT time, close, signal
FROM (
SELECT time, close,
CASE WHEN cross_above(sma(close, 3), sma(close, 8)) OVER (ORDER BY time) THEN 'golden'
WHEN cross_below(sma(close, 3), sma(close, 8)) OVER (ORDER BY time) THEN 'death' END AS signal
FROM ochl_day
WHERE symbol = 'CITY' AND time IN '2026'
) s
WHERE signal IS NOT NULL
ORDER BY time;
```
Result:
| time (TIMESTAMP) | close (DOUBLE) | signal (STRING) |
|---|---|---|
| 2026-09-10T00:00:00.000Z | 504.9 | golden |
(1 row)
**Example: Symbols whose 3-day average crossed above their 8-day one on the last day** (sample data: sample)
```sql
SELECT symbol, time
FROM (
SELECT symbol, time,
cross_above(sma(close, 3), sma(close, 8)) OVER (PARTITION BY symbol ORDER BY time) AS up,
row_number() OVER (PARTITION BY symbol ORDER BY time DESC) AS rn
FROM ochl_day
) s
WHERE rn = 1 AND up
ORDER BY symbol;
```
Result:
| symbol (SYMBOL) | time (TIMESTAMP) |
|---|---|
| CITY | 2026-09-10T00:00:00.000Z |
(1 row)
## plus_di, minus_di
Syntax:
```sql
plus_di(high, low, close)
plus_di(high, low, close, n)
minus_di(high, low, close, n) OVER ([PARTITION BY ...] [ORDER BY ...])
```
The **directional indicators** +DI and -DI (Welles Wilder's "directional movement"). **+DI** measures how strongly the highs push **up**; **-DI** how strongly the lows push **down**. Both are in percent of the [true range](functions-indicators.md#atr). When +DI is above -DI, the buyers are stronger (an up trend); when -DI is above +DI, the sellers are stronger. A cross of the two lines is a common buy or sell signal.
Each candle's upward move is `high - previous high`, its downward move is `previous low - low`; only the bigger one counts (and only when it is more than 0). Both moves and the true range get Wilder's smoothing over `n` candles, and `+DI = 100 × smoothed up move / smoothed true range`.
| Argument | Type | Meaning |
|---|---|---|
| `high` | number | the candle's high |
| `low` | number | the candle's low |
| `close` | number | the candle's close |
| `n` | whole number, 2 or more | the period; default `14` |
- **Returns:** `DOUBLE` from 0 to 100.
- **NULL:** `NULL` for the first `n` candles. A NULL `high`, `low` or `close` makes that candle and every later one `NULL`.
- **Notes:** the same as TA-Lib `PLUS_DI` and `MINUS_DI`. Use [adx](#adx) to see whether the trend is strong enough to follow.
**Example: +DI and -DI of COAST in the last 5 minutes** (sample data: sample)
```sql
SELECT time, close, plus_di14, minus_di14,
CASE WHEN plus_di14 > minus_di14 THEN 'buyers stronger' ELSE 'sellers stronger' END AS side
FROM (SELECT time, close,
round(plus_di(high, low, close), 1) AS plus_di14,
round(minus_di(high, low, close), 1) AS minus_di14
FROM ochl_minutes
WHERE symbol = 'COAST') t
ORDER BY time DESC
LIMIT 5;
```
Result:
| time (TIMESTAMP) | close (DOUBLE) | plus_di14 (DOUBLE) | minus_di14 (DOUBLE) | side (STRING) |
|---|---|---|---|---|
| 2026-09-10T09:14:00.000Z | 376.1 | 27.7 | 14.4 | buyers stronger |
| 2026-09-10T09:13:00.000Z | 376.2 | 26.9 | 15.4 | buyers stronger |
| 2026-09-10T09:12:00.000Z | 376 | 29 | 16.7 | buyers stronger |
| 2026-09-10T09:11:00.000Z | 375.5 | 22.5 | 18.5 | buyers stronger |
| 2026-09-10T09:10:00.000Z | 375.6 | 23.8 | 19.5 | buyers stronger |
(5 rows)
**Example: DI crossover: the first 5 minutes of 10 September when CITY's +DI crossed above -DI** (sample data: sample)
```sql
SELECT time, close, plus_di14, minus_di14
FROM (SELECT time, close, plus_di14, minus_di14,
lag(plus_di14) AS prev_plus, lag(minus_di14) AS prev_minus
FROM (SELECT time, close,
round(plus_di(high, low, close), 1) AS plus_di14,
round(minus_di(high, low, close), 1) AS minus_di14
FROM ochl_minutes
WHERE symbol = 'CITY') a) b
WHERE plus_di14 > minus_di14 AND prev_plus <= prev_minus AND time IN '2026-09-10'
ORDER BY time
LIMIT 5;
```
Result:
| time (TIMESTAMP) | close (DOUBLE) | plus_di14 (DOUBLE) | minus_di14 (DOUBLE) |
|---|---|---|---|
| 2026-09-10T05:41:00.000Z | 505.4 | 18.5 | 16.6 |
| 2026-09-10T05:59:00.000Z | 506.4 | 22.6 | 16.4 |
| 2026-09-10T06:06:00.000Z | 505.5 | 16.2 | 14.7 |
| 2026-09-10T06:10:00.000Z | 505.9 | 15.1 | 14.9 |
| 2026-09-10T06:18:00.000Z | 505.6 | 19.7 | 19.3 |
(5 rows)
**Example: The period must be 2 or more** (sample data: sample; shows an error)
```sql
SELECT plus_di(high, low, close, 1) FROM ochl_day;
```
Result:
Error: `line 1, column 8: plus_di: the period must be a whole number of rows (2 or more)`
## dx
Syntax:
```sql
dx(high, low, close)
dx(high, low, close, n)
dx(high, low, close, n) OVER ([PARTITION BY ...] [ORDER BY ...])
```
The **directional movement index**: how far apart +DI and -DI are, `DX = 100 × |+DI - -DI| / (+DI + -DI)`. A big DX means one side (buyers or sellers) is much stronger. DX jumps a lot from candle to candle; most traders use its smooth average, [adx](#adx).
| Argument | Type | Meaning |
|---|---|---|
| `high`, `low`, `close` | number | the candle's high, low and close |
| `n` | whole number, 2 or more | the period; default `14` |
- **Returns:** `DOUBLE` from 0 to 100.
- **NULL:** `NULL` for the first `n` candles. A NULL price stops the series (that candle and every later one is `NULL`).
- **Notes:** the same as TA-Lib `DX`. When +DI and -DI are both 0, DX keeps its previous value.
**Example: DX jumps, ADX is smooth (CITY daily, 3-day period)** (sample data: sample)
```sql
SELECT time, close,
round(dx(high, low, close, 3), 1) AS dx3,
round(adx(high, low, close, 3), 1) AS adx3
FROM ochl_day
WHERE symbol = 'CITY' AND time >= '2026-08-23'
ORDER BY time;
```
Result:
| time (TIMESTAMP) | close (DOUBLE) | dx3 (DOUBLE) | adx3 (DOUBLE) |
|---|---|---|---|
| 2026-08-23T00:00:00.000Z | 507.9 | NULL | NULL |
| 2026-08-24T00:00:00.000Z | 510.3 | NULL | NULL |
| 2026-08-25T00:00:00.000Z | 513.3 | NULL | NULL |
| 2026-08-26T00:00:00.000Z | 513.2 | 100 | NULL |
| 2026-08-27T00:00:00.000Z | 505.9 | 20.9 | NULL |
| 2026-08-30T00:00:00.000Z | 503.1 | 36.4 | 52.4 |
| 2026-08-31T00:00:00.000Z | 505.7 | 51.1 | 52 |
| 2026-09-01T00:00:00.000Z | 502.4 | 17.9 | 40.6 |
| 2026-09-02T00:00:00.000Z | 496.4 | 70.9 | 50.7 |
| 2026-09-03T00:00:00.000Z | 497.9 | 70.9 | 57.5 |
| 2026-09-06T00:00:00.000Z | 499 | 70.9 | 61.9 |
| 2026-09-07T00:00:00.000Z | 498.7 | 77.7 | 67.2 |
| 2026-09-08T00:00:00.000Z | 497.5 | 71.7 | 68.7 |
| 2026-09-09T00:00:00.000Z | 504.6 | 74.4 | 70.6 |
| 2026-09-10T00:00:00.000Z | 504.9 | 27 | 56.1 |
(15 rows)
## adx
Syntax:
```sql
adx(high, low, close)
adx(high, low, close, n)
adx(high, low, close, n) OVER ([PARTITION BY ...] [ORDER BY ...])
```
The **average directional index**: the **strength** of the trend, from 0 to 100, without its direction. It is Wilder's average of [dx](#dx) over `n` candles. Traders read it like this:
- **below 20:** no real trend, the price moves sideways;
- **above 25:** a strong trend. Then look at [+DI and -DI](#plus_di) for the direction: +DI above -DI is a strong up trend, -DI above +DI a strong down trend;
- **ADX rising:** the trend is getting stronger.
| Argument | Type | Meaning |
|---|---|---|
| `high`, `low`, `close` | number | the candle's high, low and close |
| `n` | whole number, 2 or more | the period; default `14` |
- **Returns:** `DOUBLE` from 0 to 100.
- **NULL:** `NULL` for the first `2n - 1` candles (27 with the default 14). A NULL price stops the series.
- **Notes:** the same as TA-Lib `ADX`. Because it is an average of an average, ADX is slow: it needs about `2n` candles to start and more to settle. Load more history than you show.
**Example: Strong or weak trend? COAST in the last 5 minutes** (sample data: sample)
```sql
SELECT time, close, adx14, plus_di14, minus_di14,
CASE WHEN adx14 > 25 AND plus_di14 > minus_di14 THEN 'strong up'
WHEN adx14 > 25 THEN 'strong down'
ELSE 'weak' END AS trend
FROM (SELECT time, close,
round(adx(high, low, close), 1) AS adx14,
round(plus_di(high, low, close), 1) AS plus_di14,
round(minus_di(high, low, close), 1) AS minus_di14
FROM ochl_minutes
WHERE symbol = 'COAST') t
ORDER BY time DESC
LIMIT 5;
```
Result:
| time (TIMESTAMP) | close (DOUBLE) | adx14 (DOUBLE) | plus_di14 (DOUBLE) | minus_di14 (DOUBLE) | trend (STRING) |
|---|---|---|---|---|---|
| 2026-09-10T09:14:00.000Z | 376.1 | 17.7 | 27.7 | 14.4 | weak |
| 2026-09-10T09:13:00.000Z | 376.2 | 16.6 | 26.9 | 15.4 | weak |
| 2026-09-10T09:12:00.000Z | 376 | 15.8 | 29 | 16.7 | weak |
| 2026-09-10T09:11:00.000Z | 375.5 | 15 | 22.5 | 18.5 | weak |
| 2026-09-10T09:10:00.000Z | 375.6 | 15.4 | 23.8 | 19.5 | weak |
(5 rows)
**Example: How many minutes each symbol spent in a strong up, strong down or weak trend** (sample data: sample)
```sql
SELECT symbol, trend, count(*) AS minutes
FROM (SELECT symbol,
CASE WHEN adx14 IS NULL THEN 'warm-up'
WHEN adx14 > 25 AND plus_di14 > minus_di14 THEN 'strong up'
WHEN adx14 > 25 THEN 'strong down'
ELSE 'weak' END AS trend
FROM (SELECT symbol,
adx(high, low, close) OVER (PARTITION BY symbol ORDER BY time) AS adx14,
plus_di(high, low, close) OVER (PARTITION BY symbol ORDER BY time) AS plus_di14,
minus_di(high, low, close) OVER (PARTITION BY symbol ORDER BY time) AS minus_di14
FROM ochl_minutes) a) b
GROUP BY symbol, trend
ORDER BY symbol, trend;
```
Result:
| symbol (SYMBOL) | trend (STRING) | minutes (LONG) |
|---|---|---|
| CITY | strong down | 153 |
| CITY | strong up | 73 |
| CITY | warm-up | 27 |
| CITY | weak | 227 |
| COAST | strong down | 159 |
| COAST | strong up | 16 |
| COAST | warm-up | 27 |
| COAST | weak | 274 |
(8 rows)
## adxr
Syntax:
```sql
adxr(high, low, close)
adxr(high, low, close, n)
adxr(high, low, close, n) OVER ([PARTITION BY ...] [ORDER BY ...])
```
The **average directional movement rating**: the average of today's [adx](#adx) and the ADX of `n - 1` candles ago. It is an even smoother trend strength. Some traders compare ADX with ADXR: ADX above ADXR means the trend is getting stronger.
| Argument | Type | Meaning |
|---|---|---|
| `high`, `low`, `close` | number | the candle's high, low and close |
| `n` | whole number, 2 or more | the period; default `14` |
- **Returns:** `DOUBLE` from 0 to 100.
- **NULL:** `NULL` for the first `3n - 2` candles (40 with the default 14). A NULL price stops the series.
- **Notes:** the same as TA-Lib `ADXR`.
**Example: ADX against ADXR: is the trend of CITY getting stronger?** (sample data: sample)
```sql
SELECT time, close, adx14, adxr14,
CASE WHEN adx14 > adxr14 THEN 'stronger' ELSE 'weaker' END AS strength
FROM (SELECT time, close,
round(adx(high, low, close), 1) AS adx14,
round(adxr(high, low, close), 1) AS adxr14
FROM ochl_minutes
WHERE symbol = 'CITY') t
ORDER BY time DESC
LIMIT 5;
```
Result:
| time (TIMESTAMP) | close (DOUBLE) | adx14 (DOUBLE) | adxr14 (DOUBLE) | strength (STRING) |
|---|---|---|---|---|
| 2026-09-10T09:14:00.000Z | 504.9 | 16.7 | 21.4 | weaker |
| 2026-09-10T09:13:00.000Z | 505.1 | 17.9 | 22.9 | weaker |
| 2026-09-10T09:12:00.000Z | 504.8 | 19.1 | 24.5 | weaker |
| 2026-09-10T09:11:00.000Z | 504.5 | 19.3 | 25.5 | weaker |
| 2026-09-10T09:10:00.000Z | 504.6 | 19.5 | 26.2 | weaker |
(5 rows)
## aroon_up, aroon_down
Syntax:
```sql
aroon_up(high)
aroon_up(high, n)
aroon_down(low, n) OVER ([PARTITION BY ...] [ORDER BY ...])
```
The **Aroon** indicator ("dawn's early light" in Sanskrit) asks: how long ago was the highest high, and how long ago the lowest low? It looks at this candle and the `n` candles before it.
- `aroon_up = 100 × (n - candles since the highest high) / n`. **100** means today made a new `n`-candle high; **0** means the high was `n` candles ago.
- `aroon_down` is the same with the lowest low.
- Aroon up above 70 and Aroon down below 30 is a strong up trend; the other way round a strong down trend. When the two lines cross, the trend may be changing.
| Argument | Type | Meaning |
|---|---|---|
| `high` (or `low`) | number | the highs for `aroon_up`, the lows for `aroon_down` |
| `n` | whole number, 1 or more | the period; default `14` |
- **Returns:** `DOUBLE` from 0 to 100.
- **NULL:** `NULL` for the first `n` candles, and while a NULL value is in the window of `n + 1` candles.
- **Notes:** the same as TA-Lib `AROON`. When the highest high appears twice in the window, the later one counts.
**Example: Aroon of VOLT on daily candles (5-day period)** (sample data: sample)
```sql
SELECT time, high, low,
round(aroon_up(high, 5), 1) AS aroon_up5,
round(aroon_down(low, 5), 1) AS aroon_down5
FROM ochl_day
WHERE symbol = 'VOLT' AND time >= '2026-08-23'
ORDER BY time;
```
Result:
| time (TIMESTAMP) | high (DOUBLE) | low (DOUBLE) | aroon_up5 (DOUBLE) | aroon_down5 (DOUBLE) |
|---|---|---|---|---|
| 2026-08-23T00:00:00.000Z | 182.6 | 178.2 | NULL | NULL |
| 2026-08-24T00:00:00.000Z | 183.6 | 178.5 | NULL | NULL |
| 2026-08-25T00:00:00.000Z | 182.2 | 172.8 | NULL | NULL |
| 2026-08-26T00:00:00.000Z | 178.4 | 172 | NULL | NULL |
| 2026-08-27T00:00:00.000Z | 175.9 | 173 | NULL | NULL |
| 2026-08-30T00:00:00.000Z | 174.2 | 170.6 | 20 | 100 |
| 2026-08-31T00:00:00.000Z | 175.6 | 171.3 | 0 | 80 |
| 2026-09-01T00:00:00.000Z | 172.1 | 167.3 | 0 | 100 |
| 2026-09-02T00:00:00.000Z | 174.6 | 169 | 0 | 80 |
| 2026-09-03T00:00:00.000Z | 177.3 | 171.9 | 100 | 60 |
| 2026-09-06T00:00:00.000Z | 183.9 | 172.4 | 100 | 40 |
| 2026-09-07T00:00:00.000Z | 183.2 | 174.3 | 80 | 20 |
| 2026-09-08T00:00:00.000Z | 183.5 | 178.2 | 60 | 0 |
| 2026-09-09T00:00:00.000Z | 189.5 | 181.7 | 100 | 0 |
| 2026-09-10T00:00:00.000Z | 192.1 | 184.2 | 100 | 0 |
(15 rows)
## aroon_osc
Syntax:
```sql
aroon_osc(high, low)
aroon_osc(high, low, n)
aroon_osc(high, low, n) OVER ([PARTITION BY ...] [ORDER BY ...])
```
The **Aroon oscillator**: `aroon_up(high, n) - aroon_down(low, n)`, one line from -100 to 100. Above 0, the recent high is newer than the recent low (the price is climbing); below 0, the other way round. Near +100 or -100, the trend is strong.
| Argument | Type | Meaning |
|---|---|---|
| `high` | number | the candle's high |
| `low` | number | the candle's low |
| `n` | whole number, 1 or more | the period; default `14` |
- **Returns:** `DOUBLE` from -100 to 100.
- **NULL:** `NULL` for the first `n` candles, and while a NULL high or low is in the window.
- **Notes:** the same as TA-Lib `AROONOSC`. Both `high` and `low` are needed: `aroon_osc(high, 5)` reads `5` as the lows.
**Example: Aroon oscillator of every symbol on the last day** (sample data: sample)
```sql
SELECT time, symbol, close, aroon_osc5
FROM (SELECT time, symbol, close,
round(aroon_osc(high, low, 5) OVER (PARTITION BY symbol ORDER BY time), 1) AS aroon_osc5
FROM ochl_day) t
WHERE time IN '2026-09-10'
ORDER BY aroon_osc5 DESC;
```
Result:
| time (TIMESTAMP) | symbol (SYMBOL) | close (DOUBLE) | aroon_osc5 (DOUBLE) |
|---|---|---|---|
| 2026-09-10T00:00:00.000Z | VOLT | 191.2 | 100 |
| 2026-09-10T00:00:00.000Z | COAST | 376.1 | 80 |
| 2026-09-10T00:00:00.000Z | CITY | 504.9 | 40 |
(3 rows)
## sar, parabolic_sar
Syntax:
```sql
sar(high, low)
sar(high, low, acceleration, maximum)
parabolic_sar(high, low, acceleration, maximum) OVER ([PARTITION BY ...] [ORDER BY ...])
```
The **parabolic SAR** ("stop and reverse"). On a chart it is a row of dots: **below** the candles in an up trend, **above** them in a down trend. Traders use it as a **trailing stop**: in an up trend, sell when the price falls under the dot. When the price crosses the dot, the SAR jumps to the other side and the trend is "reversed". `parabolic_sar` is the same function.
Each candle, the dot moves toward the price by `acceleration factor × (extreme price - SAR)`. The factor starts at `acceleration`, grows by `acceleration` every time the trend makes a new high (or low), and never goes above `maximum`. So the dot follows the price faster the longer the trend lasts.
| Argument | Type | Meaning |
|---|---|---|
| `high` | number | the candle's high |
| `low` | number | the candle's low |
| `acceleration` | number, more than 0 | the start and the step of the factor; default `0.02` |
| `maximum` | number, more than 0 | the biggest factor; default `0.2` |
- **Returns:** `DOUBLE` (a price).
- **NULL:** `NULL` for the first candle. A NULL `high` or `low` stops the series (that candle and every later one is `NULL`).
- **Notes:** the same as TA-Lib `SAR`. The first trend (up or down) is chosen from the first two candles. An `acceleration` bigger than `maximum` is lowered to `maximum`. A smaller `acceleration` gives a slower dot, further from the price.
**Example: SAR of CITY's daily candles: where the trend reversed** (sample data: sample)
```sql
SELECT time, low, high, close, sar,
CASE WHEN sar <= low THEN 'dot below: up trend'
WHEN sar >= high THEN 'dot above: down trend' END AS trend
FROM (SELECT time, low, high, close, round(sar(high, low), 2) AS sar
FROM ochl_day
WHERE symbol = 'CITY' AND time >= '2026-08-23') t
ORDER BY time;
```
Result:
| time (TIMESTAMP) | low (DOUBLE) | high (DOUBLE) | close (DOUBLE) | sar (DOUBLE) | trend (STRING) |
|---|---|---|---|---|---|
| 2026-08-23T00:00:00.000Z | 502 | 508.4 | 507.9 | NULL | NULL |
| 2026-08-24T00:00:00.000Z | 508.2 | 510.9 | 510.3 | 502 | dot below: up trend |
| 2026-08-25T00:00:00.000Z | 507.6 | 517 | 513.3 | 502.18 | dot below: up trend |
| 2026-08-26T00:00:00.000Z | 508 | 515.3 | 513.2 | 502.77 | dot below: up trend |
| 2026-08-27T00:00:00.000Z | 505.5 | 514.3 | 505.9 | 503.34 | dot below: up trend |
| 2026-08-30T00:00:00.000Z | 501.7 | 511.2 | 503.1 | 517 | dot above: down trend |
| 2026-08-31T00:00:00.000Z | 500.1 | 506 | 505.7 | 516.69 | dot above: down trend |
| 2026-09-01T00:00:00.000Z | 501.3 | 507.3 | 502.4 | 516.03 | dot above: down trend |
| 2026-09-02T00:00:00.000Z | 494.1 | 502.8 | 496.4 | 515.39 | dot above: down trend |
| 2026-09-03T00:00:00.000Z | 495.5 | 500.9 | 497.9 | 514.12 | dot above: down trend |
| 2026-09-06T00:00:00.000Z | 496 | 500.1 | 499 | 512.91 | dot above: down trend |
| 2026-09-07T00:00:00.000Z | 495 | 501 | 498.7 | 511.79 | dot above: down trend |
| 2026-09-08T00:00:00.000Z | 497.5 | 501.1 | 497.5 | 510.72 | dot above: down trend |
| 2026-09-09T00:00:00.000Z | 504 | 512.4 | 504.6 | 494.1 | dot below: up trend |
| 2026-09-10T00:00:00.000Z | 500.7 | 507 | 504.9 | 494.47 | dot below: up trend |
(15 rows)
**Example: A trailing stop from the SAR, for the last 5 minutes of CITY** (sample data: sample)
```sql
SELECT time, close, sar_stop,
CASE WHEN sar_stop < close THEN 'hold: sell below the stop' ELSE 'down trend: stay out' END AS action
FROM (SELECT time, close, round(sar(high, low), 2) AS sar_stop
FROM ochl_minutes
WHERE symbol = 'CITY') t
ORDER BY time DESC
LIMIT 5;
```
Result:
| time (TIMESTAMP) | close (DOUBLE) | sar_stop (DOUBLE) | action (STRING) |
|---|---|---|---|
| 2026-09-10T09:14:00.000Z | 504.9 | 505.78 | down trend: stay out |
| 2026-09-10T09:13:00.000Z | 505.1 | 505.84 | down trend: stay out |
| 2026-09-10T09:12:00.000Z | 504.8 | 505.9 | down trend: stay out |
| 2026-09-10T09:11:00.000Z | 504.5 | 505.97 | down trend: stay out |
| 2026-09-10T09:10:00.000Z | 504.6 | 506.04 | down trend: stay out |
(5 rows)
**Example: A slower SAR: acceleration 0.01, maximum 0.1** (sample data: sample)
```sql
SELECT time, close,
round(sar(high, low), 2) AS sar_default,
round(parabolic_sar(high, low, 0.01, 0.1), 2) AS sar_slow
FROM ochl_day
WHERE symbol = 'COAST' AND time >= '2026-09-01'
ORDER BY time;
```
Result:
| time (TIMESTAMP) | close (DOUBLE) | sar_default (DOUBLE) | sar_slow (DOUBLE) |
|---|---|---|---|
| 2026-09-01T00:00:00.000Z | 367.4 | NULL | NULL |
| 2026-09-02T00:00:00.000Z | 369.2 | 365.3 | 365.3 |
| 2026-09-03T00:00:00.000Z | 373.2 | 373.6 | 373.6 |
| 2026-09-06T00:00:00.000Z | 375.4 | 364.1 | 364.1 |
| 2026-09-07T00:00:00.000Z | 379.2 | 364.1 | 364.1 |
| 2026-09-08T00:00:00.000Z | 377.2 | 364.78 | 364.44 |
| 2026-09-09T00:00:00.000Z | 380.1 | 365.43 | 364.77 |
| 2026-09-10T00:00:00.000Z | 376.1 | 367.43 | 365.79 |
(8 rows)
**Example: acceleration and maximum must be more than 0** (sample data: sample; shows an error)
```sql
SELECT sar(high, low, 0, 0.2) FROM ochl_day;
```
Result:
Error: `line 1, column 8: sar: the acceleration and the maximum must be more than 0`
## ichimoku_tenkan, ichimoku_kijun
Syntax:
```sql
ichimoku_tenkan(high, low)
ichimoku_tenkan(high, low, n)
ichimoku_kijun(high, low, n) OVER ([PARTITION BY ...] [ORDER BY ...])
```
**Ichimoku** ("one look" in Japanese) is a whole trend system drawn on the chart. Its lines are middle prices: `(highest high + lowest low) / 2` of the last `n` candles.
- `ichimoku_tenkan` is the **tenkan-sen** (conversion line), a fast line: default 9 candles.
- `ichimoku_kijun` is the **kijun-sen** (base line), a slow line: default 26 candles.
- Tenkan above kijun is a sign of an up trend; a cross of tenkan above kijun is a buy signal ("TK cross").
| Argument | Type | Meaning |
|---|---|---|
| `high` | number | the candle's high |
| `low` | number | the candle's low |
| `n` | whole number, 1 or more | the period; default `9` for tenkan, `26` for kijun |
- **Returns:** `DOUBLE` (a price).
- **NULL:** `NULL` for the first `n - 1` candles, and while a NULL high or low is in the window.
- **Notes:** the classic periods 9, 26 and 52 were made for a 6-day trading week. Some traders use other periods; all of them can be changed.
**Example: Tenkan and kijun of COAST in the last 5 minutes** (sample data: sample)
```sql
SELECT time, close, tenkan, kijun,
CASE WHEN tenkan > kijun THEN 'up' WHEN tenkan < kijun THEN 'down' ELSE 'flat' END AS signal
FROM (SELECT time, close,
round(ichimoku_tenkan(high, low), 2) AS tenkan,
round(ichimoku_kijun(high, low), 2) AS kijun
FROM ochl_minutes
WHERE symbol = 'COAST') t
ORDER BY time DESC
LIMIT 5;
```
Result:
| time (TIMESTAMP) | close (DOUBLE) | tenkan (DOUBLE) | kijun (DOUBLE) | signal (STRING) |
|---|---|---|---|---|
| 2026-09-10T09:14:00.000Z | 376.1 | 375.45 | 375.4 | up |
| 2026-09-10T09:13:00.000Z | 376.2 | 375.35 | 375.25 | up |
| 2026-09-10T09:12:00.000Z | 376 | 375.3 | 375.05 | up |
| 2026-09-10T09:11:00.000Z | 375.5 | 375 | 374.85 | up |
| 2026-09-10T09:10:00.000Z | 375.6 | 375 | 374.75 | up |
(5 rows)
## ichimoku_senkou_a
Syntax:
```sql
ichimoku_senkou_a(high, low)
ichimoku_senkou_a(high, low, short, long, shift)
ichimoku_senkou_a(high, low, short, long, shift) OVER ([PARTITION BY ...] [ORDER BY ...])
```
**Senkou span A**, the first edge of the Ichimoku **cloud** (kumo): `(tenkan + kijun) / 2`, made `shift` candles ago. A chart draws the cloud 26 candles ahead of the price; KDB puts on each row the cloud value that the chart shows **under that candle**, so you can compare it with the row's price directly.
| Argument | Type | Meaning |
|---|---|---|
| `high`, `low` | number | the candle's high and low |
| `short` | whole number, 1 or more | the tenkan period; default `9` |
| `long` | whole number, 1 or more | the kijun period; default `26` |
| `shift` | whole number, 0 or more | how many candles back the value was made; default `26` |
- **Returns:** `DOUBLE` (a price).
- **NULL:** `NULL` for the first `long + shift - 1` candles (51 with the defaults; when `short` is the bigger period, `short + shift - 1`), and while a NULL high or low is in the window.
- **Notes:** with `shift` 0 you get the value made on this candle, as a chart would draw it 26 candles later.
**Example: Senkou span A with shift 0 is the average of tenkan and kijun** (sample data: sample)
```sql
SELECT time, tenkan, kijun, span_a_now
FROM (SELECT time,
round(ichimoku_tenkan(high, low), 2) AS tenkan,
round(ichimoku_kijun(high, low), 2) AS kijun,
round(ichimoku_senkou_a(high, low, 9, 26, 0), 2) AS span_a_now
FROM ochl_minutes
WHERE symbol = 'CITY') t
ORDER BY time DESC
LIMIT 3;
```
Result:
| time (TIMESTAMP) | tenkan (DOUBLE) | kijun (DOUBLE) | span_a_now (DOUBLE) |
|---|---|---|---|
| 2026-09-10T09:14:00.000Z | 505.1 | 505.35 | 505.23 |
| 2026-09-10T09:13:00.000Z | 505.2 | 505.35 | 505.28 |
| 2026-09-10T09:12:00.000Z | 505.2 | 505.35 | 505.28 |
(3 rows)
## ichimoku_senkou_b
Syntax:
```sql
ichimoku_senkou_b(high, low)
ichimoku_senkou_b(high, low, n, shift)
ichimoku_senkou_b(high, low, n, shift) OVER ([PARTITION BY ...] [ORDER BY ...])
```
**Senkou span B**, the second edge of the Ichimoku cloud: the middle price `(highest high + lowest low) / 2` of the last `n` candles (default 52), made `shift` candles ago. Together with [senkou span A](#ichimoku_senkou_a):
- price **above** the cloud (above both spans): up trend;
- price **below** the cloud: down trend;
- price **inside** the cloud: no clear trend. A thick cloud is strong support or resistance (a price level that is hard to break).
| Argument | Type | Meaning |
|---|---|---|
| `high`, `low` | number | the candle's high and low |
| `n` | whole number, 1 or more | the period; default `52` |
| `shift` | whole number, 0 or more | how many candles back the value was made; default `26` |
- **Returns:** `DOUBLE` (a price).
- **NULL:** `NULL` for the first `n + shift - 1` candles (77 with the defaults), and while a NULL high or low is in the window.
**Example: Is CITY above, below or inside the Ichimoku cloud?** (sample data: sample)
```sql
SELECT time, close, span_a, span_b,
CASE WHEN close > greatest(span_a, span_b) THEN 'above the cloud: up trend'
WHEN close < least(span_a, span_b) THEN 'below the cloud: down trend'
ELSE 'inside the cloud' END AS position
FROM (SELECT time, close,
round(ichimoku_senkou_a(high, low), 2) AS span_a,
round(ichimoku_senkou_b(high, low), 2) AS span_b
FROM ochl_minutes
WHERE symbol = 'CITY') t
ORDER BY time DESC
LIMIT 5;
```
Result:
| time (TIMESTAMP) | close (DOUBLE) | span_a (DOUBLE) | span_b (DOUBLE) | position (STRING) |
|---|---|---|---|---|
| 2026-09-10T09:14:00.000Z | 504.9 | 505.95 | 504.45 | inside the cloud |
| 2026-09-10T09:13:00.000Z | 505.1 | 505.93 | 504.45 | inside the cloud |
| 2026-09-10T09:12:00.000Z | 504.8 | 505.88 | 504.45 | inside the cloud |
| 2026-09-10T09:11:00.000Z | 504.5 | 505.65 | 504.45 | inside the cloud |
| 2026-09-10T09:10:00.000Z | 504.6 | 505.48 | 504.45 | inside the cloud |
(5 rows)
## ichimoku_chikou
Syntax:
```sql
ichimoku_chikou(close)
ichimoku_chikou(close, shift)
ichimoku_chikou(close, shift) OVER ([PARTITION BY ...] [ORDER BY ...])
```
**Chikou span** (the lagging line) of Ichimoku. A chart draws today's close `shift` candles **back** in time. So in KDB, each row gets the close from `shift` rows **later**. It lets you compare the price then with the price 26 candles later: chikou above the old price means the price has risen since.
| Argument | Type | Meaning |
|---|---|---|
| `close` | number | the candle's close |
| `shift` | whole number, 0 or more | how many candles later the close comes from; default `26` |
- **Returns:** `DOUBLE` (a price).
- **NULL:** the **last** `shift` candles are `NULL` (there is no later close yet). A NULL close gives `NULL` on the row `shift` candles before it.
- **Notes:** the same as `lead(close, shift)` ([lead](functions-window.md#lead)). Because it looks into the future of each row, never use it as a signal on the last rows.
**Example: Chikou with shift 5 on CITY's daily closes: the last 5 days are NULL** (sample data: sample)
```sql
SELECT time, close, ichimoku_chikou(close, 5) AS close_5_days_later
FROM ochl_day
WHERE symbol = 'CITY' AND time >= '2026-08-30'
ORDER BY time;
```
Result:
| time (TIMESTAMP) | close (DOUBLE) | close_5_days_later (DOUBLE) |
|---|---|---|
| 2026-08-30T00:00:00.000Z | 503.1 | 499 |
| 2026-08-31T00:00:00.000Z | 505.7 | 498.7 |
| 2026-09-01T00:00:00.000Z | 502.4 | 497.5 |
| 2026-09-02T00:00:00.000Z | 496.4 | 504.6 |
| 2026-09-03T00:00:00.000Z | 497.9 | 504.9 |
| 2026-09-06T00:00:00.000Z | 499 | NULL |
| 2026-09-07T00:00:00.000Z | 498.7 | NULL |
| 2026-09-08T00:00:00.000Z | 497.5 | NULL |
| 2026-09-09T00:00:00.000Z | 504.6 | NULL |
| 2026-09-10T00:00:00.000Z | 504.9 | NULL |
(10 rows)
## supertrend
Syntax:
```sql
supertrend(high, low, close)
supertrend(high, low, close, n, multiplier)
supertrend(high, low, close, n, multiplier) OVER ([PARTITION BY ...] [ORDER BY ...])
```
**SuperTrend** is one line that follows the price. In an **up trend** the line is **below** the price, in a **down trend** it is **above**. When the close crosses the line, the trend flips. Traders like it as a simple trailing stop: in an up trend, sell when the close falls below the SuperTrend line.
The line is `(high + low) / 2 ± multiplier × ATR`. The ATR (average true range, see [atr](functions-indicators.md#atr)) makes the line keep a bigger distance when the price moves a lot. The line only moves toward the price, never away, until the trend flips. Use [supertrend_direction](#supertrend_direction) to know which side the line is on.
| Argument | Type | Meaning |
|---|---|---|
| `high`, `low`, `close` | number | the candle's high, low and close |
| `n` | whole number, 1 or more | the ATR period; default `10` |
| `multiplier` | number | how many ATRs away the line is; default `3` |
- **Returns:** `DOUBLE` (a price).
- **NULL:** `NULL` for the first `n - 1` candles. A NULL price stops the series (that candle and every later one is `NULL`).
- **Notes:** the same as TradingView `ta.supertrend(multiplier, n)`. Its ATR is TradingView's: the first true range is `high - low`, and the first ATR is on candle `n`. So it is a little different from KDB's [atr](functions-indicators.md#atr), which starts on candle `n + 1`. A bigger `multiplier` gives fewer, later flips.
**Example: SuperTrend of CITY in the last 5 minutes** (sample data: sample)
```sql
SELECT time, close, supertrend, direction,
CASE WHEN direction = 1 THEN 'up: line is the stop below' ELSE 'down: line is above' END AS meaning
FROM (SELECT time, close,
round(supertrend(high, low, close), 2) AS supertrend,
supertrend_direction(high, low, close) AS direction
FROM ochl_minutes
WHERE symbol = 'CITY') t
ORDER BY time DESC
LIMIT 5;
```
Result:
| time (TIMESTAMP) | close (DOUBLE) | supertrend (DOUBLE) | direction (LONG) | meaning (STRING) |
|---|---|---|---|---|
| 2026-09-10T09:14:00.000Z | 504.9 | 506.15 | -1 | down: line is above |
| 2026-09-10T09:13:00.000Z | 505.1 | 506.15 | -1 | down: line is above |
| 2026-09-10T09:12:00.000Z | 504.8 | 506.15 | -1 | down: line is above |
| 2026-09-10T09:11:00.000Z | 504.5 | 506.15 | -1 | down: line is above |
| 2026-09-10T09:10:00.000Z | 504.6 | 506.15 | -1 | down: line is above |
(5 rows)
**Example: Daily SuperTrend of every symbol (5 days, multiplier 2) on the last day** (sample data: sample)
```sql
SELECT time, symbol, close, st, direction
FROM (SELECT time, symbol, close,
round(supertrend(high, low, close, 5, 2) OVER (PARTITION BY symbol ORDER BY time), 2) AS st,
supertrend_direction(high, low, close, 5, 2) OVER (PARTITION BY symbol ORDER BY time) AS direction
FROM ochl_day) t
WHERE time IN '2026-09-10'
ORDER BY symbol;
```
Result:
| time (TIMESTAMP) | symbol (SYMBOL) | close (DOUBLE) | st (DOUBLE) | direction (LONG) |
|---|---|---|---|---|
| 2026-09-10T00:00:00.000Z | CITY | 504.9 | 511.5 | -1 |
| 2026-09-10T00:00:00.000Z | COAST | 376.1 | 384.2 | -1 |
| 2026-09-10T00:00:00.000Z | VOLT | 191.2 | 173.34 | 1 |
(3 rows)
## supertrend_direction
Syntax:
```sql
supertrend_direction(high, low, close)
supertrend_direction(high, low, close, n, multiplier)
supertrend_direction(high, low, close, n, multiplier) OVER ([PARTITION BY ...] [ORDER BY ...])
```
The side of the [SuperTrend](#supertrend) line: **1** for an up trend (the line is below the price), **-1** for a down trend (the line is above). When it changes from -1 to 1, traders take it as a **buy** signal; from 1 to -1 as a **sell** signal.
| Argument | Type | Meaning |
|---|---|---|
| `high`, `low`, `close` | number | the candle's high, low and close |
| `n` | whole number, 1 or more | the ATR period; default `10` |
| `multiplier` | number | how many ATRs away the line is; default `3` |
- **Returns:** `LONG`: `1` or `-1`.
- **NULL:** `NULL` for the first `n - 1` candles. A NULL price stops the series.
- **Notes:** **careful:** TradingView's `ta.supertrend` gives the opposite sign (-1 for up). KDB uses 1 for up because it is easier to read. The first value is always `-1`, like TradingView's first value.
**Example: SuperTrend flips of CITY: buy and sell signals** (sample data: sample)
```sql
SELECT time, close, supertrend,
CASE WHEN direction = 1 THEN 'BUY' ELSE 'SELL' END AS signal
FROM (SELECT time, close, supertrend, direction, lag(direction) AS prev_direction
FROM (SELECT time, close,
round(supertrend(high, low, close), 2) AS supertrend,
supertrend_direction(high, low, close) AS direction
FROM ochl_minutes
WHERE symbol = 'CITY') a) b
WHERE direction <> prev_direction
ORDER BY time;
```
Result:
| time (TIMESTAMP) | close (DOUBLE) | supertrend (DOUBLE) | signal (STRING) |
|---|---|---|---|
| 2026-09-09T06:47:00.000Z | 505.8 | 504.08 | BUY |
| 2026-09-09T08:42:00.000Z | 508.2 | 510.31 | SELL |
| 2026-09-10T05:43:00.000Z | 506 | 504.19 | BUY |
| 2026-09-10T06:34:00.000Z | 504.8 | 506.76 | SELL |
| 2026-09-10T07:13:00.000Z | 502.9 | 501.07 | BUY |
| 2026-09-10T08:52:00.000Z | 504.7 | 506.91 | SELL |
(6 rows)
**Example: How many minutes COAST spent in each SuperTrend direction, per day** (sample data: sample)
```sql
SELECT to_date(time) AS day, direction, count(*) AS minutes
FROM (SELECT time, supertrend_direction(high, low, close) AS direction
FROM ochl_minutes
WHERE symbol = 'COAST') t
WHERE direction IS NOT NULL
GROUP BY to_date(time), direction
ORDER BY day, direction;
```
Result:
| day (DATE) | direction (LONG) | minutes (LONG) |
|---|---|---|
| 2026-09-09 | -1 | 200 |
| 2026-09-09 | 1 | 31 |
| 2026-09-10 | -1 | 149 |
| 2026-09-10 | 1 | 87 |
(4 rows)
## linearreg, linreg
Syntax:
```sql
linearreg(x)
linearreg(x, n)
linreg(x, n) OVER ([PARTITION BY ...] [ORDER BY ...])
```
A **moving linear regression**. For every candle, KDB draws the best straight line through the last `n` values (the "least squares" line: the one with the smallest squared distances to the points). `linearreg` is the value of that line **on this candle**. It is a moving average that follows the trend with less delay than [sma](functions-indicators.md#sma). `linreg` is the same function.
The other functions of this family read the same line: [linearreg_slope](#linearreg_slope) (its steepness), [linearreg_intercept](#linearreg_intercept) (its start), [linearreg_angle](#linearreg_angle) and [tsf](#tsf) (the next candle's forecast).
| Argument | Type | Meaning |
|---|---|---|
| `x` | number | the price, usually `close` |
| `n` | whole number, 2 or more | how many candles the line goes through; default `14` |
- **Returns:** `DOUBLE`.
- **NULL:** `NULL` for the first `n - 1` candles, and while a NULL value is in the window.
- **Notes:** the same as TA-Lib `LINEARREG`. For one line through **all** rows of a group, use the aggregate [regr_slope](functions-correlation.md#regr_slope).
**Example: Linear regression line against SMA (CITY daily, 5 days)** (sample data: sample)
```sql
SELECT time, close,
round(linearreg(close, 5), 2) AS linreg5,
round(sma(close, 5), 2) AS sma5
FROM ochl_day
WHERE symbol = 'CITY' AND time >= '2026-08-23'
ORDER BY time;
```
Result:
| time (TIMESTAMP) | close (DOUBLE) | linreg5 (DOUBLE) | sma5 (DOUBLE) |
|---|---|---|---|
| 2026-08-23T00:00:00.000Z | 507.9 | NULL | NULL |
| 2026-08-24T00:00:00.000Z | 510.3 | NULL | NULL |
| 2026-08-25T00:00:00.000Z | 513.3 | NULL | NULL |
| 2026-08-26T00:00:00.000Z | 513.2 | NULL | NULL |
| 2026-08-27T00:00:00.000Z | 505.9 | 509.9 | 510.12 |
| 2026-08-30T00:00:00.000Z | 503.1 | 504.8 | 509.16 |
| 2026-08-31T00:00:00.000Z | 505.7 | 503.18 | 508.24 |
| 2026-09-01T00:00:00.000Z | 502.4 | 501.7 | 506.06 |
| 2026-09-02T00:00:00.000Z | 496.4 | 498.76 | 502.7 |
| 2026-09-03T00:00:00.000Z | 497.9 | 497.16 | 501.1 |
| 2026-09-06T00:00:00.000Z | 499 | 496.7 | 500.28 |
| 2026-09-07T00:00:00.000Z | 498.7 | 497.92 | 498.88 |
| 2026-09-08T00:00:00.000Z | 497.5 | 498.5 | 497.9 |
| 2026-09-09T00:00:00.000Z | 504.6 | 501.92 | 499.54 |
| 2026-09-10T00:00:00.000Z | 504.9 | 504.48 | 500.94 |
(15 rows)
**Example: The period must be 2 or more: one point makes no line** (sample data: sample; shows an error)
```sql
SELECT linreg(close, 1) FROM ochl_day;
```
Result:
Error: `line 1, column 8: linreg: the period must be a whole number of rows (2 or more)`
## linearreg_slope, linreg_slope
Syntax:
```sql
linearreg_slope(x)
linearreg_slope(x, n)
linreg_slope(x, n) OVER ([PARTITION BY ...] [ORDER BY ...])
```
The **slope** of the moving regression line: how much the line goes up (or down, when negative) per candle. On one-minute closes, a slope of `0.05` means the trend adds about 5 cents a minute. A positive slope is an up trend, a negative slope a down trend; a bigger number is a steeper trend. `linreg_slope` is the same function.
| Argument | Type | Meaning |
|---|---|---|
| `x` | number | the price, usually `close` |
| `n` | whole number, 2 or more | how many candles; default `14` |
- **Returns:** `DOUBLE` (price per candle).
- **NULL:** `NULL` for the first `n - 1` candles, and while a NULL value is in the window.
- **Notes:** the same as TA-Lib `LINEARREG_SLOPE`. To compare shares with different prices, divide by the price: `slope / close × 100` is the trend in percent per candle.
**Example: Trend of the last 14 minutes, per symbol, at the close** (sample data: sample)
```sql
SELECT time, symbol, close, slope14, round(slope14 / close * 100 * 60, 3) AS pct_per_hour
FROM (SELECT time, symbol, close,
round(linearreg_slope(close, 14) OVER (PARTITION BY symbol ORDER BY time), 4) AS slope14
FROM ochl_minutes) t
WHERE time = '2026-09-10T09:14:00Z'
ORDER BY symbol;
```
Result:
| time (TIMESTAMP) | symbol (SYMBOL) | close (DOUBLE) | slope14 (DOUBLE) | pct_per_hour (DOUBLE) |
|---|---|---|---|---|
| 2026-09-10T09:14:00.000Z | CITY | 504.9 | -0.0534 | -0.635 |
| 2026-09-10T09:14:00.000Z | COAST | 376.1 | 0.107 | 1.707 |
(2 rows)
## linearreg_intercept, linreg_intercept
Syntax:
```sql
linearreg_intercept(x)
linearreg_intercept(x, n)
linreg_intercept(x, n) OVER ([PARTITION BY ...] [ORDER BY ...])
```
The **intercept** of the moving regression line: the value of the line on the **first** (oldest) candle of its window. With the slope, it gives the whole line: `linearreg = intercept + slope × (n - 1)`. `linreg_intercept` is the same function.
| Argument | Type | Meaning |
|---|---|---|
| `x` | number | the price, usually `close` |
| `n` | whole number, 2 or more | how many candles; default `14` |
- **Returns:** `DOUBLE`.
- **NULL:** `NULL` for the first `n - 1` candles, and while a NULL value is in the window.
- **Notes:** the same as TA-Lib `LINEARREG_INTERCEPT`.
**Example: Intercept + slope × (n - 1) is the linearreg value** (sample data: sample)
```sql
SELECT time, close,
round(linearreg_intercept(close, 5), 2) AS intercept5,
round(linearreg_slope(close, 5), 3) AS slope5,
round(linearreg_intercept(close, 5) + linearreg_slope(close, 5) * 4, 2) AS line_today,
round(linearreg(close, 5), 2) AS linreg5
FROM ochl_day
WHERE symbol = 'VOLT' AND time >= '2026-09-01'
ORDER BY time;
```
Result:
| time (TIMESTAMP) | close (DOUBLE) | intercept5 (DOUBLE) | slope5 (DOUBLE) | line_today (DOUBLE) | linreg5 (DOUBLE) |
|---|---|---|---|---|---|
| 2026-09-01T00:00:00.000Z | 169 | NULL | NULL | NULL | NULL |
| 2026-09-02T00:00:00.000Z | 171.5 | NULL | NULL | NULL | NULL |
| 2026-09-03T00:00:00.000Z | 176 | NULL | NULL | NULL | NULL |
| 2026-09-06T00:00:00.000Z | 180.1 | NULL | NULL | NULL | NULL |
| 2026-09-07T00:00:00.000Z | 176.9 | 169.82 | 2.44 | 179.58 | 179.58 |
| 2026-09-08T00:00:00.000Z | 182.4 | 172.84 | 2.27 | 181.92 | 181.92 |
| 2026-09-09T00:00:00.000Z | 187.3 | 175.56 | 2.49 | 185.52 | 185.52 |
| 2026-09-10T00:00:00.000Z | 191.2 | 177.06 | 3.26 | 190.1 | 190.1 |
(8 rows)
## linearreg_angle, linreg_angle
Syntax:
```sql
linearreg_angle(x)
linearreg_angle(x, n)
linreg_angle(x, n) OVER ([PARTITION BY ...] [ORDER BY ...])
```
The slope of the moving regression line as an **angle in degrees**: `atan(slope)` changed to degrees. Near `0` is flat; near `90` a very steep rise, near `-90` a very steep fall. `linreg_angle` is the same function.
| Argument | Type | Meaning |
|---|---|---|
| `x` | number | the price, usually `close` |
| `n` | whole number, 2 or more | how many candles; default `14` |
- **Returns:** `DOUBLE` from -90 to 90.
- **NULL:** `NULL` for the first `n - 1` candles, and while a NULL value is in the window.
- **Notes:** the same as TA-Lib `LINEARREG_ANGLE`. The angle depends on the price unit (dollars per candle), so a 500-dollar share shows steeper angles than a 50-dollar share with the same move in percent. Only compare angles of the same share.
**Example: Angle of the 14-minute trend of COAST** (sample data: sample)
```sql
SELECT time, close, angle14
FROM (SELECT time, close, round(linearreg_angle(close), 1) AS angle14
FROM ochl_minutes
WHERE symbol = 'COAST') t
ORDER BY time DESC
LIMIT 5;
```
Result:
| time (TIMESTAMP) | close (DOUBLE) | angle14 (DOUBLE) |
|---|---|---|
| 2026-09-10T09:14:00.000Z | 376.1 | 6.1 |
| 2026-09-10T09:13:00.000Z | 376.2 | 4.2 |
| 2026-09-10T09:12:00.000Z | 376 | 1.8 |
| 2026-09-10T09:11:00.000Z | 375.5 | -0.4 |
| 2026-09-10T09:10:00.000Z | 375.6 | -1.8 |
(5 rows)
## tsf
Syntax:
```sql
tsf(x)
tsf(x, n)
tsf(x, n) OVER ([PARTITION BY ...] [ORDER BY ...])
```
The **time series forecast**: the moving regression line carried **one candle further**. It is a simple guess of the next value if the trend of the last `n` candles goes on: `tsf = linearreg + slope`.
| Argument | Type | Meaning |
|---|---|---|
| `x` | number | the price, usually `close` |
| `n` | whole number, 2 or more | how many candles; default `14` |
- **Returns:** `DOUBLE`.
- **NULL:** `NULL` for the first `n - 1` candles, and while a NULL value is in the window.
- **Notes:** the same as TA-Lib `TSF`. It is only a straight line: it does not know news or sudden turns.
**Example: Forecast against the real next close (CITY daily, 5 days)** (sample data: sample)
```sql
SELECT time, close, forecast_for_next_day, next_close,
round(next_close - forecast_for_next_day, 2) AS error
FROM (SELECT time, close,
round(tsf(close, 5), 2) AS forecast_for_next_day,
lead(close) AS next_close
FROM ochl_day
WHERE symbol = 'CITY' AND time >= '2026-08-23') t
WHERE forecast_for_next_day IS NOT NULL
ORDER BY time;
```
Result:
| time (TIMESTAMP) | close (DOUBLE) | forecast_for_next_day (DOUBLE) | next_close (DOUBLE) | error (DOUBLE) |
|---|---|---|---|---|
| 2026-08-27T00:00:00.000Z | 505.9 | 509.79 | 503.1 | -6.69 |
| 2026-08-30T00:00:00.000Z | 503.1 | 502.62 | 505.7 | 3.08 |
| 2026-08-31T00:00:00.000Z | 505.7 | 500.65 | 502.4 | 1.75 |
| 2026-09-01T00:00:00.000Z | 502.4 | 499.52 | 496.4 | -3.12 |
| 2026-09-02T00:00:00.000Z | 496.4 | 496.79 | 497.9 | 1.11 |
| 2026-09-03T00:00:00.000Z | 497.9 | 495.19 | 499 | 3.81 |
| 2026-09-06T00:00:00.000Z | 499 | 494.91 | 498.7 | 3.79 |
| 2026-09-07T00:00:00.000Z | 498.7 | 497.44 | 497.5 | 0.06 |
| 2026-09-08T00:00:00.000Z | 497.5 | 498.8 | 504.6 | 5.8 |
| 2026-09-09T00:00:00.000Z | 504.6 | 503.11 | 504.9 | 1.79 |
| 2026-09-10T00:00:00.000Z | 504.9 | 506.25 | NULL | NULL |
(11 rows)
## ha_open, ha_high, ha_low, ha_close
Syntax:
```sql
ha_open(open, high, low, close)
ha_high(open, high, low, close)
ha_low(open, high, low, close)
ha_close(open, high, low, close) OVER ([PARTITION BY ...] [ORDER BY ...])
```
**Heikin-Ashi** ("average bar" in Japanese) candles are smoothed candles. Each one mixes the candle with the one before, so the chart shows long runs of same-colored candles in a trend and fewer false turns. Many traders read them like this: green candles with no lower wick mean a strong up trend.
| Function | Formula |
|---|---|
| `ha_close` | `(open + high + low + close) / 4` |
| `ha_open` | `(previous ha_open + previous ha_close) / 2`; on the first candle `(open + close) / 2` |
| `ha_high` | the biggest of `high`, `ha_open` and `ha_close` |
| `ha_low` | the smallest of `low`, `ha_open` and `ha_close` |
| Argument | Type | Meaning |
|---|---|---|
| `open`, `high`, `low`, `close` | number | the normal candle |
- **Returns:** `DOUBLE` (a price).
- **NULL:** every candle has a value, from the first. A NULL `open`, `high`, `low` or `close` stops the series (that candle and every later one is `NULL`).
- **Notes:** all four arguments are needed for every function, in the order open, high, low, close. The Heikin-Ashi close is **not** a real trade price: use the normal close for orders and stop-losses.
**Example: Heikin-Ashi candles of VOLT, with their color** (sample data: sample)
```sql
SELECT time, open, close, ha_open, ha_high, ha_low, ha_close,
CASE WHEN ha_close >= ha_open THEN 'green' ELSE 'red' END AS ha_color
FROM (SELECT time, open, close,
round(ha_open(open, high, low, close), 2) AS ha_open,
round(ha_high(open, high, low, close), 2) AS ha_high,
round(ha_low(open, high, low, close), 2) AS ha_low,
round(ha_close(open, high, low, close), 2) AS ha_close
FROM ochl_day
WHERE symbol = 'VOLT' AND time >= '2026-08-23') t
ORDER BY time;
```
Result:
| time (TIMESTAMP) | open (DOUBLE) | close (DOUBLE) | ha_open (DOUBLE) | ha_high (DOUBLE) | ha_low (DOUBLE) | ha_close (DOUBLE) | ha_color (STRING) |
|---|---|---|---|---|---|---|---|
| 2026-08-23T00:00:00.000Z | 182.4 | 181 | 181.7 | 182.6 | 178.2 | 181.05 | red |
| 2026-08-24T00:00:00.000Z | 179.8 | 178.5 | 181.38 | 183.6 | 178.5 | 180.1 | red |
| 2026-08-25T00:00:00.000Z | 179.3 | 176.8 | 180.74 | 182.2 | 172.8 | 177.78 | red |
| 2026-08-26T00:00:00.000Z | 177.6 | 174.2 | 179.26 | 179.26 | 172 | 175.55 | red |
| 2026-08-27T00:00:00.000Z | 175.4 | 173.3 | 177.4 | 177.4 | 173 | 174.4 | red |
| 2026-08-30T00:00:00.000Z | 172.5 | 173 | 175.9 | 175.9 | 170.6 | 172.58 | red |
| 2026-08-31T00:00:00.000Z | 173.7 | 172.6 | 174.24 | 175.6 | 171.3 | 173.3 | red |
| 2026-09-01T00:00:00.000Z | 171.3 | 169 | 173.77 | 173.77 | 167.3 | 169.93 | red |
| 2026-09-02T00:00:00.000Z | 170.6 | 171.5 | 171.85 | 174.6 | 169 | 171.43 | red |
| 2026-09-03T00:00:00.000Z | 172.7 | 176 | 171.64 | 177.3 | 171.64 | 174.48 | green |
| 2026-09-06T00:00:00.000Z | 174.2 | 180.1 | 173.06 | 183.9 | 172.4 | 177.65 | green |
| 2026-09-07T00:00:00.000Z | 181.4 | 176.9 | 175.35 | 183.2 | 174.3 | 178.95 | green |
| 2026-09-08T00:00:00.000Z | 178.7 | 182.4 | 177.15 | 183.5 | 177.15 | 180.7 | green |
| 2026-09-09T00:00:00.000Z | 182 | 187.3 | 178.93 | 189.5 | 178.93 | 185.13 | green |
| 2026-09-10T00:00:00.000Z | 185.6 | 191.2 | 182.03 | 192.1 | 182.03 | 188.28 | green |
(15 rows)
**Example: 30-minute Heikin-Ashi candles: SAMPLE BY inside, Heikin-Ashi outside** (sample data: sample)
```sql
SELECT time,
round(ha_open(open, high, low, close), 2) AS ha_open,
round(ha_high(open, high, low, close), 2) AS ha_high,
round(ha_low(open, high, low, close), 2) AS ha_low,
round(ha_close(open, high, low, close), 2) AS ha_close
FROM (SELECT time, first(open) AS open, max(high) AS high, min(low) AS low, last(close) AS close
FROM ochl_minutes
WHERE symbol = 'COAST' AND time IN '2026-09-10'
SAMPLE BY 30m) c
ORDER BY time;
```
Result:
| time (TIMESTAMP) | ha_open (DOUBLE) | ha_high (DOUBLE) | ha_low (DOUBLE) | ha_close (DOUBLE) |
|---|---|---|---|---|
| 2026-09-10T05:00:00.000Z | 380.9 | 382 | 379.4 | 380.8 |
| 2026-09-10T05:30:00.000Z | 380.85 | 381.9 | 379 | 380.58 |
| 2026-09-10T06:00:00.000Z | 380.71 | 382 | 379.3 | 380.15 |
| 2026-09-10T06:30:00.000Z | 380.43 | 380.7 | 378.8 | 379.58 |
| 2026-09-10T07:00:00.000Z | 380 | 380 | 376.9 | 378.3 |
| 2026-09-10T07:30:00.000Z | 379.15 | 379.15 | 376.9 | 377.93 |
| 2026-09-10T08:00:00.000Z | 378.54 | 378.54 | 375.7 | 377.55 |
| 2026-09-10T08:30:00.000Z | 378.04 | 378.04 | 373.2 | 376.05 |
| 2026-09-10T09:00:00.000Z | 377.05 | 377.05 | 374.3 | 375.6 |
(9 rows)