Skip to main content

Hyperliquid Mark Price vs Oracle Price, Measured

How Hyperliquid sets oracle and mark prices, what each one drives, how far they split in a crash, and what stock perp oracles do at the weekend.

11 min read
Explore with your agent

Key findings

  1. Step 3: what a crash does to mark and oracle-740.6 bpsHYPE's mark below its oracle at 21:36 UTC on 10 October 2025.
  2. Step 1: measure the gap over a month92.5%Minutes in 30 days when BTC's mark sat below its oracle.
  3. Step 2: why a discount does not mean shorts get paid574 of 720Hours BTC funding sat exactly at the interest rate despite a negative premium.
  4. Step 4: stock perp oracles when the market is shut86% vs 21%Share of updates where xyz:NVDA's oracle moved, US session vs market closed.

Every Hyperliquid perp has three prices at once: the price on the order book, the oracle price, and the mark price. Hyperliquid is an onchain crypto exchange, and which of the three it uses decides when you get liquidated, when your stop triggers and what funding you pay. Most of the time they sit within a few basis points. On 10 October 2025 HYPE's mark was 740 bps below its oracle. The sections below show how each price is set, measure how far they drift apart, and follow stock perp oracles through a weekend, using requests run on 13 November 2026.

Short answer

  • The oracle price is a weighted median of spot prices on eight exchanges, published every 3 seconds. It sets funding.
  • The mark price is a median of three estimates, one of them built on the oracle. It drives margin, liquidations, take-profit and stop-loss triggers, and unrealized PnL.
  • They usually agree within a few bps. Over 30 days BTC's mark sat a median 3.08 bps below its oracle and never strayed more than 8.5 bps.
  • In a crash they split. At 21:21 UTC on 10 October 2025, ETH's mark was 303.5 bps below its oracle and the book mid was 180 bps below the mark.
  • Stock perp oracles slow down at the weekend. xyz:NVDA's oracle changed on 86% of updates in the US session and 21% while the market was closed.

How Hyperliquid sets each price

From Hyperliquid's oracle documentation: validators publish spot oracle prices "every 3 seconds". Each is "the weighted median of Binance, OKX, Bybit, Kraken, Kucoin, Gate IO, MEXC, and Hyperliquid spot mid prices", with weights 3, 2, 2, 1, 1, 1, 1, 1. The final price is the stake-weighted median of what the validators submit.

The mark price is the median of three numbers:

  1. The oracle price plus a 150-second moving average of the gap between Hyperliquid's mid price and the oracle.
  2. The median of Hyperliquid's best bid, best ask and last trade.
  3. A weighted median of perp mid prices on Binance, OKX, Bybit, Gate IO and MEXC.

Taking the median of three means one bad input cannot move the mark. It also means the mark follows the book only partly: when the book moves faster than the oracle, the mark lags.

Used forPrice
Margin required: position_size × mark_price / leverageMark
LiquidationsMark
Take-profit and stop-loss triggersMark
Unrealized PnLMark
Funding rate and paymentOracle
Your fillThe book

The liquidation docs warn that the "mark price may be significantly different from book price." The data below shows when.

What you need

  • Python 3 with requests. The snippets are short prototypes without retry logic.
  • A 0xArchive API key in OXARCHIVE_API_KEY. The Free plan covers the price routes with a rolling 30 days of history. Step 3 uses October 2025 and needs the full archive; see pricing.

Step 1: measure the gap over a month

The prices route returns mark, oracle and mid together, about once a minute, from 20 May 2023. Measure the mark-oracle gap in basis points over 30 days:

import os, statistics, requests
from datetime import datetime, timezone
H = {"X-API-Key": os.environ["OXARCHIVE_API_KEY"]}
B = "https://api.0xarchive.io/v1"

def prices(path, start, end):
    """Raw mark, oracle and mid rows, following the cursor."""
    rows, cursor = [], None
    while True:
        p = {"start": start, "end": end, "limit": 1000}
        if cursor:
            p["cursor"] = cursor
        j = requests.get(f"{B}{path}", headers=H, params=p, timeout=120).json()
        rows += j["data"]
        cursor = j["meta"].get("next_cursor")
        if not j["meta"].get("has_more"):
            return rows

def bps(r):
    return (float(r["mark_price"]) / float(r["oracle_price"]) - 1) * 1e4

for sym in ["BTC", "HYPE"]:
    rows = prices(f"/hyperliquid/prices/{sym}", "2026-10-14T00:00:00Z", "2026-11-13T00:00:00Z")
    s = sorted(bps(r) for r in rows)
    print(sym, len(rows), "rows | median %.2f bps | p1 %.2f | p99 %.2f | below oracle %.1f%% of minutes"
          % (statistics.median(s), s[len(s) // 100], s[len(s) * 99 // 100], 100 * sum(x < 0 for x in s) / len(s)))
BTC 42839 rows | median -3.08 bps | p1 -5.64 | p99 1.81 | below oracle 92.5% of minutes
HYPE 42839 rows | median -1.97 bps | p1 -8.66 | p99 13.21 | below oracle 69.3% of minutes

BTC's mark was below its oracle in 92.5% of minutes. Ninety-eight percent of minutes fell between 5.64 bps below and 1.81 bps above. HYPE was noisier: in the same month it reached 85.8 bps above its oracle at 01:56 UTC on 30 October. On OP, a market at the middle of Hyperliquid's volume ranking, the gap was beyond 10 bps in 14.2% of minutes. HYPE and OP, with thinner books than BTC, drifted much further.

Step 2: why a discount does not mean shorts get paid

A mark below the oracle looks like a discount that should make shorts pay longs. It did not. Funding is set from the oracle and a premium measured from the book, and Hyperliquid adds an interest component. From the funding docs: F = P + clamp(interest rate − P, −0.0005, 0.0005), with interest of 0.01% every 8 hours, or 0.00125% an hour. A small negative premium is clamped away, and funding lands on the interest rate. Hyperliquid's own funding history shows it:

ms = lambda *d: int(datetime(*d, tzinfo=timezone.utc).timestamp() * 1000)
T0, T1 = ms(2026, 10, 14), ms(2026, 11, 13)          # the 30 days to 13 November 2026
N = "https://api.hyperliquid.xyz/info"
f = requests.post(N, json={"type": "fundingHistory", "coin": "BTC",
                           "startTime": T0, "endTime": T1}, timeout=60).json()
f += requests.post(N, json={"type": "fundingHistory", "coin": "BTC",
                            "startTime": f[-1]["time"] + 1, "endTime": T1}, timeout=60).json()
rates = [float(x["fundingRate"]) for x in f]
prem = [float(x["premium"]) for x in f]
print(len(f), "hours | premium below zero in %d | funding exactly 0.00125%% in %d | mean funding %.4f%%/h"
      % (sum(p < 0 for p in prem), sum(abs(r - 0.0000125) < 1e-12 for r in rates), 100 * statistics.mean(rates)))
720 hours | premium below zero in 666 | funding exactly 0.00125% in 574 | mean funding 0.0011%/h

In 666 of 720 hours the BTC premium was negative. In 574 hours funding was exactly 0.00125%, paid by longs to shorts. Funding averaged 0.0011% an hour, about 9.6% a year, paid by longs. It only turned negative when the discount was large, as in the 17:00 UTC hour on 4 November, when the spread averaged −6.4 bps. Across BTC, ETH, HYPE and OP, the mark-oracle spread tracked the premium with a correlation of 0.91 to 0.996, but funding itself only 0.51 to 0.77.

Step 3: what a crash does to mark and oracle

On 10 October 2025 open interest on Hyperliquid fell 62.9% in under eight hours. Pull the minute rows for that night and find each market's widest gap:

for sym in ["BTC", "ETH", "HYPE"]:
    rows = prices(f"/hyperliquid/prices/{sym}", "2025-10-10T19:00:00Z", "2025-10-11T01:00:00Z")
    worst = min(rows, key=bps)
    print(sym, worst["timestamp"][11:16], "mark", worst["mark_price"], "oracle", worst["oracle_price"],
          "mid", worst["mid_price"], "| %.1f bps" % bps(worst))
BTC 21:18 mark 107199 oracle 107988 mid 105823 | -73.1 bps
ETH 21:21 mark 3383.7 oracle 3489.6 mid 3322.95 | -303.5 bps
HYPE 21:36 mark 39.381 oracle 42.531 mid 40.838 | -740.6 bps

The oracle follows spot prices on other exchanges, and the book was falling faster. ETH's book mid at 21:21 UTC was $3,322.95, against a mark of $3,383.70 and an oracle of $3,489.60. Trades filled at book prices 180 bps below the mark that margin checks used. HYPE's mark ran 740.6 bps below its oracle at 21:36. Then the gap reversed: BTC's mark was 179.5 bps above its oracle at 21:28.

These are one-minute snapshots. Hyperliquid updates both prices every few seconds, so one-minute snapshots miss the extremes inside each minute. To see who was liquidated in that window, see the liquidation history guide.

Step 4: stock perp oracles when the market is shut

HIP-3 markets, such as the stock perps on trade.xyz, use an oracle pushed by the market's deployer instead of the validator oracle. Hyperliquid's deployer docs say mark moves are "clamped to 1% from previous markPx" per update, and that external prices "prevent sudden mark price deviations". What happens when the stock exchange closes? HIP-3 price rows arrive about every 3 seconds, so we split one weekend by session:

rows = prices("/hyperliquid/hip3/prices/xyz:NVDA", "2026-11-06T14:30:00Z", "2026-11-09T21:00:00Z")
def session(ts):
    t = datetime.fromisoformat(ts.replace("Z", "+00:00"))
    open_ = t.weekday() < 5 and (14, 30) <= (t.hour, t.minute) < (21, 0)
    return "US session" if open_ else "market closed"
moved = {"US session": [0, 0], "market closed": [0, 0]}
gap = {"US session": [], "market closed": []}
for a, b in zip(rows, rows[1:]):
    k = session(b["timestamp"])
    moved[k][0] += a["oracle_price"] != b["oracle_price"]
    moved[k][1] += 1
    if b.get("mark_price"):
        gap[k].append(abs(bps(b)))
for k in moved:
    g = sorted(gap[k])
    print(k, "| oracle changed in %.1f%% of rows" % (100 * moved[k][0] / moved[k][1]),
          "| mark vs oracle median %.1f bps, p99 %.1f, max %.1f" % (statistics.median(g), g[len(g) * 99 // 100], g[-1]))
US session | oracle changed in 86.1% of rows | mark vs oracle median 3.7 bps, p99 11.8, max 14.6
market closed | oracle changed in 20.6% of rows | mark vs oracle median 3.7 bps, p99 74.8, max 225.7

During the US session the xyz:NVDA oracle changed on 86.1% of updates. From the Friday close to the Monday open it changed on 20.6%. On Saturday its 3-hour range was as narrow as 0.06%. The median mark-oracle gap was 3.7 bps in both periods, but the tail was not: while closed it reached 225.7 bps, against 14.6 bps in the session. Activity picked up on Sunday afternoon UTC, when the oracle's 3-hour range reached 1.6%.

The weekend price was not always a forecast. TSLA's mark just before the Monday open was 421 bps below where the oracle stood 15 minutes after it. NVDA's gap was 170 bps and SP500's 61 bps. For the trading side of these markets, see our research on weekend price discovery.

Step 5: read a HIP-3 market's oracle and bounds now

Two routes return the current deployer price and the range a HIP-3 market's price can move within:

for sym in ["xyz:NVDA", "xyz:SP500"]:
    e = requests.get(f"{B}/hyperliquid/hip3/oracle/external-price/{sym}", headers=H, timeout=30).json()["data"]
    d = requests.get(f"{B}/hyperliquid/hip3/oracle/discovery-bounds/{sym}", headers=H, timeout=30).json()["data"]
    print(sym, {k: e.get(k) for k in ("external_price", "mark_price")},
          {k: d.get(k) for k in ("lower_bound", "upper_bound", "bound_fraction", "max_leverage")})
xyz:NVDA {'external_price': 163.01, 'mark_price': 163.08} {'lower_bound': 154.8595, 'upper_bound': 171.16049999999998, 'bound_fraction': 0.05, 'max_leverage': 20}
xyz:SP500 {'external_price': 7556.8, 'mark_price': 7555.8} {'lower_bound': 7405.664, 'upper_bound': 7707.936000000001, 'bound_fraction': 0.02, 'max_leverage': 50}

The bounds were the reference price plus or minus 1 / max_leverage: 5% for xyz:NVDA at 20x, 2% for xyz:SP500 at 50x. These two routes return the latest reading only. Date parameters are ignored. For history, use the HIP-3 prices route as in Step 4.

How Lighter sets its mark price

Lighter, another onchain crypto exchange, uses a similar design with different inputs. Its fair price marking docs define Mark Price = Median(Impact Price, price1, price2), with an index price from Chainlink, Stork and Pyth. Markets with no continuous feed, such as stocks outside trading hours, switch to an internal pricing mechanism. Lighter's funding premium is measured against the index price once a minute. The 0xArchive Lighter prices route returns mark, oracle and index from 25 August 2025.

Side by side: native APIs and 0xArchive

We ran the same jobs against each source on 13 November 2026.

JobHyperliquid API0xArchive
Current mark and oraclemetaAndAssetCtxs, every market in one callPer market
Mark and oracle historyNot availableAbout one row a minute from 20 May 2023
Hourly premium and fundingfundingHistory from 12 May 2023, 500 rows per callFrom 20 May 2023
HIP-3 oracle historyNot availableAbout every 3 seconds from 13 October 2025
30 days of BTC at one-minute resolutionNot possible42,839 rows, 43 requests

Hyperliquid's API is the right source for live prices and for hourly funding and premium history, which it holds from slightly earlier than we do. It has no record of past mark or oracle levels, so any question about how they diverged needs the archive. Lighter's API returns mark price candles back to August 2025.

What to know before you build

  • Early HIP-3 rows carry the oracle only. xyz:XYZ100 rows have no mark price until 8 June 2026. A HIP-3 mark-oracle series starts then for that market, not at the route's first row.
  • Funding values differ by source. Hyperliquid's fundingHistory returns the settled hourly rate. Our hourly funding rows average the predicted rate sampled through the hour. Use settled rates for payments and ours for intra-hour behavior.
  • Sign conventions. In this guide the gap is (mark / oracle − 1) × 10,000, so a negative number means mark below oracle.
  • Weekend gaps are short and sharp. The NVDA median gap did not change at the weekend. Look at the 99th percentile and the maximum.

FAQ

What is the difference between mark price and oracle price on Hyperliquid?

The oracle is a weighted median of spot prices on eight exchanges and sets funding. The mark is a median of three estimates that include the oracle and Hyperliquid's own book, and it sets margin, liquidations and stop triggers.

Does Hyperliquid liquidate on mark price or last price?

Mark price. In fast markets the book can trade well away from it: 180 bps below it on ETH on 10 October 2025.

Which price triggers my stop-loss on Hyperliquid?

The mark price triggers take-profit and stop-loss orders. The fill happens on the book.

Why is funding positive when mark is below oracle?

Hyperliquid's funding adds an interest rate of 0.00125% an hour and clamps small premiums to it. A small discount leaves funding at the interest rate, paid by longs.

What happens to stock perp prices on Hyperliquid at the weekend?

The deployer's oracle keeps updating but moves far less often, and the mark can drift further from it. The perp still trades. See Step 4.

Next steps