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.

The UZSE Backtest App builds OHLCV candlestick series in 11 timeframes simultaneously, ranging from 1-minute granularity up to a full trading day. All higher timeframes are derived from the same 1-minute base series, which is itself aggregated directly from raw trade records. Every timeframe is produced in a single pass over each day’s trades and written to the candle-items collection in one batch, making the build process efficient even for multi-year histories.

Supported Timeframes

IntervalMinutesStep (ms)Description
1m160,0001-minute — base timeframe, directly aggregated from trades
3m3180,0003-minute
5m5300,0005-minute
15m15900,00015-minute
30m301,800,00030-minute
1h603,600,0001-hour
2h1207,200,0002-hour
4h24014,400,0004-hour
6h36021,600,0006-hour
8h48028,800,0008-hour
1d144086,400,000Daily
The CandleInterval type exported from backtest-kit constrains valid interval values to this exact set. The same values are enforced at the database layer via the Mongoose enum constraint on the interval field in CandleSchema.

Aggregation Algorithm

The candle builder follows a four-step process, derived directly from the build-candles.ts source:
1

Aggregate trades into 1-minute buckets

All trades for a given day are loaded from the trade-results collection (queried by ISIN and UTC day boundaries). Each trade is placed into a 1-minute bucket by flooring its timestamp:
const ts = Math.floor(tradeTimeMs / 60_000) * 60_000;
Within each bucket, OHLCV values are accumulated:
  • opentradePrice of the first trade in the bucket
  • high — maximum tradePrice across all trades in the bucket
  • low — minimum tradePrice across all trades in the bucket
  • closetradePrice of the last trade in the bucket
  • volume — sum of quantity (share count) across all trades in the bucket
2

Fill intraday gaps

A continuous minute-by-minute series is generated from 00:00:00 to 23:59:00 UTC for each calendar day. For any minute that has no real trade activity, a synthetic candle is inserted:
  • open = high = low = close = close of the most recent real candle
  • volume = 0
This ensures the 1-minute series is gapless within every day.
3

Fill non-trading days

Weekends and exchange holidays are days that have no trades at all. The builder detects these implicitly — when the trade-results query for a day returns zero rows, all 1440 minutes of that day are gap-filled using the last known closing price from the most recent trading day:
  • open = high = low = close = close of the last candle from the previous trading day
  • volume = 0
This keeps the series fully continuous across the entire date range, from the first trade ever recorded to the last.
4

Aggregate higher timeframes from the filled 1m series

After the full-day 1-minute series (including gap-filled candles) is built in memory, all 11 timeframes are accumulated in a single forward pass over the 1440-minute array. Each higher timeframe N is computed by flooring each 1m timestamp to the nearest N-minute boundary:
function floorToMin(tsMs: number, minutes: number): number {
  const stepMs = minutes * 60_000;
  return Math.floor(tsMs / stepMs) * stepMs;
}
Within each N-minute period:
  • open — taken from the first 1m candle falling in the period
  • high — maximum high across all 1m candles in the period
  • low — minimum low across all 1m candles in the period
  • close — taken from the last 1m candle in the period
  • volume — sum of all 1m candle volume values in the period
All timeframes are written to the candle-items collection in a single insertMany call at the end of each day.

Gap-Fill Reference

ScenarioOHLC ValuesVolume
Minute with real trade(s)Derived from trade pricesSum of quantity
Minute with no trades (intraday gap)= previous candle close0
Non-trading day (weekend / holiday)= last trading day close0
Why gap-filling matters for Pine Script: Pine Script technical analysis indicators (moving averages, RSI, MACD, Bollinger Bands, etc.) require a continuous time series with no missing bars. If gaps exist in the data — even for a single missing day — indicator calculations will shift, lookback windows will reference wrong bars, and backtest results will be incorrect. By filling every minute of every calendar day, the UZSE Backtest App ensures that Pine Script indicators running inside backtest-kit behave identically to how they would on a live TradingView chart.

Idempotency

The { symbol: 1, interval: 1, timestamp: 1 } unique index on candle-items guarantees that re-running build-candles.ts for an already-processed symbol and date range is safe. The script calls insertMany with { ordered: false }, so existing candles generate write errors that are caught and counted as skipped rather than causing the process to abort.
async function insertBatch(docs: object[]): Promise<{ inserted: number; skipped: number }> {
  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 };
  }
}

Using Timeframes with the Exchange Module

When the backtest-kit editor calls getCandles, it passes one of the 11 interval strings along with the symbol and a since date. The exchange module translates this directly into a MongoDB query:
const candles = await CandleModel.find(
  { symbol, interval, timestamp: { $gte: since.getTime() } },
  { timestamp: 1, open: 1, high: 1, low: 1, close: 1, volume: 1, _id: 0 }
)
  .sort({ timestamp: 1 })
  .limit(limit)
  .lean();
Because every timeframe is pre-computed and stored, there is no runtime aggregation cost — candle retrieval for any supported timeframe is a single indexed MongoDB read.

Candle Schema

Full field reference for the candle-items collection including types and indexes.

Build Candles

How to run build-candles.ts to populate all 11 timeframes for a symbol.

Exchange Module

How getCandles() queries the candle-items collection for Pine Script analysis.

Data Model

End-to-end overview of how trades become candles and reach the editor.

Build docs developers (and LLMs) love