import { timeScaleDB } from '@repo/timescaledb';const db = timeScaleDB();await db.connect();await db.setupTimescale();// Now ready to insert and query trade data
CREATE TABLE trades ( time TIMESTAMPTZ NOT NULL, symbol VARCHAR(20) NOT NULL, price NUMERIC(20,8) NOT NULL, volume NUMERIC(20,8) NOT NULL, trade_id BIGINT NOT NULL, side VARCHAR(4) NOT NULL CHECK (side IN ('buy', 'sell')), PRIMARY KEY (time, symbol, trade_id));
This table is automatically converted to a hypertable for optimal time-series performance.
const client = db.getClient();const result = await client.query(` SELECT time, symbol, price, volume, side FROM trades WHERE time > NOW() - INTERVAL '1 hour' ORDER BY time DESC LIMIT 100`);console.log(`Found ${result.rows.length} recent trades`);
const candles = await client.query(` SELECT bucket as time, symbol, open, high, low, close, volume, trade_count FROM candles_1m WHERE symbol = $1 AND bucket > NOW() - INTERVAL '1 hour' ORDER BY bucket ASC`, ['BTCUSDT']);console.log(`Retrieved ${candles.rows.length} 1m candles`);
const hourlyCandles = await client.query(` SELECT bucket as time, open, high, low, close, volume FROM candles_1h WHERE symbol = $1 AND bucket > NOW() - INTERVAL '7 days' ORDER BY bucket ASC`, ['BTCUSDT']);
const dailyCandles = await client.query(` SELECT bucket as time, open, high, low, close, volume FROM candles_1d WHERE symbol = $1 AND bucket > NOW() - INTERVAL '90 days' ORDER BY bucket ASC`, ['BTCUSDT']);
const multiSymbol = await client.query(` SELECT bucket as time, symbol, close as price, volume FROM candles_5m WHERE symbol = ANY($1) AND bucket > NOW() - INTERVAL '1 day' ORDER BY symbol, bucket ASC`, [['BTCUSDT', 'ETHUSDT', 'SOLUSDT']]);
// Calculate average price over timeconst avgPrice = await client.query(` SELECT time_bucket('1 hour', time) as hour, symbol, AVG(price) as avg_price, SUM(volume) as total_volume FROM trades WHERE time > NOW() - INTERVAL '24 hours' GROUP BY hour, symbol ORDER BY hour DESC`);// Find high/low for the dayconst dayStats = await client.query(` SELECT symbol, MIN(price) as low, MAX(price) as high, FIRST(price, time) as open, LAST(price, time) as close FROM trades WHERE time > date_trunc('day', NOW()) GROUP BY symbol`);
// Refresh specific time rangeconst result = await db.refreshAllContinuousAggregates({ from: '2024-01-01T00:00:00Z', to: '2024-01-31T23:59:59Z'});console.log(`Refreshed ${result.refreshed.length} aggregates`);console.log(`Time range: ${result.timeRange.from} to ${result.timeRange.to}`);
Choose the right continuous aggregate for your use case:
Real-time charts: Use 1m or 5m candles
Historical analysis: Use 1h, 4h, or 1d candles
Long-term trends: Use 1w, 1mo, or 1y candles
This avoids querying raw trades data unnecessarily.
Query by symbol and time range
Always include both symbol and time filters:
// Good - uses indexes efficientlyWHERE symbol = 'BTCUSDT' AND time > NOW() - INTERVAL '1 hour'// Avoid - slower without symbol filterWHERE time > NOW() - INTERVAL '1 hour'
Use time_bucket for custom aggregations
Instead of querying raw data, use time_bucket:
SELECT time_bucket('5 minutes', time) as bucket, AVG(price) as avg_priceFROM tradesWHERE symbol = 'BTCUSDT'GROUP BY bucket