Documentation Index
Fetch the complete documentation index at: https://mintlify.com/backtest-kit/uzse-backtest-app/llms.txt
Use this file to discover all available pages before exploring further.
build-candles.ts implements a deterministic 5-step algorithm for generating continuous OHLCV candle series from raw trade data stored in the trade-results collection. Given a display ticker and its corresponding ISIN, the script produces gap-free candle series for all eleven supported timeframes in a single pass and writes them to the candle-items collection. Because the algorithm processes one calendar day at a time and relies on a unique compound index for duplicate rejection, it is both memory-efficient and safe to re-run at any time without corrupting existing data.
Usage
symbol is the short display ticker written to candle-items; isin is the ISIN code used to query trade-results. Both arguments default to HMKB / UZ7011340005 if omitted.
Algorithm Overview
Trades for the current calendar day are loaded from MongoDB and bucketed into 1-minute intervals using the
floorToMin helper, which floors any millisecond timestamp to the nearest multiple of minutes × 60,000 ms:function floorToMin(tsMs: number, minutes: number): number {
const stepMs = minutes * MIN_MS;
return Math.floor(tsMs / stepMs) * stepMs;
}
openhighlowclosevolumequantity across all tradesThe algorithm iterates over every minute of the calendar day — from
00:00:00 UTC to 23:59:00 UTC (1440 steps of MIN_MS = 60,000 ms). For each minute:lastClose is updated to its close price.open = high = low = close = lastClose, volume = 0.This guarantees that after Step 2 every minute of the trading day has exactly one entry in the in-memory series, regardless of how sparse the actual trade activity was.
lastClose is initialised to the tradePrice of the very first trade record in the entire date range, and is then carried forward across both intraday gaps and non-trading days.When the outer day loop encounters a day that has zero trades (a weekend, public holiday, or exchange closure), the trade query returns an empty array. The algorithm still walks all 1440 minutes of that day and fills them with
open = high = low = close = lastClose, volume = 0, using the closing price carried over from the last day that had real trades.This ensures the final candle series is truly continuous from the first to the last trading day in the dataset — there are no gaps at the day boundary that would confuse Pine Script indicators or
backtest-kit strategy runners.All eleven timeframes are built simultaneously from the gap-filled 1-minute series produced in Steps 2 and 3. For each timeframe
T the algorithm groups 1-minute candles by flooring their timestamp to the nearest T-minute boundary using the same floorToMin function:openopen of the first 1m candle in the periodhighhigh across all 1m candleslowlow across all 1m candlescloseclose of the last 1m candle in the periodvolumevolume across all 1m candlesThe eleven accumulators (
Map<number, OHLCV>) are updated inside the same loop that walks the 1-minute series, so the entire rollup for all timeframes is completed in a single O(1440) pass per day.All candle documents produced for the current day are flattened into a single array and written to MongoDB in one call:
The
ordered: false option instructs MongoDB to continue inserting remaining documents even when it encounters a duplicate key. The unique compound index { symbol, interval, timestamp } rejects any document that already exists in the collection with error code 11000. The insertBatch helper catches this error and extracts insertedCount from the error result, returning { inserted, skipped } counts rather than propagating the exception.async function insertBatch(docs: object[]): Promise<{ inserted: number; skipped: number }> {
if (docs.length === 0) return { inserted: 0, skipped: 0 };
try {
const res = await CandleModel.insertMany(docs, { ordered: false });
return { inserted: res.length, skipped: 0 };
} catch (e: any) {
const inserted = e.result?.insertedCount ?? 0;
return { inserted, skipped: docs.length - inserted };
}
}
Day-by-Day Processing
The outer loop increments one calendar day at a time from the UTC date of the earliest trade to the UTC date of the latest trade. Only the trades belonging to the current day are loaded from MongoDB in each iteration, capping peak memory usage to the volume of a single day’s trades regardless of the total dataset size.Timestamp Storage
Alltimestamp values are stored as milliseconds since the Unix epoch (Date.getTime() convention). The 1d candle for a given UTC date has timestamp equal to the midnight boundary of that date in milliseconds: