diff --git a/docs/perpetuals/index.md b/docs/perpetuals/index.md index 66fdd338..248d8197 100644 --- a/docs/perpetuals/index.md +++ b/docs/perpetuals/index.md @@ -145,7 +145,10 @@ subscription { - **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. +page — it documents every cube with working queries and streams. Then jump to the +[Perps Trader Cookbook](/docs/perpetuals/solana/perps-trader-cookbook) for +workflow-shaped recipes: copy-trading a wallet, trader win-rate report cards, top +unrealized positions, whale fills, OHLC candles, open-interest and order-flow series. 0` tells you how +they treat risk. Add a `Block: { Time: { since: … } }` filter to score a recent window +instead of all time. + +## Positions & PnL + +### Top unrealized positions and traders + +Unrealized PnL is `(mark − entry) × size` over each trader's latest open position. +One request returns both the position snapshot and fresh marks: + +```graphql +query { + Solana { + openPositions: PerpetualPositions( + limitBy: { by: [Position_Trader, Position_Asset_Id], count: 1 } + orderBy: { descending: Block_Time } + limit: { count: 3000 } + where: { Position: { TraderIsAmm: false } } + ) { + Block { Time } + Position { + Trader + Asset { Id Symbol } + Position { EntryPrice Size } + MarkPrice + } + } + marks: PerpetualPrices( + limitBy: { by: Price_Asset_Id, count: 1 } + orderBy: { descending: Block_Time } + limit: { count: 200 } + ) { + Price { Asset { Id Symbol } Mark } + } + } +} +``` + +Then a few lines client-side: + +```python +marks = {m["Price"]["Asset"]["Id"]: m["Price"]["Mark"] for m in d["marks"]} +open_pos = [] +for r in d["openPositions"]: + p = r["Position"]; size = p["Position"]["Size"] + if size == 0: + continue # flat = closed + mark = marks.get(p["Asset"]["Id"]) or p["MarkPrice"] + upnl = (mark - p["Position"]["EntryPrice"]) * size # signed Size handles shorts + open_pos.append((p["Trader"], p["Asset"]["Symbol"], size, upnl)) + +top_positions = sorted(open_pos, key=lambda x: x[3], reverse=True) +``` + +Sum per `Trader` for a whale-exposure leaderboard. Prefer the `marks` alias over the +position row's own `MarkPrice` — the latter is denormalized and can be `0`. + +### Funding a trader has paid or received + +Funding settlements are their own rows — `Funding` non-zero, size unchanged: + +```graphql +query { + Solana { + PerpetualPositions( + limit: { count: 100 } + orderBy: { descending: Block_Time } + where: { + Position: { + Trader: { is: "DUGirckBgoaW3zoEPhTVVo68pZpXrTKuJrsLBLWcZQo2" } + Funding: { ne: 0 } + } + } + ) { + Block { Time } + Position { Asset { Symbol } Funding } + } + } +} +``` + +Positive = received, negative = paid. Replace the field list with +`total: sum(of: Position_Funding)` for the net carry cost of holding their positions. + +## Market signals + +### Whale fills + +Every fill above a notional threshold — as history or a live tape: + +```graphql +subscription { + Solana { + PerpetualFills(where: { Fill: { Amount: { Quote: { gt: 5000 } } } }) { + Block { Time } + Fill { + Asset { Symbol } + Side + ExecutionPrice + Amount { Filled Quote } + Trader + Liquidation + } + } + } +} +``` + +As a `query`, add `orderBy: { descending: Block_Time }` and a `limit` for the recent +whale prints. + +### OHLC candles from the mark price + +Strategy builders and backtesters: bucket `PerpetualPrices` into intervals and take +argMin/argMax aggregates — + +```graphql +query { + Solana { + PerpetualPrices( + where: { Price: { Asset: { Symbol: { is: "BTC" } } } } + orderBy: { ascendingByField: "Block_Time" } + limit: { count: 96 } + ) { + Block { Time(interval: { in: minutes, count: 15 }) } + Price { + open: Mark(minimum: Block_Time) + high: Mark(maximum: Price_Mark) + low: Mark(minimum: Price_Mark) + close: Mark(maximum: Block_Time) + } + } + } +} +``` + +`Mark(minimum: Block_Time)` reads "the Mark at the earliest time in the bucket" — +open; `Mark(maximum: Price_Mark)` is the bucket's high. Price rows are emitted on +trading activity, so an interval with no trades produces no candle (no +zero-filled bars). + +### Open interest, basis and fee revenue over time + +One query per market gives an OI series, the perp-vs-spot basis, and — because +`TakerFees`/`MakerFees` are cumulative counters — per-bucket fee revenue as +end-minus-start: + +```graphql +query { + Solana { + PerpetualMarketSummaries( + where: { MarketSummary: { Asset: { Symbol: { is: "SOL" } } } } + orderBy: { ascendingByField: "Block_Time" } + limit: { count: 168 } + ) { + Block { Time(interval: { in: hours, count: 1 }) } + MarketSummary { + oi: OpenInterest(maximum: Block_Time) + mark: Mark(maximum: Block_Time) + spot: SpotIndex(maximum: Block_Time) + takerFeesEnd: TakerFees(maximum: Block_Time) + takerFeesStart: TakerFees(minimum: Block_Time) + } + } + } +} +``` + +Basis = `mark − spot`; hourly taker fees = `takerFeesEnd − takerFeesStart`. Rising OI +with a widening basis is the classic crowded-longs signal. + +### Order-flow pressure — taker buys vs sells + +Conditional sums split taker volume by side per bucket: + +```graphql +query { + Solana { + PerpetualFills( + where: { Fill: { Asset: { Symbol: { is: "SOL" } } } } + orderBy: { ascendingByField: "Block_Time" } + limit: { count: 168 } + ) { + Block { Time(interval: { in: hours, count: 1 }) } + buyVol: sum(of: Fill_Amount_Quote, if: { Fill: { Side: { is: "bid" } } }) + sellVol: sum(of: Fill_Amount_Quote, if: { Fill: { Side: { is: "ask" } } }) + trades: count + } + } +} +``` + +`(buyVol − sellVol) / (buyVol + sellVol)` is a ready order-flow-imbalance series. + +## Risk + +### Biggest liquidations + +Rank forced closes by what they took: + +```graphql +query { + Solana { + PerpetualPositions( + limit: { count: 20 } + orderBy: { descendingByField: "lost" } + where: { Position: { Type: { is: "Liquidation" } } } + ) { + Position { Trader Asset { Symbol } } + lost: sum(of: Position_LiquidatedQuote) + events: count + } + } +} +``` + +For the live feed version and the multi-row anatomy of a liquidation, see the +[liquidation section](/docs/perpetuals/solana/phoenix-perpetuals-api#positions-pnl--liquidations--perpetualpositions) +of the Phoenix page. + +--- + +Every `query` above becomes a live stream by switching to `subscription` and removing +`limit`/`orderBy`/`limitBy` — except the snapshot and interval recipes, which are +inherently query-shaped. Run them over Kafka instead with the +[`solana.perpetual.proto` topic](/docs/streams/protobuf/chains/Solana-perpetual-protobuf) +when you need the full firehose. + + diff --git a/docs/perpetuals/solana/phoenix-perpetuals-api.md b/docs/perpetuals/solana/phoenix-perpetuals-api.md index 3d6991ea..53fb2f8f 100644 --- a/docs/perpetuals/solana/phoenix-perpetuals-api.md +++ b/docs/perpetuals/solana/phoenix-perpetuals-api.md @@ -508,6 +508,10 @@ subscription { ## Ideas to build +Worked, runnable versions of the recipes below — copy-trade feeds, trader report +cards, unrealized-PnL rankings, OHLC candles, OI/basis series, order-flow pressure — +live in the [Perps Trader Cookbook](/docs/perpetuals/solana/perps-trader-cookbook). + - **Liquidation alerts** — the subscription above, pushed to Telegram/Discord. - **PnL leaderboard** — aggregate `RealizedPnl` by `Trader` over `PerpetualPositions`, excluding `TraderIsAmm: true`. diff --git a/docs/streams/protobuf/chains/Solana-perpetual-protobuf.md b/docs/streams/protobuf/chains/Solana-perpetual-protobuf.md index 5ef0a9fb..1eba310d 100644 --- a/docs/streams/protobuf/chains/Solana-perpetual-protobuf.md +++ b/docs/streams/protobuf/chains/Solana-perpetual-protobuf.md @@ -1,7 +1,7 @@ --- title: "Solana Perpetuals Kafka Stream — solana.perpetual.proto" sidebar_label: "Solana Perpetuals Stream" -description: "Consume Solana perpetual futures data over Kafka: orders, fills, positions, PnL, liquidations, prices and open interest as protobuf messages on solana.perpetual.proto." +description: "Solana perpetual futures over Kafka: orders, fills, positions, PnL, liquidations, prices and open interest as protobuf on the solana.perpetual.proto topic." keywords: - solana perpetuals kafka - solana.perpetual.proto diff --git a/sidebars.js b/sidebars.js index 4ab63159..709b8a73 100644 --- a/sidebars.js +++ b/sidebars.js @@ -1090,7 +1090,10 @@ const sidebars = { type: "doc", id: "perpetuals/index", }, - items: ["perpetuals/solana/phoenix-perpetuals-api"], + items: [ + "perpetuals/solana/phoenix-perpetuals-api", + "perpetuals/solana/perps-trader-cookbook", + ], }, { type: "category",