KDB knowledge

Candles cookbook

Ready market-data queries: candles from minute data and from trades, daily change, top gainers, volume leaders, 52-week high and low, moving averages and filled gaps.

Copy these queries and change the symbol, the dates and the periods. All of them run on the sample market data (the prices are made up).

Most recipes use three building blocks:

Candles from minute data

Syntax:

SELECT time, symbol,
       first(open) AS open, max(high) AS high, min(low) AS low, last(close) AS close,
       sum(volume) AS volume
FROM ochl_minutes
WHERE time IN 'day'
SAMPLE BY 5m | 15m | 30m
ORDER BY symbol, time

Makes bigger candles from 1-minute candles. Open is the first open, close the last close, high the biggest high, low the smallest low, volume the sum.

Example: 5-minute candles of both symbols, first 3 of each (sample data: sample)

SELECT time, symbol,
       first(open) AS open, max(high) AS high, min(low) AS low, last(close) AS close,
       sum(volume) AS volume
FROM ochl_minutes
WHERE time IN '2026-09-09' AND time < '2026-09-09T11:15:00+05:45'
SAMPLE BY 5m
ORDER BY symbol, time;

Result:

time (TIMESTAMP)symbol (SYMBOL)open (DOUBLE)high (DOUBLE)low (DOUBLE)close (DOUBLE)volume (DOUBLE)
2026-09-09T05:15:00.000ZCITY512512.4511.25124490
2026-09-09T05:20:00.000ZCITY512512.3509.8509.95340
2026-09-09T05:25:00.000ZCITY509.9511.3509.8510.73900
2026-09-09T05:15:00.000ZCOAST398398.5397.5398.52640
2026-09-09T05:20:00.000ZCOAST398.5398.8397.2397.74500
2026-09-09T05:25:00.000ZCOAST397.7398.4396.6396.93260

(6 rows)

Example: The newest 3 candles first (for a chart that loads backwards) (sample data: sample)

SELECT time, first(open) AS open, max(high) AS high, min(low) AS low, last(close) AS close, sum(volume) AS volume
FROM ochl_minutes
WHERE symbol = 'COAST' AND time IN '2026-09-10'
SAMPLE BY 15m
ORDER BY time DESC
LIMIT 3;

Result:

time (TIMESTAMP)open (DOUBLE)high (DOUBLE)low (DOUBLE)close (DOUBLE)volume (DOUBLE)
2026-09-10T09:00:00.000Z375.5376.5374.3376.114450
2026-09-10T08:45:00.000Z373.5376.3373.2375.512030
2026-09-10T08:30:00.000Z377.6377.9373.4373.510730

(3 rows)

Hourly candles on a local clock (Nepal)

Syntax:

SELECT time, first(open), max(high), min(low), last(close), sum(volume)
FROM ochl_minutes
WHERE symbol = '...' AND time IN 'day'
SAMPLE BY 1h TIME ZONE 'Asia/Kathmandu'

The sample market opens at 11:00 local time in Nepal (05:15 UTC). The same idea works for any zone, for example America/New_York. Plain SAMPLE BY 1h makes UTC hours, which cut the trading day at the wrong places. TIME ZONE 'Asia/Kathmandu' makes the hours 11:00, 12:00, 13:00 and 14:00 Nepal time.

Example: Hourly candles with VWAP for both symbols (sample data: sample)

SELECT time, symbol,
       first(open) AS open, max(high) AS high, min(low) AS low, last(close) AS close,
       sum(volume) AS volume, round(vwap(close, volume), 2) AS vwap
FROM ochl_minutes
WHERE time IN '2026-09-10'
SAMPLE BY 1h TIME ZONE 'Asia/Kathmandu'
ORDER BY symbol, time;

Result:

time (TIMESTAMP)symbol (SYMBOL)open (DOUBLE)high (DOUBLE)low (DOUBLE)close (DOUBLE)volume (DOUBLE)vwap (DOUBLE)
2026-09-10T05:15:00.000ZCITY504.6507503.8505.347020505.3
2026-09-10T06:15:00.000ZCITY505.3506.6500.7502.741230503.78
2026-09-10T07:15:00.000ZCITY502.7504.1501.5502.646180502.84
2026-09-10T08:15:00.000ZCITY502.6507502.2504.948610504.95
2026-09-10T05:15:00.000ZCOAST380.1382379381.347660380.44
2026-09-10T06:15:00.000ZCOAST381.3382377.9378.445870379.71
2026-09-10T07:15:00.000ZCOAST378.4379.1375.7376.443950377.6
2026-09-10T08:15:00.000ZCOAST376.4378.3373.2376.148720375.67

(8 rows)

Daily candles from trade log trades

Syntax:

SELECT date, symbol,
       first(rate) AS open, max(rate) AS high, min(rate) AS low, last(rate) AS close,
       sum(quantity) AS volume, sum(amount) AS turnover, count(*) AS trades
FROM trade_log
SAMPLE BY 1d

Makes candles from single trades. The price is rate, the volume is quantity, the turnover is amount (quantity × rate).

Example: Daily candles and turnover from trades (sample data: sample)

SELECT date, symbol,
       first(rate) AS open, max(rate) AS high, min(rate) AS low, last(rate) AS close,
       sum(quantity) AS volume, round(sum(amount), 2) AS turnover, count(*) AS trades
FROM trade_log
SAMPLE BY 1d
ORDER BY symbol, date;

Result:

date (TIMESTAMP)symbol (SYMBOL)open (DOUBLE)high (DOUBLE)low (DOUBLE)close (DOUBLE)volume (LONG)turnover (DOUBLE)trades (LONG)
2026-09-09T00:00:00.000ZCITY51551751351715107774004
2026-09-10T00:00:00.000ZCITY517518514.55159104698307
2026-09-09T00:00:00.000ZCOAST401401398.5398.5220881702
2026-09-10T00:00:00.000ZCOAST400402398402285011393409
2026-09-09T00:00:00.000ZTELCO881881879879200017600002
2026-09-10T00:00:00.000ZTELCO8818838788817706782604
2026-09-10T00:00:00.000ZVOLT184187184187400739504

(7 rows)

Weekly candles, Sunday to Thursday

Syntax:

SELECT time, symbol, first(open), max(high), min(low), last(close), sum(volume)
FROM ochl_day
WHERE time IN 'year'
SAMPLE BY 1w WEEK START SUNDAY

The sample market trades Sunday to Thursday, as some exchanges do. WEEK START SUNDAY makes each week start on Sunday, so one trading week is one candle.

Example: Weekly candles of every symbol (sample data: sample)

SELECT time, symbol,
       first(open) AS open, max(high) AS high, min(low) AS low, last(close) AS close,
       sum(volume) AS volume
FROM ochl_day
WHERE time IN '2026'
SAMPLE BY 1w WEEK START SUNDAY
ORDER BY symbol, time;

Result:

time (TIMESTAMP)symbol (SYMBOL)open (DOUBLE)high (DOUBLE)low (DOUBLE)close (DOUBLE)volume (DOUBLE)
2026-08-23T00:00:00.000ZCITY505517502505.9601770
2026-08-30T00:00:00.000ZCITY507.3511.2494.1497.9538130
2026-09-06T00:00:00.000ZCITY497.4512.4495504.9591860
2026-08-23T00:00:00.000ZCOAST392.1393.3366.2367.1421000
2026-08-30T00:00:00.000ZCOAST368375.4364.1373.2356390
2026-09-06T00:00:00.000ZCOAST374.6398.8372.5376.1690140
2026-08-23T00:00:00.000ZVOLT182.4183.6172173.3535470
2026-08-30T00:00:00.000ZVOLT172.5177.3167.3176368150
2026-09-06T00:00:00.000ZVOLT174.2192.1172.4191.2486760

(9 rows)

Monthly and yearly candles

Syntax:

SAMPLE BY 1M      -- month: capital M
SAMPLE BY 1y      -- year

1M is a calendar month; small 1m is one minute. Months without rows are not shown.

Example: Monthly candles of COAST (sample data: sample)

SELECT time, first(open) AS open, max(high) AS high, min(low) AS low, last(close) AS close, sum(volume) AS volume
FROM ochl_day
WHERE symbol = 'COAST'
SAMPLE BY 1M;

Result:

time (TIMESTAMP)open (DOUBLE)high (DOUBLE)low (DOUBLE)close (DOUBLE)volume (DOUBLE)
2025-12-01T00:00:00.000Z420.3429.9417.6426.5206980
2026-08-01T00:00:00.000Z392.1393.3364.2368.2557260
2026-09-01T00:00:00.000Z367398.8364.1376.1910270

(3 rows)

Example: Yearly high, low and last close of every symbol (sample data: sample)

SELECT time, symbol, max(high) AS year_high, min(low) AS year_low, last(close) AS year_close
FROM ochl_day
SAMPLE BY 1y
ORDER BY symbol, time;

Result:

time (TIMESTAMP)symbol (SYMBOL)year_high (DOUBLE)year_low (DOUBLE)year_close (DOUBLE)
2025-01-01T00:00:00.000ZCITY546.2536.4542.4
2026-01-01T00:00:00.000ZCITY517494.1504.9
2025-01-01T00:00:00.000ZCOAST429.9417.6426.5
2026-01-01T00:00:00.000ZCOAST398.8364.1376.1
2025-01-01T00:00:00.000ZVOLT199.7187.5194.3
2026-01-01T00:00:00.000ZVOLT192.1167.3191.2

(6 rows)

Daily change %

Syntax:

SELECT time, symbol, close,
       lag(close) OVER (PARTITION BY symbol ORDER BY time) AS prev_close,
       pct_change(close) OVER (PARTITION BY symbol ORDER BY time) AS change_pct
FROM ochl_day

The change from the day before: (close - previous close) / previous close × 100. lag(close) is the close of the row before; pct_change(close) computes the percent for you.

Example: Change % of the last three days (sample data: sample)

SELECT time, symbol, close,
       lag(close) OVER (PARTITION BY symbol ORDER BY time) AS prev_close,
       round(pct_change(close) OVER (PARTITION BY symbol ORDER BY time), 2) AS change_pct
FROM ochl_day
WHERE time >= '2026-09-08'
ORDER BY symbol, time;

Result:

time (TIMESTAMP)symbol (SYMBOL)close (DOUBLE)prev_close (DOUBLE)change_pct (DOUBLE)
2026-09-08T00:00:00.000ZCITY497.5NULLNULL
2026-09-09T00:00:00.000ZCITY504.6497.51.43
2026-09-10T00:00:00.000ZCITY504.9504.60.06
2026-09-08T00:00:00.000ZCOAST377.2NULLNULL
2026-09-09T00:00:00.000ZCOAST380.1377.20.77
2026-09-10T00:00:00.000ZCOAST376.1380.1-1.05
2026-09-08T00:00:00.000ZVOLT182.4NULLNULL
2026-09-09T00:00:00.000ZVOLT187.3182.42.69
2026-09-10T00:00:00.000ZVOLT191.2187.32.08

(9 rows)

Example: Wrong: WHERE keeps one day, so there is no day before (sample data: sample)

SELECT time, symbol, close, round(pct_change(close) OVER (PARTITION BY symbol ORDER BY time), 2) AS change_pct
FROM ochl_day
WHERE time IN '2026-09-10';

Result:

time (TIMESTAMP)symbol (SYMBOL)close (DOUBLE)change_pct (STRING)
2026-09-10T00:00:00.000ZCITY504.9NULL
2026-09-10T00:00:00.000ZCOAST376.1NULL
2026-09-10T00:00:00.000ZVOLT191.2NULL

(3 rows)

Top gainers and losers

Syntax:

SELECT symbol, prev_close, close, change_pct
FROM (SELECT time, symbol, close, lag(close) OVER (PARTITION BY symbol ORDER BY time) AS prev_close
      FROM ochl_day WHERE time >= 'a few days before') t
WHERE time IN 'the day'
ORDER BY change_pct DESC
LIMIT 10

Gainers of a day: the change %, biggest first. Losers: the same with ORDER BY change_pct (smallest first).

Example: Top gainers of 2026-09-10 from daily candles (sample data: sample)

SELECT symbol, prev_close, close,
       round((close - prev_close) / prev_close * 100, 2) AS change_pct
FROM (
  SELECT time, symbol, close,
         lag(close) OVER (PARTITION BY symbol ORDER BY time) AS prev_close
  FROM ochl_day
  WHERE time >= '2026-09-01'
) t
WHERE time IN '2026-09-10'
ORDER BY change_pct DESC;

Result:

symbol (SYMBOL)prev_close (DOUBLE)close (DOUBLE)change_pct (DOUBLE)
VOLT187.3191.22.08
CITY504.6504.90.06
COAST380.1376.1-1.05

(3 rows)

Example: Top 2 gainers and top 2 losers right now (live snapshots) (sample data: sample)

SELECT symbol, ltp, pct_change FROM live_data
LATEST ON timestamp PARTITION BY symbol
ORDER BY pct_change DESC LIMIT 2;

SELECT symbol, ltp, pct_change FROM live_data
LATEST ON timestamp PARTITION BY symbol
ORDER BY pct_change LIMIT 2;

Result:

SELECT symbol, ltp, pct_change FROM live_data LATEST ON timestamp PARTITION BY symbol ORDER BY pct_c

symbol (SYMBOL)ltp (DOUBLE)pct_change (DOUBLE)
CITY527.12.71
TELCO897.71.9

(2 rows)

SELECT symbol, ltp, pct_change FROM live_data LATEST ON timestamp PARTITION BY symbol ORDER BY pct_c

symbol (SYMBOL)ltp (DOUBLE)pct_change (DOUBLE)
VOLT184-0.86
COAST401.90.7

(2 rows)

Volume and turnover leaders

Syntax:

SELECT symbol, sum(volume) AS volume FROM ochl_day WHERE time IN 'day' GROUP BY symbol ORDER BY volume DESC LIMIT n
SELECT symbol, sum(quantity), sum(amount) AS turnover FROM trade_log WHERE date IN 'day' GROUP BY symbol ORDER BY turnover DESC

The most traded shares of a day, by volume (number of shares) or by turnover (money).

Example: Volume leaders of a day (daily candles) (sample data: sample)

SELECT symbol, volume, close
FROM ochl_day
WHERE time IN '2026-09-10'
ORDER BY volume DESC
LIMIT 3;

Result:

symbol (SYMBOL)volume (DOUBLE)close (DOUBLE)
COAST186200376.1
CITY183040504.9
VOLT41470191.2

(3 rows)

Example: Turnover leaders of a day (trade log) (sample data: sample)

SELECT symbol, sum(quantity) AS shares, round(sum(amount), 2) AS turnover, count(*) AS trades
FROM trade_log
WHERE date IN '2026-09-10'
GROUP BY symbol
ORDER BY turnover DESC;

Result:

symbol (SYMBOL)shares (LONG)turnover (DOUBLE)trades (LONG)
COAST285011393409
TELCO7706782604
CITY9104698307
VOLT400739504

(4 rows)

Example: Brokers with the biggest turnover (bought + sold) (sample data: sample)

SELECT b.name, round(sum(t.amount), 2) AS turnover, count(*) AS trades
FROM (
  SELECT buyer AS broker, amount FROM trade_log WHERE date IN '2026-09-10'
  UNION ALL
  SELECT seller AS broker, amount FROM trade_log WHERE date IN '2026-09-10'
) t
JOIN brokers b ON b.id = t.broker
GROUP BY b.name
ORDER BY turnover DESC
LIMIT 3;

Result:

name (STRING)turnover (DOUBLE)trades (LONG)
Imperial Securities128816011
Online Securities11735809
Vision Securities7732609

(3 rows)

52-week high and low

Syntax:

SELECT symbol, max(high) AS high_52w, min(low) AS low_52w
FROM ochl_day
WHERE time > dateadd('d', -365, now())
GROUP BY symbol

The highest high and lowest low of the last 365 days. dateadd('d', -365, t) is the time 365 days before t (date and time functions).

Example: 52-week high and low of every symbol (sample data: sample)

SELECT symbol, max(high) AS high_52w, min(low) AS low_52w
FROM ochl_day
WHERE time > dateadd('d', -365, CAST('2026-09-10' AS TIMESTAMP))
GROUP BY symbol
ORDER BY symbol;

Result:

symbol (SYMBOL)high_52w (DOUBLE)low_52w (DOUBLE)
CITY546.2494.1
COAST429.9364.1
VOLT199.7167.3

(3 rows)

Example: How far the last close is below the 52-week high (sample data: sample)

SELECT h.symbol, l.close, h.high_52w, h.low_52w,
       round((l.close - h.high_52w) / h.high_52w * 100, 2) AS below_high_pct
FROM (SELECT symbol, max(high) AS high_52w, min(low) AS low_52w
      FROM ochl_day
      WHERE time > dateadd('d', -365, CAST('2026-09-10' AS TIMESTAMP))
      GROUP BY symbol) h
JOIN (SELECT symbol, close FROM ochl_day LATEST ON time PARTITION BY symbol) l ON l.symbol = h.symbol
ORDER BY below_high_pct DESC;

Result:

symbol (SYMBOL)close (DOUBLE)high_52w (DOUBLE)low_52w (DOUBLE)below_high_pct (DOUBLE)
VOLT191.2199.7167.3-4.26
CITY504.9546.2494.1-7.56
COAST376.1429.9364.1-12.51

(3 rows)

Prices after a bonus share or a split jump on the ex-date. To chart or compute indicators over the history without that jump, read the table with ADJUSTED BY.

Moving averages and crossovers

Syntax:

sma(close, n) OVER (PARTITION BY symbol ORDER BY time)
ema(close, n) OVER (PARTITION BY symbol ORDER BY time)
rsi(close, n) OVER (PARTITION BY symbol ORDER BY time)

sma is the simple moving average of the last n rows, ema the exponential one, rsi the Relative Strength Index (0 to 100). See indicator functions for all of them.

Example: 5-day SMA and EMA, newest rows of CITY (sample data: sample)

SELECT time, symbol, close,
       round(sma(close, 5) OVER (PARTITION BY symbol ORDER BY time), 2) AS sma5,
       round(ema(close, 5) OVER (PARTITION BY symbol ORDER BY time), 2) AS ema5
FROM ochl_day
WHERE symbol = 'CITY' AND time IN '2026'
ORDER BY time DESC
LIMIT 3;

Result:

time (TIMESTAMP)symbol (SYMBOL)close (DOUBLE)sma5 (DOUBLE)ema5 (DOUBLE)
2026-09-10T00:00:00.000ZCITY504.9500.94502.22
2026-09-09T00:00:00.000ZCITY504.6499.54500.88
2026-09-08T00:00:00.000ZCITY497.5497.9499.01

(3 rows)

Example: RSI of every symbol on the last day (computed over all days) (sample data: sample)

SELECT time, symbol, close, rsi5
FROM (
  SELECT time, symbol, close,
         round(rsi(close, 5) OVER (PARTITION BY symbol ORDER BY time), 1) AS rsi5
  FROM ochl_day
) t
WHERE time IN '2026-09-10'
ORDER BY rsi5 DESC;

Result:

time (TIMESTAMP)symbol (SYMBOL)close (DOUBLE)rsi5 (DOUBLE)
2026-09-10T00:00:00.000ZVOLT191.279.4
2026-09-10T00:00:00.000ZCITY504.955.1
2026-09-10T00:00:00.000ZCOAST376.143.7

(3 rows)

Example: Crossover in one line: cross_above and cross_below (sample data: sample)

SELECT time, close,
       CASE WHEN cross_above(sma(close, 3), sma(close, 5)) OVER (ORDER BY time) THEN 'BUY'
            WHEN cross_below(sma(close, 3), sma(close, 5)) OVER (ORDER BY time) THEN 'SELL'
            ELSE '' END AS signal
FROM ochl_day
WHERE symbol = 'VOLT' AND time IN '2026'
ORDER BY time DESC
LIMIT 5;

Result:

time (TIMESTAMP)close (DOUBLE)signal (STRING)
2026-09-10T00:00:00.000Z191.2
2026-09-09T00:00:00.000Z187.3
2026-09-08T00:00:00.000Z182.4
2026-09-07T00:00:00.000Z176.9
2026-09-06T00:00:00.000Z180.1BUY

(5 rows)

Example: Crossover written out: BUY when the 3-day SMA goes above the 5-day SMA (sample data: sample)

SELECT time, close, fast, slow,
       CASE WHEN fast > slow AND prev_fast <= prev_slow THEN 'BUY'
            WHEN fast < slow AND prev_fast >= prev_slow THEN 'SELL'
            ELSE '' END AS signal
FROM (
  SELECT time, close, fast, slow, lag(fast) AS prev_fast, lag(slow) AS prev_slow
  FROM (
    SELECT time, close, round(sma(close, 3), 2) AS fast, round(sma(close, 5), 2) AS slow
    FROM ochl_day
    WHERE symbol = 'VOLT' AND time IN '2026'
  ) a
) b
WHERE time >= '2026-09-01';

Result:

time (TIMESTAMP)close (DOUBLE)fast (DOUBLE)slow (DOUBLE)signal (STRING)
2026-09-01T00:00:00.000Z169171.53172.42
2026-09-02T00:00:00.000Z171.5171.03171.88
2026-09-03T00:00:00.000Z176172.17172.42
2026-09-06T00:00:00.000Z180.1175.87173.84BUY
2026-09-07T00:00:00.000Z176.9177.67174.7
2026-09-08T00:00:00.000Z182.4179.8177.38
2026-09-09T00:00:00.000Z187.3182.2180.54
2026-09-10T00:00:00.000Z191.2186.97183.58

(8 rows)

Indicators on candles

Syntax:

SELECT time, close, sma(close, n) AS sma
FROM (SELECT time, last(close) AS close FROM ... SAMPLE BY interval) candles

To put an indicator on weekly or hourly candles, make the candles in a subquery and compute the indicator outside. In one SELECT with SAMPLE BY, a window function runs over the candles, so it may only use the time bucket, group keys or aggregates, not a raw column like close.

Example: SAMPLE BY and sma(close) in one SELECT: close is not a candle value (sample data: sample; shows an error)

SELECT time, last(close) AS close, sma(close, 2) AS sma2
FROM ochl_day
WHERE symbol = 'CITY'
SAMPLE BY 1w WEEK START SUNDAY;

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: The fix: hourly Nepal candles inside, a 2-candle SMA outside (sample data: sample)

SELECT time, close, round(sma(close, 2), 2) AS sma2
FROM (
  SELECT time, last(close) AS close
  FROM ochl_minutes
  WHERE symbol = 'CITY' AND time IN '2026-09-10'
  SAMPLE BY 1h TIME ZONE 'Asia/Kathmandu'
) h;

Result:

time (TIMESTAMP)close (DOUBLE)sma2 (DOUBLE)
2026-09-10T05:15:00.000Z505.3NULL
2026-09-10T06:15:00.000Z502.7504
2026-09-10T07:15:00.000Z502.6502.65
2026-09-10T08:15:00.000Z504.9503.75

(4 rows)

Candle and indicator tables kept by a timer

Syntax:

-- 1. candles, kept up to date after every insert
CREATE MATERIALIZED VIEW ta_v_h1 AS (
  SELECT time, symbol, first(open) AS open, max(high) AS high, min(low) AS low, last(close) AS close, sum(volume) AS volume
  FROM minute_table SAMPLE BY 1h ALIGN TO CALENDAR WITH OFFSET '00:15');
-- 2. a table for the candles plus indicator columns
CREATE TABLE ta_h1 (time TIMESTAMP, symbol SYMBOL, open DOUBLE, ..., rsi14 DOUBLE, ...) TIMESTAMP(time);
-- 3. a procedure that rebuilds it in one transaction
CREATE PROCEDURE ta_build_h1() AS BEGIN
  DELETE FROM ta_h1;
  INSERT INTO ta_h1 SELECT time, symbol, open, ..., rsi(close, 14) OVER (PARTITION BY symbol ORDER BY time) AS rsi14, ... FROM ta_v_h1;
END;
-- 4. a timer that runs it after each trading day (POST /api/timers)
{"name": "ta_h1", "schedule": "trading nepse 15:10", "sql": "REFRESH MATERIALIZED VIEW ta_v_h1 INCREMENTAL; CALL ta_build_h1()"}

Screeners and charts often read the same candles again and again: 5-minute, 15-minute, hourly, weekly and monthly, each with a set of indicators. Build them once in KDB, and read the table.

PartWhy
Materialized viewHolds the candles. KDB updates only the buckets an insert touches.
Table with indicator columnsAn indicator needs every candle before it, so it cannot be in the view. The procedure writes it.
ProcedureDELETE and INSERT in one CALL are one transaction: a reader sees the old rows or the new ones, never an empty table.
Timer on a calendartrading nepse 15:10 runs after the close on trading days only. Start the server with --timezone Asia/Kathmandu so 15:10 is Nepal time.

Example: Hourly Nepal candles with indicators, rebuilt by one CALL (sample data: sample)

Setup:

CREATE MATERIALIZED VIEW ta_v_h1 AS (
  SELECT time, symbol, first(open) AS open, max(high) AS high, min(low) AS low, last(close) AS close, sum(volume) AS volume
  FROM ochl_minutes SAMPLE BY 1h ALIGN TO CALENDAR WITH OFFSET '00:15');
CREATE TABLE ta_h1 (time TIMESTAMP, symbol SYMBOL, close DOUBLE, volume DOUBLE, sma3 DOUBLE, rsi3 DOUBLE) TIMESTAMP(time);
CREATE PROCEDURE ta_build_h1() AS BEGIN
  DELETE FROM ta_h1;
  INSERT INTO ta_h1
  SELECT time, symbol, close, volume,
         sma(close, 3) OVER (PARTITION BY symbol ORDER BY time),
         rsi(close, 3) OVER (PARTITION BY symbol ORDER BY time)
  FROM ta_v_h1;
END;
CALL ta_build_h1();
SELECT time, close, round(sma3, 2) AS sma3, round(rsi3, 1) AS rsi3
FROM ta_h1 WHERE symbol = 'CITY' ORDER BY time DESC LIMIT 4;

Result:

CALL ta_build_h1()

procedure 'ta_build_h1' done: 16 row(s) changed

SELECT time, close, round(sma3, 2) AS sma3, round(rsi3, 1) AS rsi3 FROM ta_h1 WHERE symbol = 'CITY'

time (TIMESTAMP)close (DOUBLE)sma3 (DOUBLE)rsi3 (DOUBLE)
2026-09-10T08:15:00.000Z504.9503.459.9
2026-09-10T07:15:00.000Z502.6503.5331.7
2026-09-10T06:15:00.000Z502.7504.232.4
2026-09-10T05:15:00.000Z505.3506.5350.7

(4 rows)

Example: Weekly candles (Sunday weeks) with a 2-week EMA (sample data: sample)

Setup:

CREATE MATERIALIZED VIEW ta_v_w1 AS (
  SELECT time, symbol, first(open) AS open, max(high) AS high, min(low) AS low, last(close) AS close, sum(volume) AS volume
  FROM ochl_day SAMPLE BY 1w WEEK START SUNDAY);
SELECT time, close, round(ema(close, 2) OVER (PARTITION BY symbol ORDER BY time), 2) AS ema2
FROM ta_v_w1 WHERE symbol = 'VOLT' ORDER BY time DESC LIMIT 3;

Result:

time (TIMESTAMP)close (DOUBLE)ema2 (DOUBLE)
2026-09-06T00:00:00.000Z191.2187
2026-08-30T00:00:00.000Z176178.6
2026-08-23T00:00:00.000Z173.3183.8

(3 rows)

Filling gaps in minute candles

Syntax:

SAMPLE BY 1m FILL(PREV)
-- open, high, low, close = the minute before, volume = 0: one rule per column
SAMPLE BY 1m FILL(PREV, PREV, PREV, PREV, 0)
-- every minute of a fixed range, also before the first trade
SAMPLE BY 1m FROM '...' TO '...' FILL(PREV, 0)

Some minutes have no trades, so there is no candle. A chart with a fixed number of minutes needs a row for every minute.

Example: COAST had no trades from 12:31 to 12:34: FILL(PREV) (sample data: sample)

SELECT time, last(close) AS close, sum(volume) AS volume
FROM ochl_minutes
WHERE symbol = 'COAST' AND time >= '2026-09-10T12:30:00+05:45' AND time < '2026-09-10T12:36:00+05:45'
SAMPLE BY 1m FILL(PREV);

Result:

time (TIMESTAMP)close (DOUBLE)volume (DOUBLE)
2026-09-10T06:45:00.000Z380.2620
2026-09-10T06:46:00.000Z380.2620
2026-09-10T06:47:00.000Z380.2620
2026-09-10T06:48:00.000Z380.2620
2026-09-10T06:49:00.000Z380.2620
2026-09-10T06:50:00.000Z379.8800

(6 rows)

Example: Candles with open, high, low, close from the minute before, volume 0 (sample data: sample)

SELECT time, first(open) AS open, max(high) AS high, min(low) AS low, last(close) AS close, sum(volume) AS volume
FROM ochl_minutes
WHERE symbol = 'COAST'
SAMPLE BY 1m FROM '2026-09-10T12:30:00+05:45' TO '2026-09-10T12:36:00+05:45'
FILL(PREV, PREV, PREV, PREV, 0);

Result:

time (TIMESTAMP)open (DOUBLE)high (DOUBLE)low (DOUBLE)close (DOUBLE)volume (DOUBLE)
2026-09-10T06:45:00.000Z379.9380.4379.9380.2620
2026-09-10T06:46:00.000Z379.9380.4379.9380.20
2026-09-10T06:47:00.000Z379.9380.4379.9380.20
2026-09-10T06:48:00.000Z379.9380.4379.9380.20
2026-09-10T06:49:00.000Z379.9380.4379.9380.20
2026-09-10T06:50:00.000Z380.2380.2379.8379.8800

(6 rows)