> ## 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.

# Strategy Tools

> propose_strategy validates a declarative StrategySpec against the frozen contract, stamps tenant_id, and stores an immutable versioned row. No agent-authored code, ever.

The strategy tools family contains a single tool — `propose_strategy` — that accepts a declarative `StrategySpec` and stores it as an immutable, versioned row. Strategies in Ithaca are **specs, not code**. The agent never writes Python, never executes arbitrary logic, and never touches the backtest engine directly.

## `propose_strategy`

Validate a `StrategySpec` dict against the frozen contract, stamp it with the caller's `tenant_id`, and store it as an immutable versioned row. Returns the stored spec with its assigned `strategy_id` and `version`.

### Arguments

| Argument | Type   | Required | Description                                                         |
| -------- | ------ | -------- | ------------------------------------------------------------------- |
| `spec`   | object | Yes      | A `StrategySpec` dict conforming to the frozen contract. See below. |

### What it does

1. **Validates** the `spec` against the frozen `StrategySpec` JSON Schema. Unknown fields, malformed values, or missing required keys are rejected with a detailed error.
2. **Stamps** the spec with the caller's `tenant_id`. This cannot be overridden — the tenant is taken from the authenticated session.
3. **Stores** the spec as an immutable, versioned row. The `strategy_id` is stable across versions; the `version` increments on each proposal. Prior versions are never mutated or deleted.
4. **Returns** the stored spec with its `strategy_id`, `version`, `created_at`, and a content hash.

<Callout type="warn">
  The schema is **closed**. There is no escape hatch, no `custom_code` field, no `eval` hook. If a strategy can't be expressed in the declarative spec, it can't be proposed. This is the core safety guarantee — the backtest engine only ever runs trusted, audited numpy code against validated specs.
</Callout>

### Return value

| Field          | Type              | Description                                |
| -------------- | ----------------- | ------------------------------------------ |
| `strategy_id`  | string (UUID)     | Stable identifier across versions.         |
| `version`      | integer           | Monotonically increasing version number.   |
| `tenant_id`    | string (UUID)     | The tenant that owns this strategy.        |
| `spec`         | object            | The stored spec (echoed back, normalized). |
| `content_hash` | string            | SHA-256 hash of the canonicalized spec.    |
| `created_at`   | string (ISO 8601) | When the spec was stored.                  |

## The `StrategySpec` contract

The `StrategySpec` is a declarative dict that describes **what** a strategy does, not **how** it does it. The backtest engine interprets the spec using trusted, audited code. The agent's job is to fill in the spec; the engine's job is to execute it.

### Top-level fields

| Field       | Type   | Required | Description                                                          |
| ----------- | ------ | -------- | -------------------------------------------------------------------- |
| `name`      | string | Yes      | Human-readable strategy name.                                        |
| `universe`  | object | Yes      | The set of instruments to trade.                                     |
| `signal`    | object | Yes      | The signal generator (entry/exit rules).                             |
| `weights`   | object | Yes      | The position sizing rule.                                            |
| `risk`      | object | Yes      | Risk constraints (max weight, stop loss, etc.).                      |
| `execution` | object | No       | Execution assumptions (slippage, fees). Defaults applied if omitted. |
| `horizon`   | string | No       | Rebalance horizon: `daily`, `weekly`, `monthly`. Default `daily`.    |

### Full example: momentum strategy on NVDA

```json theme={null}
{
  "name": "NVDA momentum (50/200 SMA cross)",
  "universe": {
    "type": "single",
    "symbol": "NVDA"
  },
  "signal": {
    "type": "sma_cross",
    "fast": 50,
    "slow": 200,
    "direction": "long_only"
  },
  "weights": {
    "type": "fixed_fraction",
    "fraction": 1.0
  },
  "risk": {
    "max_weight": 1.0,
    "stop_loss_pct": null,
    "max_positions": 1
  },
  "execution": {
    "slippage_bps": 5,
    "fee_bps": 1
  },
  "horizon": "daily"
}
```

### How the engine interprets this

* **`universe`** — the engine resolves the universe to a concrete set of symbols. `single` means one symbol; `screen` means run a screener; `index` means an index constituent list.
* **`signal`** — the engine computes the signal series. `sma_cross` with `fast=50`, `slow=200` means: go long when the 50-day SMA crosses above the 200-day SMA; exit when it crosses below. `long_only` prevents shorting.
* **`weights`** — the engine sizes positions. `fixed_fraction` with `fraction=1.0` means allocate 100% of capital to the signal.
* **`risk`** — the engine applies risk constraints after sizing. `max_weight` caps any single position; `stop_loss_pct` exits on a drawdown threshold; `max_positions` limits breadth.
* **`execution`** — the engine applies slippage and fees on every fill. `5bps` slippage and `1bp` fees are conservative defaults for liquid US equities.
* **`horizon`** — the engine rebalances at this frequency. `daily` means the signal is evaluated every trading day.

## Why a closed schema

<Callout type="info">
  The closed schema is the boundary between "agent creativity" and "trusted execution." The agent can compose any combination of supported signal, weight, and risk primitives — but it cannot introduce new ones. New primitives are added by the Ithaca team after security review and backtest-engine integration.
</Callout>

This means:

* **No arbitrary code execution.** The agent never sends Python, JS, or any executable. The backtest engine only runs its own audited numpy code.
* **Reproducible specs.** A stored spec is a complete description of the strategy. Anyone with the `strategy_id` and `version` can reproduce the exact same backtest.
* **Auditable.** Every spec is a small JSON document that a human can read and reason about. There is no hidden logic.
* **Versioned.** If the agent proposes a tweaked strategy, it gets a new `version` on the same `strategy_id`. The old version is preserved for comparison.

## Example call

```json theme={null}
{
  "spec": {
    "name": "NVDA momentum (50/200 SMA cross)",
    "universe": { "type": "single", "symbol": "NVDA" },
    "signal": { "type": "sma_cross", "fast": 50, "slow": 200, "direction": "long_only" },
    "weights": { "type": "fixed_fraction", "fraction": 1.0 },
    "risk": { "max_weight": 1.0, "stop_loss_pct": null, "max_positions": 1 },
    "execution": { "slippage_bps": 5, "fee_bps": 1 },
    "horizon": "daily"
  }
}
```

## Example response

```json theme={null}
{
  "strategy_id": "strat_01HQKX2J3K4M5N6P7R8S9T0V1Y",
  "version": 1,
  "tenant_id": "tnt_01HQKX2J3K4M5N6P7R8S9T0V1X",
  "spec": {
    "name": "NVDA momentum (50/200 SMA cross)",
    "universe": { "type": "single", "symbol": "NVDA" },
    "signal": { "type": "sma_cross", "fast": 50, "slow": 200, "direction": "long_only" },
    "weights": { "type": "fixed_fraction", "fraction": 1.0 },
    "risk": { "max_weight": 1.0, "stop_loss_pct": null, "max_positions": 1 },
    "execution": { "slippage_bps": 5, "fee_bps": 1 },
    "horizon": "daily"
  },
  "content_hash": "sha256:a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2",
  "created_at": "2025-01-15T14:33:12.001Z"
}
```

## Validation errors

If the spec fails validation, `propose_strategy` returns a structured error with the JSON Schema path that failed:

```json theme={null}
{
  "error": "spec_validation_failed",
  "details": [
    {
      "path": "signal.fast",
      "message": "must be less than signal.slow"
    }
  ]
}
```

The agent should fix the spec and re-call `propose_strategy`. No partial state is stored on validation failure.

<Tip>
  If you want to iterate on a strategy, propose multiple versions under the same logical name. The `strategy_id` will differ per proposal unless you explicitly resume — but grouping by `name` in the web UI makes comparison easy.
</Tip>

## Related

* [StrategySpec reference](/strategy/spec) — the full schema with all supported primitives
* [Backtest tools](/tools/backtest) — run a deterministic backtest on a stored spec
* [Approvals](/tools/approvals) — promote a backtested strategy to paper trading
