openapi: 3.0.3
info:
  title: DSTA Data Service
  description: Market data, analytics, backtesting, and strategy ideas.
  version: 0.1.0
servers:
  - url: http://localhost:8003
    description: Local development
components:
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      bearerFormat: JWT
  schemas:
    PageResponse:
      type: object
      properties:
        items:
          type: array
          items: {}
        total:
          type: integer
        page:
          type: integer
        size:
          type: integer
    CandlestickRead:
      type: object
      properties:
        id:
          type: integer
        exchange:
          type: string
        symbol:
          type: string
        interval:
          type: string
        open_time:
          type: string
          format: date-time
        open:
          type: string
        high:
          type: string
        low:
          type: string
        close:
          type: string
        volume:
          type: string
    BacktestResultRead:
      type: object
      properties:
        id:
          type: integer
        strategy_name:
          type: string
        status:
          type: string
          enum: [pending, running, completed, failed]
        sharpe_ratio:
          type: number
          nullable: true
        max_drawdown:
          type: number
          nullable: true
        total_return:
          type: number
          nullable: true
        created_at:
          type: string
          format: date-time
    TradeJournalRead:
      type: object
      properties:
        id:
          type: integer
        symbol:
          type: string
        side:
          type: string
          enum: [buy, sell]
        entry_price:
          type: string
        exit_price:
          type: string
          nullable: true
        notes:
          type: string
          nullable: true
        created_at:
          type: string
          format: date-time
    StrategyIdeaRead:
      type: object
      properties:
        id:
          type: integer
        name:
          type: string
        description:
          type: string
        asset_class:
          type: string
        status:
          type: string
        created_at:
          type: string
          format: date-time
    RegimeResponse:
      type: object
      properties:
        symbol:
          type: string
        exchange:
          type: string
        interval:
          type: string
        regime:
          type: string
          enum: [bull, bear, ranging]
        state_history:
          type: array
          items:
            type: string
        confidence:
          type: number
        computed_at:
          type: string
          format: date-time
    SentimentResponse:
      type: object
      properties:
        symbol:
          type: string
        sentiment_score:
          type: number
        signal:
          type: string
          enum: [bullish, bearish, neutral]
        headline_count:
          type: integer
        sample_headlines:
          type: array
          items:
            type: string
        computed_at:
          type: string
          format: date-time
    Error:
      type: object
      properties:
        detail:
          type: string
security:
  - bearerAuth: []
paths:
  /health:
    get:
      operationId: health
      summary: Liveness probe
      tags: [health]
      security: []
      responses:
        '200':
          description: Service is alive
          content:
            application/json:
              schema:
                type: object
                properties:
                  status:
                    type: string
  /ready:
    get:
      operationId: ready
      summary: Readiness probe
      tags: [health]
      security: []
      responses:
        '200':
          description: Service is ready
          content:
            application/json:
              schema:
                type: object
                properties:
                  status:
                    type: string

  # ── Market Data ──────────────────────────────────────────────────────────
  /api/v1/market-data/candlesticks:
    get:
      operationId: listCandlesticks
      summary: List candlesticks
      tags: [market-data]
      parameters:
        - name: exchange
          in: query
          schema:
            type: string
        - name: symbol
          in: query
          schema:
            type: string
        - name: interval
          in: query
          schema:
            type: string
        - name: page
          in: query
          schema:
            type: integer
            default: 1
        - name: size
          in: query
          schema:
            type: integer
            default: 50
      responses:
        '200':
          description: Paginated candlestick list
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PageResponse'
    post:
      operationId: createCandlestick
      summary: Create a candlestick record
      tags: [market-data]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [exchange, symbol, interval, open_time, open, high, low, close, volume]
              properties:
                exchange:
                  type: string
                symbol:
                  type: string
                interval:
                  type: string
                open_time:
                  type: string
                  format: date-time
                open:
                  type: string
                high:
                  type: string
                low:
                  type: string
                close:
                  type: string
                volume:
                  type: string
      responses:
        '201':
          description: Created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CandlestickRead'
  /api/v1/market-data/candlesticks/query:
    post:
      operationId: queryCandlesticks
      summary: Query candlesticks with date range filter
      tags: [market-data]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                exchange:
                  type: string
                symbol:
                  type: string
                interval:
                  type: string
                start:
                  type: string
                  format: date-time
                end:
                  type: string
                  format: date-time
                limit:
                  type: integer
                  default: 500
      responses:
        '200':
          description: List of candlesticks
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/CandlestickRead'
  /api/v1/market-data/candlesticks/{c_id}:
    get:
      operationId: getCandlestick
      summary: Get a single candlestick
      tags: [market-data]
      parameters:
        - name: c_id
          in: path
          required: true
          schema:
            type: integer
      responses:
        '200':
          description: Candlestick record
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CandlestickRead'
        '404':
          description: Not found

  # ── Backtesting ──────────────────────────────────────────────────────────
  /api/v1/backtesting:
    get:
      operationId: listBacktests
      summary: List backtest results
      tags: [backtesting]
      parameters:
        - name: page
          in: query
          schema:
            type: integer
            default: 1
        - name: size
          in: query
          schema:
            type: integer
            default: 20
      responses:
        '200':
          description: Paginated list
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PageResponse'
    post:
      operationId: submitBacktest
      summary: Submit a backtest job
      tags: [backtesting]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [strategy_name, symbol, interval, start_date, end_date]
              properties:
                strategy_name:
                  type: string
                symbol:
                  type: string
                interval:
                  type: string
                start_date:
                  type: string
                  format: date
                end_date:
                  type: string
                  format: date
                parameters:
                  type: object
      responses:
        '202':
          description: Accepted
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/BacktestResultRead'
  /api/v1/backtesting/{b_id}:
    get:
      operationId: getBacktest
      summary: Get backtest result
      tags: [backtesting]
      parameters:
        - name: b_id
          in: path
          required: true
          schema:
            type: integer
      responses:
        '200':
          description: Backtest result
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/BacktestResultRead'
        '404':
          description: Not found
    patch:
      operationId: updateBacktest
      summary: Update backtest record
      tags: [backtesting]
      parameters:
        - name: b_id
          in: path
          required: true
          schema:
            type: integer
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
      responses:
        '200':
          description: Updated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/BacktestResultRead'
    delete:
      operationId: deleteBacktest
      summary: Delete backtest record
      tags: [backtesting]
      parameters:
        - name: b_id
          in: path
          required: true
          schema:
            type: integer
      responses:
        '204':
          description: Deleted
  /api/v1/backtesting/fee-presets:
    get:
      operationId: getFeePresets
      summary: Get fee presets for all exchanges
      tags: [backtesting]
      responses:
        '200':
          description: Fee presets dictionary
          content:
            application/json:
              schema:
                type: object
  /api/v1/backtesting/walk-forward:
    post:
      operationId: runWalkForward
      summary: Run walk-forward validation
      tags: [backtesting]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
      responses:
        '200':
          description: Walk-forward result
          content:
            application/json:
              schema:
                type: object

  # ── Trade Journal ─────────────────────────────────────────────────────────
  /api/v1/trade-journal:
    get:
      operationId: listJournalEntries
      summary: List trade journal entries
      tags: [trade-journal]
      parameters:
        - name: page
          in: query
          schema:
            type: integer
            default: 1
        - name: size
          in: query
          schema:
            type: integer
            default: 20
      responses:
        '200':
          description: Paginated list
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PageResponse'
    post:
      operationId: createJournalEntry
      summary: Create a journal entry
      tags: [trade-journal]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [symbol, side, entry_price]
              properties:
                symbol:
                  type: string
                side:
                  type: string
                  enum: [buy, sell]
                entry_price:
                  type: string
                exit_price:
                  type: string
                notes:
                  type: string
      responses:
        '201':
          description: Created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TradeJournalRead'
  /api/v1/trade-journal/{j_id}:
    get:
      operationId: getJournalEntry
      summary: Get a journal entry
      tags: [trade-journal]
      parameters:
        - name: j_id
          in: path
          required: true
          schema:
            type: integer
      responses:
        '200':
          description: Journal entry
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TradeJournalRead'
        '404':
          description: Not found
    patch:
      operationId: updateJournalEntry
      summary: Update a journal entry
      tags: [trade-journal]
      parameters:
        - name: j_id
          in: path
          required: true
          schema:
            type: integer
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
      responses:
        '200':
          description: Updated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TradeJournalRead'
    delete:
      operationId: deleteJournalEntry
      summary: Delete a journal entry
      tags: [trade-journal]
      parameters:
        - name: j_id
          in: path
          required: true
          schema:
            type: integer
      responses:
        '204':
          description: Deleted

  # ── Strategy Ideas ────────────────────────────────────────────────────────
  /api/v1/strategy-ideas:
    get:
      operationId: listStrategyIdeas
      summary: List strategy ideas
      tags: [strategy-ideas]
      parameters:
        - name: page
          in: query
          schema:
            type: integer
            default: 1
        - name: size
          in: query
          schema:
            type: integer
            default: 20
      responses:
        '200':
          description: Paginated list
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PageResponse'
    post:
      operationId: createStrategyIdea
      summary: Create a strategy idea
      tags: [strategy-ideas]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [name, description]
              properties:
                name:
                  type: string
                description:
                  type: string
                asset_class:
                  type: string
      responses:
        '201':
          description: Created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/StrategyIdeaRead'
  /api/v1/strategy-ideas/{idea_id}:
    get:
      operationId: getStrategyIdea
      summary: Get a strategy idea
      tags: [strategy-ideas]
      parameters:
        - name: idea_id
          in: path
          required: true
          schema:
            type: integer
      responses:
        '200':
          description: Strategy idea
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/StrategyIdeaRead'
        '404':
          description: Not found
    patch:
      operationId: updateStrategyIdea
      summary: Update a strategy idea
      tags: [strategy-ideas]
      parameters:
        - name: idea_id
          in: path
          required: true
          schema:
            type: integer
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
      responses:
        '200':
          description: Updated
    delete:
      operationId: deleteStrategyIdea
      summary: Delete a strategy idea
      tags: [strategy-ideas]
      parameters:
        - name: idea_id
          in: path
          required: true
          schema:
            type: integer
      responses:
        '204':
          description: Deleted

  # ── Analysis ──────────────────────────────────────────────────────────────
  /api/v1/analysis/regime:
    post:
      operationId: detectRegime
      summary: Detect market regime via HMM
      tags: [analysis]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [symbol]
              properties:
                symbol:
                  type: string
                  example: BTCUSDT
                exchange:
                  type: string
                  default: binance
                interval:
                  type: string
                  default: 1h
                lookback_days:
                  type: integer
                  default: 90
      responses:
        '200':
          description: Regime detection result
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/RegimeResponse'
    get:
      operationId: getRegime
      summary: Get cached regime for a symbol
      tags: [analysis]
      parameters:
        - name: symbol
          in: query
          required: true
          schema:
            type: string
        - name: exchange
          in: query
          schema:
            type: string
            default: binance
        - name: interval
          in: query
          schema:
            type: string
            default: 1h
      responses:
        '200':
          description: Cached regime
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/RegimeResponse'
        '404':
          description: No regime cached for this symbol
  /api/v1/analysis/sentiment:
    get:
      operationId: getSentiment
      summary: Get sentiment analysis for a symbol
      tags: [analysis]
      parameters:
        - name: symbol
          in: query
          required: true
          schema:
            type: string
      responses:
        '200':
          description: Sentiment analysis result
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SentimentResponse'

  # ── Funding Rate ──────────────────────────────────────────────────────────
  /api/v1/data/funding-rate/{symbol}:
    get:
      operationId: getFundingRateSeries
      summary: Get funding rate history for a symbol
      tags: [funding-rate]
      parameters:
        - name: symbol
          in: path
          required: true
          schema:
            type: string
        - name: limit
          in: query
          schema:
            type: integer
            default: 100
      responses:
        '200':
          description: Funding rate series
          content:
            application/json:
              schema:
                type: array
                items:
                  type: object
                  properties:
                    symbol:
                      type: string
                    funding_rate:
                      type: string
                    funding_time:
                      type: string
                      format: date-time
  /api/v1/data/funding-rate/{symbol}/latest:
    get:
      operationId: getLatestFundingRate
      summary: Get the latest funding rate for a symbol
      tags: [funding-rate]
      parameters:
        - name: symbol
          in: path
          required: true
          schema:
            type: string
      responses:
        '200':
          description: Latest funding rate or null
          content:
            application/json:
              schema:
                type: object
                nullable: true

  # ── VNStock ───────────────────────────────────────────────────────────────
  /api/v1/vnstock/ingest:
    post:
      operationId: triggerVNStockBackfill
      summary: Trigger VNStock data backfill
      tags: [vnstock]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                symbols:
                  type: array
                  items:
                    type: string
                start_date:
                  type: string
                  format: date
                end_date:
                  type: string
                  format: date
      responses:
        '202':
          description: Backfill job enqueued
  /api/v1/vnstock/status:
    get:
      operationId: getVNStockStatus
      summary: Get VNStock ingestion status
      tags: [vnstock]
      responses:
        '200':
          description: Status summary
          content:
            application/json:
              schema:
                type: object

  # ── Streams ───────────────────────────────────────────────────────────────
  /api/v1/streams/active:
    get:
      operationId: getActiveStreams
      summary: List active SSE/WebSocket streams
      tags: [streams]
      responses:
        '200':
          description: Active streams
          content:
            application/json:
              schema:
                type: object
  /api/v1/streams/ohlcv:
    get:
      operationId: streamOHLCV
      summary: Server-sent events stream of OHLCV data
      tags: [streams]
      parameters:
        - name: exchange
          in: query
          required: true
          schema:
            type: string
        - name: symbol
          in: query
          required: true
          schema:
            type: string
        - name: interval
          in: query
          schema:
            type: string
            default: 1m
      responses:
        '200':
          description: SSE stream
          content:
            text/event-stream:
              schema:
                type: string

  # ── Features ──────────────────────────────────────────────────────────────
  /api/v1/features:
    get:
      operationId: getFeatures
      summary: Get precomputed features for a symbol
      tags: [features]
      parameters:
        - name: symbol
          in: query
          required: true
          schema:
            type: string
        - name: interval
          in: query
          schema:
            type: string
            default: 1h
      responses:
        '200':
          description: Feature set
          content:
            application/json:
              schema:
                type: object
  /api/v1/features/precompute:
    post:
      operationId: triggerFeaturePrecompute
      summary: Trigger feature precomputation job
      tags: [features]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                symbol:
                  type: string
                interval:
                  type: string
      responses:
        '202':
          description: Job enqueued
