Skip to content

Historical queries

HistoricalService returns archived trades and candles for any pair within the last 30 days. Authenticated; pulls from the deployment's historical archive (when configured).

What's available

RPCReturnsCap
GetTradesTrades within [from_sec, to_sec] for a pair.1,000 rows per call
GetCandlesOHLCV candles at one of seven intervals.10,000 rows per call

If the deployment doesn't have the historical archive enabled, every RPC returns FAILED_PRECONDITION. Read it once at boot to feature-gate the UI.

Range semantics

  • from_sec and to_sec are both inclusive.
  • Units are unix seconds. Values ≥ 10¹¹ auto-convert from milliseconds with a Warning header.
  • Historical trades come back oldest-first. (Live trades from MarketDataService come back newest-first — different paths, different conventions.)
  • Maximum window is 30 days per call. Larger spans need to be paginated.

Backfill recipe

To paginate a long span (e.g. a year of 1-minute candles), chunk by the row cap:

rust
use superis::proto::{Interval, GetCandlesRequest, Pair, SpotId};

let interval_sec = 60u64;
let max_rows = 10_000u64;
let window_sec = max_rows * interval_sec;

let mut from = start_sec;
let mut all = Vec::new();
while from < end_sec {
    let to = (from + window_sec).min(end_sec);
    let res = historical
        .get_candles(GetCandlesRequest {
            pair: Some(Pair { base: Some(SpotId { id: 1 }), quote: Some(SpotId { id: 0 }) }),
            interval: Interval::Interval5m.into(),
            from_sec: Some(from),
            to_sec: Some(to),
        })
        .await?
        .into_inner();
    all.extend(res.candles);
    from = to + 1;
}
go
const intervalSec = uint64(60)
const maxRows = uint64(10_000)
windowSec := maxRows * intervalSec

from := startSec
var all []*pb.Candle
for from < endSec {
    to := from + windowSec
    if to > endSec { to = endSec }
    res, err := historical.GetCandles(ctx, &pb.GetCandlesRequest{
        Pair:     &pb.Pair{Base: &pb.SpotId{Id: 1}, Quote: &pb.SpotId{Id: 0}},
        Interval: pb.Interval_INTERVAL_5M,
        FromSec:  &from,
        ToSec:    &to,
    })
    if err != nil { log.Fatal(err) }
    all = append(all, res.Candles...)
    from = to + 1
}
ts
const intervalSec = 60n;
const maxRows = 10_000n;
const windowSec = maxRows * intervalSec;

let from = startSec;
const all: Candle[] = [];
while (from < endSec) {
  const to = from + windowSec > endSec ? endSec : from + windowSec;
  const { candles } = await historical.getCandles({
    pair: { base: { id: 1n }, quote: { id: 0n } },
    interval: "INTERVAL_5M",
    fromSec: from,
    toSec: to,
  });
  all.push(...candles);
  from = to + 1n;
}

Stitching with live

For a chart that shows the last 24h:

  1. GetCandles with from_sec = now - 86_400, to_sec = now to pull the historical body.
  2. Subscribe to MarketDataService.Subscribe for the pair to drive live updates from now forward, computing the last candle yourself from the fill stream — or just GetCandles again every interval for less aggressive UIs.

The historical and live paths align on the interval boundary.

When to prefer live

HistoricalService.GetTrades over a recent window is more expensive than just streaming MarketDataService.SubscribeFills and ringing your own buffer. Use the historical path for backfill or for ranges older than your in-memory retention; use the live path for anything inside the active session.

Apache 2.0