diff --git a/docs/blockchain/Solana/Solana-Phoenix-api.md b/docs/blockchain/Solana/Solana-Phoenix-api.md index 3073a22d..d30400c6 100644 --- a/docs/blockchain/Solana/Solana-Phoenix-api.md +++ b/docs/blockchain/Solana/Solana-Phoenix-api.md @@ -4,6 +4,12 @@ description: "Query the Phoenix order book on Solana with Bitquery GraphQL: real --- # Phoenix DEX API +:::info Looking for Phoenix **Perpetuals**? +This page covers the Phoenix **spot** order book. For perpetual futures — orders, fills, +positions, realized PnL, liquidations, funding, mark price and open interest — see the +[**Phoenix Perpetuals API**](/docs/perpetuals/solana/phoenix-perpetuals-api). +::: + :::tip Need real-time Phoenix data or anything from the last ~30 days? For **real-time + last ~30 days**, use the [**Trading cube**](/docs/trading/trading-data-overview) — [`Trading.Trades`](/docs/trading/crypto-trades-api/trades-api) gives you clean, MEV-filtered Phoenix swaps with **USD price, market cap, and supply on every row** across **9 chains in one API**. Use this page when you need **historical Phoenix data older than ~30 days**, raw per-swap detail, or call / event context. ::: diff --git a/docs/examples/futures-dexs/asterdex-api.md b/docs/examples/futures-dexs/asterdex-api.md index 5a108231..67c57db1 100644 --- a/docs/examples/futures-dexs/asterdex-api.md +++ b/docs/examples/futures-dexs/asterdex-api.md @@ -35,6 +35,12 @@ import FAQ from "@site/src/components/FAQ"; # AsterDEX API Documentation - Complete Guide to BNB Smart Chain Perpetual Futures Trading +:::info Perp DEX data on other chains +This page covers AsterDEX on BNB Smart Chain via decoded contract events. For dedicated +perpetual-futures cubes — orders, fills, positions, PnL, liquidations, funding and open +interest — see the [**Perp DEX API**](/docs/perpetuals/) section. +::: + ## Quick Start Guide ### Prerequisites for AsterDEX API Integration diff --git a/docs/perpetuals/index.md b/docs/perpetuals/index.md new file mode 100644 index 00000000..a261a28c --- /dev/null +++ b/docs/perpetuals/index.md @@ -0,0 +1,159 @@ +--- +title: "Perp DEX API — Onchain Perpetual Futures Data & Streams" +sidebar_label: "Overview" +sidebar_position: 1 +description: "Perp DEX API for onchain perpetual futures: orders, trades, positions, PnL, liquidations, funding, mark price and open interest on Solana, via GraphQL and WebSocket." +keywords: + - perp dex api + - perp dex data + - perpetual futures api + - onchain perpetuals data + - perpetual trading data + - solana perps api + - solana perpetual futures data + - perps order book api + - crypto liquidations api + - open interest api + - funding rate api + - mark price websocket + - crypto derivatives api + - perpetual positions api + - realized pnl api + - phoenix perpetuals api +--- + +import FAQ from "@site/src/components/FAQ"; + +# Perp DEX API — Onchain Perpetual Futures Data & Streams + +Bitquery indexes **onchain perpetual futures DEXs** at event level and exposes the data +as five GraphQL cubes. Every order placement and cancellation, every fill, every position +change, every liquidation, every order-book price tick and every open-interest update is +queryable over HTTP **and** streamable over WebSocket — the same query text works as a +`query` (history) and as a `subscription` (live stream). + +| | | +| ------------- | ------------------------------------------------------------------------------------- | +| **Cubes** | `PerpetualOrders`, `PerpetualFills`, `PerpetualPositions`, `PerpetualPrices`, `PerpetualMarketSummaries` | +| **Endpoints** | `https://streaming.bitquery.io/graphql` and `https://streaming.bitquery.io/eap` | +| **Streaming** | `wss://streaming.bitquery.io/graphql` — see [WebSocket docs](/docs/subscriptions/websockets) | +| **Auth** | [OAuth token](/docs/authorization/how-to-generate) as `Authorization: Bearer ` | + +## The five cubes + +| Cube | One row per… | What it answers | +| -------------------------- | ------------------------------------- | ------------------------------------------------------------------------------- | +| `PerpetualOrders` | order lifecycle event | Who placed, cancelled, or got rejected; limit/market/post-only/stop orders; cancel and reject reasons | +| `PerpetualFills` | trade execution | Executions with price, size, fee, taker side, maker counterparty, and the position that resulted | +| `PerpetualPositions` | position state change | Entry price, size before/after, realized PnL, funding settlements, liquidations | +| `PerpetualPrices` | order-book price tick | Best bid, best ask, mark price, last trade — tick by tick | +| `PerpetualMarketSummaries` | market state update | Open interest, spot index vs mark price, cumulative maker/taker fees | + +Together they cover the full trading loop: an order enters the book (`PerpetualOrders`), +matches (`PerpetualFills`), moves a position (`PerpetualPositions`), and the market's +price and open interest move with it (`PerpetualPrices`, `PerpetualMarketSummaries`). + +## Why this data is different + +- **Cross-asset markets.** Onchain perp DEXs now list far more than crypto pairs: the + currently indexed venue trades crypto majors and memecoins alongside **US equities, + commodities like gold, silver and oil, and pre-IPO names** — all as perpetual futures, + all settled onchain, all in one API. +- **Order-book depth of detail.** This is not OHLC candles. You see individual + post-only quotes, stop-loss placements, cancel reasons, and which fills were matched + by the AMM backstop versus another trader's resting order. +- **Liquidations as first-class events.** Liquidation fills and liquidated positions + carry the liquidator address, liquidated size and quote value — enough to build a + live liquidation feed or long-term liquidation analytics. +- **PnL without reconstruction.** Position rows carry `RealizedPnl`, entry price and + size transitions, so trader leaderboards don't require you to replay fills yourself. + +## Supported DEXs + +Coverage is organized by chain, then by protocol: + +| Chain | Protocol | Docs | +| ------ | -------------------------------------------------------------- | ----------------------------------------------------------- | +| Solana | **Phoenix Perpetuals** (`phoenix_eternal`, by Ellipsis Labs) | [Phoenix Perpetuals API](/docs/perpetuals/solana/phoenix-perpetuals-api) | + +More venues will appear here as they are enabled. To check what is indexed at any moment, +group any cube by `Exchange`: + +```graphql +query { + Solana { + PerpetualFills(limit: { count: 20 }, orderBy: { descendingByField: "count" }) { + count + Fill { + Exchange { + Family + Name + Program + Version + } + } + } + } +} +``` + +## Query or stream — your choice + +Every cube is available in both forms. A historical query: + +```graphql +query { + Solana { + PerpetualFills(limit: { count: 10 }, orderBy: { descending: Block_Time }) { + Block { Time } + Fill { + Asset { Symbol } + Side + ExecutionPrice + Amount { Filled Quote } + } + } + } +} +``` + +…becomes a live stream by changing one word and dropping the pagination arguments: + +```graphql +subscription { + Solana { + PerpetualFills { + Block { Time } + Fill { + Asset { Symbol } + Side + ExecutionPrice + Amount { Filled Quote } + } + } + } +} +``` + +## What people build with it + +- **Liquidation alert bots** — stream `PerpetualPositions` filtered to `Liquidation: true` +- **Trader analytics and leaderboards** — aggregate `RealizedPnl` per `Trader` +- **Open-interest and fee dashboards** — snapshot `PerpetualMarketSummaries` per market +- **Live tickers and charting** — stream `PerpetualPrices` best bid/ask and mark +- **Market-maker monitoring** — follow order lifecycle and AMM-vs-book fill share + +Start with the [Phoenix Perpetuals API](/docs/perpetuals/solana/phoenix-perpetuals-api) +page — it documents every cube with working queries and streams. + + diff --git a/docs/perpetuals/solana/phoenix-perpetuals-api.md b/docs/perpetuals/solana/phoenix-perpetuals-api.md new file mode 100644 index 00000000..6b7cda30 --- /dev/null +++ b/docs/perpetuals/solana/phoenix-perpetuals-api.md @@ -0,0 +1,532 @@ +--- +title: "Phoenix Perpetuals API — Solana Perp DEX Data & Streams" +sidebar_label: "Phoenix Perpetuals (Solana)" +sidebar_position: 2 +description: "Phoenix Perpetuals API on Solana: query and stream orders, fills, positions, realized PnL, liquidations, funding, best bid/ask, mark price and open interest." +keywords: + - phoenix perpetuals api + - phoenix perps solana + - phoenix perp dex + - ellipsis labs phoenix + - phoenix eternal program + - solana perp dex api + - solana perps api + - solana perpetual futures data + - solana liquidations api + - solana open interest api + - perps order lifecycle + - solana mark price stream + - stock perpetuals solana + - commodity perps api + - solana derivatives websocket + - realized pnl api +--- + +import FAQ from "@site/src/components/FAQ"; + +# Phoenix Perpetuals API — Solana Perp DEX Data & Streams + +[Phoenix Perpetuals](https://www.ellipsislabs.xyz/) is the fully onchain perpetual +futures exchange built by Ellipsis Labs, the team behind the Phoenix spot order book on +Solana. Bitquery indexes it at event level into five cubes, each available as a GraphQL +`query` and as a WebSocket `subscription`. + +| | | +| ------------------- | ------------------------------------------------------ | +| **Exchange family** | `Phoenix` | +| **Exchange name** | `phoenix_eternal` | +| **Program** | `EtrnLzgbS7nMMy5fbD42kXiUzGg8XQzJ972Xtk1cjWih` | +| **Quote currency** | `PhUsd` — mint `PhUsd11YkbjSaWjFncfAAmatntsjx3MgDR9B6g1ks3A`, 6 decimals | +| **Markets** | Crypto majors and memecoins, US equities, commodities, pre-IPO names | +| **Endpoints** | `https://streaming.bitquery.io/graphql` and `…/eap`; streams via `wss://streaming.bitquery.io/graphql` | + +## Reading the data model + +Every cube shares the same `Asset` block — the perpetual market being traded: + +- **`Asset.Id`** — the venue's numeric market id (e.g. BTC is `"1"`). Stable key; filter on it or on `Symbol`. +- **`Asset.Symbol`** — the underlying: `BTC`, `SOL`, `AAPL`, `TSLA`, `GOLD`, `WTIOIL`, … +- **`Asset.LotSize` / `Asset.TickSize`** — minimum size and price increments. +- **`Asset.QuoteCurrency`** — always `PhUsd` on Phoenix; all prices and quote amounts are in it. +- **Sizes are signed** where direction matters: negative size = short / sell side. +- **`Trader` vs `Signer`** — `Trader` is the account whose position or order it is; + `Signer` signed the transaction (they differ for liquidations, AMM flow, and delegated flows). +- **`TraderIsAmm` / `CounterpartyIsAmm`** — Phoenix runs an AMM backstop alongside the + order book. Rows flag whether each side is the AMM. + +To see which markets exist right now, group fills (or any cube) by asset: + +```graphql +query { + Solana { + PerpetualFills(limit: { count: 100 }, orderBy: { descendingByField: "count" }) { + count + Fill { + Asset { + Id + Symbol + LotSize + TickSize + QuoteCurrency { Symbol } + } + } + } + } +} +``` + +## Live prices — `PerpetualPrices` + +One row per order-book price tick: best bid, best ask, mark price, and the last trade +price when the tick was caused by a trade. + +```graphql +query { + Solana { + PerpetualPrices(limit: { count: 10 }, orderBy: { descending: Block_Time }) { + Block { Time } + Transaction { Signature Signer } + Price { + Asset { + Symbol + QuoteCurrency { Symbol } + } + BestAsk + BestBid + LastTrade + Mark + SequenceNumber + } + } + } +} +``` + +Notes: + +- `LastTrade` is `0` on ticks not caused by a trade (quote updates, cancels). Filter + `Price: { LastTrade: { gt: 0 } }` for trade prints only. +- `SequenceNumber` is the venue's monotonic sequence — use it to order ticks within a slot. + +Stream the BBO for one market live: + +```graphql +subscription { + Solana { + PerpetualPrices(where: { Price: { Asset: { Symbol: { is: "BTC" } } } }) { + Block { Time } + Price { + Asset { Symbol } + BestBid + BestAsk + Mark + } + } + } +} +``` + +## Open interest & fees — `PerpetualMarketSummaries` + +Market-level state updates: mark price, the spot index it tracks, open interest, and +fee counters. + +```graphql +query { + Solana { + PerpetualMarketSummaries( + where: { MarketSummary: { Asset: { Symbol: { is: "JTO" } } } } + limit: { count: 10 } + orderBy: { descending: Block_Time } + ) { + Block { Time } + Transaction { Signature } + MarketSummary { + Asset { + Id + Symbol + QuoteCurrency { Symbol } + } + Mark + SpotIndex + OpenInterest + MakerFees + TakerFees + } + } + } +} +``` + +You can filter by the numeric market id instead — `where: { MarketSummary: { Asset: { Id: { eq: "22" } } } }` +selects the same JTO market. + +Notes: + +- `OpenInterest` is in base units of the asset (e.g. BTC for the BTC market). +- **`MakerFees` and `TakerFees` are cumulative counters** since market inception, in + `PhUsd`. To get fees generated over an interval, take the difference between the + latest value and the value at the start of the interval — don't read a single row + as a per-block fee. +- `Mark` vs `SpotIndex` gives you the perp premium/discount at any moment. + +For a dashboard, grab the **latest snapshot of every market in one query** with `limitBy` +on the asset id: + +```graphql +query { + Solana { + PerpetualMarketSummaries( + limitBy: { by: MarketSummary_Asset_Id, count: 1 } + limit: { count: 100 } + orderBy: { descending: Block_Time } + ) { + Block { Time } + MarketSummary { + Asset { Id Symbol } + Mark + SpotIndex + OpenInterest + } + } + } +} +``` + +Stream open-interest changes across all markets: + +```graphql +subscription { + Solana { + PerpetualMarketSummaries { + Block { Time } + MarketSummary { + Asset { Symbol } + Mark + SpotIndex + OpenInterest + } + } + } +} +``` + +## Order lifecycle — `PerpetualOrders` + +One row per order event. `Order.Type` is the **event**, and the nested +`Order.Order.Type` is the **order kind**: + +| Field | Values seen | +| -------------------- | ------------------------------------------------------------------------------------------------------------ | +| `Order.Type` (event) | `OrderRequested`, `OrderPlaced`, `OrderCancelled`, `OrderRejected`, `StopLossPlaced`, `TakeProfitPlaced`, `TriggerPlaced`, `TriggerExecuted`, `TriggerCancelled`, `ConditionalExecuted`, `ConditionalCancelled` | +| `Order.Order.Type` (kind) | `limit`, `market`, `post-only`, `stop-loss`, `take-profit` (empty on events where kind isn't re-stated, e.g. cancels) | +| `Order.Order.CancelReason` | `UserRequested`, `Expired`, `ReduceOnlyInvalidated`, `SelfTradeCancelProvide` | +| `Order.Order.RejectReason` | `TiFInvalid`, `PostOnlyCross` | + +A typical placement produces `OrderRequested` followed by `OrderPlaced` (which carries +the assigned `Order.Order.Id`) in the same transaction. + +More lifecycle details, all observable in the data: + +- **Conditional & trigger events** (`Conditional*`, `Trigger*`) describe the stop/take-profit + machinery: a `StopLossPlaced` row carries the `Price.Trigger` level and a + `Order.ConditionalId` that later `TriggerExecuted` / `ConditionalCancelled` rows + reference. These bookkeeping rows have an empty `Side`. +- **Time-in-force**: `Order.Order.ValidUntilSlot` is a slot-based expiry for resting + quotes (`0` = no expiry). Orders that hit it are cancelled with + `CancelReason: "Expired"`. +- **`Order.Order.ClientId`** is the trader's own hex order identifier, when supplied — + useful for reconciling your execution system against the chain. + +```graphql +query { + Solana { + PerpetualOrders(limit: { count: 10 }, orderBy: { descending: Block_Time }) { + Block { Time } + Transaction { Signature } + Order { + Asset { Symbol } + Type + Side + Trader + Signer + Price { + Limit + Trigger + Mark + } + Amount { + Size + Remaining + Quote + } + Order { + Id + Type + ReduceOnly + CancelReason + RejectReason + } + } + } + } +} +``` + +Stream every stop-loss and take-profit placement as it happens: + +```graphql +subscription { + Solana { + PerpetualOrders( + where: { Order: { Type: { in: ["StopLossPlaced", "TakeProfitPlaced"] } } } + ) { + Block { Time } + Order { + Asset { Symbol } + Type + Side + Trader + Price { Trigger Mark } + Amount { Size } + } + } + } +} +``` + +## Trades — `PerpetualFills` + +One row per execution. `Side` is the taker's side (`bid` = taker bought, +`ask` = taker sold); `Amount.Size` is signed by direction while `Amount.Filled` is the +unsigned fill quantity and `Amount.Quote` the quote value. + +```graphql +query { + Solana { + PerpetualFills(limit: { count: 10 }, orderBy: { descending: Block_Time }) { + Block { Time } + Transaction { Signature } + Fill { + Asset { + Symbol + QuoteCurrency { Symbol Decimals } + } + Side + ExecutionPrice + MarkPrice + Amount { + Filled + Size + Quote + Fee + Remaining + } + Trader + TraderIsAmm + Counterparty + CounterpartyIsAmm + MakerOrderId + SplineId + Collateral + Position { + EntryPrice + Size + } + Liquidation + Liquidator + } + } + } +} +``` + +Notes: + +- **AMM fills**: when `CounterpartyIsAmm` is `true`, the fill matched the AMM backstop — + `MakerOrderId` is empty and `SplineId` identifies the AMM curve segment. Book fills + carry the maker's `MakerOrderId` instead. The AMM currently absorbs the large majority + of taker flow, so segment by this flag before drawing conclusions about book liquidity. +- `Amount.Fee` is the fee charged on the fill in `PhUsd`; it is `0` on most fills and + never negative in observed data. +- `Position { EntryPrice, Size }` is the trader's position **after** this fill — you + can follow a position's evolution from fills alone. +- `Collateral` is the trader's collateral balance snapshot in `PhUsd`. +- `Liquidation: true` marks forced fills, with the `Liquidator` address populated. + +All fills of one trader (excluding liquidations): + +```graphql +query { + Solana { + PerpetualFills( + limit: { count: 10 } + orderBy: { descending: Block_Time } + where: { + Fill: { + Liquidation: false + Signer: { is: "7Kjwrohbf49adi5Gg4WM1M9h68UZSBFvLVRdw7PoeX5E" } + } + } + ) { + Block { Time } + Transaction { Signature } + Fill { + Asset { Symbol } + Side + ExecutionPrice + Amount { Filled Quote Fee } + Position { EntryPrice Size } + } + } + } +} +``` + +## Positions, PnL & liquidations — `PerpetualPositions` + +One row per position state change: size transitions, realized PnL, funding settlements, +and liquidations. + +```graphql +query { + Solana { + PerpetualPositions(limit: { count: 10 }, orderBy: { descending: Block_Time }) { + Block { Time } + Transaction { Signature Signer } + Position { + Asset { + Symbol + QuoteCurrency { Symbol } + } + Type + Trader + TraderIsAmm + Position { + EntryPrice + Size + SizeBefore + } + MarkPrice + RealizedPnl + Funding + Closed + Liquidation + Liquidator + LiquidatedQuote + LiquidatedSize + } + } + } +} +``` + +Notes: + +- `Position.Type` is `PnL` for normal position accounting rows and `Liquidation` for + the dedicated liquidation rows. +- `SizeBefore → Size` is the transition; `Closed: true` marks a full close. +- `RealizedPnl` (in `PhUsd`) is booked on closes and reductions; `Funding` is non-zero + on funding settlement rows. +- **Funding settlements are their own rows**: `Funding ≠ 0`, position size unchanged + (`SizeBefore` = `Size`), `RealizedPnl: 0` and `MarkPrice: 0`. The sign is from the + trader's perspective — positive means the position received funding, negative means + it paid. Filter `Position: { Funding: { ne: 0 } }` for a funding history. +- **A liquidation emits multiple rows in one transaction**: the trader's forced close + (`Type: "PnL"`, `Closed: true`, negative `RealizedPnl`) plus a `Type: "Liquidation"` + row carrying `LiquidatedSize` and `LiquidatedQuote`, with `Liquidator` set on each — + and the liquidator's own position rows alongside. Count *events*, not rows, when + measuring liquidation activity. + +Profitable closed trades — every close that realized more than 100 `PhUsd`: + +```graphql +query { + Solana { + PerpetualPositions( + limit: { count: 10 } + orderBy: { descending: Block_Time } + where: { Position: { RealizedPnl: { gt: 100 }, Closed: true } } + ) { + Block { Time } + Transaction { Signature Signer } + Position { + Asset { Symbol } + Trader + Position { EntryPrice Size SizeBefore } + MarkPrice + RealizedPnl + Closed + } + } + } +} +``` + +A realized-PnL leaderboard falls out of one aggregation — total booked PnL per trader +across closed positions, AMM excluded: + +```graphql +query { + Solana { + PerpetualPositions( + limit: { count: 10 } + orderBy: { descendingByField: "pnl" } + where: { Position: { TraderIsAmm: false, Closed: true } } + ) { + Position { Trader } + pnl: sum(of: Position_RealizedPnl) + closes: count + } + } +} +``` + +Live liquidation feed: + +```graphql +subscription { + Solana { + PerpetualPositions(where: { Position: { Liquidation: true } }) { + Block { Time } + Transaction { Signature } + Position { + Asset { Symbol } + Type + Trader + Liquidator + LiquidatedSize + LiquidatedQuote + RealizedPnl + MarkPrice + } + } + } +} +``` + +## Ideas to build + +- **Liquidation alerts** — the subscription above, pushed to Telegram/Discord. +- **PnL leaderboard** — aggregate `RealizedPnl` by `Trader` over `PerpetualPositions`, + excluding `TraderIsAmm: true`. +- **OI & premium dashboard** — periodic snapshots of `PerpetualMarketSummaries` + (`OpenInterest`, `Mark` vs `SpotIndex`, fee-counter diffs). +- **Equity & commodity perps tracker** — filter any cube to `AAPL`, `TSLA`, `GOLD`, + `WTIOIL` markets: stock and commodity price action, settled onchain, streaming in + real time. +- **Execution analytics** — compare `ExecutionPrice` to `MarkPrice` on fills; split + volume by AMM vs order-book counterparty. + + diff --git a/docs/subscriptions/which-cubes-stream.md b/docs/subscriptions/which-cubes-stream.md index 09c494fe..3311db8a 100644 --- a/docs/subscriptions/which-cubes-stream.md +++ b/docs/subscriptions/which-cubes-stream.md @@ -55,6 +55,11 @@ guarantee. | `DEXTradeByTokens` | Streams | Moderate | | `Rewards` | Streams | Moderate | | `DEXOrders` | Streams | Low | +| `PerpetualOrders` | Streams | Moderate | +| `PerpetualFills` | Streams | Low | +| `PerpetualPositions` | Streams | Low | +| `PerpetualPrices` | Streams | Low | +| `PerpetualMarketSummaries` | Streams | Low | | `Instructions` | **Filter required** | Very high | | `BalanceUpdates` | **Filter required** | Very high | | `InstructionBalanceUpdates` | **Filter required** | Very high | diff --git a/sidebars.js b/sidebars.js index 259d145a..e67bebe0 100644 --- a/sidebars.js +++ b/sidebars.js @@ -1082,6 +1082,15 @@ const sidebars = { }, items: ["labels/address-labels-api"], }, + { + type: "category", + label: "Perp DEX Data", + link: { + type: "doc", + id: "perpetuals/index", + }, + items: ["perpetuals/solana/phoenix-perpetuals-api"], + }, { type: "category", label: "MCP",