This question was answered here, let’s keep a copy of the answer here too:
Timescaledb is testing an experimental set of functions to cover ohlc and soon ohlcv will also be available.
You can rollup it over ohlc data but not use materialized views over materialized views (at least for now).
Let me share a small example that maybe can help.
First, a simple ticks table to store the trade events:
CREATE TABLE ticks
( time TIMESTAMPTZ NOT NULL,
symbol varchar,
price double precision,
volume int);
SELECT create_hypertable('ticks', 'time', chunk_time_interval => INTERVAL '1 day');
Now, let’s create the base ohlcv from 1 minute being the most granular pre-calculated materialized view:
CREATE MATERIALIZED VIEW _ohlcv_1m
WITH (timescaledb.continuous) AS
SELECT time_bucket('1 minute'::interval, time),
symbol,
toolkit_experimental.ohlc(time, price),
sum(volume) as volume
FROM ticks
GROUP BY 1,2 WITH DATA;
As the continuous aggregates in the top of another continuous aggregates is not allowed, we can create a simple view and benefit from rollup that will “merge” the previous ohlc structures.
CREATE VIEW _ohlcv_1h AS
SELECT time_bucket('1 hour'::interval, time_bucket),
symbol,
toolkit_experimental.rollup(ohlc) as ohlc,
sum(volume) as volume
FROM _ohlcv_1m
GROUP BY 1,2;
The views don’t have access to the values directly, so, we can create views to access the ohlcv values and the time of each event.
CREATE VIEW ohlcv_1m as
SELECT time_bucket,
symbol,
toolkit_experimental.open(ohlc),
toolkit_experimental.open_time(ohlc),
toolkit_experimental.high(ohlc),
toolkit_experimental.high_time(ohlc),
toolkit_experimental.low(ohlc),
toolkit_experimental.low_time(ohlc),
toolkit_experimental.close(ohlc),
toolkit_experimental.close_time(ohlc),
volume
FROM _ohlcv_1m;
CREATE VIEW ohlcv_1h as
SELECT time_bucket,
symbol,
toolkit_experimental.open(ohlc),
toolkit_experimental.open_time(ohlc),
toolkit_experimental.high(ohlc),
toolkit_experimental.high_time(ohlc),
toolkit_experimental.low(ohlc),
toolkit_experimental.low_time(ohlc),
toolkit_experimental.close(ohlc),
toolkit_experimental.close_time(ohlc),
volume
FROM _ohlcv_1h;
Now, seeding some data for tests:
INSERT INTO ticks
SELECT time, 'SYMBOL', (random()*30)::int, 100*(random()*10)::int
FROM generate_series(TIMESTAMP '2000-01-01 00:00:00',
TIMESTAMP '2000-01-02 00:00:00' + INTERVAL '1 hour',
INTERVAL '1 second') AS time;
Testing the final views:
playground=# select time_bucket, open, high, low, close, volume from ohlcv_1h ORDER BY time_bucket LIMIT 1;
┌────────────────────────┬──────┬──────┬─────┬───────┬─────────┐
│ time_bucket │ open │ high │ low │ close │ volume │
├────────────────────────┼──────┼──────┼─────┼───────┼─────────┤
│ 2000-01-01 00:00:00-02 │ 13 │ 30 │ 0 │ 8 │ 1809600 │
└────────────────────────┴──────┴──────┴─────┴───────┴─────────┘
(1 row)
playground=# select time_bucket, open, high, low, close, volume from ohlcv_1m ORDER BY time_bucket LIMIT 60
;
┌────────────────────────┬──────┬──────┬─────┬───────┬────────┐
│ time_bucket │ open │ high │ low │ close │ volume │
├────────────────────────┼──────┼──────┼─────┼───────┼────────┤
│ 2000-01-01 00:00:00-02 │ 13 │ 30 │ 0 │ 25 │ 32700 │
│ 2000-01-01 00:01:00-02 │ 0 │ 30 │ 0 │ 2 │ 33000 │
│ 2000-01-01 00:02:00-02 │ 1 │ 30 │ 0 │ 6 │ 26300 │
│ 2000-01-01 00:03:00-02 │ 4 │ 30 │ 1 │ 28 │ 30300 │
│ 2000-01-01 00:04:00-02 │ 30 │ 30 │ 0 │ 27 │ 29200 │
│ 2000-01-01 00:05:00-02 │ 24 │ 29 │ 2 │ 16 │ 27500 │
│ 2000-01-01 00:06:00-02 │ 9 │ 28 │ 0 │ 15 │ 30200 │
│ 2000-01-01 00:07:00-02 │ 21 │ 30 │ 2 │ 3 │ 30300 │
│ 2000-01-01 00:08:00-02 │ 10 │ 29 │ 1 │ 3 │ 35300 │
│ 2000-01-01 00:09:00-02 │ 6 │ 30 │ 0 │ 16 │ 26700 │
│ 2000-01-01 00:10:00-02 │ 10 │ 30 │ 0 │ 24 │ 30300 │
│ 2000-01-01 00:11:00-02 │ 30 │ 30 │ 1 │ 27 │ 29800 │
│ 2000-01-01 00:12:00-02 │ 6 │ 30 │ 1 │ 26 │ 25500 │
│ 2000-01-01 00:13:00-02 │ 13 │ 29 │ 0 │ 22 │ 30400 │
│ 2000-01-01 00:14:00-02 │ 2 │ 30 │ 1 │ 21 │ 30800 │
│ 2000-01-01 00:15:00-02 │ 22 │ 30 │ 0 │ 15 │ 31400 │
│ 2000-01-01 00:16:00-02 │ 10 │ 30 │ 1 │ 8 │ 33700 │
│ 2000-01-01 00:17:00-02 │ 3 │ 30 │ 1 │ 22 │ 27600 │
│ 2000-01-01 00:18:00-02 │ 13 │ 29 │ 0 │ 5 │ 31400 │
│ 2000-01-01 00:19:00-02 │ 17 │ 29 │ 1 │ 16 │ 30900 │
│ 2000-01-01 00:20:00-02 │ 24 │ 29 │ 1 │ 10 │ 26500 │
│ 2000-01-01 00:21:00-02 │ 16 │ 29 │ 1 │ 9 │ 34100 │
│ 2000-01-01 00:22:00-02 │ 14 │ 30 │ 0 │ 22 │ 31500 │
│ 2000-01-01 00:23:00-02 │ 10 │ 30 │ 1 │ 1 │ 28700 │
│ 2000-01-01 00:24:00-02 │ 1 │ 30 │ 1 │ 1 │ 32900 │
│ 2000-01-01 00:25:00-02 │ 11 │ 30 │ 1 │ 24 │ 25700 │
│ 2000-01-01 00:26:00-02 │ 21 │ 28 │ 0 │ 1 │ 32100 │
│ 2000-01-01 00:27:00-02 │ 1 │ 30 │ 0 │ 23 │ 33800 │
│ 2000-01-01 00:28:00-02 │ 1 │ 30 │ 1 │ 22 │ 30100 │
│ 2000-01-01 00:29:00-02 │ 15 │ 30 │ 0 │ 6 │ 26900 │
│ 2000-01-01 00:30:00-02 │ 15 │ 29 │ 1 │ 7 │ 32800 │
│ 2000-01-01 00:31:00-02 │ 9 │ 30 │ 0 │ 7 │ 30200 │
│ 2000-01-01 00:32:00-02 │ 16 │ 30 │ 0 │ 10 │ 28000 │
│ 2000-01-01 00:33:00-02 │ 6 │ 30 │ 1 │ 9 │ 28800 │
│ 2000-01-01 00:34:00-02 │ 6 │ 30 │ 1 │ 9 │ 30300 │
│ 2000-01-01 00:35:00-02 │ 20 │ 29 │ 0 │ 17 │ 30800 │
│ 2000-01-01 00:36:00-02 │ 27 │ 29 │ 0 │ 21 │ 28400 │
│ 2000-01-01 00:37:00-02 │ 29 │ 30 │ 0 │ 20 │ 30100 │
│ 2000-01-01 00:38:00-02 │ 23 │ 30 │ 0 │ 5 │ 30300 │
│ 2000-01-01 00:39:00-02 │ 1 │ 30 │ 0 │ 24 │ 34000 │
│ 2000-01-01 00:40:00-02 │ 10 │ 30 │ 0 │ 30 │ 27500 │
│ 2000-01-01 00:41:00-02 │ 3 │ 30 │ 0 │ 20 │ 32200 │
│ 2000-01-01 00:42:00-02 │ 2 │ 29 │ 0 │ 12 │ 29400 │
│ 2000-01-01 00:43:00-02 │ 5 │ 29 │ 0 │ 4 │ 31800 │
│ 2000-01-01 00:44:00-02 │ 22 │ 30 │ 0 │ 6 │ 32700 │
│ 2000-01-01 00:45:00-02 │ 11 │ 29 │ 1 │ 9 │ 30400 │
│ 2000-01-01 00:46:00-02 │ 10 │ 30 │ 0 │ 1 │ 26700 │
│ 2000-01-01 00:47:00-02 │ 12 │ 30 │ 0 │ 17 │ 28400 │
│ 2000-01-01 00:48:00-02 │ 15 │ 30 │ 0 │ 16 │ 30800 │
│ 2000-01-01 00:49:00-02 │ 6 │ 30 │ 0 │ 29 │ 27900 │
│ 2000-01-01 00:50:00-02 │ 20 │ 30 │ 0 │ 28 │ 26500 │
│ 2000-01-01 00:51:00-02 │ 7 │ 30 │ 0 │ 14 │ 30500 │
│ 2000-01-01 00:52:00-02 │ 23 │ 29 │ 0 │ 4 │ 31200 │
│ 2000-01-01 00:53:00-02 │ 26 │ 30 │ 1 │ 10 │ 31000 │
│ 2000-01-01 00:54:00-02 │ 24 │ 30 │ 1 │ 3 │ 30200 │
│ 2000-01-01 00:55:00-02 │ 3 │ 30 │ 0 │ 20 │ 32800 │
│ 2000-01-01 00:56:00-02 │ 24 │ 30 │ 1 │ 12 │ 27300 │
│ 2000-01-01 00:57:00-02 │ 19 │ 30 │ 0 │ 26 │ 30600 │
│ 2000-01-01 00:58:00-02 │ 8 │ 29 │ 1 │ 21 │ 33500 │
│ 2000-01-01 00:59:00-02 │ 10 │ 30 │ 1 │ 8 │ 28900 │
└────────────────────────┴──────┴──────┴─────┴───────┴────────┘
(60 rows)
For the part 2 of your question, you can use array_agg
from postgresql to be concatenating the data. Example:
select array_agg(time_bucket) as t,
array_agg(open) as o,
array_agg(high) as h,
array_agg(low) as l,
array_agg(close) as c,
array_agg(volume) as v from ohlcv_1m group BY time_bucket('5 m', time_bucket) limit 3 ;
I’m limiting to 3 records just for the sake of readability, here are the results:
┌────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┬─────────────────┬──────────────────┬─────────────┬────────────────┬─────────────────────────────────┐
│ t │ o │ h │ l │ c │ v │
├────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┼─────────────────┼──────────────────┼─────────────┼────────────────┼─────────────────────────────────┤
│ {"2000-01-01 00:02:00-02","2000-01-01 00:00:00-02","2000-01-01 00:01:00-02","2000-01-01 00:03:00-02","2000-01-01 00:04:00-02"} │ {16,0,27,29,14} │ {30,30,30,29,30} │ {1,0,2,1,0} │ {27,14,23,1,0} │ {29500,32400,32100,28500,25400} │
│ {"2000-01-01 00:07:00-02","2000-01-01 00:08:00-02","2000-01-01 00:05:00-02","2000-01-01 00:09:00-02","2000-01-01 00:06:00-02"} │ {22,26,9,17,5} │ {30,30,29,30,29} │ {0,2,0,1,0} │ {21,5,18,14,5} │ {29000,28700,31600,30700,31200} │
│ {"2000-01-01 00:12:00-02","2000-01-01 00:11:00-02","2000-01-01 00:13:00-02","2000-01-01 00:10:00-02","2000-01-01 00:14:00-02"} │ {29,20,27,19,2} │ {30,30,29,30,30} │ {0,0,0,1,0} │ {1,17,21,9,16} │ {28400,31500,30800,25900,32100} │
└────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┴─────────────────┴──────────────────┴─────────────┴────────────────┴─────────────────────────────────┘
Note that the final aggregated array is not sorted.
Also, the nested continuous aggregates feature is in progress.