Strategies API
Preview, save, and read strategies and their backtest results.
A saved strategy is an immutable trading program authored with the Strategy Builder (or the AI assistant) that owns its backtest results — there is no separate backtest object and there are no /backtests* endpoints. See Strategy results for the concepts. Most endpoints require authentication; reading a public strategy (and its results) does not.
The model
- Keyed by source hash. A form
configcompiles to a deterministic program, and every distinct configuration is identified by itssource_hash— the key the preview and WebSocket endpoints use. Strategies are immutable — to change one, save a new configuration (a different hash). - Private by default, public means "anyone with the link". Each saved strategy carries a
privacyflag —private(the default) orpublic. Public is unlisted, not discoverable: there is no directory of public strategies, nothing crawls or lists them, and the id is a random UUID that can't be guessed — so a public strategy is reachable only by someone you hand the link to. Once they have it, any user (or an unauthenticated visitor) can view and clone it; a private strategy is visible only to you. Flip it any time withPATCH /strategies/{id}. - Results are computed the same way for every strategy: every trading session, run independently at a fixed $100,000 per-session capital, fee-free. Fees are applied at read time from the viewer's fee schedule (the default schedule for unauthenticated visitors), so results always render with your costs.
- Full history. Results cover every trading session, and newly added sessions can be appended at any time. Every results response labels the window (
window_label— alwaysall trading sessions— pluswindow_from,window_to). - No run credits. The backtester is protected by a per-user cap of 3 concurrently-executing runs (
429 too_many_active_backtests— retry with backoff) and a priority queue. Only the session drill-in draws rate-limit credits.
Endpoints
| Method | Path | Purpose |
|---|---|---|
POST |
/strategies/preview |
Preview a config (a persisted backtest) |
GET |
/strategies/preview/{source_hash}/results |
Read preview results (pre-save, heartbeat) |
POST |
/strategies/preview/abandon |
Abandon a preview |
GET |
/strategies |
List your saved strategies (the leaderboard) |
POST |
/strategies |
Save (publish) a strategy |
GET |
/strategies/{id} |
Get the logic + results snapshot |
PATCH |
/strategies/{id} |
Update per-user metadata |
DELETE |
/strategies/{id} |
Remove the strategy from your list |
GET |
/strategies/{id}/results/days |
Per-session day list |
GET |
/strategies/{id}/results/days.csv |
The day list as a CSV attachment |
GET |
/strategies/{id}/results/days/{date} |
Session drill-in (recomputed on demand) |
POST |
/strategies/{id}/results/update |
Append newly added sessions |
POST |
/strategies/assistant/drafts |
Create an AI-assistant draft |
GET |
/strategies/assistant/drafts |
List your drafts |
GET |
/strategies/assistant/drafts/{id} |
Get a draft + chat transcript |
POST |
/strategies/assistant/drafts/{id}/messages |
Send a message (run one turn) |
POST |
/strategies/assistant/drafts/{id}/cancel |
Cancel the in-flight turn |
POST |
/strategies/assistant/drafts/{id}/fork |
Clone a draft into a new editable one |
DELETE |
/strategies/assistant/drafts/{id} |
Discard a draft |
Preview a configuration
POST /strategies/preview is the builder's per-change action. There is no ephemeral dry-run: every distinct configuration is a real, persisted backtest. The call validates the config, ensures the backtest for its hash exists, starts (or resumes) the coverage run when sessions are missing, registers your interest, and returns the current fee-overlaid snapshot — instant for a configuration that already has results (one you previewed or saved before).
curl -s https://api.0dtespx.com/strategies/preview \
-X POST -H "Authorization: $TOKEN" -H 'Content-Type: application/json' \
-d '{
"config": {
"legs": [
{"direction":"sell","type":"put","qty":1,"strike":{"method":"delta","value":0.3}},
{"direction":"buy","type":"put","qty":1,"strike":{"method":"delta","value":0.2}}
],
"entry": {"time":"10:00"},
"exit": {"profit_target_pct":50,"time":"15:55"}
}
}'
entry.time is a single entry time — a strategy opens one trade per session. For several entries in a day, save one strategy per entry time and combine them in a portfolio.
Every saved strategy also exposes a derived, declarative entry window — entry: {start_time, end_time, days_of_week?} (ET; days_of_week uses Monday=0 … Sunday=6, absent = every trading day) — on GET /strategies/{id}. For a builder strategy it is the form's entry time plus one minute (10:00 → {"start_time":"10:00","end_time":"10:01"}); an AI-assistant strategy can declare either a specific time or a genuine range ("try to enter between 09:35 and 10:00"). Bots use it to refuse session starts after the window has passed.
The response carries the generated artifacts and a snapshot with source_hash, exec_status, coverage counters (covered_days, total_sessions), the labeled window, and the folded metrics so far. When you change a parameter, send the new config with abandon_hash set to the previous source_hash — the superseded run stops at the next session boundary (completed sessions are kept; returning to that configuration resumes where it left off).
Errors: a malformed config returns 400 invalid_config with a fields map keyed by dotted config path — nothing is created. 429 too_many_active_backtests means you already have 3 runs executing; retry with backoff (a steady builder session never trips this, since each change abandons the previous run first).
Read preview results
GET /strategies/preview/{source_hash}/results is the pre-save read path: the full day list and folded summary, net of your fee schedule. Calling it also refreshes your interest heartbeat — an unsaved preview keeps running only while you're watching (interest expires 15 minutes after the last fetch; saved strategies always run to completion).
curl -s https://api.0dtespx.com/strategies/preview/$HASH/results -H "Authorization: $TOKEN"
Poll until exec_status is idle and covered_days == total_sessions — or subscribe to the backtest_events WebSocket channel with the source_hash and read each frame's data.snapshot (the full fee-overlaid results snapshot; the HTTP endpoint backs the initial load + reconnect). Access requires live interest in the hash (from a recent preview) or a saved strategy of yours with this source — otherwise 404.
When you leave the builder without saving, POST /strategies/preview/abandon with {"source_hash": "…"} drops your interest explicitly (best-effort, always 204 — the 15-minute TTL backstops it).
Save (publish)
POST /strategies publishes the immutable strategy for the hash and keeps the backtest already running or done for it — a config you just previewed publishes with its results intact, no recompute.
curl -s https://api.0dtespx.com/strategies \
-X POST -H "Authorization: $TOKEN" -H 'Content-Type: application/json' \
-d '{"config": { … }, "privacy": "public"}'
Returns 201 with your saved-strategy id, the source_hash, the saved privacy, and the current results snapshot. Saving never trips the run cap — at the cap the coverage run is parked and starts automatically when one of your runs finishes. Re-saving a configuration you previously removed resurrects the same id with its results.
privacy is optional and defaults to private. Pass "public" to publish on save, or leave it off and publish later from the strategy page (or via PATCH).
AI assistant drafts
The AI assistant authors strategies through a per-user draft instead of a form config. Create one with POST /strategies/assistant/drafts (it seeds a canonical short iron condor and returns the draft id + the initial snapshot + source_hash), then converse with POST /strategies/assistant/drafts/{id}/messages (returns 202 {turn_id}; the reply, the updated preview hash, and the refreshed description + risk scenarios stream over the assistant_draft_events WebSocket channel). The live preview is read through the same GET /strategies/preview/{source_hash}/results endpoint as the Builder, keyed by the draft's current source_hash.
To save a draft, call POST /strategies with { "draft_id": "<uuid>", "privacy": "private" } instead of {config} — it publishes the draft's current source as an origin: "assistant" strategy and returns the same {id, source_hash, privacy, snapshot} envelope.
To clone an assistant strategy, POST /strategies/assistant/drafts/{id}/fork: because a saved conversation is frozen, cloning duplicates that conversation (its strategy bundle and chat transcript) into a new editable draft you own, so you can keep chatting and save a variant while the original is untouched. It returns the same envelope as create; it's owner-only (a draft you don't own returns 404).
List and read
curl -s https://api.0dtespx.com/strategies -H "Authorization: $TOKEN"
curl -s https://api.0dtespx.com/strategies/$STRAT_ID -H "Authorization: $TOKEN"
GET /strategies is the leaderboard: every saved strategy with its full-history headline metrics (return, Sharpe, max drawdown, win rate — net of the default fee schedule), coverage counters, and staleness (new_sessions_available), ordered by updated_at descending. The generated source is never included.
GET /strategies/{id} returns the immutable logic (config, generated description, summary, risks) plus the results snapshot over all trading sessions, net of the viewer's fee schedule, with the labeled window. origin is "builder" or "assistant"; for an assistant-authored strategy config is null (it was written as source, not a form config, so it can't be opened in the Builder). The payload also carries privacy and is_owner (whether the caller owns this link) so a client can show owner-only controls. For your own assistant strategy it additionally carries draft_id — the originating conversation, used to route the Clone action back to it (owner-only; never returned to others). Auth is optional: a public strategy is readable by anyone holding the link (including unauthenticated visitors) — but it's unlisted, so they have to be given the link; a private strategy you don't own returns 404 — its existence is never revealed. Viewing as an authenticated user keeps an in-flight run's interest fresh.
Per-session results
curl -s https://api.0dtespx.com/strategies/$STRAT_ID/results/days -H "Authorization: $TOKEN"
curl -sOJ https://api.0dtespx.com/strategies/$STRAT_ID/results/days.csv -H "Authorization: $TOKEN"
/results/days returns the full day list — one row per trading session: date, status (completed | skipped | failed | halted), gross_pnl, fees (your schedule), net_pnl, and order counts — plus the window bounds. The .csv variant renders the same list as an attachment with two extra engine columns: intraday_max_dd (fee-free intraday max drawdown) and spx_close, plus error_message and halt_reason.
Drill into one session
curl -s https://api.0dtespx.com/strategies/$STRAT_ID/results/days/2025-01-15 -H "Authorization: $TOKEN"
The heavy per-session detail — orders, transactions (each with an overlay_fee from your schedule), decision log, and the second-by-second intraday equity curve — is not stored. It is recomputed by re-running that single session through the engine. Expect a response time of seconds, not milliseconds; the result is cached server-side for a few minutes and costs 10 rate-limit credits.
The drill-in's headline net_pnl is gross minus both fees (your schedule) and slippage (your per-contract slippage applied to the day's option fills). The response always includes the second-by-second intraday_curve, cost-adjusted for fees + slippage. reconciliation_warning: true means the recomputed gross P&L no longer matches the stored result — the engine changed since the session was computed, and the stored results will be recomputed.
Update with new sessions
curl -s -X POST https://api.0dtespx.com/strategies/$STRAT_ID/results/update -H "Authorization: $TOKEN"
As new market days become available, results go stale (new_sessions_available > 0). An update runs only the missing sessions and refolds the summaries — nothing already computed is re-run. Returns 202 {"status":"queued"}. It's allowed whenever new sessions exist (update_allowed simply mirrors that). Subject to the 3-active-runs cap (429). Failed sessions are retried by an explicit update.
Manage
curl -s -X PATCH https://api.0dtespx.com/strategies/$STRAT_ID \
-H "Authorization: $TOKEN" -H 'Content-Type: application/json' \
-d '{"description":"my spread","privacy":"public"}'
curl -s -X DELETE https://api.0dtespx.com/strategies/$STRAT_ID -H "Authorization: $TOKEN"
PATCH (owner only) touches per-user metadata — a description override and the privacy flag ("public" publishes, "private" unpublishes); each field is applied only when present, and the logic itself is immutable (clone to change it). Publishing only makes the strategy reachable by link — it's never listed or discoverable — so to actually share it, send someone its URL (https://www.0dtespx.com/strategies/{id}); the strategy page has a share button that copies the link for you. DELETE removes the strategy from your list; re-saving the exact same configuration restores it with the same id and its results.
Clone someone else's strategy
For a Builder strategy there is no clone endpoint — cloning is just saving a configuration you read from a public strategy. Fetch a public strategy with GET /strategies/{id}, take its config, and POST /strategies it (optionally tweaked). You get your own private link and your own results overlay; the original owner is unaffected. Because config is only returned for strategies you can read, you can only clone public strategies (or your own).
An assistant strategy has no config (it was written as source), so it's cloned differently: POST /strategies/assistant/drafts/{id}/fork forks its conversation into a new editable draft (see AI assistant drafts above). This is owner-only — it copies your private chat, so it's never available to someone viewing a strategy you shared.
Next: track run progress over the WebSocket backtest_events channel.