openapi: 3.1.0
info:
  title: Hyperlight API
  description: |
    Enhanced APIs for Lighter. Batch queries, enriched WebSocket streams,
    historical data lake, and production API infrastructure.
  version: 1.0.0
  contact:
    email: hello@hyperlightapi.xyz

servers:
  - url: https://api.hyperlightapi.xyz
    description: Production
  - url: http://localhost:8080
    description: Local development

security:
  - apiKey: []
  - bearerAuth: []

tags:
  - name: Auth
    description: User registration and login
  - name: API Keys
    description: API key management
  - name: Accounts
    description: Batch account queries
  - name: Trades
    description: Trade and fill queries
  - name: Liquidations
    description: Liquidation queries
  - name: Candles
    description: OHLCV candlestick data
  - name: Funding Rates
    description: Funding rate data
  - name: Orderbook
    description: Orderbook data and snapshots
  - name: Data Lake
    description: Parquet data exports
  - name: Billing
    description: Stripe billing integration
  - name: Leaderboard
    description: Top accounts by position size
  - name: Liquidation Heatmap
    description: Liquidation level heatmaps and cascade analysis
  - name: Signals
    description: Custom alert rules with webhook delivery
  - name: Health
    description: Service health checks

paths:
  /health:
    get:
      tags: [Health]
      summary: Health check
      security: []
      responses:
        "200":
          description: Service healthy
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/HealthResponse"

  /status:
    get:
      tags: [Health]
      summary: Service status
      security: []
      responses:
        "200":
          description: Service running
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/StatusResponse"

  /api/v1/auth/register:
    post:
      tags: [Auth]
      summary: Register a new account
      description: Creates a user account, issues a JWT, and generates a free-tier API key.
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/RegisterRequest"
      responses:
        "200":
          description: Account created
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiEnvelope"
                properties:
                  data:
                    $ref: "#/components/schemas/RegisterPayload"

  /api/v1/auth/login:
    post:
      tags: [Auth]
      summary: Log in
      description: Validates credentials and returns a JWT (24h expiry).
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/LoginRequest"
      responses:
        "200":
          description: Login successful
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiEnvelope"
                properties:
                  data:
                    $ref: "#/components/schemas/LoginPayload"
        "401":
          description: Invalid credentials

  /api/v1/keys:
    post:
      tags: [API Keys]
      summary: Create an API key
      description: Creates a new API key. The full key is returned only once.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CreateApiKeyRequest"
      responses:
        "200":
          description: Key created
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiEnvelope"
                properties:
                  data:
                    $ref: "#/components/schemas/ApiKeyCreatedPayload"
    get:
      tags: [API Keys]
      summary: List API keys
      parameters:
        - name: limit
          in: query
          schema:
            type: integer
            default: 50
        - name: offset
          in: query
          schema:
            type: integer
            default: 0
      responses:
        "200":
          description: Key list
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiEnvelope"
                properties:
                  data:
                    $ref: "#/components/schemas/ApiKeysPayload"

  /api/v1/keys/{key_id}:
    patch:
      tags: [API Keys]
      summary: Update an API key
      parameters:
        - name: key_id
          in: path
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/UpdateApiKeyRequest"
      responses:
        "200":
          description: Key updated
    delete:
      tags: [API Keys]
      summary: Delete (deactivate) an API key
      parameters:
        - name: key_id
          in: path
          required: true
          schema:
            type: string
      responses:
        "200":
          description: Key deactivated

  /api/v1/keys/{key_id}/usage:
    get:
      tags: [API Keys]
      summary: Get API key usage
      description: Returns hourly usage breakdown for an API key.
      parameters:
        - name: key_id
          in: path
          required: true
          schema:
            type: string
        - name: start
          in: query
          required: true
          schema:
            type: integer
            description: Start timestamp (Unix seconds)
        - name: end
          in: query
          required: true
          schema:
            type: integer
            description: End timestamp (Unix seconds)
      responses:
        "200":
          description: Usage data
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiEnvelope"
                properties:
                  data:
                    $ref: "#/components/schemas/ApiKeyUsagePayload"

  /api/v1/accounts/batch:
    post:
      tags: [Accounts]
      summary: Batch fetch accounts
      description: Query up to 1,000 accounts in a single request (tier-dependent limit).
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/AccountBatchRequest"
      responses:
        "200":
          description: Account data
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiEnvelope"
                properties:
                  data:
                    $ref: "#/components/schemas/BatchAccountsPayload"

  /api/v1/accounts/batch/positions:
    post:
      tags: [Accounts]
      summary: Batch fetch positions
      description: Extract positions for multiple accounts.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/BatchPositionsRequest"
      responses:
        "200":
          description: Position data

  /api/v1/accounts/batch/fills:
    post:
      tags: [Trades]
      summary: Batch fetch fills
      description: Query fills across multiple accounts with time range filtering.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/BatchFillsRequest"
      responses:
        "200":
          description: Fill data
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiEnvelope"
                properties:
                  data:
                    $ref: "#/components/schemas/BatchFillsPayload"

  /api/v1/accounts/batch/liquidations:
    post:
      tags: [Liquidations]
      summary: Batch fetch liquidations
      description: Query liquidations for multiple accounts.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/BatchLiquidationsRequest"
      responses:
        "200":
          description: Liquidation data

  /api/v1/trades/by-time:
    get:
      tags: [Trades]
      summary: Get trades by time range
      security: []
      parameters:
        - name: market_id
          in: query
          required: true
          schema:
            type: integer
        - name: start
          in: query
          required: true
          schema:
            type: integer
            description: Start timestamp (milliseconds)
        - name: end
          in: query
          required: true
          schema:
            type: integer
            description: End timestamp (milliseconds)
        - name: limit
          in: query
          schema:
            type: integer
            default: 1000
            maximum: 10000
        - name: offset
          in: query
          schema:
            type: integer
            default: 0
      responses:
        "200":
          description: Trade data
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiEnvelope"
                properties:
                  data:
                    $ref: "#/components/schemas/TradesByTimePayload"

  /api/v1/liquidations/by-time:
    get:
      tags: [Liquidations]
      summary: Get liquidations by time range
      security: []
      parameters:
        - name: market_id
          in: query
          required: true
          schema:
            type: integer
        - name: start
          in: query
          required: true
          schema:
            type: integer
            description: Start timestamp (milliseconds)
        - name: end
          in: query
          required: true
          schema:
            type: integer
            description: End timestamp (milliseconds)
        - name: limit
          in: query
          schema:
            type: integer
            default: 100
            maximum: 1000
        - name: offset
          in: query
          schema:
            type: integer
            default: 0
      responses:
        "200":
          description: Liquidation data

  /api/v1/candles:
    get:
      tags: [Candles]
      summary: Get OHLCV candles
      security: []
      parameters:
        - name: market_id
          in: query
          required: true
          schema:
            type: integer
        - name: resolution
          in: query
          schema:
            type: string
            enum: ["1m", "5m", "15m", "30m", "1h", "4h", "12h", "1d", "1w"]
            default: "1h"
        - name: start
          in: query
          schema:
            type: integer
            description: Start timestamp (milliseconds)
        - name: end
          in: query
          schema:
            type: integer
            description: End timestamp (milliseconds)
        - name: limit
          in: query
          schema:
            type: integer
            default: 500
            maximum: 500
      responses:
        "200":
          description: Candle data
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiEnvelope"
                properties:
                  data:
                    $ref: "#/components/schemas/CandlesPayload"

  /api/v1/funding-rates:
    get:
      tags: [Funding Rates]
      summary: Get latest funding rates
      description: Returns the most recent funding rate for each market.
      security: []
      responses:
        "200":
          description: Funding rate data
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiEnvelope"
                properties:
                  data:
                    $ref: "#/components/schemas/FundingRatesPayload"

  /api/v1/funding-rates/history:
    get:
      tags: [Funding Rates]
      summary: Get funding rate history
      security: []
      parameters:
        - name: market_id
          in: query
          required: true
          schema:
            type: integer
        - name: start
          in: query
          required: true
          schema:
            type: integer
            description: Start timestamp (milliseconds)
        - name: end
          in: query
          required: true
          schema:
            type: integer
            description: End timestamp (milliseconds)
        - name: limit
          in: query
          schema:
            type: integer
            default: 500
            maximum: 5000
        - name: offset
          in: query
          schema:
            type: integer
            default: 0
      responses:
        "200":
          description: Historical funding rates

  /api/v1/orderbook/{market_id}:
    get:
      tags: [Orderbook]
      summary: Get current orderbook
      description: Returns the current orderbook from Redis cache.
      security: []
      parameters:
        - name: market_id
          in: path
          required: true
          schema:
            type: integer
      responses:
        "200":
          description: Raw orderbook data

  /api/v1/orderbook/{market_id}/depth:
    get:
      tags: [Orderbook]
      summary: Get aggregated orderbook depth
      security: []
      parameters:
        - name: market_id
          in: path
          required: true
          schema:
            type: integer
        - name: levels
          in: query
          schema:
            type: integer
            default: 20
            maximum: 200
        - name: group
          in: query
          schema:
            type: string
            default: "1"
            description: Tick size for price grouping
      responses:
        "200":
          description: Aggregated depth
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiEnvelope"
                properties:
                  data:
                    $ref: "#/components/schemas/OrderbookDepthPayload"

  /api/v1/orderbook/{market_id}/snapshots:
    get:
      tags: [Orderbook]
      summary: Get historical orderbook snapshots
      security: []
      parameters:
        - name: market_id
          in: path
          required: true
          schema:
            type: integer
        - name: start
          in: query
          required: true
          schema:
            type: integer
            description: Start timestamp (milliseconds)
        - name: end
          in: query
          required: true
          schema:
            type: integer
            description: End timestamp (milliseconds)
        - name: limit
          in: query
          schema:
            type: integer
            default: 100
            maximum: 1000
        - name: offset
          in: query
          schema:
            type: integer
            default: 0
      responses:
        "200":
          description: Orderbook snapshots

  /api/v1/data-lake/exports:
    get:
      tags: [Data Lake]
      summary: List data lake exports
      parameters:
        - name: export_type
          in: query
          schema:
            type: string
            enum: [trades, orderbook, candles, funding, liquidations]
        - name: market_id
          in: query
          schema:
            type: integer
        - name: limit
          in: query
          schema:
            type: integer
            default: 50
            maximum: 200
        - name: offset
          in: query
          schema:
            type: integer
            default: 0
      responses:
        "200":
          description: Export list

  /api/v1/data-lake/download/{export_id}:
    get:
      tags: [Data Lake]
      summary: Download a Parquet export
      description: Returns a pre-signed S3 URL (1h expiry). Requires Starter tier or higher.
      parameters:
        - name: export_id
          in: path
          required: true
          schema:
            type: string
      responses:
        "200":
          description: Download URL
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiEnvelope"
                properties:
                  data:
                    $ref: "#/components/schemas/DataLakeDownloadPayload"
        "403":
          description: Free tier cannot download exports

  /api/v1/billing/checkout:
    post:
      tags: [Billing]
      summary: Create Stripe checkout session
      description: Creates a Stripe Checkout session for upgrading to a paid tier.
      security:
        - bearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CheckoutRequest"
      responses:
        "200":
          description: Checkout URL
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiEnvelope"
                properties:
                  data:
                    $ref: "#/components/schemas/CheckoutPayload"

  /api/v1/billing/portal:
    post:
      tags: [Billing]
      summary: Create Stripe billing portal session
      security:
        - bearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/PortalRequest"
      responses:
        "200":
          description: Portal URL
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiEnvelope"
                properties:
                  data:
                    $ref: "#/components/schemas/PortalPayload"

  /api/v1/markets/{market_id}/top-accounts:
    get:
      tags: [Leaderboard]
      summary: Top accounts in a market by position size
      parameters:
        - name: market_id
          in: path
          required: true
          schema:
            type: integer
        - name: limit
          in: query
          schema:
            type: integer
            default: 20
            maximum: 100
        - name: side
          in: query
          schema:
            type: string
            enum: [long, short, both]
            default: both
      responses:
        "200":
          description: Market leaderboard
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiEnvelope"
        "503":
          description: Leaderboard not yet computed

  /api/v1/exchange/top-accounts:
    get:
      tags: [Leaderboard]
      summary: Top accounts across all markets by total position notional
      parameters:
        - name: limit
          in: query
          schema:
            type: integer
            default: 20
            maximum: 100
      responses:
        "200":
          description: Exchange-wide leaderboard
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiEnvelope"
        "503":
          description: Leaderboard not yet computed

  /api/v1/liquidation-heatmap/{market_id}:
    get:
      tags: [Liquidation Heatmap]
      summary: Liquidation risk heatmap for a market
      description: Per-market liquidation risk distribution showing notional at risk and account counts per price bucket.
      parameters:
        - name: market_id
          in: path
          required: true
          schema:
            type: integer
      responses:
        "200":
          description: Liquidation heatmap
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiEnvelope"
        "503":
          description: Heatmap not yet computed for this market

  /api/v1/liquidation-heatmap/{market_id}/cascade:
    get:
      tags: [Liquidation Heatmap]
      summary: Simulate cascading liquidations from a price shock
      description: Models how initial liquidations trigger market orders that push price further, causing additional liquidations. Paid tiers only.
      parameters:
        - name: market_id
          in: path
          required: true
          schema:
            type: integer
        - name: price_shock
          in: query
          required: true
          description: "Price shock percentage (-50.0 to +50.0). Negative = downward (liquidates longs)."
          schema:
            type: number
            minimum: -50
            maximum: 50
      responses:
        "200":
          description: Cascade simulation result
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiEnvelope"
        "403":
          description: Free tier not allowed

  /api/v1/accounts/{account_id}/liquidation-risk:
    get:
      tags: [Accounts]
      summary: Account liquidation risk assessment
      description: Per-account risk summary across all open positions with margin ratios and distance to liquidation.
      parameters:
        - name: account_id
          in: path
          required: true
          schema:
            type: integer
      responses:
        "200":
          description: Account liquidation risk
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiEnvelope"

  /api/v1/signals:
    post:
      tags: [Signals]
      summary: Create a signal rule
      description: Define an alert rule using a JSON DSL. Requires JWT authentication (API keys cannot create signals).
      security:
        - bearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CreateSignalRequest"
      responses:
        "200":
          description: Signal created
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiEnvelope"
    get:
      tags: [Signals]
      summary: List user's signals
      security:
        - bearerAuth: []
      parameters:
        - name: limit
          in: query
          schema:
            type: integer
            default: 100
            maximum: 500
        - name: offset
          in: query
          schema:
            type: integer
            default: 0
        - name: is_active
          in: query
          schema:
            type: boolean
      responses:
        "200":
          description: Paginated signal list
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiEnvelope"

  /api/v1/signals/{signal_id}:
    get:
      tags: [Signals]
      summary: Get a signal by ID
      security:
        - bearerAuth: []
      parameters:
        - name: signal_id
          in: path
          required: true
          schema:
            type: string
      responses:
        "200":
          description: Signal details
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiEnvelope"
    patch:
      tags: [Signals]
      summary: Update a signal
      security:
        - bearerAuth: []
      parameters:
        - name: signal_id
          in: path
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/UpdateSignalRequest"
      responses:
        "200":
          description: Signal updated
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiEnvelope"
    delete:
      tags: [Signals]
      summary: Delete a signal
      security:
        - bearerAuth: []
      parameters:
        - name: signal_id
          in: path
          required: true
          schema:
            type: string
      responses:
        "200":
          description: Signal deleted

  /api/v1/signals/{signal_id}/backtest:
    get:
      tags: [Signals]
      summary: Backtest a signal against historical trades
      description: "Returns how many times the rule would have triggered. Only trade fields (trade_price, trade_size, trade_notional) are evaluated; other fields are reported as unsupported."
      security:
        - bearerAuth: []
      parameters:
        - name: signal_id
          in: path
          required: true
          schema:
            type: string
        - name: start
          in: query
          required: true
          description: Start timestamp (epoch milliseconds)
          schema:
            type: integer
        - name: end
          in: query
          required: true
          description: End timestamp (epoch milliseconds)
          schema:
            type: integer
      responses:
        "200":
          description: Backtest results
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiEnvelope"

components:
  securitySchemes:
    apiKey:
      type: apiKey
      in: header
      name: x-api-key
      description: "API key with `hlt_` prefix. Also accepted via `Authorization: Bearer hlt_...`"
    bearerAuth:
      type: http
      scheme: bearer
      description: JWT token from /auth/login or /auth/register
    adminSecret:
      type: apiKey
      in: header
      name: x-admin-secret
      description: Admin secret for privileged operations

  schemas:
    ApiEnvelope:
      type: object
      properties:
        success:
          type: boolean
        data:
          type: object
        meta:
          $ref: "#/components/schemas/ApiMeta"

    ApiMeta:
      type: object
      properties:
        request_id:
          type: string
          format: uuid
        timestamp:
          type: string
          format: date-time
        total:
          type: integer
          nullable: true
        limit:
          type: integer
          nullable: true
        offset:
          type: integer
          nullable: true

    RegisterRequest:
      type: object
      required: [email, password]
      properties:
        email:
          type: string
          format: email
        password:
          type: string
          minLength: 8

    LoginRequest:
      type: object
      required: [email, password]
      properties:
        email:
          type: string
          format: email
        password:
          type: string

    RegisterPayload:
      type: object
      properties:
        token:
          type: string
          description: JWT token (24h expiry)
        user:
          $ref: "#/components/schemas/UserInfo"
        api_key:
          type: string
          description: Full API key (shown once)
        key:
          $ref: "#/components/schemas/ApiKeyInfo"

    LoginPayload:
      type: object
      properties:
        token:
          type: string
          description: JWT token (24h expiry)
        user:
          $ref: "#/components/schemas/UserInfo"

    UserInfo:
      type: object
      properties:
        id:
          type: string
          format: uuid
        email:
          type: string
        created_at:
          type: integer
          description: Unix timestamp (milliseconds)

    ApiKeyInfo:
      type: object
      properties:
        id:
          type: string
          format: uuid
        key_prefix:
          type: string
          description: "First 12 chars of the key (e.g. hlt_abc12345)"
        name:
          type: string
          nullable: true
        owner_email:
          type: string
          nullable: true
        tier:
          type: string
          enum: [free, starter, growth, scale, enterprise]
        is_active:
          type: boolean
        allowed_ips:
          type: array
          items:
            type: string
          nullable: true
        created_at:
          type: integer
        expires_at:
          type: integer
          nullable: true
        last_used_at:
          type: integer
          nullable: true
        user_id:
          type: string
          nullable: true

    CreateApiKeyRequest:
      type: object
      properties:
        name:
          type: string
        owner_email:
          type: string
        tier:
          type: string
        allowed_ips:
          type: array
          items:
            type: string
        expires_at:
          type: integer

    UpdateApiKeyRequest:
      type: object
      properties:
        name:
          type: string
          nullable: true
        tier:
          type: string
        allowed_ips:
          type: array
          items:
            type: string
          nullable: true
        expires_at:
          type: integer
          nullable: true

    ApiKeyCreatedPayload:
      type: object
      properties:
        api_key:
          type: string
          description: Full API key (shown once, store securely)
        key:
          $ref: "#/components/schemas/ApiKeyInfo"

    ApiKeysPayload:
      type: object
      properties:
        keys:
          type: array
          items:
            $ref: "#/components/schemas/ApiKeyInfo"

    ApiKeyUsagePayload:
      type: object
      properties:
        key_id:
          type: string
        usage:
          type: array
          items:
            $ref: "#/components/schemas/ApiUsageEntry"

    ApiUsageEntry:
      type: object
      properties:
        hour:
          type: integer
          description: Hour bucket (Unix timestamp seconds)
        request_count:
          type: integer
        error_count:
          type: integer
        bytes_out:
          type: integer

    AccountBatchRequest:
      type: object
      required: [accounts]
      properties:
        accounts:
          type: array
          items:
            oneOf:
              - type: integer
              - type: string
          description: Account indices (integers) or L1 addresses (strings)
        by:
          type: string
          enum: [index, l1_address]
          default: index

    BatchPositionsRequest:
      type: object
      required: [accounts]
      properties:
        accounts:
          type: array
          items:
            oneOf:
              - type: integer
              - type: string
        by:
          type: string
          enum: [index, l1_address]
          default: index

    BatchAccountsPayload:
      type: object
      properties:
        accounts:
          type: array
          items:
            type: object
            description: Raw account data from Lighter API
        not_found:
          type: array
          items:
            oneOf:
              - type: integer
              - type: string

    BatchFillsRequest:
      type: object
      required: [accounts, market_id, start, end]
      properties:
        accounts:
          type: array
          items:
            type: integer
          description: Account indices
        market_id:
          type: integer
        start:
          type: integer
          description: Start timestamp (milliseconds)
        end:
          type: integer
          description: End timestamp (milliseconds)
        limit:
          type: integer
          default: 100
          maximum: 1000
        offset:
          type: integer
          default: 0

    BatchFillsPayload:
      type: object
      properties:
        fills:
          type: array
          items:
            $ref: "#/components/schemas/ApiFill"

    BatchLiquidationsRequest:
      type: object
      required: [accounts, start, end]
      properties:
        accounts:
          type: array
          items:
            type: integer
        start:
          type: integer
        end:
          type: integer
        limit:
          type: integer
          default: 100
          maximum: 1000

    ApiTrade:
      type: object
      properties:
        trade_id:
          type: string
        market_id:
          type: integer
        price:
          type: string
        size:
          type: string
        side:
          type: string
          enum: [buy, sell]
        maker_account:
          type: integer
          nullable: true
        taker_account:
          type: integer
          nullable: true
        block_height:
          type: integer
          nullable: true
        timestamp:
          type: integer
          description: Unix timestamp (milliseconds)

    ApiFill:
      type: object
      properties:
        account_index:
          type: integer
        trade_id:
          type: string
        market_id:
          type: integer
        price:
          type: string
        size:
          type: string
        side:
          type: string
          enum: [buy, sell]
        role:
          type: string
          enum: [maker, taker]
        fee:
          type: string
        traded_at:
          type: integer

    TradesByTimePayload:
      type: object
      properties:
        trades:
          type: array
          items:
            $ref: "#/components/schemas/ApiTrade"

    ApiCandle:
      type: object
      properties:
        market_id:
          type: integer
        resolution:
          type: string
        open_time:
          type: integer
        open:
          type: string
        high:
          type: string
        low:
          type: string
        close:
          type: string
        volume:
          type: string
        quote_volume:
          type: string
          nullable: true
        trade_count:
          type: integer

    CandlesPayload:
      type: object
      properties:
        candles:
          type: array
          items:
            $ref: "#/components/schemas/ApiCandle"

    ApiFundingRate:
      type: object
      properties:
        market_id:
          type: integer
        funding_rate:
          type: string
        mark_price:
          type: string
          nullable: true
        index_price:
          type: string
          nullable: true
        open_interest:
          type: string
          nullable: true
        timestamp:
          type: integer

    FundingRatesPayload:
      type: object
      properties:
        rates:
          type: array
          items:
            $ref: "#/components/schemas/ApiFundingRate"

    OrderbookDepthLevel:
      type: object
      properties:
        price:
          type: string
        size:
          type: string
        cumulative_size:
          type: string

    OrderbookDepthPayload:
      type: object
      properties:
        market_id:
          type: integer
        bid_depth:
          type: array
          items:
            $ref: "#/components/schemas/OrderbookDepthLevel"
        ask_depth:
          type: array
          items:
            $ref: "#/components/schemas/OrderbookDepthLevel"
        total_bid_size:
          type: string
        total_ask_size:
          type: string

    CheckoutRequest:
      type: object
      required: [tier, success_url, cancel_url]
      properties:
        tier:
          type: string
          enum: [starter, growth, scale]
        success_url:
          type: string
          format: uri
        cancel_url:
          type: string
          format: uri

    CheckoutPayload:
      type: object
      properties:
        url:
          type: string
          format: uri
          description: Stripe Checkout session URL

    PortalRequest:
      type: object
      required: [return_url]
      properties:
        return_url:
          type: string
          format: uri

    PortalPayload:
      type: object
      properties:
        url:
          type: string
          format: uri
          description: Stripe Customer Portal URL

    DataLakeDownloadPayload:
      type: object
      properties:
        download_url:
          type: string
          format: uri
          description: Pre-signed S3 URL (1h expiry)
        export:
          type: object
          properties:
            id:
              type: string
            export_type:
              type: string
            market_id:
              type: integer
              nullable: true
            time_start:
              type: integer
            time_end:
              type: integer
            file_size_bytes:
              type: integer
              nullable: true
            row_count:
              type: integer
              nullable: true
            format:
              type: string
            compression:
              type: string
            created_at:
              type: integer

    HealthResponse:
      type: object
      properties:
        success:
          type: boolean
        data:
          type: object
          properties:
            status:
              type: string
            postgres:
              type: string
            redis:
              type: string

    StatusResponse:
      type: object
      properties:
        success:
          type: boolean
        data:
          type: object
          properties:
            service:
              type: string
            status:
              type: string

    ErrorResponse:
      type: object
      properties:
        success:
          type: boolean
          example: false
        data:
          type: object
          properties:
            code:
              type: string
              description: Machine-readable error code
              enum:
                - BAD_REQUEST
                - UNAUTHORIZED
                - FORBIDDEN
                - NOT_FOUND
                - RATE_LIMIT_EXCEEDED
                - UPSTREAM_ERROR
                - INTERNAL_ERROR
            message:
              type: string
              description: Human-readable error message
        meta:
          $ref: "#/components/schemas/ApiMeta"

    HeatmapBucket:
      type: object
      properties:
        price_level:
          type: string
        price_low:
          type: string
        price_high:
          type: string
        long_notional_at_risk:
          type: string
        short_notional_at_risk:
          type: string
        long_account_count:
          type: integer
        short_account_count:
          type: integer
        largest_position_notional:
          type: string

    LiquidationHeatmap:
      type: object
      properties:
        market_id:
          type: integer
        current_price:
          type: string
        bucket_size_percent:
          type: string
        buckets:
          type: array
          items:
            $ref: "#/components/schemas/HeatmapBucket"
        total_long_at_risk:
          type: string
        total_short_at_risk:
          type: string
        total_accounts_surveyed:
          type: integer
        total_accounts_with_positions:
          type: integer
        last_scan_at:
          type: integer
        scan_duration_ms:
          type: integer

    CascadeStep:
      type: object
      properties:
        order:
          type: integer
        trigger_price:
          type: string
        liquidated_notional:
          type: string
        accounts_affected:
          type: integer
        price_after_impact:
          type: string

    CascadeSimulationResult:
      type: object
      properties:
        market_id:
          type: integer
        initial_price:
          type: string
        shock_percent:
          type: number
        total_liquidated_notional:
          type: string
        cascade_depth:
          type: integer
        final_price_estimate:
          type: string
        steps:
          type: array
          items:
            $ref: "#/components/schemas/CascadeStep"

    PositionRisk:
      type: object
      properties:
        market_id:
          type: integer
        side:
          type: string
          enum: [long, short]
        size:
          type: string
        notional:
          type: string
        liquidation_price:
          type: string
        current_price:
          type: string
        distance_to_liquidation_pct:
          type: string
        distance_to_liquidation_usd:
          type: string
        heatmap_bucket:
          type: string
          nullable: true

    AccountLiquidationRisk:
      type: object
      properties:
        account_index:
          type: integer
        collateral:
          type: string
        total_maintenance_margin:
          type: string
        margin_ratio:
          type: string
        positions:
          type: array
          items:
            $ref: "#/components/schemas/PositionRisk"
        skipped_markets:
          type: integer

    SignalCondition:
      type: object
      required: [field, op, value]
      properties:
        field:
          type: string
          description: "Field to evaluate. Valid: trade_price, trade_size, trade_notional, orderbook_spread, bid_depth, ask_depth, funding_rate, heatmap_long_at_risk, heatmap_short_at_risk"
        op:
          type: string
          enum: [gt, lt, gte, lte, eq, ne, crosses_above, crosses_below]
        value:
          type: number

    SignalRuleDefinition:
      type: object
      required: [conditions, logic]
      properties:
        conditions:
          type: array
          items:
            $ref: "#/components/schemas/SignalCondition"
        logic:
          type: string
          enum: [AND, OR]
        window:
          type: string
          nullable: true

    CreateSignalRequest:
      type: object
      required: [name, rule_definition]
      properties:
        name:
          type: string
          maxLength: 255
        market_id:
          type: integer
          nullable: true
          description: Market to evaluate (omit for cross-market)
        rule_definition:
          $ref: "#/components/schemas/SignalRuleDefinition"
        delivery_channels:
          type: array
          items:
            type: object
            properties:
              type:
                type: string
                enum: [webhook]
              url:
                type: string
                format: uri
              secret:
                type: string
                minLength: 16
        cooldown_secs:
          type: integer
          default: 60
          minimum: 0
          maximum: 604800

    UpdateSignalRequest:
      type: object
      properties:
        name:
          type: string
        market_id:
          type: integer
          nullable: true
        rule_definition:
          $ref: "#/components/schemas/SignalRuleDefinition"
        delivery_channels:
          type: array
          items:
            type: object
        cooldown_secs:
          type: integer
        is_active:
          type: boolean

    SignalPayload:
      type: object
      properties:
        id:
          type: string
        name:
          type: string
        market_id:
          type: integer
          nullable: true
        rule_definition:
          $ref: "#/components/schemas/SignalRuleDefinition"
        is_active:
          type: boolean
        delivery_channels:
          type: array
          items:
            type: object
        cooldown_secs:
          type: integer
        created_at:
          type: integer
        updated_at:
          type: integer
        last_triggered_at:
          type: integer
          nullable: true

    BacktestResult:
      type: object
      properties:
        triggers:
          type: integer
        trigger_timestamps:
          type: array
          items:
            type: integer
        truncated:
          type: boolean
        window_start:
          type: integer
        window_end:
          type: integer
        trades_evaluated:
          type: integer
        unsupported_fields:
          type: array
          items:
            type: string
