# Indicator contract

## Required fields

| Field | Purpose |
|-------|---------|
| `id` | Stable identifier (e.g. `reference.sma`) |
| `name` | Display name |
| `version` | Semver for the implementation |
| `description` | Human-readable summary |
| `category` | trend / momentum / … / reference |
| `parameters` | Typed schema with defaults |
| `renderPlacement` | overlay / panel / marker |
| `requiredLookback(params)` | Bars needed before first valid value |
| `calculate({ candles, params })` | Pure calculation → outputs |

## Parameters

Declared with `ParameterSchema` (integer/number, min/max). Invalid values return a controlled error from `runIndicator()` — never silent coercion of invalid integers.

## Outputs

- **line** — `{ kind: "line", placement, seriesId, points: { time, value | null }[] }`
- **marker** — `{ kind: "marker", placement: "overlay", markers: [...] }`

`value: null` marks insufficient history (not a misleading numeric).

## Determinism

`calculate` must not use network, randomness, or `Date.now()`. Same candles + params → same outputs.

## Input candles

`IndicatorCandle` matches `ChartCandle` (`time` as Unix seconds, OHLC). Source-agnostic.
