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
What it does
- Validates the
specagainst the frozenStrategySpecJSON Schema. Unknown fields, malformed values, or missing required keys are rejected with a detailed error. - Stamps the spec with the caller’s
tenant_id. This cannot be overridden — the tenant is taken from the authenticated session. - Stores the spec as an immutable, versioned row. The
strategy_idis stable across versions; theversionincrements on each proposal. Prior versions are never mutated or deleted. - Returns the stored spec with its
strategy_id,version,created_at, and a content hash.
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.Return value
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
Full example: momentum strategy on NVDA
How the engine interprets this
universe— the engine resolves the universe to a concrete set of symbols.singlemeans one symbol;screenmeans run a screener;indexmeans an index constituent list.signal— the engine computes the signal series.sma_crosswithfast=50,slow=200means: go long when the 50-day SMA crosses above the 200-day SMA; exit when it crosses below.long_onlyprevents shorting.weights— the engine sizes positions.fixed_fractionwithfraction=1.0means allocate 100% of capital to the signal.risk— the engine applies risk constraints after sizing.max_weightcaps any single position;stop_loss_pctexits on a drawdown threshold;max_positionslimits breadth.execution— the engine applies slippage and fees on every fill.5bpsslippage and1bpfees are conservative defaults for liquid US equities.horizon— the engine rebalances at this frequency.dailymeans the signal is evaluated every trading day.
Why a closed schema
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.
- 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_idandversioncan 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
versionon the samestrategy_id. The old version is preserved for comparison.
Example call
Example response
Validation errors
If the spec fails validation,propose_strategy returns a structured error with the JSON Schema path that failed:
propose_strategy. No partial state is stored on validation failure.
Related
- StrategySpec reference — the full schema with all supported primitives
- Backtest tools — run a deterministic backtest on a stored spec
- Approvals — promote a backtested strategy to paper trading