> ## Documentation Index
> Fetch the complete documentation index at: https://docs.doomberg.me/llms.txt
> Use this file to discover all available pages before exploring further.

# StrategySpec

> The full declarative strategy schema — a closed, versioned contract that agents propose and the backtest engine executes.

A `StrategySpec` is a declarative, JSON-serializable description of a trading strategy. Agents author specs; the Ithaca backtest engine executes them. **No agent-authored code is ever run** — the spec is matched against a frozen, closed schema and dispatched to trusted numpy kernels.

## Design principles

<Callout type="info">
  The schema is **closed**: `additionalProperties: false` on every object. Unknown fields are rejected at validation time, not silently dropped. This prevents agents from smuggling arbitrary keys into the engine.
</Callout>

* **Declarative, not imperative.** The agent describes *what* to trade, not *how* to compute it.
* **Closed schema.** Every field is enumerated. No extension points, no freeform blobs.
* **Server-stamped identity.** `id`, `version`, and `tenant_id` are assigned by the server. If an agent supplies them, they are **ignored**.
* **Deterministic.** A `seed` field makes every backtest reproducible to the bit.

## Top-level fields

| Field          | Type    | Required | Description                                             |
| -------------- | ------- | -------- | ------------------------------------------------------- |
| `spec_version` | string  | yes      | Always `"1.0"`. Bumped only on breaking schema changes. |
| `name`         | string  | yes      | Human-readable strategy name.                           |
| `universe`     | object  | yes      | Tradable universe definition.                           |
| `strategy`     | object  | yes      | Signal logic selector + parameters.                     |
| `construction` | object  | yes      | Portfolio construction scheme.                          |
| `risk`         | object  | yes      | Risk guardrails.                                        |
| `execution`    | object  | yes      | Execution assumptions.                                  |
| `resources`    | object  | yes      | Compute tier reservation.                               |
| `seed`         | integer | no       | RNG seed. Defaults to `42`.                             |

### Server-stamped fields (ignored if supplied)

These fields are **never** accepted from the agent. The server assigns them on persist:

| Field       | Source                 | Notes                                   |
| ----------- | ---------------------- | --------------------------------------- |
| `id`        | server (UUID)          | Ignored if present in the request body. |
| `version`   | server (monotonic int) | Increments per tenant + name.           |
| `tenant_id` | server (from auth)     | Derived from the caller's principal.    |

<Callout type="warn">
  If an agent sends `"id": "..."` or `"tenant_id": "..."`, the server silently discards them. This prevents an agent from impersonating another tenant or overwriting an existing strategy.
</Callout>

## `universe`

| Field     | Type      | Required | Description                                |
| --------- | --------- | -------- | ------------------------------------------ |
| `symbols` | string\[] | yes      | Ticker list, e.g. `["NVDA","AMD","INTC"]`. |
| `source`  | string    | yes      | Price panel source. Currently `"polygon"`. |

## `strategy`

| Field    | Type   | Required | Description                                                      |
| -------- | ------ | -------- | ---------------------------------------------------------------- |
| `id`     | enum   | yes      | One of `momentum`, `ma_cross`, `mean_reversion`, `rank_returns`. |
| `params` | object | yes      | Strategy-specific parameters (see below).                        |

### Strategy `id` reference

<Accordion title="momentum">
  Rolling-window momentum. `params`: `lookback` (days, int), `skip` (days, int, default 0).
</Accordion>

<Accordion title="ma_cross">
  Moving-average crossover. `params`: `fast` (days), `slow` (days).
</Accordion>

<Accordion title="mean_reversion">
  Z-score reversion to rolling mean. `params`: `lookback` (days), `entry_z` (float), `exit_z` (float).
</Accordion>

<Accordion title="rank_returns">
  Cross-sectional return ranking. `params`: `lookback` (days), `top_n` (int).
</Accordion>

## `construction`

| Field       | Type    | Required | Description                                           |
| ----------- | ------- | -------- | ----------------------------------------------------- |
| `scheme`    | enum    | yes      | `equal_weight`, `rank_weight`, or `vol_weight`.       |
| `long_only` | boolean | yes      | If `true`, weights are clipped at 0 (no shorts).      |
| `gross`     | float   | yes      | Target gross exposure, e.g. `1.0` for fully invested. |

## `risk`

| Field          | Type  | Required | Description                                  |
| -------------- | ----- | -------- | -------------------------------------------- |
| `max_gross`    | float | yes      | Maximum gross exposure.                      |
| `max_net`      | float | yes      | Maximum net exposure.                        |
| `max_name_pct` | float | yes      | Maximum per-name weight (e.g. `0.10` = 10%). |

## `execution`

| Field       | Type  | Required | Description                                           |
| ----------- | ----- | -------- | ----------------------------------------------------- |
| `cost_bps`  | float | yes      | Round-trip cost in basis points.                      |
| `algo`      | enum  | yes      | Fill model. Currently `"twap"`.                       |
| `rebalance` | enum  | yes      | Rebalance frequency: `daily`, `weekly`, or `monthly`. |

## `resources`

| Field  | Type | Required | Description                        |
| ------ | ---- | -------- | ---------------------------------- |
| `tier` | enum | yes      | Compute tier: `"dev"` or `"prod"`. |

## Validation flow

<Steps>
  <Step title="Parse">
    The request body is parsed as JSON. Malformed JSON is rejected with `400`.
  </Step>

  <Step title="Schema validate">
    The JSON is validated against the frozen `StrategySpec` schema. Unknown fields, type mismatches, and missing required fields return `422` with a field-level error list.
  </Step>

  <Step title="Strip server fields">
    `id`, `version`, and `tenant_id` are removed from the payload if present.
  </Step>

  <Step title="Semantic validate">
    Cross-field rules are checked (e.g. `fast < slow` for `ma_cross`, `max_name_pct <= max_gross`).
  </Step>

  <Step title="Stamp + persist">
    The server assigns `id`, `version`, `tenant_id` and writes the immutable spec row.
  </Step>
</Steps>

## Complete example

```json theme={null}
{
  "spec_version": "1.0",
  "name": "Semis momentum 60/20",
  "universe": {
    "symbols": ["NVDA", "AMD", "INTC", "TSM", "QCOM"],
    "source": "polygon"
  },
  "strategy": {
    "id": "momentum",
    "params": { "lookback": 60, "skip": 5 }
  },
  "construction": {
    "scheme": "vol_weight",
    "long_only": true,
    "gross": 1.0
  },
  "risk": {
    "max_gross": 1.05,
    "max_net": 1.0,
    "max_name_pct": 0.25
  },
  "execution": {
    "cost_bps": 5.0,
    "algo": "twap",
    "rebalance": "weekly"
  },
  "resources": { "tier": "prod" },
  "seed": 42
}
```

<Tip>
  The `seed` field is what makes a backtest reproducible. The same spec + the same price panel + the same seed always produces the same result hash. Change the seed and you get a different Monte Carlo path (where applicable).
</Tip>

## Related

* [Backtest system](/strategy/backtest) — how a spec becomes an equity curve
* [Artifacts](/strategy/artifacts) — the 8-file bundle produced by every run
* [Approval workflow](/strategy/approvals) — promoting a backtest to paper trading
* [Strategy endpoints](/api/strategies) — the REST API for proposing specs
