openapi: 3.0.3
info:
  title: trading-data API
  description: |
    Self-hosted MT5 market-data store. Collectors push OHLCV bars; consumers read them.
    Only the newest `WINDOW_SIZE` candles per (symbol, timeframe) are retained.
  version: "1.0.0"
servers:
  - url: /
components:
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
  schemas:
    Bar:
      type: object
      description: One OHLCV candle. `ts` is the bar OPEN time in UTC.
      required: [ts, o, h, l, c, v]
      properties:
        ts: { type: string, format: date-time, example: "2026-06-12T10:00:00Z" }
        o:  { type: number, format: double, example: 1.0812 }
        h:  { type: number, format: double, example: 1.0815 }
        l:  { type: number, format: double, example: 1.0810 }
        c:  { type: number, format: double, example: 1.0814 }
        v:  { type: integer, format: int64, example: 231 }
    OutBar:
      allOf:
        - type: object
          properties:
            symbol:    { type: string, example: EURUSD }
            timeframe: { type: string, example: M1 }
        - $ref: '#/components/schemas/Bar'
    IngestRequest:
      type: object
      required: [symbol, timeframe, bars]
      properties:
        symbol:    { type: string, example: EURUSD }
        timeframe: { type: string, example: M1 }
        bars:
          type: array
          items: { $ref: '#/components/schemas/Bar' }
    Series:
      type: object
      properties:
        symbol:    { type: string, example: EURUSD }
        timeframe: { type: string, example: M1 }
    Error:
      type: object
      properties:
        error: { type: string }
paths:
  /health:
    get:
      summary: Liveness + DB ping
      security: []
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                type: object
                properties: { status: { type: string, example: ok } }
        '503':
          description: DB unavailable
          content:
            application/json:
              schema: { $ref: '#/components/schemas/Error' }
  /ingest:
    post:
      summary: Upsert a batch of bars for one (symbol, timeframe)
      security: [{ bearerAuth: [] }]
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: '#/components/schemas/IngestRequest' }
      responses:
        '200':
          description: Rows written
          content:
            application/json:
              schema:
                type: object
                properties: { written: { type: integer, example: 50 } }
        '400': { description: Bad request, content: { application/json: { schema: { $ref: '#/components/schemas/Error' } } } }
        '401': { description: Unauthorized, content: { application/json: { schema: { $ref: '#/components/schemas/Error' } } } }
  /bars:
    get:
      summary: Read a range of bars for a series (ASC by ts)
      security: [{ bearerAuth: [] }]
      parameters:
        - { name: symbol, in: query, required: true, schema: { type: string }, example: EURUSD }
        - { name: tf, in: query, required: true, schema: { type: string }, example: M1 }
        - { name: from, in: query, required: false, schema: { type: string, format: date-time }, description: RFC3339 lower bound (inclusive) }
        - { name: to, in: query, required: false, schema: { type: string, format: date-time }, description: RFC3339 upper bound (inclusive) }
        - { name: limit, in: query, required: false, schema: { type: integer }, example: 100 }
      responses:
        '200':
          description: Bars
          content:
            application/json:
              schema:
                type: array
                items: { $ref: '#/components/schemas/OutBar' }
        '400': { description: Bad request, content: { application/json: { schema: { $ref: '#/components/schemas/Error' } } } }
        '401': { description: Unauthorized, content: { application/json: { schema: { $ref: '#/components/schemas/Error' } } } }
  /latest:
    get:
      summary: Newest bar for a series
      security: [{ bearerAuth: [] }]
      parameters:
        - { name: symbol, in: query, required: true, schema: { type: string }, example: EURUSD }
        - { name: tf, in: query, required: true, schema: { type: string }, example: M1 }
      responses:
        '200':
          description: Bar
          content:
            application/json:
              schema: { $ref: '#/components/schemas/OutBar' }
        '404': { description: No bars, content: { application/json: { schema: { $ref: '#/components/schemas/Error' } } } }
        '401': { description: Unauthorized, content: { application/json: { schema: { $ref: '#/components/schemas/Error' } } } }
  /symbols:
    get:
      summary: Distinct (symbol, timeframe) series stored
      security: [{ bearerAuth: [] }]
      responses:
        '200':
          description: Series list
          content:
            application/json:
              schema:
                type: array
                items: { $ref: '#/components/schemas/Series' }
        '401': { description: Unauthorized, content: { application/json: { schema: { $ref: '#/components/schemas/Error' } } } }
