Skip to content

Historical queries

HistoricalService returns archived fills and candles for any pair within the deployment's retention window. It is public and runs on the public listener. Pulls from the deployment's historical archive when configured.

What's available

RPCReturnsCap
GetFillsFills within [start, end) 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

  • start and end carry Unix microseconds in Timestamp.micros.
  • start is inclusive.
  • end is exclusive. Unset or micros = 0 means "now".
  • GetFills returns rows newest-first, each row a FillEvent — the same wire shape as the live fill stream.
  • GetCandles returns rows oldest-first.
  • Maximum window is 30 days per call. Larger spans need to be paginated.
  • Historical price, size, and OHLCV values are Decimal strings — the same wire format as the live book and fill streams. Parse with a fixed-precision type (rust_decimaldecimal.Decimalbignumber.js).

Backfill recipe

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

rust
use flint_api_client::api::proto::{CandleInterval, GetCandlesRequest, Pair, Timestamp};

let mut historical = client.historical();

let interval_us = 5 * 60 * 1_000_000u64;
let max_rows = 10_000u64;
let window_us = max_rows * interval_us;

let mut start = start_us;
let mut all = Vec::new();
while start < end_us {
    let end = (start + window_us).min(end_us);
    let res = historical
        .get_candles(GetCandlesRequest {
            pair: Some(Pair {
                base_id: Some(1),
                base: "SOL".into(),
                quote_id: Some(0),
                quote: "USDC".into(),
                label: "SOL/USDC".into(),
            }),
            interval: CandleInterval::CandleInterval5m.into(),
            start: Some(Timestamp { micros: start }),
            end: Some(Timestamp { micros: end }),
        })
        .await?
        .into_inner();
    all.extend(res.candles);
    start = end;
}
ts
import {
  GrpcWebTransport,
  HistoricalService,
  createServiceClient,
} from "@superis-labs/flint-api-client";
import {
  CandleInterval,
  type Candle,
} from "@superis-labs/flint-api-client/gen/flint/spot/v1/historical/messages_pb.js";

const historical = createServiceClient(
  HistoricalService,
  new GrpcWebTransport(),
);

const intervalUs = 5n * 60n * 1_000_000n;
const maxRows = 10_000n;
const windowUs = maxRows * intervalUs;

let start = startUs;
const all: Candle[] = [];
while (start < endUs) {
  const end = start + windowUs > endUs ? endUs : start + windowUs;
  const { candles } = await historical.getCandles({
    pair: { baseId: 1n, quoteId: 0n },
    interval: CandleInterval.CANDLE_INTERVAL_5M,
    start: { micros: start },
    end: { micros: end },
  });
  all.push(...candles);
  start = end;
}
python
from flint import Client
from flint.gen.flint.spot.v1 import common_pb2
from flint.gen.flint.spot.v1.historical import messages_pb2 as hist

client = Client()

request = hist.GetCandlesRequest(
    pair=common_pb2.Pair(base_id=1, quote_id=0, label="SOL/USDC"),
    interval=hist.CANDLE_INTERVAL_5M,
    start=common_pb2.Timestamp(micros=start_us),
    end=common_pb2.Timestamp(micros=end_us),
)
response = await client.historical_candles(request)

Stitching with live

For a chart that shows the last 24h:

  1. GetCandles with start.micros = now_us - 86_400_000_000 and end.micros = now_us 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.GetFills 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.

Built on Solana