asyncapi: 3.0.0
info:
  title: TickFlow WebSocket API
  version: 2.0.0
  description: |
    TickFlow 实时推送 WebSocket API，支持按频道订阅行情和市场深度数据。

    ## 接口列表

    | 接口 | 地址 | 说明 |
    |------|------|------|
    | **统一推送**（推荐） | `/v1/ws/stream` | 按频道订阅，支持 `quotes` 和 `depth` |
    | 行情推送（旧版） | `/v1/ws/quotes` | 仅推送行情，兼容旧版 |

    - 数据格式: JSON 文本帧，支持 permessage-deflate 压缩
    - 认证方式: 通过 URL 查询参数 `api_key` 传递
    - 连接保活: 服务端每 30 秒发送 Ping 帧，客户端需回复 Pong
    - 断开连接后订阅自动清除，重连需重新发送订阅请求
  contact:
    email: support@tickflow.org

servers:
  production:
    host: api.tickflow.org
    protocol: wss
    description: TickFlow 生产环境
    pathname: /v1/ws/stream
    security:
      - $ref: "#/components/securitySchemes/apiKey"

components:
  securitySchemes:
    apiKey:
      type: httpApiKey
      name: api_key
      in: query

  schemas:
    Quote:
      type: object
      additionalProperties: true
      description: 单条行情快照
      properties:
        symbol:
          type: string
          example: "600000.SH"
          description: 标的代码
        region:
          type: string
          enum: ["CN", "US", "HK"]
          example: "CN"
          description: 市场区域
        last_price:
          type: number
          example: 9.72
          description: 最新价
        prev_close:
          type: number
          example: 9.78
          description: 昨收价
        open:
          type: number
          example: 9.78
          description: 开盘价
        high:
          type: number
          example: 9.78
          description: 最高价
        low:
          type: number
          example: 9.68
          description: 最低价
        volume:
          type: integer
          example: 426585
          description: 成交量
        amount:
          type: number
          example: 422430500
          description: 成交额
        timestamp:
          type: integer
          example: 1776754802000
          description: 行情时间戳（毫秒）
        session:
          type: string
          example: "trading"
          description: 交易时段（可选）
        ext:
          type: object
          description: 扩展数据（名称、涨跌幅等，可选）
      required:
        - symbol
        - region
        - last_price
        - prev_close
        - open
        - high
        - low
        - volume
        - amount
        - timestamp

    MarketDepth:
      type: object
      description: 单条五档市场深度
      properties:
        symbol:
          type: string
          example: "600000.SH"
          description: 标的代码
        region:
          type: string
          enum: ["CN", "US", "HK"]
          example: "CN"
          description: 市场区域
        timestamp:
          type: integer
          example: 1776754802000
          description: 时间戳（毫秒）
        bid_prices:
          type: array
          items:
            type: number
          example: [9.72, 9.71, 9.70, 9.69, 9.68]
          description: 买入价（买1-买5，降序）
        bid_volumes:
          type: array
          items:
            type: integer
          example: [3192, 3870, 26168, 5849, 5480]
          description: 买入量
        ask_prices:
          type: array
          items:
            type: number
          example: [9.73, 9.74, 9.75, 9.76, 9.77]
          description: 卖出价（卖1-卖5，升序）
        ask_volumes:
          type: array
          items:
            type: integer
          example: [74, 1602, 1148, 1209, 1109]
          description: 卖出量
      required:
        - symbol
        - region
        - timestamp
        - bid_prices
        - bid_volumes
        - ask_prices
        - ask_volumes

channels:
  # ================================================================
  # 统一推送 /v1/ws/stream（推荐）
  # ================================================================
  stream:
    address: /v1/ws/stream
    description: |
      统一推送接口，按频道（channel）订阅行情和市场深度。
      支持频道: `quotes`（需 WebSocket 实时行情权限）、`depth`（需市场深度权限）。
    messages:
      subscribeRequest:
        name: StreamSubscribeRequest
        title: 订阅频道
        summary: 按频道和标的列表订阅，订阅成功后推送缓存快照
        contentType: application/json
        payload:
          type: object
          additionalProperties: false
          properties:
            op:
              type: string
              enum: [subscribe]
              description: 操作类型
            channel:
              type: string
              enum: [quotes, depth]
              description: 频道名称
            symbols:
              type: array
              items:
                type: string
              description: 要订阅的标的代码列表
          required:
            - op
            - channel
            - symbols
          examples:
            - op: subscribe
              channel: quotes
              symbols: ["600000.SH", "000001.SZ"]
            - op: subscribe
              channel: depth
              symbols: ["600000.SH"]

      unsubscribeRequest:
        name: StreamUnsubscribeRequest
        title: 退订频道
        summary: 按频道退订指定标的
        contentType: application/json
        payload:
          type: object
          additionalProperties: false
          properties:
            op:
              type: string
              enum: [unsubscribe]
              description: 操作类型
            channel:
              type: string
              enum: [quotes, depth]
              description: 频道名称
            symbols:
              type: array
              items:
                type: string
              description: 要退订的标的代码列表
          required:
            - op
            - channel
            - symbols
          examples:
            - op: unsubscribe
              channel: depth
              symbols: ["600000.SH"]

      subscribedResponse:
        name: StreamSubscribedResponse
        title: 频道订阅状态
        summary: subscribe / unsubscribe 操作后返回该频道的完整订阅列表
        contentType: application/json
        payload:
          type: object
          additionalProperties: false
          properties:
            op:
              type: string
              enum: [subscribed]
              description: 消息类型
            channel:
              type: string
              enum: [quotes, depth]
              description: 频道名称
            symbols:
              type: array
              items:
                type: string
              description: 当前该频道已订阅的全部标的
            total:
              type: integer
              description: 当前该频道已订阅标的总数
          required:
            - op
            - channel
            - symbols
            - total
          examples:
            - op: subscribed
              channel: quotes
              symbols: ["600000.SH", "000001.SZ"]
              total: 2

      quotesData:
        name: StreamQuotesData
        title: 行情推送
        summary: 实时行情数据，仅包含已订阅且有更新的标的
        contentType: application/json
        payload:
          type: object
          additionalProperties: false
          properties:
            op:
              type: string
              enum: [quotes]
              description: 消息类型
            data:
              type: array
              items:
                $ref: "#/components/schemas/Quote"
              description: 行情快照列表
          required:
            - op
            - data
          examples:
            - {
                "op": "quotes",
                "data":
                  [
                    {
                      "symbol": "600000.SH",
                      "region": "CN",
                      "last_price": 9.72,
                      "prev_close": 9.78,
                      "open": 9.78,
                      "high": 9.78,
                      "low": 9.68,
                      "volume": 426585,
                      "amount": 422430500,
                      "timestamp": 1776754802000,
                      "ext":
                        {
                          "type": "cn_equity",
                          "name": "浦发银行",
                          "change_pct": -0.006135,
                          "change_amount": -0.06,
                          "amplitude": 0.010225,
                          "turnover_rate": 0.001281,
                        },
                    },
                  ],
              }

      depthData:
        name: StreamDepthData
        title: 市场深度推送
        summary: 五档盘口数据，仅包含已订阅 depth 频道且有更新的标的
        contentType: application/json
        payload:
          type: object
          additionalProperties: false
          properties:
            op:
              type: string
              enum: [depth]
              description: 消息类型
            data:
              type: array
              items:
                $ref: "#/components/schemas/MarketDepth"
              description: 市场深度列表
          required:
            - op
            - data
          examples:
            - {
                "op": "depth",
                "data":
                  [
                    {
                      "symbol": "600000.SH",
                      "region": "CN",
                      "timestamp": 1776754802000,
                      "bid_prices": [9.72, 9.71, 9.70, 9.69, 9.68],
                      "bid_volumes": [3192, 3870, 26168, 5849, 5480],
                      "ask_prices": [9.73, 9.74, 9.75, 9.76, 9.77],
                      "ask_volumes": [74, 1602, 1148, 1209, 1109],
                    },
                  ],
              }

      errorResponse:
        name: StreamErrorResponse
        title: 错误消息
        summary: 操作失败时返回的错误信息
        contentType: application/json
        payload:
          type: object
          additionalProperties: false
          properties:
            op:
              type: string
              enum: [error]
              description: 消息类型
            message:
              type: string
              description: 错误详情
          required:
            - op
            - message
          examples:
            - op: error
              message: "no permission for channel: depth"
            - op: error
              message: "exceeded max 50 symbols (total unique: 53)"
            - op: error
              message: "unknown channel: foo"
            - op: error
              message: "invalid message: expected value at line 1 column 1"

  # ================================================================
  # 旧版行情推送 /v1/ws/quotes
  # ================================================================
  quotes:
    address: /v1/ws/quotes
    description: |
      旧版行情推送接口，仅推送行情数据，不支持市场深度。
      新接入建议使用 `/v1/ws/stream`。
    messages:
      subscribeRequest:
        name: LegacySubscribeRequest
        title: 订阅标的
        summary: 订阅一组标的的实时行情推送（无 channel 字段）
        contentType: application/json
        payload:
          type: object
          additionalProperties: false
          properties:
            op:
              type: string
              enum: [subscribe]
              description: 操作类型
            symbols:
              type: array
              items:
                type: string
              description: 要订阅的标的代码列表
          required:
            - op
            - symbols
          examples:
            - op: subscribe
              symbols: ["600000.SH", "000001.SZ"]

      unsubscribeRequest:
        name: LegacyUnsubscribeRequest
        title: 退订标的
        summary: 退订一组标的
        contentType: application/json
        payload:
          type: object
          additionalProperties: false
          properties:
            op:
              type: string
              enum: [unsubscribe]
              description: 操作类型
            symbols:
              type: array
              items:
                type: string
              description: 要退订的标的代码列表
          required:
            - op
            - symbols
          examples:
            - op: unsubscribe
              symbols: ["600000.SH"]

      subscribedResponse:
        name: LegacySubscribedResponse
        title: 订阅状态
        summary: subscribe / unsubscribe 操作后返回当前完整订阅列表
        contentType: application/json
        payload:
          type: object
          additionalProperties: false
          properties:
            op:
              type: string
              enum: [subscribed]
              description: 消息类型
            symbols:
              type: array
              items:
                type: string
              description: 当前已订阅的全部标的
            total:
              type: integer
              description: 当前已订阅标的总数
          required:
            - op
            - symbols
            - total
          examples:
            - op: subscribed
              symbols: ["600000.SH", "000001.SZ"]
              total: 2

      quotesData:
        name: LegacyQuotesData
        title: 行情推送
        summary: 实时行情数据，仅包含已订阅且有更新的标的
        contentType: application/json
        payload:
          type: object
          additionalProperties: false
          properties:
            op:
              type: string
              enum: [quotes]
              description: 消息类型
            data:
              type: array
              items:
                $ref: "#/components/schemas/Quote"
              description: 行情快照列表
          required:
            - op
            - data

      errorResponse:
        name: LegacyErrorResponse
        title: 错误消息
        summary: 操作失败时返回的错误信息
        contentType: application/json
        payload:
          type: object
          additionalProperties: false
          properties:
            op:
              type: string
              enum: [error]
              description: 消息类型
            message:
              type: string
              description: 错误详情
          required:
            - op
            - message

operations:
  # ================================================================
  # 统一推送操作
  # ================================================================
  streamSubscribe:
    action: receive
    channel:
      $ref: "#/channels/stream"
    summary: 客户端按频道订阅
    description: |
      按频道订阅标的的实时推送。可多次调用追加订阅。
      订阅成功后服务端返回 `subscribed` 确认（含频道信息），并推送该频道的缓存快照。
      若新增后超出套餐的最大标的数，返回 `error`，本次订阅不生效。
    messages:
      - $ref: "#/channels/stream/messages/subscribeRequest"

  streamUnsubscribe:
    action: receive
    channel:
      $ref: "#/channels/stream"
    summary: 客户端按频道退订
    description: 退订指定频道的标的。退订后服务端返回该频道更新后的订阅列表。
    messages:
      - $ref: "#/channels/stream/messages/unsubscribeRequest"

  streamReceiveSubscribed:
    action: send
    channel:
      $ref: "#/channels/stream"
    summary: 服务端返回频道订阅状态
    description: subscribe / unsubscribe 操作后返回，包含频道名、当前订阅列表和总数。
    messages:
      - $ref: "#/channels/stream/messages/subscribedResponse"

  streamReceiveQuotes:
    action: send
    channel:
      $ref: "#/channels/stream"
    summary: 服务端推送实时行情
    description: 持续推送已订阅 quotes 频道标的的最新行情。首次订阅后推送缓存快照。
    messages:
      - $ref: "#/channels/stream/messages/quotesData"

  streamReceiveDepth:
    action: send
    channel:
      $ref: "#/channels/stream"
    summary: 服务端推送市场深度
    description: 持续推送已订阅 depth 频道标的的五档盘口。首次订阅后推送缓存快照。
    messages:
      - $ref: "#/channels/stream/messages/depthData"

  streamReceiveError:
    action: send
    channel:
      $ref: "#/channels/stream"
    summary: 服务端返回错误
    description: 无频道权限、超出订阅上限、消息格式错误或未知操作时返回。
    messages:
      - $ref: "#/channels/stream/messages/errorResponse"

  # ================================================================
  # 旧版行情推送操作
  # ================================================================
  legacySendSubscribe:
    action: receive
    channel:
      $ref: "#/channels/quotes"
    summary: 客户端发送订阅请求（旧版）
    description: |
      旧版接口，订阅一组标的的实时行情。新接入建议使用 `/v1/ws/stream`。
    messages:
      - $ref: "#/channels/quotes/messages/subscribeRequest"

  legacySendUnsubscribe:
    action: receive
    channel:
      $ref: "#/channels/quotes"
    summary: 客户端发送退订请求（旧版）
    description: 退订指定标的。
    messages:
      - $ref: "#/channels/quotes/messages/unsubscribeRequest"

  legacyReceiveSubscribed:
    action: send
    channel:
      $ref: "#/channels/quotes"
    summary: 服务端返回订阅状态（旧版）
    description: subscribe / unsubscribe 操作后返回当前完整订阅列表。
    messages:
      - $ref: "#/channels/quotes/messages/subscribedResponse"

  legacyReceiveQuotes:
    action: send
    channel:
      $ref: "#/channels/quotes"
    summary: 服务端推送实时行情（旧版）
    description: 推送已订阅标的的最新行情。
    messages:
      - $ref: "#/channels/quotes/messages/quotesData"

  legacyReceiveError:
    action: send
    channel:
      $ref: "#/channels/quotes"
    summary: 服务端返回错误（旧版）
    description: 超出订阅上限、消息格式错误或未知操作时返回。
    messages:
      - $ref: "#/channels/quotes/messages/errorResponse"
