{"openapi":"3.1.0","info":{"title":"Tektii API","description":"RESTful API for the Tektii algorithmic trading backtesting platform.","contact":{"name":"Tektii Team"},"license":{"name":"MIT","identifier":"MIT"},"version":"1.0.0"},"servers":[{"url":"https://api.tektii.com","description":"Production"}],"paths":{"/v1/api-keys":{"get":{"tags":["api-keys"],"summary":"List all API keys for the authenticated user","operationId":"list_api_keys","responses":{"200":{"description":"List of API keys","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListApiKeysResponseDTO"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"security":[{"bearer_auth":[]}]},"post":{"tags":["api-keys"],"summary":"Create a new API key","operationId":"create_api_key","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateApiKeyRequestDTO"}}},"required":true},"responses":{"201":{"description":"API key created (key value only shown once)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateApiKeyResponseDTO"}}}},"400":{"description":"Invalid request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"security":[{"bearer_auth":[]}]}},"/v1/api-keys/{prefix}":{"delete":{"tags":["api-keys"],"summary":"Revoke an API key by its prefix","operationId":"revoke_api_key","parameters":[{"name":"prefix","in":"path","description":"API key prefix (8 characters)","required":true,"schema":{"type":"string"}}],"responses":{"204":{"description":"API key revoked"},"400":{"description":"Invalid prefix format","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"API key not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"security":[{"bearer_auth":[]}]}},"/v1/configurations":{"get":{"tags":["configurations"],"summary":"List configurations for the current user","operationId":"list_configurations","parameters":[{"name":"limit","in":"query","description":"Number of items to return (1-100, default: 20)","required":false,"schema":{"type":"integer","format":"int32","minimum":0}},{"name":"cursor","in":"query","description":"Pagination cursor for next page","required":false,"schema":{"type":"string"}},{"name":"activeOnly","in":"query","description":"Filter to active configurations only","required":false,"schema":{"type":"boolean"}}],"responses":{"200":{"description":"List of configurations","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListScenarioConfigurationsResponseDTO"}}}},"400":{"description":"Invalid parameters","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"security":[{"bearer_auth":[]},{"api_key":[]}]},"post":{"tags":["configurations"],"summary":"Create a new configuration (user-scoped)","operationId":"create_configuration","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateScenarioConfigurationRequestDTO"}}},"required":true},"responses":{"201":{"description":"Configuration created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateScenarioConfigurationResponseDTO"}}}},"400":{"description":"Invalid request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"security":[{"bearer_auth":[]},{"api_key":[]}]}},"/v1/configurations/{config_id}":{"get":{"tags":["configurations"],"summary":"Get a configuration by ID","operationId":"get_configuration","parameters":[{"name":"config_id","in":"path","description":"Configuration UUID","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Configuration found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GetScenarioConfigurationResponseDTO"}}}},"400":{"description":"Invalid ID format","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Configuration not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"security":[{"bearer_auth":[]},{"api_key":[]}]},"put":{"tags":["configurations"],"summary":"Update a configuration","operationId":"update_configuration","parameters":[{"name":"config_id","in":"path","description":"Configuration UUID","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateScenarioConfigurationRequestDTO"}}},"required":true},"responses":{"200":{"description":"Configuration updated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateScenarioConfigurationResponseDTO"}}}},"400":{"description":"Invalid request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Configuration not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"security":[{"bearer_auth":[]},{"api_key":[]}]},"delete":{"tags":["configurations"],"summary":"Delete a configuration","operationId":"delete_configuration","parameters":[{"name":"config_id","in":"path","description":"Configuration UUID","required":true,"schema":{"type":"string"}}],"responses":{"204":{"description":"Configuration deleted"},"400":{"description":"Invalid ID format","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Configuration not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"Configuration is linked to strategy auto-run bindings","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"security":[{"bearer_auth":[]},{"api_key":[]}]}},"/v1/configurations/{config_id}/clone":{"post":{"tags":["configurations"],"summary":"Clone a configuration","operationId":"clone_configuration","parameters":[{"name":"config_id","in":"path","description":"Configuration UUID to clone","required":true,"schema":{"type":"string"}}],"responses":{"201":{"description":"Configuration cloned","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateScenarioConfigurationResponseDTO"}}}},"400":{"description":"Invalid ID format","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Configuration not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"security":[{"bearer_auth":[]},{"api_key":[]}]}},"/v1/events":{"get":{"tags":["events"],"summary":"Lists all available events.","description":"# Endpoint\n\n`GET /v1/events`\n\n# Response\n\nReturns a JSON array of all event definitions.\n\n# Example\n\n```bash\ncurl -X GET http://localhost:8080/v1/events\n```\nList all available events","operationId":"list_events","responses":{"200":{"description":"List of all available events","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListEventsResponseDTO"}}}}}}},"/v1/instruments":{"get":{"tags":["instruments"],"summary":"GET /v1/instruments","description":"Lists all available trading instruments with optional filtering.\n\n# Query Parameters\n\n- `market` (optional, case-insensitive): Filter by market type. Valid: CRYPTO, FOREX\n- `status` (optional, case-insensitive): Filter by availability status. Valid: `available`, `coming_soon`\n\n# Response\n\nReturns a JSON response with instruments:\n```json\n{\n  \"data\": [\n    {\n      \"symbol\": \"C:BTCUSD\",\n      \"name\": \"Bitcoin/US Dollar\",\n      \"market\": \"CRYPTO\",\n      \"dataSymbol\": \"BTCUSD\",\n      \"underlying\": \"BTC\",\n      \"denomination\": \"USD\"\n    },\n    {\n      \"symbol\": \"F:EURUSD\",\n      \"name\": \"Euro/US Dollar\",\n      \"market\": \"FOREX\",\n      \"dataSymbol\": \"EUR-USD\",\n      \"underlying\": \"EUR\",\n      \"denomination\": \"USD\"\n    }\n  ]\n}\n```\n\n# Errors\n\n- `400 Bad Request` - If an invalid market or status filter value is provided\n- `500 Internal Server Error` - If instruments cannot be loaded\nList all available trading instruments with optional market and status filters","operationId":"list_instruments","parameters":[{"name":"market","in":"query","description":"Filter by market type (CRYPTO, FOREX, FUTURES, STOCKS). Case-insensitive. Recognised markets with no instruments yet return an empty list.","required":false,"schema":{"type":"string"}},{"name":"status","in":"query","description":"Filter by availability status (available, coming_soon). Case-insensitive.","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"List of instruments","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListInstrumentsResponse"}}}},"400":{"description":"Invalid filter value","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/instruments/markets":{"get":{"tags":["instruments"],"summary":"GET /v1/instruments/markets","description":"Lists all available markets.\n\n# Response\n\nReturns a JSON response with all markets:\n```json\n{\n  \"data\": [\n    {\n      \"id\": \"CRYPTO\",\n      \"name\": \"Cryptocurrency\"\n    }\n  ]\n}\n```\n\n# Errors\n\nThis endpoint does not return errors as markets are hardcoded.\nList all available markets","operationId":"list_markets","responses":{"200":{"description":"List of all markets","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListMarketsResponse"}}}}}}},"/v1/instruments/search":{"get":{"tags":["instruments"],"summary":"GET /v1/instruments/search?q=...","description":"Searches for instruments by partial symbol or name match.\n\n# Query Parameters\n\n- `q` - Search query (case-insensitive, searches both symbol and name)\n\n# Response\n\nReturns a JSON response with matching instruments:\n```json\n{\n  \"data\": [\n    {\n      \"symbol\": \"C:BTCUSD\",\n      \"name\": \"Bitcoin/US Dollar\",\n      \"market\": \"CRYPTO\",\n      \"dataSymbol\": \"BTCUSD\"\n    }\n  ]\n}\n```\n\n# Errors\n\n- `400 Bad Request` - If query parameter is empty\n- `500 Internal Server Error` - If instruments cannot be loaded\nSearch instruments by partial symbol or name match","operationId":"search_instruments_handler","parameters":[{"name":"q","in":"query","description":"Search query (case-insensitive, max 100 chars)","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"List of matching instruments","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SearchInstrumentsResponse"}}}},"400":{"description":"Invalid query","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/instruments/{symbol}":{"get":{"tags":["instruments"],"summary":"GET /v1/instruments/:symbol","description":"Gets a specific instrument by its symbol.\n\n# Path Parameters\n\n- `symbol` - The instrument symbol (e.g., \"F:EURUSD\")\n\n# Response\n\nReturns a JSON response with the instrument:\n```json\n{\n  \"data\": {\n    \"symbol\": \"C:BTCUSD\",\n    \"name\": \"Bitcoin/US Dollar\",\n    \"market\": \"CRYPTO\",\n    \"dataSymbol\": \"BTCUSD\"\n  }\n}\n```\n\n# Errors\n\n- `404 Not Found` - If no instrument with the given symbol exists\n- `500 Internal Server Error` - If instruments cannot be loaded\nGet an instrument by symbol","operationId":"get_instrument","parameters":[{"name":"symbol","in":"path","description":"Instrument symbol (e.g., 'F:EURUSD')","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Instrument found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InstrumentResponse"}}}},"404":{"description":"Instrument not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/scenarios/compare":{"post":{"tags":["scenarios"],"summary":"Compare multiple scenarios by fetching metrics for each","operationId":"compare_scenarios","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CompareRequest"}}},"required":true},"responses":{"200":{"description":"Comparison metrics returned successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ComparisonResponseDTO"}}}},"400":{"description":"Invalid request (empty list, too many, duplicates, or non-completed scenario)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"One or more scenarios not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"security":[{"bearer_auth":[]},{"api_key":[]}]}},"/v1/scenarios/{scenario_id}":{"get":{"tags":["scenarios"],"summary":"Get a scenario by ID without the owning strategy in the path.","description":"Scenarios are keyed on their own id within an owner, so this flat path\nreturns exactly the same payload as the nested\n`/v1/strategies/{strategy_id}/scenarios/{scenario_id}` route. It exists so\ncallers that only hold a scenario id (e.g. result pollers) don't have to\nalso carry the strategy id.","operationId":"get_scenario_by_id","parameters":[{"name":"scenario_id","in":"path","description":"Scenario UUID","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Scenario found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GetScenarioResponseDTO"}}}},"400":{"description":"Invalid ID format","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Scenario not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"security":[{"bearer_auth":[]},{"api_key":[]}]}},"/v1/strategies":{"get":{"tags":["strategies"],"summary":"List all strategies for the authenticated user","operationId":"list_strategies","parameters":[{"name":"limit","in":"query","description":"Number of items to return (1-100, default: 20)","required":false,"schema":{"type":"integer","format":"int32","minimum":0}},{"name":"cursor","in":"query","description":"Pagination cursor for next page","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"List of strategies","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListStrategiesResponseDTO"}}}},"400":{"description":"Invalid parameters","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"security":[{"bearer_auth":[]},{"api_key":[]}]},"post":{"tags":["strategies"],"summary":"Create a new strategy","operationId":"create_strategy","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateStrategyRequestDTO"}}},"required":true},"responses":{"201":{"description":"Strategy created successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateStrategyResponseDTO"}}}},"400":{"description":"Invalid request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"security":[{"bearer_auth":[]},{"api_key":[]}]}},"/v1/strategies/{strategy_id}":{"get":{"tags":["strategies"],"summary":"Get a strategy by ID","operationId":"get_strategy","parameters":[{"name":"strategy_id","in":"path","description":"Strategy UUID","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Strategy found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GetStrategyResponseDTO"}}}},"400":{"description":"Invalid strategy ID format","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Strategy not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"security":[{"bearer_auth":[]},{"api_key":[]}]},"put":{"tags":["strategies"],"summary":"Update a strategy's name and/or description","operationId":"update_strategy","parameters":[{"name":"strategy_id","in":"path","description":"Strategy UUID","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateStrategyRequestDTO"}}},"required":true},"responses":{"200":{"description":"Strategy updated successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateStrategyResponseDTO"}}}},"400":{"description":"Invalid request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Strategy not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"security":[{"bearer_auth":[]},{"api_key":[]}]},"delete":{"tags":["strategies"],"summary":"Delete (archive) a strategy","operationId":"delete_strategy","parameters":[{"name":"strategy_id","in":"path","description":"Strategy UUID","required":true,"schema":{"type":"string"}}],"responses":{"204":{"description":"Strategy archived successfully"},"400":{"description":"Invalid strategy ID format","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Strategy not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"Strategy already archived","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"security":[{"bearer_auth":[]},{"api_key":[]}]}},"/v1/strategies/{strategy_id}/auto-run-configs":{"put":{"tags":["strategies"],"summary":"Set auto-run configurations for a strategy","operationId":"set_auto_run_configs","parameters":[{"name":"strategy_id","in":"path","description":"Strategy UUID","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SetAutoRunConfigsRequestDTO"}}},"required":true},"responses":{"200":{"description":"Auto-run configs set successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SetAutoRunConfigsResponseDTO"}}}},"400":{"description":"Invalid request or config IDs","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Strategy not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"security":[{"bearer_auth":[]},{"api_key":[]}]}},"/v1/strategies/{strategy_id}/scenarios":{"get":{"tags":["scenarios"],"summary":"List scenarios for a strategy with optional filters","operationId":"list_scenarios","parameters":[{"name":"strategy_id","in":"path","description":"Strategy UUID","required":true,"schema":{"type":"string"}},{"name":"limit","in":"query","description":"Number of items to return (1-100, default: 20)","required":false,"schema":{"type":"integer","format":"int32","minimum":0}},{"name":"cursor","in":"query","description":"Pagination cursor for next page","required":false,"schema":{"type":"string"}},{"name":"state","in":"query","description":"Filter by state: QUEUED, PENDING, RUNNING, COMPLETE, FAILED, CANCELLED","required":false,"schema":{"type":"string"}},{"name":"strategyVersionId","in":"query","description":"Filter by version ID","required":false,"schema":{"type":"string"}},{"name":"isCompleted","in":"query","description":"Filter by completion: 'true' or 'false'","required":false,"schema":{"type":"string"}},{"name":"createdAfter","in":"query","description":"Filter by creation date (RFC3339)","required":false,"schema":{"type":"string"}},{"name":"createdBefore","in":"query","description":"Filter by creation date (RFC3339)","required":false,"schema":{"type":"string"}},{"name":"instruments","in":"query","description":"Filter by instruments (comma-separated)","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"List of scenarios","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListScenariosResponseDTO"}}}},"400":{"description":"Invalid parameters","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"security":[{"bearer_auth":[]},{"api_key":[]}]},"post":{"tags":["scenarios"],"summary":"Create a new backtest scenario","operationId":"create_scenario","parameters":[{"name":"strategy_id","in":"path","description":"Strategy UUID","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateScenarioRequestDTO"}}},"required":true},"responses":{"201":{"description":"Scenario created and queued for execution","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateScenarioResponseDTO"}}}},"400":{"description":"Invalid request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Quota exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Strategy version not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"security":[{"bearer_auth":[]},{"api_key":[]}]}},"/v1/strategies/{strategy_id}/scenarios/batch":{"post":{"tags":["scenarios"],"summary":"Create multiple backtest scenarios in a single request","operationId":"batch_create_scenarios","parameters":[{"name":"strategy_id","in":"path","description":"Strategy UUID","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BatchCreateScenarioRequestDTO"}}},"required":true},"responses":{"201":{"description":"Scenarios created and queued for execution","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BatchCreateScenarioResponseDTO"}}}},"400":{"description":"Invalid request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Quota exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Strategy version not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"security":[{"bearer_auth":[]},{"api_key":[]}]}},"/v1/strategies/{strategy_id}/scenarios/from-configs":{"post":{"tags":["scenarios"],"summary":"Run saved configuration(s) as scenarios against a strategy version.","description":"Resolves each saved config (owner-scoped), then creates one scenario per\nconfig using the config's subscriptions / window / position mode / initial\ncapital and the supplied version. Each scenario records its source config id\nfor provenance. Configs are never mutated and carry no strategy\nbinding — strategies stay isolated.\n\nBad config ids (404) and invalid merged env (400) are rejected atomically up\nfront, before any scenario is launched. The launch itself is **best-effort**:\neach config reserves its own quota slot independently, so a failure on one\nconfig (e.g. quota exhausted mid-loop) keeps the scenarios already created for\nthe others rather than blanket-failing the request. Returns `201` with one\nlabelled result per requested config in order — `created` carries its QUEUED\nscenario, `failed` carries the error. The call errors as a whole only when no\nscenario was created at all.","operationId":"create_scenarios_from_configs","parameters":[{"name":"strategy_id","in":"path","description":"Strategy UUID","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/FromConfigsRequestDTO"}}},"required":true},"responses":{"201":{"description":"Scenarios created from configurations","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FromConfigsResponseDTO"}}}},"400":{"description":"Invalid request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Quota exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Configuration or strategy version not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"security":[{"bearer_auth":[]},{"api_key":[]}]}},"/v1/strategies/{strategy_id}/scenarios/{scenario_id}":{"get":{"tags":["scenarios"],"summary":"Get a scenario by ID","operationId":"get_scenario","parameters":[{"name":"strategy_id","in":"path","description":"Strategy UUID","required":true,"schema":{"type":"string"}},{"name":"scenario_id","in":"path","description":"Scenario UUID","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Scenario found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GetScenarioResponseDTO"}}}},"400":{"description":"Invalid ID format","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Scenario not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"security":[{"bearer_auth":[]},{"api_key":[]}]},"delete":{"tags":["scenarios"],"summary":"Delete a scenario","operationId":"delete_scenario","parameters":[{"name":"strategy_id","in":"path","description":"Strategy UUID","required":true,"schema":{"type":"string"}},{"name":"scenario_id","in":"path","description":"Scenario UUID","required":true,"schema":{"type":"string"}}],"responses":{"204":{"description":"Scenario deleted successfully"},"400":{"description":"Invalid ID format","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Scenario not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"security":[{"bearer_auth":[]},{"api_key":[]}]}},"/v1/strategies/{strategy_id}/scenarios/{scenario_id}/cancel":{"post":{"tags":["scenarios"],"summary":"Cancel a pending or running scenario","operationId":"cancel_scenario","parameters":[{"name":"strategy_id","in":"path","description":"Strategy UUID","required":true,"schema":{"type":"string"}},{"name":"scenario_id","in":"path","description":"Scenario UUID","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Scenario cancelled successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GetScenarioResponseDTO"}}}},"400":{"description":"Scenario cannot be cancelled (already terminal)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Scenario not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"security":[{"bearer_auth":[]},{"api_key":[]}]}},"/v1/strategies/{strategy_id}/scenarios/{scenario_id}/metadata":{"get":{"tags":["scenarios"],"summary":"Download scenario metadata file (compressed)","operationId":"get_scenario_metadata","parameters":[{"name":"strategy_id","in":"path","description":"Strategy UUID","required":true,"schema":{"type":"string"}},{"name":"scenario_id","in":"path","description":"Scenario UUID","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Metadata file (zstd compressed)","content":{"application/octet-stream":{}}},"400":{"description":"Invalid ID format or scenario not completed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Scenario not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"security":[{"bearer_auth":[]},{"api_key":[]}]}},"/v1/strategies/{strategy_id}/scenarios/{scenario_id}/metrics":{"get":{"tags":["scenarios"],"summary":"Get calculated performance metrics for a completed scenario","operationId":"get_scenario_metrics","parameters":[{"name":"strategy_id","in":"path","description":"Strategy UUID","required":true,"schema":{"type":"string"}},{"name":"scenario_id","in":"path","description":"Scenario UUID","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Metrics calculated successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ScenarioMetricsResponseDTO"}}}},"400":{"description":"Invalid ID format or scenario not completed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Scenario not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"security":[{"bearer_auth":[]},{"api_key":[]}]}},"/v1/strategies/{strategy_id}/scenarios/{scenario_id}/timeseries":{"get":{"tags":["scenarios"],"summary":"Download scenario timeseries file (compressed)","operationId":"get_scenario_timeseries","parameters":[{"name":"strategy_id","in":"path","description":"Strategy UUID","required":true,"schema":{"type":"string"}},{"name":"scenario_id","in":"path","description":"Scenario UUID","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Timeseries file (zstd compressed)","content":{"application/octet-stream":{}}},"400":{"description":"Invalid ID format or scenario not completed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Scenario not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"security":[{"bearer_auth":[]},{"api_key":[]}]}},"/v1/strategies/{strategy_id}/scenarios/{scenario_id}/trades":{"get":{"tags":["scenarios"],"summary":"Download scenario trades file (compressed)","operationId":"get_scenario_trades","parameters":[{"name":"strategy_id","in":"path","description":"Strategy UUID","required":true,"schema":{"type":"string"}},{"name":"scenario_id","in":"path","description":"Scenario UUID","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Trades file (zstd compressed)","content":{"application/octet-stream":{}}},"400":{"description":"Invalid ID format or scenario not completed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Scenario not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"security":[{"bearer_auth":[]},{"api_key":[]}]}},"/v1/strategies/{strategy_id}/versions":{"get":{"tags":["versions"],"summary":"List all versions for a strategy","operationId":"list_strategy_versions","parameters":[{"name":"strategy_id","in":"path","description":"Strategy UUID","required":true,"schema":{"type":"string"}},{"name":"limit","in":"query","description":"Number of items to return (1-100, default: 20)","required":false,"schema":{"type":"integer","format":"int32","minimum":0}},{"name":"cursor","in":"query","description":"Pagination cursor for next page","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"List of versions","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListStrategyVersionsResponseDTO"}}}},"400":{"description":"Invalid parameters","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Strategy not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"security":[{"bearer_auth":[]},{"api_key":[]}]},"post":{"tags":["versions"],"summary":"Create a new strategy version","description":"Either provide `git_sha` to upload your own code, or `template_id` to create from a template.\n\nResponse always includes `data` with the created version. For `git_sha` flow, also includes\n`repositoryUrl` and `accessToken` for Docker image upload.","operationId":"create_strategy_version","parameters":[{"name":"strategy_id","in":"path","description":"Strategy UUID","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateStrategyVersionRequestDTO"}}},"required":true},"responses":{"201":{"description":"Version created successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateStrategyVersionResponseDTO"}}}},"400":{"description":"Invalid request or strategy ID","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Strategy or template not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"security":[{"bearer_auth":[]},{"api_key":[]}]}},"/v1/strategies/{strategy_id}/versions/{version_id}":{"get":{"tags":["versions"],"summary":"Get a strategy version by ID","operationId":"get_strategy_version","parameters":[{"name":"strategy_id","in":"path","description":"Strategy UUID","required":true,"schema":{"type":"string"}},{"name":"version_id","in":"path","description":"Version UUID","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Version found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GetStrategyVersionResponseDTO"}}}},"400":{"description":"Invalid ID format","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Version not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"security":[{"bearer_auth":[]},{"api_key":[]}]},"patch":{"tags":["versions"],"summary":"Set or clear the keep flag on a strategy version","description":"Marking a version keep exempts it from the 30-day auto-eviction and consumes\na billable kept-version slot (per-user, per-plan). Clearing keep returns it\nto the eviction pool and frees the slot. Owner-only.","operationId":"patch_strategy_version","parameters":[{"name":"strategy_id","in":"path","description":"Strategy UUID","required":true,"schema":{"type":"string"}},{"name":"version_id","in":"path","description":"Version UUID","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PatchStrategyVersionRequestDTO"}}},"required":true},"responses":{"200":{"description":"Keep flag updated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PatchStrategyVersionResponseDTO"}}}},"400":{"description":"Invalid ID format","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Kept-version quota exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Version not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"security":[{"bearer_auth":[]},{"api_key":[]}]}},"/v1/strategies/{strategy_id}/versions/{version_id}/image":{"delete":{"tags":["versions"],"summary":"Delete a strategy version's image (early eviction)","description":"Removes the image bytes from the registry now, ahead of the 30-day policy,\nand flips the version to the `evicted` tombstone state. The version record\nitself is preserved so the lineage graph can still render it. Owner-only.","operationId":"delete_strategy_version_image","parameters":[{"name":"strategy_id","in":"path","description":"Strategy UUID","required":true,"schema":{"type":"string"}},{"name":"version_id","in":"path","description":"Version UUID","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Image evicted; version tombstoned","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeleteStrategyVersionImageResponseDTO"}}}},"400":{"description":"Invalid ID format","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Version not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"security":[{"bearer_auth":[]},{"api_key":[]}]}},"/v1/templates":{"get":{"tags":["templates"],"summary":"GET /v1/templates","description":"Lists all available strategy templates.\n\n# Response\n\nReturns a JSON response with templates:\n```json\n{\n  \"data\": [\n    {\n      \"id\": \"ma-crossover\",\n      \"name\": \"Moving Average Crossover\",\n      \"description\": \"Buy when short MA crosses above long MA...\",\n      \"difficulty\": \"beginner\",\n      \"estimatedSetupTimeSeconds\": 30,\n      \"suggestedScenario\": {\n        \"instrument\": \"BTC-USD\",\n        \"period\": \"1y\",\n        \"granularity\": \"1h\"\n      }\n    }\n  ]\n}\n```","operationId":"list_templates","responses":{"200":{"description":"List of available strategy templates","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListTemplatesResponse"}}}}}}}},"components":{"schemas":{"ApiKeyDTO":{"type":"object","required":["name","status","prefix","createdAt"],"properties":{"createdAt":{"type":"string","format":"date-time"},"lastUsedAt":{"type":["string","null"],"format":"date-time"},"name":{"type":"string"},"prefix":{"type":"string"},"status":{"type":"string"}}},"AutoRunConfigsDTO":{"type":"object","description":"Auto-run configuration data","required":["strategyId","autoRunConfigIds"],"properties":{"autoRunConfigIds":{"type":"array","items":{"type":"string"}},"strategyId":{"type":"string"}}},"BatchCreateScenarioRequestDTO":{"type":"object","description":"Request to create multiple scenarios in a single batch","required":["strategyVersionId","scenarios"],"properties":{"env":{"type":"object","description":"Per-run env overrides applied to every scenario in the batch,\nas the shared base layer beneath each item's own `env`. Values are\nstored in plain text — do not put secrets here.","additionalProperties":{"type":"string"},"propertyNames":{"type":"string"}},"initialCapital":{"type":"number","format":"double","description":"Initial capital for all scenarios in the batch. Defaults to 100,000 if omitted."},"positionMode":{"$ref":"#/components/schemas/PositionMode","description":"Position mode for all scenarios in the batch. Defaults to \"netting\" if omitted."},"scenarios":{"type":"array","items":{"$ref":"#/components/schemas/BatchScenarioItemDTO"},"description":"Array of scenario specifications. At least one; the upper bound is the\nsubmitter's remaining subscription quota, enforced server-side."},"strategyVersionId":{"type":"string","description":"Strategy version ID to use for all scenarios in the batch"}},"additionalProperties":false},"BatchCreateScenarioResponseDTO":{"type":"object","description":"The outcome of every requested batch item (TEK-1119, TEK-1365).\n\n`data` holds the admitted runs (each `created` or `linked`), `dropped` names\nany item that was not admitted, and `requested` is the total submitted so the\ncaller can reconcile `requested == data.len() + dropped.len()` — no more\nsilently shrinking the count.","required":["data","requested","dropped"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/BatchScenarioResultDTO"},"description":"Admitted runs, in request order (deduped items labelled `linked`)."},"dropped":{"type":"array","items":{"$ref":"#/components/schemas/BatchDroppedItemDTO"},"description":"Items that were not admitted, each with its request index and reason."},"requested":{"type":"integer","description":"Total number of items in the submitted batch.","minimum":0}}},"BatchDroppedItemDTO":{"type":"object","description":"A requested batch item that was not admitted as a live run (TEK-1365).\n\nNamed by its 0-based position in the request so the caller can reconcile the\nset instead of guessing which items vanished. Near-always empty: the slow\nCloud Run job creation moved to the dispatch worker, so a transient infra\nblip there is retried, not dropped — only a rare submit-time failure lands\nhere.","required":["index","reason"],"properties":{"index":{"type":"integer","description":"0-based index of the dropped item in the requested `scenarios` array.","minimum":0},"reason":{"type":"string","description":"Sanitized, user-facing reason the item was not admitted."}}},"BatchScenarioItemDTO":{"type":"object","description":"Single scenario specification within a batch create request","required":["subscriptions","startTime","endTime"],"properties":{"costs":{"$ref":"#/components/schemas/CostsConfig","description":"Typed transaction-cost config (commission + slippage + funding) for this\nitem. Optional; defaults to all-zero (a frictionless backtest). Per-item\nso a batch can sweep cost assumptions — two items differing only in their\ncosts run as distinct backtests."},"endTime":{"type":"string","format":"date-time"},"env":{"type":"object","description":"Per-item env overrides, layered over the batch-level `env` (this item\nwins on a key clash). Lets each item of a parameter sweep carry a\ndistinct config so the runs don't collapse to one fingerprint.\nValues are stored in plain text — do not put secrets here.","additionalProperties":{"type":"string"},"propertyNames":{"type":"string"}},"startTime":{"type":"string","format":"date-time"},"subscriptions":{"type":"array","items":{"$ref":"#/components/schemas/InstrumentSubscriptionDTO"}}},"additionalProperties":false},"BatchScenarioOutcomeDTO":{"type":"string","description":"Whether a batch item produced a fresh run or deduped to an existing one.","enum":["created","linked"]},"BatchScenarioResultDTO":{"type":"object","description":"One requested batch item's outcome: the run it resolved to plus whether that\nrun was freshly created or linked from a dedup match.","required":["scenario","outcome"],"properties":{"outcome":{"$ref":"#/components/schemas/BatchScenarioOutcomeDTO"},"scenario":{"$ref":"#/components/schemas/ScenarioDTO"}}},"CompareRequest":{"type":"object","description":"Request body for comparing multiple scenarios","required":["scenarioIds"],"properties":{"scenarioIds":{"type":"array","items":{"type":"string"},"description":"Scenario IDs to compare (1-10 scenarios)"}}},"ComparisonData":{"type":"object","description":"Response data for scenario comparison","required":["scenarios"],"properties":{"scenarios":{"type":"array","items":{"$ref":"#/components/schemas/ScenarioMetricsDTO"},"description":"Metrics for each requested scenario"}}},"ComparisonResponseDTO":{"type":"object","description":"Response for scenario comparison","required":["data"],"properties":{"data":{"$ref":"#/components/schemas/ComparisonData"}}},"CostsConfig":{"type":"object","description":"The transaction-cost configuration for a single backtest scenario.\n\nGroups the three cost dimensions the engine simulates — commission,\nslippage, and overnight/perpetual funding — into one typed object carried on\nthe scenario. Every field defaults to its zero/off variant, so an absent or\ndefault `CostsConfig` is a frictionless backtest, identical to the\npre-costs behaviour.\n\nv1 is global per-scenario: a single model applies to every subscribed\ninstrument. Per-instrument overrides are a later increment.","properties":{"commission":{"$ref":"#/components/schemas/FeeConfig","description":"Per-trade commission model. Defaults to [`FeeConfig::Zero`]."},"funding":{"$ref":"#/components/schemas/FinancingModel","description":"Overnight / perpetual funding model. Defaults to\n[`FinancingModel::Disabled`]."},"slippage":{"$ref":"#/components/schemas/SlippageConfig","description":"Fill slippage model. Defaults to no slippage."}}},"CreateApiKeyData":{"type":"object","required":["apiKey","keyValue"],"properties":{"apiKey":{"$ref":"#/components/schemas/ApiKeyDTO"},"keyValue":{"type":"string"}}},"CreateApiKeyRequestDTO":{"type":"object","required":["name"],"properties":{"name":{"type":"string"}}},"CreateApiKeyResponseDTO":{"type":"object","required":["data"],"properties":{"data":{"$ref":"#/components/schemas/CreateApiKeyData"}}},"CreateScenarioConfigurationRequestDTO":{"type":"object","required":["name","subscriptions","startTime","endTime"],"properties":{"costs":{"$ref":"#/components/schemas/CostsConfig","description":"Typed transaction-cost config (commission + slippage + funding) inherited\nby scenario runs from this configuration. Optional; defaults to all-zero\n(a frictionless backtest)."},"description":{"type":["string","null"]},"endTime":{"type":"string","format":"date-time"},"env":{"type":"object","description":"Default env overrides for the strategy container (string keys to\nstring values). Per-run env layers on top at launch.\nValues are stored in plain text — do not put secrets here.","additionalProperties":{"type":"string"},"propertyNames":{"type":"string"}},"initialCapital":{"type":"number","format":"double"},"isActive":{"type":"boolean"},"name":{"type":"string"},"positionMode":{"$ref":"#/components/schemas/PositionMode"},"startTime":{"type":"string","format":"date-time"},"subscriptions":{"type":"array","items":{"$ref":"#/components/schemas/InstrumentSubscriptionDTO"}}},"additionalProperties":false},"CreateScenarioConfigurationResponseDTO":{"type":"object","required":["data"],"properties":{"data":{"$ref":"#/components/schemas/ScenarioConfigurationDTO"}}},"CreateScenarioRequestDTO":{"type":"object","required":["strategyVersionId","subscriptions","startTime","endTime"],"properties":{"costs":{"$ref":"#/components/schemas/CostsConfig","description":"Typed transaction-cost config (commission + slippage + funding) for the\nbacktest. Optional; defaults to all-zero (a frictionless backtest)."},"endTime":{"type":"string","format":"date-time"},"env":{"type":"object","description":"Per-run env overrides for the strategy container (string keys to\nstring values). Layered over the saved config's default env at launch.\nValues are stored in plain text — do not put secrets here.","additionalProperties":{"type":"string"},"propertyNames":{"type":"string"}},"initialCapital":{"type":"number","format":"double","description":"Initial capital for the backtest. Defaults to 100,000 if omitted."},"positionMode":{"$ref":"#/components/schemas/PositionMode","description":"Position mode for the backtest. Defaults to \"netting\" if omitted."},"startTime":{"type":"string","format":"date-time"},"strategyVersionId":{"type":"string"},"subscriptions":{"type":"array","items":{"$ref":"#/components/schemas/InstrumentSubscriptionDTO"}}},"additionalProperties":false},"CreateScenarioResponseDTO":{"type":"object","required":["data"],"properties":{"data":{"$ref":"#/components/schemas/ScenarioDTO"}}},"CreateStrategyRequestDTO":{"type":"object","required":["name"],"properties":{"description":{"type":["string","null"]},"name":{"type":"string"}}},"CreateStrategyResponseDTO":{"type":"object","required":["data"],"properties":{"data":{"$ref":"#/components/schemas/StrategyDTO"}}},"CreateStrategyVersionRequestDTO":{"type":"object","description":"Request to create a new strategy version.\n\nEither `gitSha` OR `templateId` must be provided (mutually exclusive):\n- `gitSha`: Create a version from user-uploaded code (returns upload credentials)\n- `templateId`: Create a version from a strategy template (copies image automatically)","properties":{"gitSha":{"type":["string","null"],"description":"Git commit SHA (7-40 characters). Required when creating from user code."},"parentVersionId":{"type":["string","null"],"description":"Optional parent version ID for lineage tracking."},"templateId":{"type":["string","null"],"description":"Template ID to create version from (e.g., \"ma-crossover\", \"rsi-momentum\").\nWhen provided, the template image is copied to the user's registry."}}},"CreateStrategyVersionResponseDTO":{"type":"object","description":"Response when creating a strategy version.\n\nContains the created version data, plus optional upload credentials\nwhen creating from `gitSha` (user needs to push Docker image).","required":["data"],"properties":{"accessToken":{"type":["string","null"],"description":"Access token for Docker push. Only present when creating from `gitSha`."},"data":{"$ref":"#/components/schemas/StrategyVersionDTO","description":"The created version data"},"repositoryUrl":{"type":["string","null"],"description":"Repository URL for Docker push. Only present when creating from `gitSha`."}}},"DeleteStrategyVersionImageResponseDTO":{"type":"object","description":"Response for `DELETE /strategies/{id}/versions/{vid}/image` — the updated\n(now tombstoned) version.","required":["data"],"properties":{"data":{"$ref":"#/components/schemas/StrategyVersionDTO"}}},"ErrorDetail":{"type":"object","required":["code","message"],"properties":{"code":{"type":"string"},"details":{"type":["string","null"]},"message":{"type":"string"}}},"ErrorResponse":{"type":"object","required":["error"],"properties":{"error":{"$ref":"#/components/schemas/ErrorDetail"}}},"EventDTO":{"type":"object","description":"Event data transfer object for API responses.\n\nThis DTO uses camelCase for JSON serialization to match frontend conventions.\nEvent names are strings (e.g., `\"candle_1m\"`) rather than integer IDs.","required":["name","type","description","subscribable"],"properties":{"description":{"type":"string","description":"Human-readable description of the event"},"name":{"type":"string","description":"Name of the event (e.g., `\"candle_1m\"`, `\"order_update\"`)"},"subscribable":{"type":"boolean","description":"Whether this event can be used in a scenario subscription.\n\n`list_events` is discovery of every event type the engine emits, not\njust the subscribable set. An event such as `quote` is real market data\nthe engine serves but maps to no engine broadcast kind, so subscribing\nto it is rejected. This flag lets consumers tell the two apart without\ntrial-and-error."},"type":{"type":"string","description":"Type of event (e.g., `\"MARKET_UPDATE\"`, `\"TRADING_UPDATE\"`)"}}},"FailureReason":{"type":"string","description":"The reason a backtest failed, drawn from a fixed, stable vocabulary.\n\nPresent only for failed backtests. The set of values is closed, so a client\ncan branch on the value and fall back to the accompanying human-readable\nmessage for any value it does not recognise.","enum":["resource_limit_exceeded","timed_out","strategy_error","strategy_unresponsive","engine_error","internal_error"]},"FeeConfig":{"oneOf":[{"type":"object","description":"No commission. Used by dealer brokers (OANDA) where cost is spread-only.","required":["model"],"properties":{"model":{"type":"string","enum":["zero"]}}},{"type":"object","description":"Commission as basis points of notional. Crypto exchanges (Binance).","required":["fee_rate_bps","model"],"properties":{"fee_rate_bps":{"type":"string","description":"Fee rate in basis points per side (e.g. 10 = 0.10%)."},"model":{"type":"string","enum":["bps_of_notional"]}}},{"type":"object","description":"Commission as fixed amount per share/unit. Equities brokers (Alpaca).","required":["per_share","model"],"properties":{"model":{"type":"string","enum":["per_share"]},"per_share":{"type":"string","description":"Commission per share/unit per side (e.g. 0.005 = $0.005/share)."}}}],"description":"Per-broker commission model.\n\nTagged JSON form makes operator-authored configs self-describing — the\ndiscriminator field is `model`:\n\n```json\n{\"model\": \"zero\"}\n{\"model\": \"bps_of_notional\", \"fee_rate_bps\": \"10\"}\n{\"model\": \"per_share\", \"per_share\": \"0.005\"}\n```"},"FinancingModel":{"oneOf":[{"type":"object","description":"No overnight financing. The default — preserves pre-TEK-773 behaviour.","required":["model"],"properties":{"model":{"type":"string","enum":["disabled"]}}},{"type":"object","description":"Daily carry at the 17:00 ET rollover. Rates are annual decimals\n(e.g. `-0.03` = −3%); negative debits the account, positive credits.","required":["long_rate","short_rate","model"],"properties":{"day_count_basis":{"type":["integer","null"],"format":"int32","description":"Day-count basis (annualisation denominator): 360 (Saxo USD/EUR/JPY/CHF,\nAlpaca) or 365 (OANDA, Saxo GBP/AUD/CAD). `None` means 365.","minimum":0},"day_multipliers":{"type":"array","items":{"type":"integer","format":"int32","minimum":0},"description":"Per-weekday day-count multipliers. Defaults to OANDA's cadence."},"long_rate":{"type":"string","description":"Annual financing rate applied to long positions (decimal)."},"model":{"type":"string","enum":["daily"]},"short_rate":{"type":"string","description":"Annual financing rate applied to short positions (decimal)."}}},{"type":"object","description":"Event-driven perpetual-futures funding from a per-symbol dataset.\nEach event charges `−signed_units × mark_price × rate` (a positive\nrate debits longs and credits shorts), gated by `accrual`.","required":["accrual","events","model"],"properties":{"accrual":{"$ref":"#/components/schemas/FundingAccrual","description":"Snapshot (holder at event pays) or continuous (pro-rata) accrual."},"events":{"type":"array","items":{"$ref":"#/components/schemas/FundingEvent"},"description":"Funding events, strictly ascending by `funding_time_ms`. In\ncontinuous mode an event's accrual interval starts at the previous\nevent, so the dataset must include one event before the backtest\nstart — the first event in the dataset charges nothing."},"model":{"type":"string","enum":["perpetual_funding"]}}}],"description":"Per-broker overnight financing model for one instrument.\n\nTagged JSON form (discriminator `model`) keeps operator-authored configs\nself-describing, mirroring [`FeeConfig`](super::fee::FeeConfig):\n\n```json\n{\"model\": \"disabled\"}\n{\"model\": \"daily\", \"long_rate\": \"-0.03\", \"short_rate\": \"-0.01\"}\n{\"model\": \"perpetual_funding\", \"accrual\": \"snapshot\",\n \"events\": [{\"funding_time_ms\": 1700000000000, \"rate\": \"0.0001\", \"mark_price\": \"50000\"}]}\n```"},"FromConfigOutcomeDTO":{"type":"string","description":"Per-config outcome label for from-configs.\n\nThe handler launches each config via the single `create_scenario`, which\ngives no dedup signal, so a config either created a run or failed — there is\nno `linked` here (unlike the batch endpoint).","enum":["created","failed"]},"FromConfigResultDTO":{"type":"object","description":"One requested config's outcome, in request order.\n\nCarries the source config id, an outcome label, and — when created — the\nscenario. A failed config carries the client-facing error message instead.\nOne element per requested config means a mid-loop failure neither\nblanket-fails the call nor silently drops the siblings already created.","required":["configId","outcome"],"properties":{"configId":{"type":"string"},"error":{"type":["string","null"]},"outcome":{"$ref":"#/components/schemas/FromConfigOutcomeDTO"},"scenario":{"oneOf":[{"type":"null"},{"$ref":"#/components/schemas/ScenarioDTO"}]}}},"FromConfigsRequestDTO":{"type":"object","description":"Request body for `POST /v1/strategies/{strategy_id}/scenarios/from-configs`.\n\nRuns one or more saved configurations as scenarios against the given strategy\nversion. Configurations stay strategy-agnostic — the strategy and\nversion are supplied here at run time.","required":["strategyVersionId","configIds"],"properties":{"configIds":{"type":"array","items":{"type":"string"},"description":"Saved configuration IDs to run (1–10). One scenario is created per\nconfiguration, using that configuration's subscriptions / window /\nposition mode / initial capital."},"env":{"type":"object","description":"Per-run env overrides applied to every scenario created by this\nrequest. Stored on the scenario as the per-run layer; merged\nover each config's default env at container launch.","additionalProperties":{"type":"string"},"propertyNames":{"type":"string"}},"strategyVersionId":{"type":"string","description":"Strategy version to run every configuration against."}},"additionalProperties":false},"FromConfigsResponseDTO":{"type":"object","description":"Response for `POST /v1/strategies/{strategy_id}/scenarios/from-configs`.\n\nOne labelled result per requested config, in request order. Each launch is\nbest-effort: a config that fails after an earlier one already committed does\nnot roll the others back, so the created-vs-failed picture survives instead\nof collapsing to a blanket error.","required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/FromConfigResultDTO"}}}},"FundingAccrual":{"type":"string","description":"How perpetual funding charges attach to a position's lifetime.","enum":["snapshot","continuous"]},"FundingEvent":{"type":"object","description":"One realised funding event for a perpetual-futures instrument, sourced\nfrom the venue's historical funding-rate endpoint (TEK-870 spike).","required":["funding_time_ms","rate"],"properties":{"funding_time_ms":{"type":"integer","format":"int64","description":"UTC milliseconds at which the funding charge realises.","minimum":0},"mark_price":{"type":["string","null"],"description":"Venue mark price at the event. `None` falls back to the engine mid\nprice at evaluation time."},"rate":{"type":"string","description":"Funding rate for this event (decimal; positive → longs pay shorts)."}}},"GetScenarioConfigurationResponseDTO":{"type":"object","required":["data"],"properties":{"data":{"$ref":"#/components/schemas/ScenarioConfigurationDTO"}}},"GetScenarioResponseDTO":{"type":"object","required":["data"],"properties":{"data":{"$ref":"#/components/schemas/ScenarioDTO"}}},"GetStrategyResponseDTO":{"type":"object","required":["data"],"properties":{"data":{"$ref":"#/components/schemas/StrategyDTO"}}},"GetStrategyVersionResponseDTO":{"type":"object","required":["data"],"properties":{"data":{"$ref":"#/components/schemas/StrategyVersionDTO"}}},"InstrumentDTO":{"type":"object","description":"Data Transfer Object for instrument data\n\nThis DTO represents instrument data returned by the API.\nAll fields use camelCase for frontend compatibility.","required":["symbol","name","market","dataSymbol","underlying","denomination","dataEndDate","granularities","status"],"properties":{"dataEndDate":{"type":"string","description":"The latest date for which historical data is available (ISO date \"YYYY-MM-DD\")\nComputed as yesterday's date in UTC - data is considered current within 24 hours"},"dataStartDate":{"type":["string","null"],"description":"The earliest date for which historical data is available (ISO date \"YYYY-MM-DD\")\nNone/null indicates data availability is unknown or the instrument is \"coming soon\""},"dataSymbol":{"type":"string","description":"The data-layer symbol used for storage and lookups (e.g., \"BTCUSD\", \"EUR-USD\")"},"denomination":{"type":"string","description":"What the asset is priced/settled in (e.g., \"USD\")"},"granularities":{"type":"array","items":{"type":"string"},"description":"Available data granularities for this instrument\nAll instruments support all timeframes (global, not per-instrument)\nOrder: tick first, then candle timeframes from smallest to largest"},"lotSize":{"type":["string","null"],"description":"Minimum order quantity increment (e.g., \"1\" for forex whole units)\nNone if trading rules are unknown for this instrument"},"market":{"type":"string","description":"The market type (CRYPTO, FOREX)"},"name":{"type":"string","description":"Human-readable name of the instrument"},"pricePrecision":{"type":["integer","null"],"format":"int32","description":"Number of decimal places for prices, derived from `tick_size`\nNone if `tick_size` is unknown","minimum":0},"status":{"type":"string","description":"Availability status: `available` or `coming_soon`\nDerived from `data_start_date`: if present, `available`; if `None`, `coming_soon`"},"symbol":{"type":"string","description":"The platform symbol (e.g., \"F:EURUSD\")"},"tickSize":{"type":["string","null"],"description":"Minimum price increment (e.g., \"0.00001\" for most forex, \"0.001\" for JPY pairs)\nNone if trading rules are unknown for this instrument"},"underlying":{"type":"string","description":"The asset being traded (e.g., \"BTC\")"}}},"InstrumentResponse":{"type":"object","description":"Response wrapper for instrument endpoints\n\nAll responses follow the format: `{ \"data\": ... }`","required":["data"],"properties":{"data":{"$ref":"#/components/schemas/InstrumentDTO","description":"The instrument data"}}},"InstrumentSubscriptionDTO":{"type":"object","description":"DTO for API layer (camelCase fields).","required":["instrument","events"],"properties":{"events":{"type":"array","items":{"type":"string"},"description":"Supports wildcards and brace expansion: `candle_*`, `candle_{1,5,15}m`.\nMax 100 patterns (security limit to prevent denial-of-service)."},"instrument":{"type":"string","description":"Supports wildcards: `*USD`, `C:BTC*`, or exact match like `F:EURUSD`.\nMax 50 characters (security limit to prevent denial-of-service)."}},"additionalProperties":false},"ListApiKeysResponseDTO":{"type":"object","required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/ApiKeyDTO"}}}},"ListEventsResponseDTO":{"type":"object","description":"Response DTO for listing events.\n\n# Example Response\n\n```json\n{\n    \"data\": [\n        {\n            \"name\": \"candle_1m\",\n            \"type\": \"MARKET_UPDATE\",\n            \"description\": \"On new 1 minute candle\"\n        }\n    ]\n}\n```","required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/EventDTO"},"description":"List of all available events"}}},"ListInstrumentsResponse":{"type":"object","description":"Response wrapper for list instruments endpoint","required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/InstrumentDTO"},"description":"The list of instruments"}}},"ListMarketsResponse":{"type":"object","description":"Response wrapper for markets endpoint","required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/MarketDTO"},"description":"The list of available markets"}}},"ListRequest":{"type":"object","properties":{"cursor":{"type":["string","null"]},"limit":{"type":"integer","format":"int32","minimum":0}}},"ListScenarioConfigurationsResponseDTO":{"type":"object","description":"Response for listing scenario configurations","required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/ScenarioConfigurationDTO"}},"nextCursor":{"type":["string","null"]}}},"ListScenariosResponseDTO":{"type":"object","description":"Response for listing scenarios","required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/ScenarioDTO"}},"nextCursor":{"type":["string","null"]}}},"ListStrategiesResponseDTO":{"type":"object","description":"Response for listing strategies","required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/StrategyDTO"}},"nextCursor":{"type":["string","null"]}}},"ListStrategyVersionsResponseDTO":{"type":"object","description":"Response for listing strategy versions","required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/StrategyVersionDTO"}},"nextCursor":{"type":["string","null"]}}},"ListTemplatesResponse":{"type":"object","description":"Response wrapper for list templates endpoint","required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/TemplateDTO"},"description":"The list of available templates"}}},"MarketDTO":{"type":"object","description":"Data Transfer Object for market data","required":["id","name"],"properties":{"id":{"type":"string","description":"The market identifier (CRYPTO, FOREX)"},"name":{"type":"string","description":"Human-readable name of the market"}}},"MetricUnavailableReason":{"type":"string","description":"Reason why a metric could not be calculated.","enum":["insufficient_data_points","zero_standard_deviation","no_negative_returns","no_winning_trades","no_losing_trades","no_closed_trades"]},"PatchStrategyVersionRequestDTO":{"type":"object","description":"Request body for `PATCH /strategies/{id}/versions/{vid}`: toggle the keep flag.","required":["keep"],"properties":{"keep":{"type":"boolean","description":"`true` marks the version keep (exempt from auto-eviction, consumes a\nbillable slot); `false` clears it (frees the slot)."}}},"PatchStrategyVersionResponseDTO":{"type":"object","description":"Response for `PATCH /strategies/{id}/versions/{vid}` — the updated version.","required":["data"],"properties":{"data":{"$ref":"#/components/schemas/StrategyVersionDTO"}}},"PositionMode":{"type":"string","description":"Position mode for the backtesting engine.\n\nDetermines how the engine tracks positions:\n- `Netting`: positions are aggregated by symbol into a single net position\n- `Hedging`: each trade is tracked as a separate position","enum":["netting","hedging"]},"RetentionState":{"type":"string","description":"Retention lifecycle state of a version's image.\n\nOne-way transition: `Stored` → `Evicted`. `Evicted` is the tombstone state —\nthe image has been removed by the Artifact Registry cleanup policy (the\ndeleter) and only the version record remains. The transition itself is\ndriven by the retention read/keep surface, not by this type — here it is\nschema only.","enum":["stored","evicted"]},"ScenarioConfigurationDTO":{"type":"object","description":"User-scoped configuration response DTO","required":["configId","name","isActive","subscriptions","startTime","endTime","positionMode","initialCapital","createdAt","updatedAt","createdBy"],"properties":{"configId":{"type":"string"},"costs":{"$ref":"#/components/schemas/CostsConfig","description":"Typed transaction-cost config inherited by scenario runs from this\nconfiguration. Omitted when all-zero (frictionless), so pre-costs\nconfigurations round-trip unchanged."},"createdAt":{"type":"string","format":"date-time"},"createdBy":{"type":"string"},"description":{"type":["string","null"]},"endTime":{"type":"string","format":"date-time"},"env":{"type":"object","description":"Default env overrides for the strategy container. Omitted\nwhen empty. Values are stored and returned in plain text — secrets are\nunsupported in v1.","additionalProperties":{"type":"string"},"propertyNames":{"type":"string"}},"initialCapital":{"type":"number","format":"double"},"isActive":{"type":"boolean"},"name":{"type":"string"},"positionMode":{"$ref":"#/components/schemas/PositionMode"},"startTime":{"type":"string","format":"date-time"},"subscriptions":{"type":"array","items":{"$ref":"#/components/schemas/InstrumentSubscriptionDTO"}},"updatedAt":{"type":"string","format":"date-time"}}},"ScenarioConfigurationFiltersQuery":{"type":"object","properties":{"active_only":{"type":["boolean","null"]}}},"ScenarioDTO":{"type":"object","required":["scenarioId","strategyId","versionId","subscriptions","startTime","endTime","createdAt","state","triggeredBy","positionMode","initialCapital"],"properties":{"cancelledAt":{"type":["string","null"],"format":"date-time","description":"Timestamp when scenario was cancelled. Only present when state is CANCELLED."},"completedAt":{"type":["string","null"],"format":"date-time"},"costs":{"$ref":"#/components/schemas/CostsConfig","description":"Typed transaction-cost config (commission + slippage + funding) applied\nto this backtest. Omitted when all-zero."},"createdAt":{"type":"string","format":"date-time"},"effectiveEnv":{"type":"object","description":"The merged env that actually reached the strategy container: the source\nconfig's default env with the per-run `env` layered on top (per-run wins\non a duplicate key). Equals `env` for an inline run with no source config.\nUnlike `env` (the per-run layer alone, empty for a config-only run), this\nconfirms what a from-config run actually launched with. Omitted when empty\n(e.g. a run with no env at all, or one created before this field existed).\nValues are returned in plain text — secrets are unsupported in v1.","additionalProperties":{"type":"string"},"propertyNames":{"type":"string"}},"endTime":{"type":"string","format":"date-time"},"env":{"type":"object","description":"Per-run env overrides for the strategy container. Omitted\nwhen empty. Values are stored and returned in plain text — secrets are\nunsupported in v1.","additionalProperties":{"type":"string"},"propertyNames":{"type":"string"}},"errorDetail":{"type":["string","null"],"description":"Diagnostic detail for a strategy-code failure: the captured exception\nand traceback from the strategy container's stderr. Present only when\n`failure_reason` is `strategy_error` (a user-code crash) and stderr was\ncaptured — platform/engine failures never populate it, so no\ninfrastructure internals are surfaced. Bounded in length."},"errorMessage":{"type":["string","null"],"description":"Human-readable explanation of a failure, matching `failure_reason`.\nPresent only when `state` is `FAILED`."},"errorType":{"oneOf":[{"type":"null"},{"$ref":"#/components/schemas/ScenarioErrorType","description":"Type of error that caused the scenario to fail.\nOnly present when state is FAILED.\n`strategy` = user's code caused the failure\n`platform` = infrastructure/platform caused the failure"}]},"eta":{"type":["string","null"],"format":"date-time","description":"Estimated completion time (linear extrapolation). Present only while\nRUNNING with a usable rate; absent at 0%, when complete, or when the\nrun is stalled."},"failureReason":{"oneOf":[{"type":"null"},{"$ref":"#/components/schemas/FailureReason","description":"The reason a backtest failed, from a fixed vocabulary. Present only when\n`state` is `FAILED`. The matching human-readable text is in\n`error_message`."}]},"initialCapital":{"type":"number","format":"double","description":"Initial capital used for this backtest."},"maxDrawdownPct":{"type":["number","null"],"format":"double","description":"Maximum drawdown as a percentage. Only present for completed scenarios."},"positionMode":{"$ref":"#/components/schemas/PositionMode","description":"Position mode used for this backtest (netting or hedging)."},"progressPct":{"type":["number","null"],"format":"double","description":"Percent of the sim window covered so far, `[0, 100]`. Present only while\nthe scenario is RUNNING and a progress file has been written."},"progressStalled":{"type":["boolean","null"],"description":"True when the engine has gone silent (progress file older than the\nstaleness window) — the percent is frozen and the ETA is suppressed.\nPresent only while RUNNING with a progress file."},"scenarioId":{"type":"string"},"sharpeRatio":{"type":["number","null"],"format":"double","description":"Sharpe ratio (annualized). Only present for completed scenarios with sufficient data."},"sourceConfigId":{"type":["string","null"],"description":"The saved configuration this scenario was launched from, if any.\nPresent for scenarios created via a saved-config run (run-from-config or\nauto-run); absent for inline manual runs."},"startTime":{"type":"string","format":"date-time"},"state":{"type":"string"},"strategyId":{"type":"string"},"subscriptions":{"type":"array","items":{"$ref":"#/components/schemas/InstrumentSubscriptionDTO"}},"totalReturn":{"type":["number","null"],"format":"double"},"totalTrades":{"type":["integer","null"],"format":"int32","description":"Total number of closed trades. Only present for completed scenarios.","minimum":0},"triggeredBy":{"$ref":"#/components/schemas/ScenarioTrigger","description":"Whether this scenario was triggered manually or automatically."},"versionId":{"type":"string"},"winRatePct":{"type":["number","null"],"format":"double","description":"Win rate as a percentage. Only present for completed scenarios."}}},"ScenarioErrorType":{"type":"string","description":"Indicates whether a scenario failure was caused by user's strategy code or platform infrastructure.\n\nThis helps users quickly identify whether the issue is in their code (they need to fix it)\nor a platform problem (they should wait or contact support).","enum":["strategy","platform"]},"ScenarioFiltersQuery":{"type":"object","properties":{"createdAfter":{"type":["string","null"]},"createdBefore":{"type":["string","null"]},"instruments":{"type":["string","null"]},"isCompleted":{"type":["string","null"]},"state":{"type":["string","null"]},"strategyVersionId":{"type":["string","null"]}}},"ScenarioMetadataContent":{"type":"object","description":"Schema for scenario metadata file content (after Zstd decompression).\n\nThis schema documents the JSON structure inside the compressed binary file.\nThe actual endpoint returns `application/octet-stream` with Zstd-compressed data.","required":["id","starting_units","final_units","final_nav","start_time","end_time","total_trades","open_trades","closed_trades","data_points","generated_at","format_version"],"properties":{"closed_trades":{"type":"integer","format":"int64","description":"Number of closed trades"},"data_points":{"type":"integer","format":"int64","description":"Number of data points in timeseries"},"end_time":{"type":"integer","format":"int64","description":"Backtest end time (Unix timestamp in milliseconds)"},"final_nav":{"type":"string","description":"Final NAV value (as decimal string)"},"final_units":{"type":"string","description":"Final portfolio units (as decimal string)"},"format_version":{"type":"string","description":"Format version for backwards compatibility"},"generated_at":{"type":"string","description":"ISO8601 timestamp when results were generated"},"id":{"type":"string","description":"Scenario ID"},"open_trades":{"type":"integer","format":"int64","description":"Number of currently open trades"},"start_time":{"type":"integer","format":"int64","description":"Backtest start time (Unix timestamp in milliseconds)"},"starting_units":{"type":"string","description":"Starting portfolio units (as decimal string)"},"total_trades":{"type":"integer","format":"int64","description":"Total number of trades executed"}}},"ScenarioMetricsDTO":{"type":"object","description":"Calculated performance metrics for a scenario","required":["scenarioId","totalReturnPct","annualizedReturnPct","maxDrawdownPct","maxDrawdownDurationDays","totalTrades","winningTrades","losingTrades","winRatePct"],"properties":{"annualizedReturnPct":{"type":"number","format":"double","description":"Annualized return as a percentage"},"avgLoss":{"type":["number","null"],"format":"double","description":"Average loss on losing trades"},"avgLossReason":{"oneOf":[{"type":"null"},{"$ref":"#/components/schemas/MetricUnavailableReason","description":"Reason why average loss could not be calculated"}]},"avgTradeDurationReason":{"oneOf":[{"type":"null"},{"$ref":"#/components/schemas/MetricUnavailableReason","description":"Reason why average trade duration could not be calculated"}]},"avgTradeDurationSeconds":{"type":["integer","null"],"format":"int64","description":"Average trade duration in seconds","minimum":0},"avgWin":{"type":["number","null"],"format":"double","description":"Average profit on winning trades"},"avgWinReason":{"oneOf":[{"type":"null"},{"$ref":"#/components/schemas/MetricUnavailableReason","description":"Reason why average win could not be calculated"}]},"grossPnl":{"type":"number","format":"double","description":"Gross profit and loss in account currency: `net_pnl + total_commission`.\nAdds back commission only — spread and slippage stay embedded in fill\nprices and are not separated out."},"largestLoss":{"type":["number","null"],"format":"double","description":"Largest single losing trade"},"largestLossReason":{"oneOf":[{"type":"null"},{"$ref":"#/components/schemas/MetricUnavailableReason","description":"Reason why largest loss could not be calculated"}]},"largestWin":{"type":["number","null"],"format":"double","description":"Largest single winning trade"},"largestWinReason":{"oneOf":[{"type":"null"},{"$ref":"#/components/schemas/MetricUnavailableReason","description":"Reason why largest win could not be calculated"}]},"losingTrades":{"type":"integer","format":"int64","description":"Number of losing trades","minimum":0},"maxDrawdownDurationDays":{"type":"integer","format":"int64","description":"Duration of maximum drawdown in days","minimum":0},"maxDrawdownPct":{"type":"number","format":"double","description":"Maximum drawdown as a percentage"},"netPnl":{"type":"number","format":"double","description":"Net profit and loss in account currency (`final_equity - initial_equity`),\nalready net of commissions since the engine deducts them from equity."},"profitFactor":{"type":["number","null"],"format":"double","description":"Profit factor (`gross_profit` / `gross_loss`)"},"profitFactorReason":{"oneOf":[{"type":"null"},{"$ref":"#/components/schemas/MetricUnavailableReason","description":"Reason why profit factor could not be calculated"}]},"scenarioId":{"type":"string","description":"Scenario ID these metrics belong to"},"sharpeRatio":{"type":["number","null"],"format":"double","description":"Sharpe ratio (null if insufficient data)"},"sharpeRatioReason":{"oneOf":[{"type":"null"},{"$ref":"#/components/schemas/MetricUnavailableReason","description":"Reason why Sharpe ratio could not be calculated"}]},"sortinoRatio":{"type":["number","null"],"format":"double","description":"Sortino ratio (null if insufficient data)"},"sortinoRatioReason":{"oneOf":[{"type":"null"},{"$ref":"#/components/schemas/MetricUnavailableReason","description":"Reason why Sortino ratio could not be calculated"}]},"totalCommission":{"type":"number","format":"double","description":"Total commission paid across all trades, including entry fees on\nstill-open trades. Spread and slippage are embedded in fill prices\nand are not part of this figure. 0 for zero-fee brokers."},"totalReturnPct":{"type":"number","format":"double","description":"Total return as a percentage (e.g., 15.32 = 15.32%)"},"totalTrades":{"type":"integer","format":"int64","description":"Total number of closed trades","minimum":0},"winRatePct":{"type":"number","format":"double","description":"Win rate as a percentage"},"winningTrades":{"type":"integer","format":"int64","description":"Number of profitable trades","minimum":0}}},"ScenarioMetricsResponseDTO":{"type":"object","description":"Response for scenario metrics","required":["data"],"properties":{"data":{"$ref":"#/components/schemas/ScenarioMetricsDTO"}}},"ScenarioTimeseriesContent":{"type":"object","description":"Schema for scenario timeseries file content (after Zstd decompression).\n\nThis schema documents the JSON structure inside the compressed binary file.\nThe actual endpoint returns `application/octet-stream` with Zstd-compressed data.","required":["timestamps","units","nav","margin_used"],"properties":{"margin_used":{"type":"array","items":{"type":"string"},"description":"Margin used at each timestamp (as decimal strings)"},"nav":{"type":"array","items":{"type":"string"},"description":"Net Asset Value at each timestamp (as decimal strings)"},"timestamps":{"type":"array","items":{"type":"integer","format":"int64"},"description":"Unix timestamps in milliseconds for each data point"},"units":{"type":"array","items":{"type":"string"},"description":"Portfolio units at each timestamp (as decimal strings)"}}},"ScenarioTradeData":{"type":"object","description":"Individual trade data within scenario results.","required":["id","state","instrument_id","open_price","units","direction","opened_at","opened_by_order_id"],"properties":{"close_price":{"type":["string","null"],"description":"Exit price (as decimal string, if closed)"},"closed_at":{"type":["integer","null"],"format":"int64","description":"Exit timestamp (Unix milliseconds, if closed)"},"closed_by_order_id":{"type":["string","null"],"description":"Order ID that closed this trade (if closed)"},"direction":{"type":"string","description":"Trade direction: \"LONG\" or \"SHORT\""},"id":{"type":"string","description":"Unique trade ID"},"instrument_id":{"type":"integer","format":"int64","description":"Instrument ID"},"open_price":{"type":"string","description":"Entry price (as decimal string)"},"opened_at":{"type":"integer","format":"int64","description":"Entry timestamp (Unix milliseconds)"},"opened_by_order_id":{"type":"string","description":"Order ID that opened this trade"},"realized_pnl":{"type":["string","null"],"description":"Realized profit/loss (as decimal string, if closed)"},"state":{"type":"string","description":"Trade state: \"Open\" or \"Closed\""},"units":{"type":"string","description":"Position size (as decimal string)"}}},"ScenarioTradesContent":{"type":"object","description":"Schema for scenario trades file content (after Zstd decompression).\n\nThis schema documents the JSON structure inside the compressed binary file.\nThe actual endpoint returns `application/octet-stream` with Zstd-compressed data.","required":["trades"],"properties":{"trades":{"type":"array","items":{"$ref":"#/components/schemas/ScenarioTradeData"},"description":"List of all trades executed during the scenario"}}},"ScenarioTrigger":{"type":"string","description":"Indicates whether a scenario was triggered manually or automatically.","enum":["manual","auto"]},"SearchInstrumentsResponse":{"type":"object","description":"Response wrapper for search instruments endpoint","required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/InstrumentDTO"},"description":"The list of matching instruments"}}},"SetAutoRunConfigsRequestDTO":{"type":"object","description":"Request to set auto-run configurations for a strategy","required":["configIds"],"properties":{"configIds":{"type":"array","items":{"type":"string"},"description":"Configuration IDs to auto-run when a new version is uploaded (max 100)","maxItems":100}}},"SetAutoRunConfigsResponseDTO":{"type":"object","description":"Response after setting auto-run configurations","required":["data"],"properties":{"data":{"$ref":"#/components/schemas/AutoRunConfigsDTO"}}},"SlippageConfig":{"type":"object","description":"Configuration for slippage calculation during order execution.\n\nSlippage is calculated as:\n```text\nslippage_bps = base_slippage_bps + (notional / 100_000) * size_impact_bps_per_100k\nslippage_bps = min(slippage_bps, max_slippage_bps)\n```\n\nFor buy orders:  `fill_price = ask × (1 + slippage_bps / 10_000)`\nFor sell orders: `fill_price = bid × (1 - slippage_bps / 10_000)`","required":["base_slippage_bps","size_impact_bps_per_100k","max_slippage_bps"],"properties":{"base_slippage_bps":{"type":"string","description":"Base slippage in basis points (e.g., 5 = 0.05%)"},"max_slippage_bps":{"type":"string","description":"Maximum slippage cap in basis points"},"size_impact_bps_per_100k":{"type":"string","description":"Additional slippage per $100k notional (e.g., 2 = 0.02% per $100k)"}}},"StrategyDTO":{"type":"object","required":["strategyId","name","ownerId","createdBy","createdAt","updatedAt"],"properties":{"autoRunConfigIds":{"type":"array","items":{"type":"string"},"description":"Configuration IDs that auto-run when a new version is uploaded"},"createdAt":{"type":"string","format":"date-time"},"createdBy":{"type":"string"},"description":{"type":["string","null"]},"metrics":{"oneOf":[{"type":"null"},{"$ref":"#/components/schemas/StrategyMetricsDTO"}]},"name":{"type":"string"},"ownerId":{"type":"string"},"strategyId":{"type":"string"},"updatedAt":{"type":"string","format":"date-time"}}},"StrategyMetricDTO":{"type":"object","required":["value","versionId","versionRef"],"properties":{"value":{"type":"string"},"versionId":{"type":"string"},"versionRef":{"type":"string"}}},"StrategyMetricsDTO":{"type":"object","properties":{"bestPerformance":{"oneOf":[{"type":"null"},{"$ref":"#/components/schemas/StrategyMetricDTO"}]},"bestSharpe":{"oneOf":[{"type":"null"},{"$ref":"#/components/schemas/StrategyMetricDTO"}]},"bestWinRate":{"oneOf":[{"type":"null"},{"$ref":"#/components/schemas/StrategyMetricDTO"}]}}},"StrategyVersionDTO":{"type":"object","required":["strategyId","versionId","gitSha","createdAt","status","retentionState","keep"],"properties":{"createdAt":{"type":"string","format":"date-time"},"evictionAt":{"type":["string","null"],"format":"date-time","description":"Absolute time the image is auto-deleted (`uploaded_at` + 30 days).\nPresent only for `stored` versions that have been uploaded; omitted for\nnot-yet-uploaded and evicted versions. The relative \"expires in N days\"\nis formatted client-side so it never goes stale."},"gitSha":{"type":"string"},"imageRef":{"type":["string","null"]},"keep":{"type":"boolean","description":"User-set keep flag: kept versions are exempt from auto-eviction and are\nthe billable retention unit."},"parentVersionId":{"type":["string","null"]},"retentionState":{"$ref":"#/components/schemas/RetentionState","description":"Retention lifecycle state of the version's image (`stored` / `evicted`).\nEvicted versions are tombstones — the record persists but the image is\ngone; the UI renders these dimmed with a re-push affordance."},"status":{"$ref":"#/components/schemas/VersionStatus","description":"Derived readiness status based on `image_ref` and `uploaded_at`."},"strategyId":{"type":"string"},"uploadedAt":{"type":["string","null"],"format":"date-time"},"versionId":{"type":"string"}}},"SuggestedScenarioDTO":{"type":"object","description":"Data Transfer Object for suggested scenario configuration","required":["instrument","period","granularity"],"properties":{"granularity":{"type":"string","description":"Suggested data granularity (e.g., \"1h\", \"1d\")"},"instrument":{"type":"string","description":"Suggested instrument symbol (e.g., \"BTC-USD\")"},"period":{"type":"string","description":"Suggested time period (e.g., \"1y\", \"6m\")"}}},"TemplateDTO":{"type":"object","description":"Data Transfer Object for strategy template data\n\nThis DTO represents a strategy template returned by the API.\nAll fields use camelCase for frontend compatibility.","required":["id","name","description","difficulty","estimatedSetupTimeSeconds","suggestedScenario"],"properties":{"description":{"type":"string","description":"Description of what the strategy does and how it works"},"difficulty":{"type":"string","description":"Difficulty level for users (e.g., \"beginner\", \"intermediate\", \"advanced\")"},"estimatedSetupTimeSeconds":{"type":"integer","format":"int32","description":"Estimated time in seconds to set up this template","minimum":0},"id":{"type":"string","description":"Unique identifier for the template (e.g., \"ma-crossover\")"},"name":{"type":"string","description":"Human-readable name of the template"},"suggestedScenario":{"$ref":"#/components/schemas/SuggestedScenarioDTO","description":"Suggested scenario configuration for this template"}}},"UpdateScenarioConfigurationRequestDTO":{"type":"object","required":["name","isActive","subscriptions","startTime","endTime"],"properties":{"costs":{"$ref":"#/components/schemas/CostsConfig","description":"Typed transaction-cost config. Full-replace like every other field on\nupdate: omitting it (or sending all-zero) clears the costs."},"description":{"type":["string","null"]},"endTime":{"type":"string","format":"date-time"},"env":{"type":"object","description":"Default env overrides for the strategy container. Full-replace\nsemantics like every other field on update: omitting it clears the\nmap.","additionalProperties":{"type":"string"},"propertyNames":{"type":"string"}},"initialCapital":{"type":"number","format":"double"},"isActive":{"type":"boolean"},"name":{"type":"string"},"positionMode":{"$ref":"#/components/schemas/PositionMode"},"startTime":{"type":"string","format":"date-time"},"subscriptions":{"type":"array","items":{"$ref":"#/components/schemas/InstrumentSubscriptionDTO"}}},"additionalProperties":false},"UpdateScenarioConfigurationResponseDTO":{"type":"object","required":["data"],"properties":{"data":{"$ref":"#/components/schemas/ScenarioConfigurationDTO"}}},"UpdateStrategyRequestDTO":{"type":"object","description":"Request to update a strategy","properties":{"clearDescription":{"type":["boolean","null"],"description":"Set to true to explicitly clear the description"},"description":{"type":["string","null"],"description":"New description for the strategy (optional, max 2000 chars).\nSend null to clear, omit to keep unchanged."},"name":{"type":["string","null"],"description":"New name for the strategy (optional, 1-255 chars)"}}},"UpdateStrategyResponseDTO":{"type":"object","required":["data"],"properties":{"data":{"$ref":"#/components/schemas/StrategyDTO"}}},"VersionStatus":{"type":"string","description":"Readiness status of a strategy version, derived from `image_ref` and `uploaded_at`.\n\n- `creating`: Version record exists but no image reference yet\n- `copying`: Image reference set (e.g. template copy started) but upload not confirmed\n- `ready`: Image uploaded and confirmed — version can be used in scenarios","enum":["creating","copying","ready"]}},"securitySchemes":{"api_key":{"type":"apiKey","in":"header","name":"X-API-Key"},"bearer_auth":{"type":"http","scheme":"bearer","bearerFormat":"JWT","description":"JWT token from Clerk authentication"}}},"tags":[{"name":"strategies","description":"Strategy management"},{"name":"versions","description":"Strategy version management"},{"name":"scenarios","description":"Backtest scenario management"},{"name":"configurations","description":"Scenario configuration management"},{"name":"api-keys","description":"API key management"},{"name":"instruments","description":"Trading instrument lookup"},{"name":"events","description":"Event definitions"},{"name":"templates","description":"Strategy templates for onboarding"}]}