The Candle adapter persists OHLCV (open/high/low/close/volume) bars to theDocumentation Index
Fetch the complete documentation index at: https://mintlify.com/theonetrade/backtest-kit-redis-postgres-pgpool-docker/llms.txt
Use this file to discover all available pages before exploring further.
candle-items table in PostgreSQL and maintains a Redis cache keyed by (symbol, interval, exchangeName, timestamp) for O(1) lookups. Unlike every other adapter in the stack, candle rows are immutable after their first insert — once a bar is written its price data is never overwritten, preserving the integrity of historical market data across restarts and replays.
Context Key and Registration
The adapter is instantiated per(symbol, interval, exchangeName) triple and registered in src/config/setup.ts:
CandleInterval Values
The interval constructor field must be one of the following string literals from backtest-kit:
- Minute-based
- Hour / Day
| Value | Duration |
|---|---|
"1m" | 1 minute |
"3m" | 3 minutes |
"5m" | 5 minutes |
"15m" | 15 minutes |
"30m" | 30 minutes |
readCandlesData method uses a lookup table (INTERVAL_MINUTES) to compute the millisecond step size between consecutive bars, iterating from sinceTimestamp for exactly limit steps. If any expected bar is missing from the database the method returns null rather than a partial array.
Table Schema
The TypeORMEntitySchema for candle-items is defined in src/schema/Candle.schema.ts:
Column Reference
Auto-generated UUID primary key using PostgreSQL
gen_random_uuid().Trading pair symbol, e.g.
"BTCUSDT". Part of the unique index.Candle timeframe string (e.g.
"1h"). Part of the unique index.Bar open time as epoch milliseconds. Stored as
bigint; the epochTransformer ensures the JavaScript layer receives a plain number. Part of the unique index.Exchange identifier. Hardcoded to
"ccxt_binance" inside CandleDbService.Opening price of the bar.
Highest price during the bar.
Lowest price during the bar.
Closing price of the bar.
Trade volume during the bar.
Set automatically by TypeORM on first insert.
Updated automatically by TypeORM on every write (but OHLCV columns are never re-written).
Immutability: The No-Op Upsert
TheCandleDbService.create() method performs a single atomic INSERT … ON CONFLICT … DO UPDATE … RETURNING *. The conflict target is the candle_items_uq index — the (symbol, interval, timestamp) triple that uniquely identifies a bar. Critically, the update clause only rewrites the symbol column to its own EXCLUDED.symbol value, which is a no-op:
Using
DO NOTHING instead of DO UPDATE is not viable here because ON CONFLICT DO NOTHING cannot use RETURNING to return the conflicting row. Without RETURNING, a follow-up SELECT would be needed to seed the Redis cache — and on a cluster with read replicas, that SELECT could be routed to a lagging replica that has not yet received the commit. The no-op DO UPDATE avoids both the race condition and the extra round-trip.Read Path and Redis Cache
findBySymbolIntervalTimestamp follows the standard two-level read path:
- Compute the Redis cache key from
(symbol, interval, exchangeName, timestamp). - On a cache hit, fetch by UUID primary key (O(1) on both Redis and PostgreSQL).
- On a miss, fall back to a full filter query on the unique index and backfill Redis.
hasCandle helper performs the same Redis-first check without fetching the full row, making it safe to call in hot loops to skip redundant fetches from the exchange.
Methods Summary
writeCandlesData(candles: CandleData[])
writeCandlesData(candles: CandleData[])
Iterates the array and calls
CandleDbService.create() for each element. Each call is an atomic INSERT … ON CONFLICT (symbol, interval, timestamp) DO UPDATE SET symbol = EXCLUDED.symbol RETURNING *. Already-existing bars are returned unchanged; new bars are inserted. The Redis cache is updated after every call.readCandlesData(limit: number, sinceTimestamp: number)
readCandlesData(limit: number, sinceTimestamp: number)
Walks
limit steps starting from sinceTimestamp, incrementing by the interval’s millisecond step size each time. Returns a CandleData[] array if all bars are present, or null if any single bar is missing. A null result signals backtest-kit to fetch the missing range from the exchange.waitForInit(initial: boolean)
waitForInit(initial: boolean)
Called by backtest-kit when a new adapter instance is first touched. If
initial is true, calls waitForInfra() to ensure PostgreSQL and Redis are ready. The singleshot wrapper guarantees this async gate runs at most once across all 16 adapters.