vike

Recipe 62 of 65

Build 1-second candles for a Hyperliquid coin

The API's time bars start at 1 minute; build 1-second bars from the archive's daily fills file by keeping the taker row of each trade, ordering by fill_ts and trade_id, and bucketing by second.

GET /v1/hyperliquid/archive/url answers it with one download, because GET /v1/hyperliquid/ohlcv serves time bars from 1m up only. Build 1-second bars from the archive's daily fills file, which has every fill with a millisecond fill_ts. Keep one row per trade (the taker side, is_maker = false), order by fill_ts then trade_id, and bucket by second. Built this way, the bars add up exactly to the API's 1-minute bars. The full parameter lists are on Archive download link and Candles.

Quick answer

Download one day, then bucket the taker rows by second in Polars.

cURL
# download one UTC day of every fill: redirect=1 answers with the link and -L follows it
curl -s -L -H "X-API-Key: $VIKE_API_KEY" -o fills-2026-10-23.parquet \
  "https://data.vike.io/v1/hyperliquid/archive/url?dataset=fills&date=2026-10-23&redirect=1"
Polars
import polars as pl

fills = pl.read_parquet("fills-2026-10-23.parquet")  # or the free fills-sample.parquet
bars = (
    fills.filter((pl.col("asset") == "BTC") & ~pl.col("is_maker"))  # the taker row: one row per trade
    .sort("fill_ts", "trade_id")
    .group_by_dynamic("fill_ts", every="1s")
    .agg(
        open=pl.col("price").first(),
        high=pl.col("price").max(),
        low=pl.col("price").min(),
        close=pl.col("price").last(),
        volume=pl.col("size_token").sum(),
        volume_usd=pl.col("size_usd").sum(),
        n_trades=pl.len(),
    )
)
print(bars.head())

Example response

Computed with the Polars code on the free fills sample, captured from the live API on 2026-10-24 at 17:16 UTC (every BTC fill of 2026-10-21 00:00 to 01:00 UTC), shown as JSON, the first 4 of 2,346 one-second bars.

fill_ts is the start of the second, UTC. volume is BTC, volume_usd USD, n_trades trades in that second. There is no bar for 00:00:01 or 00:00:03: nothing traded. Rolled up to minutes, these bars match the API's 1-minute bars for the same minutes exactly (00:00: open 82,991, high 82,991, low 82,918, close 82,932, 11.14182 BTC, 235 trades).

Example response
[
  {"fill_ts": "2026-10-21T00:00:00.000Z", "open": 82991.0, "high": 82991.0, "low": 82991.0, "close": 82991.0, "volume": 0.001, "volume_usd": 82.991, "n_trades": 1},
  {"fill_ts": "2026-10-21T00:00:02.000Z", "open": 82991.0, "high": 82991.0, "low": 82988.0, "close": 82989.0, "volume": 0.16127, "volume_usd": 13383.94634, "n_trades": 6},
  {"fill_ts": "2026-10-21T00:00:04.000Z", "open": 82976.0, "high": 82977.0, "low": 82976.0, "close": 82977.0, "volume": 0.00727, "volume_usd": 603.24264, "n_trades": 2},
  {"fill_ts": "2026-10-21T00:00:05.000Z", "open": 82975.0, "high": 82975.0, "low": 82975.0, "close": 82975.0, "volume": 0.00148, "volume_usd": 122.803, "n_trades": 1}
]

Variations

Check against the API. The same minutes from the API, to confirm your build.

cURL — 1m check
# the API's first three BTC minutes of 2026-10-21
curl -H "X-API-Key: $VIKE_API_KEY" \
  "https://data.vike.io/v1/hyperliquid/ohlcv?symbol=BTC&interval=1m&start=2026-10-21T00:00:00Z&limit=3"

Trade-count bars from the API. If what you want is finer than a minute rather than exactly a second, the API serves bars of N trades from the same fills, up to the present.

cURL — 100-trade bars
# 100-trade BTC bars over ten minutes
curl -H "X-API-Key: $VIKE_API_KEY" \
  "https://data.vike.io/v1/hyperliquid/ohlcv?symbol=BTC&interval=100t&start=2026-10-23T12:00:00Z&end=2026-10-23T12:10:00Z&limit=500"

Fill the empty seconds. A backtest that expects one bar per second needs the gaps filled: carry close forward with zero volume.

Polars — full grid
grid = pl.datetime_range(bars["fill_ts"].min(), bars["fill_ts"].max(), "1s", time_unit="ms", time_zone="UTC", eager=True).alias("fill_ts").to_frame()
full = grid.join(bars, on="fill_ts", how="left").with_columns(
    pl.col("close").forward_fill(),
    pl.col("volume", "volume_usd", "n_trades").fill_null(0),
).with_columns(pl.col("open", "high", "low").fill_null(pl.col("close")))

On the sample hour this turns 2,346 bars into the full 3,600.

Before you rely on it

History only, from the daily files

The fills file of a UTC day is published in the early UTC morning after the day ends, from 2026-05-01, so 1-second bars are history, not live. For finer-than-minute bars up to now, use the API's trade-count bars (interval=100t). Its 1-trade bars (interval=1t) carry whole-second timestamps and come back in no fixed order within a second, so they give a second's high, low and volume but not its open and close.

Count each trade once; liquidations are not flagged

Both sides of every trade are in the file. Keeping is_maker = false counts each trade once, as the API does; summing all rows doubles the volume. Order by fill_ts and trade_id together: many trades share a millisecond. Nothing in the file marks a fill as a liquidation.

Ask your agent

No MCP tool returns sub-minute bars: hl_ohlcv starts at 1 minute, so use the file.

Example response
[  {"fill_ts": "2026-10-21T00:00:00.000Z", "open": 82991.0, "high": 82991.0, "low": 82991.0, "close": 82991.0, "volume": 0.001, "volume_usd": 82.991, "n_trades": 1},  {"fill_ts": "2026-10-21T00:00:02.000Z", "open": 82991.0, "high": 82991.0, "low": 82988.0, "close": 82989.0, "volume": 0.16127, "volume_usd": 13383.94634, "n_trades": 6},  {"fill_ts": "2026-10-21T00:00:04.000Z", "open": 82976.0, "high": 82977.0, "low": 82976.0, "close": 82977.0, "volume": 0.00727, "volume_usd": 603.24264, "n_trades": 2},  {"fill_ts": "2026-10-21T00:00:05.000Z", "open": 82975.0, "high": 82975.0, "low": 82975.0, "close": 82975.0, "volume": 0.00148, "volume_usd": 122.803, "n_trades": 1}]