Skip to content
CTRL K
    Chart windows

    Chart windows

    How to load price history (OHLCV candles) for a chart and keep it updated without downloading the full history on every poll.

    OHLCV reads use the same window options on:

    Both return a CandleSeries.


    What is a candle?

    Each candle is one time bucket on your chart (e.g. one hour if interval: '1h'). It has:

    • time — when that bucket starts (Unix timestamp in seconds)
    • open, high, low, close — prices for that bucket

    Closed candles are finished buckets — they will not change. The current open bucket (the bar still forming) may be returned separately as partialCandle — see Response fields below.


    Four ways to fetch data

    You will use different calls depending on what the screen needs:

    PatternWhen you need itWhat to call
    BootstrapUser opens the chart, or changes pair / intervalgetChart({ pair, interval, limit: 300 })
    Tail refreshA closed candle may have appeared since last loadgetChart({ pair, interval, after: … })
    Partial barChart is open; update the live rightmost bar oftengetChart({ pair, interval, scope: 'partialOnly' })
    TickHeader / ticker — latest price only, not OHLCgetTokenPrice({ instrument })

    Bootstrap = first load of history. Tail refresh = append newly closed candles. Partial bar = cheap poll for the open bucket only. Tick = spot price without candle shape.

    Do not repeat bootstrap on a timer. Use partial bar for frequent live updates and tail refresh when you need closed candles (often less frequent than partial bar, or right after a bucket closes).


    Window parameters (CandleQuery)

    Optional fields on getChart / getCandles: limit, after, before, scope.

    If an interval is not available for the request or config, the call fails with CAPABILITY_NOT_AVAILABLE — not an empty series.

    If you omit all of them, Nutrimatic returns a default slice of recent history — the same as omitting only limit (default 300 closed candles). That is scope: 'default'.

    You can combine before + limit when loading older history. Do not pass after and before together unless you need a narrow slice between two times.

    Do not combine scope: 'partialOnly' with limit, before, or bootstrap — it is only for updating the open bar when history is already loaded.


    limit

    What it means: “Return at most this many closed candles.” Nutrimatic picks the most recent candles up to that count.

    Optional — default 300 if omitted. Responses cap at 1000; if you ask for more, you receive at most the cap.

    When to use it

    SituationShould you pass limit?Example
    First chart load (bootstrap)Yeslimit: 300 — you decide how much history to load initially
    Tail refresh (you already passed after)Usually noafter already limits the response to new candles only
    User scrolls left for older historyYes, together with beforebefore: 1719705600, limit: 100
    Latest price in a header (tick)Not usedCall getTokenPrice instead

    after

    What it means: “I already have candles up to this time — only send me newer ones.”

    after is a Unix timestamp in seconds (same unit as each candle’s time). Nutrimatic returns closed candles whose time is strictly greater than after.

    When to use it: after bootstrap, on each chart refresh (every N seconds, or when the user returns to the tab).

    What value to pass: the time of the last closed candle you already have in memory.

    import { lastClosedCandleTime } from '@nutrimatic/sdk'
    
    const after = lastClosedCandleTime(series.candles)
    // Pass `after` on the next getChart call
    

    What you do with the response: merge it into what you already have — do not throw away your previous candles. Use mergeCandleSeries (see Recommended refresh loop).

    Plain-language example: your last closed hourly candle starts at 1719792000. You call after: 1719792000. You get only buckets that started after that moment — not the whole chart again.


    before

    What it means: “I already have recent candles — send me older ones.”

    before is a Unix timestamp in seconds. Nutrimatic returns closed candles whose time is strictly less than before.

    When to use it: the user scrolls or zooms left on the chart and you need more history than bootstrap loaded.

    What value to pass: the time of the oldest candle you already display — that becomes before, so the API returns buckets before that point.

    import { firstClosedCandleTime } from '@nutrimatic/sdk'
    
    const before = firstClosedCandleTime(series.candles)
    // Pass `before` together with `limit` on the next getChart call
    

    Always pair with limit: e.g. before: oldestTime, limit: 100 loads up to 100 older candles.

    What you do with the response: prepend merged candles to your existing series (oldest new data first).

    Plain-language example: your oldest displayed candle starts at 1719705600. You call before: 1719705600, limit: 100 to fetch up to 100 candles that ended before that time.


    ledgers (getChart only)

    What it means: “Resolve this pair on these blockchains only.”

    You only need this when your SDK config lists more than one ledger and a pair label could exist on multiple chains.

    Pass ledger IDs or aliases from your config, e.g. ledgers: ['infinite-mainnet'].

    When to omit it: single-ledger apps — Nutrimatic uses the only configured ledger.

    This parameter does not change how many candles you get; it only affects which chain the pair is resolved on.


    scope

    What it means: whether Nutrimatic should work out closed candles for this request, or only return the open bucket.

    type CandleReadScope = 'default' | 'partialOnly'
    Valuecandles in responsepartialCandleWhen to use
    'default' or omitNew or matching closed candles (per limit / after / before)Included when availableBootstrap, tail refresh, page backward
    'partialOnly'Always [] (empty)Current open bar onlyFrequent poll to animate the rightmost bar

    When to use 'partialOnly': you already ran bootstrap, the chart is on screen, and you want to refresh the live bar every few seconds without reloading the full candle history.

    const live = await sdk.easy.read.getChart({
      pair: '42-USDT',
      interval: '1h',
      scope: 'partialOnly',
    })
    
    series = {
      ...series,
      partialCandle: live.partialCandle,
      asOf: live.asOf,
    }
    // Or: series = mergeCandleSeries(series, live)
    // partialOnly returns no closed candles — the helper keeps your history and only updates partialCandle and asOf
    

    Both forms do the same thing for partialOnly: keep your existing candles, replace partialCandle and asOf from the new response. Use whichever reads clearer in your app — mergeCandleSeries is the same helper as tail refresh (Step 2b).

    Optional after with 'partialOnly': not required (pair + interval identify the open bucket). You may pass after: lastClosedCandleTime(series.candles) to anchor the open bucket to your current chart window.

    Do not use 'partialOnly' with: limit, before, or the first chart load. For header price without OHLC, use getTokenPrice instead.


    Response fields

    Every successful chart call returns a CandleSeries. Important fields:

    FieldWhat it isWhat you do with it
    candlesArray of closed candles, oldest → newestDraw the main chart lines / bars
    partialCandleThe current open bucket (still updating)Draw the rightmost “live” bar; update on tail refresh
    asOfWhen Nutrimatic built this snapshot (Unix seconds)Optional “data as of …” label in UI
    priceAxisLabelHuman-readable axis label (e.g. 42/USDT)Chart axis / legend
    intervalBucket size you requestedMust stay the same when merging tail responses

    partialCandle is not duplicated inside candles. On tail refresh, update your live bar from the latest partialCandle in the response.


    Recommended refresh loop

    Copy this shape for a minimal live chart:

    import {
      createNutrimaticSdk,
      lastClosedCandleTime,
      mergeCandleSeries,
    } from '@nutrimatic/sdk'
    
    const sdk = createNutrimaticSdk({ /* … */ })
    
    // Step 1 — Bootstrap (once when the chart opens, or pair/interval changes)
    let series = await sdk.easy.read.getChart({
      pair: '42-USDT',
      interval: '1h',
      limit: 300,
    })
    // Draw series.candles and series.partialCandle
    
    // Step 2a — Partial bar (often — e.g. every 5s while the chart is visible)
    async function refreshLiveBar() {
      const live = await sdk.easy.read.getChart({
        pair: '42-USDT',
        interval: '1h',
        scope: 'partialOnly',
      })
      series = { ...series, partialCandle: live.partialCandle, asOf: live.asOf }
      // Or: series = mergeCandleSeries(series, live)
      // partialOnly returns no closed candles — the helper keeps your history and only updates partialCandle and asOf
    }
    
    // Step 2b — Tail refresh (when a bucket may have closed — e.g. every 30s or on interval boundary)
    async function refreshClosedCandles() {
      const after = lastClosedCandleTime(series.candles)
      if (after === undefined) return
    
      const tail = await sdk.easy.read.getChart({
        pair: '42-USDT',
        interval: '1h',
        after,
      })
    
      series = mergeCandleSeries(series, tail)
    }
    
    // Step 3 — Tick (optional — price in header, no OHLC bar)
    const tick = await sdk.easy.read.getTokenPrice({ instrument: '42' })

    For Step 2a, the spread assignment and the commented mergeCandleSeries line are equivalent when scope: 'partialOnly' — the response has no closed candles, so the helper only updates partialCandle and asOf. Step 2b always uses mergeCandleSeries because new closed candles may arrive.

    For a live chart in product UI, prefer sdk.live.watchPair with chart: { interval } — Nutrimatic owns freshness. The window helpers on this page are for bootstrap, load-older, and advanced composition. You still choose errors, loading states, and chart library.


    Kernel helpers

    Small pure functions (no network, no cache) from @nutrimatic/kernel, also exported by @nutrimatic/sdk:

    import {
      mergeCandles,
      mergeCandleSeries,
      lastClosedCandleTime,
      firstClosedCandleTime,
    } from '@nutrimatic/sdk'

    Candle[] vs CandleSeries — which merge to use?

    Every getChart / getCandles response is a CandleSeries object — the full API payload:

    CandleSeries                          ← what getChart / getCandles return
    ├── venueId
    ├── interval
    ├── priceAxisLabel
    ├── asOf?
    ├── partialCandle?                    ← current open bar (not inside candles)
    └── candles: Candle[]                 ← only the closed bars
            ├── { time, open, high, low, close }
            ├── { time, open, high, low, close }
            └── …

    A Candle[] is just the inner list (series.candles). Nothing else from the response is included.

    You have…Use
    Two full API responses (let series + const tail = await getChart(…))mergeCandleSeries(series, tail) — usual case for tail refresh
    Only two candle arrays (e.g. you store candles separately in app state)mergeCandles(base, incoming) — lower level; you update metadata yourself

    Default rule: if both sides came from getChart or getCandles, keep the whole CandleSeries in a variable and call mergeCandleSeries. It merges candles and carries forward asOf and partialCandle from the newer response.

    When mergeCandles makes sense: you scrolled backward and only merged arrays before building your own object, or your UI state holds candles apart from partialCandle. Example — prepend older history:

    const before = firstClosedCandleTime(series.candles)
    if (before === undefined) return
    
    const older = await sdk.easy.read.getChart({
      pair: '42-USDT',
      interval: '1h',
      before,
      limit: 100,
    })
    
    series = {
      ...series,
      candles: mergeCandles(older.candles, series.candles),
    }

    Here mergeCandles prepends: older response first, your existing candles second (result is sorted by time).

    Helper reference

    FunctionInputOutputTypical use
    lastClosedCandleTime(candles)Candle[]number | undefinedValue for after (tail refresh) — newest closed candle
    firstClosedCandleTime(candles)Candle[]number | undefinedValue for before (page backward) — oldest closed candle
    mergeCandles(base, incoming)two Candle[]Candle[]Merge arrays only; you keep partialCandle / asOf in your own code
    mergeCandleSeries(base, tail)two CandleSeriesCandleSeriesTail refresh after getChartstart here

    mergeCandleSeries throws if venueId or interval differ — that means pair or bucket size changed; run bootstrap again instead of merging.

    Nutrimatic returns a closed candle only when that bucket had market activity. Plot bars by each candle’s time so quiet gaps stay honest — the series is already correct as returned.


    Related