Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/theonetrade/uzse-backtest-app/llms.txt

Use this file to discover all available pages before exploring further.

build-candles.ts is the third step in the data pipeline. It reads the raw tick-level records from the trade-results collection, aggregates them into OHLCV candlestick documents, fills every intraday gap and non-trading day so the series is perfectly continuous, and writes the results into the candle-items collection across all eleven supported timeframes. The script is fully idempotent — re-running it never creates duplicate candles.

Usage

npx tsx scripts/build-candles.ts [symbol] [isin]

Arguments

symbol
string
Display ticker symbol stored in candle-items.symbol, e.g. HMKB. This is the name your charting or analysis layer will use to look up candles. Defaults to HMKB.
isin
string
ISIN code used to query matching records from trade-results.symbol, e.g. UZ7011340005. Defaults to UZ7011340005.
Example:
npx tsx scripts/build-candles.ts HMKB UZ7011340005

Supported Timeframes

The script produces candles for all eleven timeframes in a single pass:
IntervalMinutes
1m1
3m3
5m5
15m15
30m30
1h60
2h120
4h240
6h360
8h480
1d1440

Timestamp Flooring

Candle boundaries are aligned using a simple floor formula. For a given trade timestamp in milliseconds and an interval step in milliseconds:
function floorToMin(tsMs: number, minutes: number): number {
  const stepMs = minutes * MIN_MS; // MIN_MS = 60_000
  return Math.floor(tsMs / stepMs) * stepMs;
}
This truncates any timestamp to the start of its enclosing N-minute window. The same function is used for both 1-minute bucketing and all higher timeframes.

Algorithm

1

Aggregate trades into 1-minute buckets

Each trade record is placed into a bucket identified by floorToMin(time, 1). Within a bucket the OHLCV values are built as:
  • open — price of the first trade in the minute
  • high — maximum trade price in the minute
  • low — minimum trade price in the minute
  • close — price of the last trade in the minute
  • volume — sum of quantity (number of securities) across all trades in the minute
2

Fill intraday gaps

A continuous 1-minute series is generated for every calendar minute of the day (00:00 to 23:59). Minutes with no real trades are filled with synthetic candles where OHLC = close of the previous candle and volume = 0. The last known close is carried forward minute-by-minute.
3

Fill non-trading days

Weekends and exchange holidays (days with zero trades) are detected by walking the full date range between the first and last trade. For each such day all 1440 minutes receive OHLC = close of the last trading day and volume = 0, maintaining a gap-free series required by Pine Script indicators.
4

Upsample to higher timeframes

Using the fully gap-filled 1-minute series as the source, each higher timeframe is built by grouping 1m candles via floorToMin(timestamp, N). Within each group:
  • open — from the first 1m candle of the period
  • high / low — max / min across all 1m candles in the period
  • close — from the last 1m candle of the period
  • volume — sum of volume across all 1m candles in the period
All eleven timeframes are accumulated in parallel in a single pass over the minute series, so each day is processed in one loop.

Idempotency

The candle-items collection carries a unique compound index on { symbol, interval, timestamp }. When insertMany is called with ordered: false, any document that collides with an existing index entry triggers a MongoDB error code 11000, which the script catches and counts as a skip rather than a failure. Re-running the script after adding new trades will only insert the genuinely new candles.

Output

A single progress line is updated in-place for each day processed:
Range: 2018-02-08 — 2026-04-20 (2993 days)
[2993/2993] 2026-04-20  trades:14  real_min:9  candles:16104  inserted:4823910  skipped:0
Done. Inserted: 4823910, skipped (duplicates): 0
ColumnMeaning
tradesRaw trade records found for this calendar day
real_minDistinct 1m buckets that contained at least one trade
candlesTotal candle documents generated across all timeframes for this day
insertedRunning total of newly inserted candles
skippedRunning total of candles skipped due to duplicate index
If you already have a pre-built dataset, you can skip scraping and candle building entirely by importing the provided JSON dump with mongoimport:
mongoimport --db backtest --collection candle-items \
  --file backtest.candle-items.json --jsonArray
See the Bulk Fetch page for the full import instructions including the trade-results dump.

Build docs developers (and LLMs) love