{"openapi":"3.1.0","info":{"title":"Swinger API","description":"Control plane for the Swinger trading + collateral-protection system.","version":"0.0.1"},"paths":{"/health/live":{"get":{"tags":["health"],"summary":"Liveness","description":"Liveness probe — returns OK as long as the process is responsive.","operationId":"liveness_health_live_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HealthResponse"}}}}}}},"/health/ready":{"get":{"tags":["health"],"summary":"Readiness","description":"Readiness probe — extends liveness with dependency checks in later phases.","operationId":"readiness_health_ready_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HealthResponse"}}}}}}},"/auth/magic-link":{"post":{"tags":["auth"],"summary":"Send a magic-link sign-in email","description":"Always returns ``{sent: true}`` regardless of whether the email\nmatched the configured operator — refusing to confirm/deny who's a\nvalid user keeps email enumeration off the table. SMTP failures\nDO surface as 500 because a missed mail looks identical to a\nrefused login from the operator's side.","operationId":"request_magic_link_auth_magic_link_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MagicLinkRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GenericResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/auth/magic-callback":{"get":{"tags":["auth"],"summary":"Consume a magic-link token (stage 1 of login)","description":"Marks the magic-link consumed, sets the stage-1 cookie, and\n303-redirects the browser to ``/auth/totp`` (or ``/auth/setup`` if\nthe operator hasn't paired an authenticator yet).","operationId":"magic_callback_auth_magic_callback_get","parameters":[{"name":"token","in":"query","required":true,"schema":{"type":"string","title":"Token"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/auth/totp/setup-start":{"post":{"tags":["auth"],"summary":"Begin first-run TOTP pairing","operationId":"totp_setup_start_auth_totp_setup_start_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TotpSetupStartResponse"}}}}}}},"/auth/totp/setup-confirm":{"post":{"tags":["auth"],"summary":"Confirm TOTP pairing — returns the 10 recovery codes","operationId":"totp_setup_confirm_auth_totp_setup_confirm_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TotpSetupConfirmRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TotpSetupConfirmResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/auth/totp/verify":{"post":{"tags":["auth"],"summary":"Verify TOTP code — issues 24h session token","operationId":"totp_verify_auth_totp_verify_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TotpVerifyRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SessionResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/auth/recovery/use":{"post":{"tags":["auth"],"summary":"Use a recovery code to sign in (consumes one of the 10)","operationId":"recovery_use_auth_recovery_use_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RecoveryUseRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SessionResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/auth/logout":{"post":{"tags":["auth"],"summary":"Revoke the current session","operationId":"logout_auth_logout_post","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GenericResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/auth/me":{"get":{"tags":["auth"],"summary":"Current operator (200 / 401)","operationId":"me_auth_me_get","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Bearer <session-token>","title":"Authorization"},"description":"Bearer <session-token>"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MeResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/guardian/state":{"get":{"tags":["guardian"],"summary":"Get State","description":"Latest published CollateralPosition. 503 if the key is missing/expired\n— that's the intended signal for \"Guardian process is down or has nothing\nto publish\".","operationId":"get_state_guardian_state_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CollateralPosition"}}}},"503":{"description":"No state published yet (Guardian not running, or no position)"}}}},"/guardian/heartbeat":{"get":{"tags":["guardian"],"summary":"Get Heartbeat","description":"Liveness signal: when did the Guardian runner last refresh its\nheartbeat? Stale > 15s = dead-man's-switch territory (MASTERPLAN §5.5).","operationId":"get_heartbeat_guardian_heartbeat_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Get Heartbeat Guardian Heartbeat Get"}}}},"503":{"description":"Heartbeat missing or stale"}}}},"/guardian/events":{"get":{"tags":["guardian"],"summary":"Recent guardian_events audit rows","description":"Most recent N rows from the ``guardian_events`` hypertable, newest\nfirst. Powers the dashboard's tier-action history panel and any\noperator post-mortem of \"what did Guardian do during that crash?\".","operationId":"list_events_guardian_events_get","parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":200,"minimum":1,"description":"Number of rows to return (1 to 200)","default":20,"title":"Limit"},"description":"Number of rows to return (1 to 200)"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/GuardianEventResponse"},"title":"Response List Events Guardian Events Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/ingestion/health":{"get":{"tags":["ingestion"],"summary":"Per-venue ingestion freshness + recent-event counts","description":"Read the trades hypertable to surface a per-venue health snapshot.\n\nReturns one ``VenueHealthRow`` per (venue, symbol) pair that has a\ntrade *somewhere* in history — not just the last 5 minutes. The\n``events_5min`` counter is the \"currently alive\" signal; an entry\nwith ``age_seconds`` > some threshold is a stalled/disconnected feed.","operationId":"health_ingestion_health_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/IngestionHealthResponse"}}}}}}},"/markets/tickers":{"get":{"tags":["markets"],"summary":"Latest ticker per (venue, symbol) — bid/ask/last + freshness","description":"Read one row per (venue, symbol) with the most recent ``ts``.\n\nPostgres DISTINCT ON makes this a single index walk; sorts the result\nvenue-then-symbol so the UI render is stable across polls.","operationId":"latest_tickers_markets_tickers_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TickersResponse"}}}}}}},"/markets/ohlcv":{"get":{"tags":["markets"],"summary":"Recent OHLCV candles for one (venue, symbol, interval)","description":"Pull the last `limit` candles for one (venue, symbol, interval) tuple.\n\nReturns ascending by ``ts`` so the UI can plot left-to-right without\nhaving to reverse. The DB query uses ``ORDER BY ts DESC LIMIT`` to use\nthe (venue, symbol, interval, ts) PK index, then we reverse in Python\n— much cheaper than a full-table scan with ascending sort.","operationId":"recent_ohlcv_markets_ohlcv_get","parameters":[{"name":"venue","in":"query","required":true,"schema":{"type":"string","description":"Canonical venue id (e.g. kraken)","title":"Venue"},"description":"Canonical venue id (e.g. kraken)"},{"name":"symbol","in":"query","required":true,"schema":{"type":"string","description":"Symbol in the venue's native format (e.g. BTC/EUR)","title":"Symbol"},"description":"Symbol in the venue's native format (e.g. BTC/EUR)"},{"name":"interval","in":"query","required":false,"schema":{"type":"string","description":"Candle interval label (1m, 5m, 1h, 1d, ...)","default":"1m","title":"Interval"},"description":"Candle interval label (1m, 5m, 1h, 1d, ...)"},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":1000,"minimum":1,"description":"Most recent N candles to return","default":60,"title":"Limit"},"description":"Most recent N candles to return"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OhlcvResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/execution/fills":{"get":{"tags":["execution"],"summary":"Most-recent paper-shadow fills (one row per simulated trade)","description":"Last ``limit`` paper fills, most recent first. Filters compose\nwith AND. Empty filters = global view across all runs/strategies.","operationId":"recent_paper_fills_execution_fills_get","parameters":[{"name":"run_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Optional filter by run identifier","title":"Run Id"},"description":"Optional filter by run identifier"},{"name":"strategy_name","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Optional filter by strategy name","title":"Strategy Name"},"description":"Optional filter by strategy name"},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":500,"minimum":1,"description":"Number of fills to return","default":50,"title":"Limit"},"description":"Number of fills to return"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PaperFillsResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/execution/snapshots":{"get":{"tags":["execution"],"summary":"Recent paper-shadow position snapshots (one row per bar close)","description":"Last ``limit`` snapshots for ``run_id``, ascending by ts so the\nUI can plot left-to-right without reversing. ``run_id`` is required\nbecause cross-run mixing on a single chart is meaningless.","operationId":"recent_paper_snapshots_execution_snapshots_get","parameters":[{"name":"run_id","in":"query","required":true,"schema":{"type":"string","description":"Required: which paper-shadow run","title":"Run Id"},"description":"Required: which paper-shadow run"},{"name":"strategy_name","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Optional filter by strategy name","title":"Strategy Name"},"description":"Optional filter by strategy name"},{"name":"symbol","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Optional filter by symbol","title":"Symbol"},"description":"Optional filter by symbol"},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":1000,"minimum":1,"description":"Number of snapshots to return","default":120,"title":"Limit"},"description":"Number of snapshots to return"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PaperSnapshotsResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/execution/kill_status":{"get":{"tags":["execution"],"summary":"Current kill-switch state (halt / flat flags + reasons)","operationId":"kill_status_execution_kill_status_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/KillStatusResponse"}}}}}}},"/execution/halt":{"post":{"tags":["execution"],"summary":"Set or clear exec.halt (block new orders, hold positions)","operationId":"set_halt_execution_halt_post","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Bearer <session-token>","title":"Authorization"},"description":"Bearer <session-token>"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/KillSwitchRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/KillSwitchAck"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/execution/flat":{"post":{"tags":["execution"],"summary":"Set or clear exec.flat (close positions then refuse orders)","operationId":"set_flat_execution_flat_post","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Bearer <session-token>","title":"Authorization"},"description":"Bearer <session-token>"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/KillSwitchRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/KillSwitchAck"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/ledger/positions":{"get":{"tags":["ledger"],"summary":"Open positions for a strategy (folded from paper_fills)","description":"Net open positions per (venue, symbol). Symbols with zero net\nqty are omitted. ``strategy_name`` is required because cross-\nstrategy aggregation has its own /ledger/portfolio endpoint and\nmixing them at this layer would force pagination-by-strategy.","operationId":"positions_ledger_positions_get","parameters":[{"name":"strategy_name","in":"query","required":true,"schema":{"type":"string","description":"Required: which strategy to query","title":"Strategy Name"},"description":"Required: which strategy to query"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PositionsResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/ledger/equity":{"get":{"tags":["ledger"],"summary":"Cash + position-value snapshot for a strategy","description":"Snapshot equity. ``mark_prices`` is intentionally omitted from\nthe API surface for v1 — the position_value reads as zero. A\ncaller wanting marked-to-market equity hits ``/markets/tickers``\n+ this endpoint and combines the two; bundling them here would\nrequire the API to know which venue+symbol marks to fetch and\nthat's a layering concern. Slice 7d ships a marked-equity helper\nonce the report endpoints crystallise.","operationId":"equity_ledger_equity_get","parameters":[{"name":"strategy_name","in":"query","required":true,"schema":{"type":"string","description":"Required: which strategy","title":"Strategy Name"},"description":"Required: which strategy"},{"name":"initial_cash","in":"query","required":true,"schema":{"anyOf":[{"type":"number"},{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"}],"description":"Starting cash for the strategy run","title":"Initial Cash"},"description":"Starting cash for the strategy run"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EquityResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/reports/trade-history.csv":{"get":{"tags":["reports"],"summary":"DAC8-friendly trade-history CSV (one row per fill)","description":"Export every fill for ``strategy_name`` (optionally filtered\nto one ``year``) in DAC8 column order. ``fee_ccy`` is left\nblank in paper mode since `paper_fills` doesn't store the fee\ncurrency; the live ledger captures it.","operationId":"trade_history_reports_trade_history_csv_get","parameters":[{"name":"strategy_name","in":"query","required":true,"schema":{"type":"string","description":"Required: which strategy","title":"Strategy Name"},"description":"Required: which strategy"},{"name":"year","in":"query","required":false,"schema":{"anyOf":[{"type":"integer"},{"type":"null"}],"description":"Optional year filter (UTC)","title":"Year"},"description":"Optional year filter (UTC)"}],"responses":{"200":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/reports/realized-pnl.csv":{"get":{"tags":["reports"],"summary":"Realised PnL by symbol for a strategy + year","description":"Realised PnL grouped by (venue, symbol). HCF (highest-cost-first)\nis the default per MASTERPLAN §11 — minimises realised gains by\nclosing the most-expensive lots first. Operators in jurisdictions\nthat require FIFO/LIFO override via ``method=``.","operationId":"realized_pnl_reports_realized_pnl_csv_get","parameters":[{"name":"strategy_name","in":"query","required":true,"schema":{"type":"string","description":"Required: which strategy","title":"Strategy Name"},"description":"Required: which strategy"},{"name":"year","in":"query","required":false,"schema":{"anyOf":[{"type":"integer"},{"type":"null"}],"description":"Optional year (closures dated by SELL ts)","title":"Year"},"description":"Optional year (closures dated by SELL ts)"},{"name":"method","in":"query","required":false,"schema":{"$ref":"#/components/schemas/TaxLotMethod","description":"Tax-lot method: hcf (default per §11), fifo, lifo","default":"hcf"},"description":"Tax-lot method: hcf (default per §11), fifo, lifo"}],"responses":{"200":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/reports/unrealized-pnl.csv":{"get":{"tags":["reports"],"summary":"Open positions snapshot with cost basis","description":"Open positions and their cost basis (VWAP avg_entry). Mark-\nto-market valuation is *not* computed here — caller combines this\nCSV with ``/markets/tickers`` for marked PnL. v1 doesn't bundle\nthem because the marks-fetching layer doesn't belong in the\nreports router.","operationId":"unrealized_pnl_reports_unrealized_pnl_csv_get","parameters":[{"name":"strategy_name","in":"query","required":true,"schema":{"type":"string","description":"Required: which strategy","title":"Strategy Name"},"description":"Required: which strategy"}],"responses":{"200":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/audit/events":{"get":{"tags":["audit"],"summary":"Recent operator audit events","description":"Last ``limit`` audit-log rows, most-recent first. Filters\ncompose with AND. Empty filters = global view across all actors\n+ actions.","operationId":"audit_events_audit_events_get","parameters":[{"name":"actor","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Optional actor filter","title":"Actor"},"description":"Optional actor filter"},{"name":"action","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Optional action filter","title":"Action"},"description":"Optional action filter"},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":500,"minimum":1,"description":"Max rows","default":50,"title":"Limit"},"description":"Max rows"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AuditEventsResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/risk/profiles":{"get":{"tags":["risk"],"summary":"Risk-profile defaults (Conservative/Moderate/Aggressive)","operationId":"profiles_risk_profiles_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProfilesResponse"}}}}}}},"/risk/snapshot":{"get":{"tags":["risk"],"summary":"RiskContext snapshot derived from the latest paper_position_snapshot","description":"Latest snapshot for ``strategy_name`` + a 30d-window drawdown\ncomputed from the equity series. Returns 404 when no snapshots\nexist (the engine hasn't run for this strategy yet).","operationId":"snapshot_risk_snapshot_get","parameters":[{"name":"strategy_name","in":"query","required":true,"schema":{"type":"string","description":"Required: which strategy","title":"Strategy Name"},"description":"Required: which strategy"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SnapshotResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/config/snapshot":{"get":{"tags":["config"],"summary":"Parsed read-only snapshot of every YAML config file","description":"Loads every config file via the existing `ConfigLoader` and\nreturns the parsed-and-redacted representation. Failures during\nparse → 503 (not 500) since they're operational, not application\nbugs.","operationId":"snapshot_config_snapshot_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConfigSnapshotResponse"}}}}}}},"/backtest/runs":{"get":{"tags":["backtest"],"summary":"List backtest runs (most-recent first)","operationId":"list_runs_backtest_runs_get","parameters":[{"name":"strategy_name","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Optional filter","title":"Strategy Name"},"description":"Optional filter"},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":200,"minimum":1,"description":"Max rows","default":50,"title":"Limit"},"description":"Max rows"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RunsResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/backtest/runs/{run_id}":{"get":{"tags":["backtest"],"summary":"Single backtest run with equity curve + params","operationId":"get_run_backtest_runs__run_id__get","parameters":[{"name":"run_id","in":"path","required":true,"schema":{"type":"integer","title":"Run Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RunDetail"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/backtest/strategies":{"get":{"tags":["backtest"],"summary":"Strategies the launcher can drive","operationId":"list_strategies_backtest_strategies_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StrategiesResponse"}}}}}}},"/backtest/datasets":{"get":{"tags":["backtest"],"summary":"Parquet datasets available to the launcher","description":"Recursive scan of ``$SWINGER_RESEARCH_DIR`` for ``*.parquet``.\n\nMost-recently-modified first. Operators add datasets by dropping\nparquet files into the mounted research dir; no DB registry yet —\nYAGNI until we need ACL or annotations.\n\nFilesystem walks are pushed off the event loop so a deeply-nested\nresearch tree doesn't stall request handling.","operationId":"list_datasets_backtest_datasets_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DatasetsResponse"}}}}}}},"/backtest/run":{"post":{"tags":["backtest"],"summary":"Kick off a backtest run (async)","operationId":"run_backtest_backtest_run_post","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Bearer <session-token>","title":"Authorization"},"description":"Bearer <session-token>"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RunBacktestRequest"}}}},"responses":{"202":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RunAck"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/strategies/state":{"get":{"tags":["strategies"],"summary":"List operator state for every catalogue strategy","operationId":"list_strategy_state_strategies_state_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StrategyStateResponse"}}}}}}},"/strategies/state/{name}":{"patch":{"tags":["strategies"],"summary":"Update enable / paper-shadow / risk-profile for one strategy","operationId":"patch_strategy_state_strategies_state__name__patch","parameters":[{"name":"name","in":"path","required":true,"schema":{"type":"string","title":"Name"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Bearer <session-token>","title":"Authorization"},"description":"Bearer <session-token>"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/StrategyStatePatch"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StrategyStateRow"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}}},"components":{"schemas":{"AuditEventRow":{"properties":{"id":{"type":"integer","title":"Id"},"ts":{"type":"string","format":"date-time","title":"Ts"},"actor":{"type":"string","title":"Actor"},"action":{"type":"string","title":"Action"},"target":{"type":"string","title":"Target"},"before_state":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Before State"},"after_state":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"After State"},"source_ip":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Source Ip"}},"additionalProperties":false,"type":"object","required":["id","ts","actor","action","target","before_state","after_state","source_ip"],"title":"AuditEventRow"},"AuditEventsResponse":{"properties":{"rows":{"items":{"$ref":"#/components/schemas/AuditEventRow"},"type":"array","title":"Rows","description":"Most-recent first."},"polled_at":{"type":"string","format":"date-time","title":"Polled At"}},"additionalProperties":false,"type":"object","required":["rows","polled_at"],"title":"AuditEventsResponse"},"CollateralPosition":{"properties":{"ts":{"type":"string","format":"date-time","title":"Ts","description":"When the snapshot was computed (UTC)"},"collateral":{"additionalProperties":{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"},"type":"object","title":"Collateral","description":"Per-currency balance in the margin wallet, e.g. {'BTC': Decimal('1.2'), 'ETH': Decimal('15')}"},"collateral_value_eur":{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$","title":"Collateral Value Eur","description":"Sum of collateral * mark_price[ccy], in EUR"},"borrow_eur":{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$","title":"Borrow Eur","description":"Outstanding EUR borrow (positive amount)"},"accrued_funding_eur":{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$","title":"Accrued Funding Eur","description":"EUR funding cost accrued since position open","default":"0"},"mark_prices":{"additionalProperties":{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"},"type":"object","title":"Mark Prices","description":"Mark price per collateral asset, e.g. {'BTC': Decimal('60000'), 'ETH': Decimal('4000')}"},"maintenance_margin_ratio":{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$","title":"Maintenance Margin Ratio","description":"Bitfinex maintenance-margin requirement (fraction). Below this → liquidation."},"ltv":{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$","title":"Ltv","description":"borrow_eur / collateral_value_eur, as fraction"},"distance_to_liquidation_pct":{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$","title":"Distance To Liquidation Pct","description":"Fraction the smallest-collateral asset can drop before liquidation"},"wall_clock_age_ms":{"type":"integer","minimum":0.0,"title":"Wall Clock Age Ms","description":"ms since the snapshot was taken — staleness signal for alerts"},"source":{"type":"string","enum":["ws","rest_fallback","synthetic","simulation"],"title":"Source","description":"Where the data came from. 'ws' / 'rest_fallback' = real Bitfinex auth feed; 'simulation' = paper-shadow `sim_*` tables (Phase 3+); 'synthetic' = legacy in-memory fixture (Phase 2 quick-test mode)."}},"additionalProperties":false,"type":"object","required":["ts","collateral","collateral_value_eur","borrow_eur","mark_prices","maintenance_margin_ratio","ltv","distance_to_liquidation_pct","wall_clock_age_ms","source"],"title":"CollateralPosition","description":"Snapshot of the Bitfinex margin account state at a point in time.\n\nAll amounts/prices are ``Decimal`` to keep precision through the JSON ↔\nPostgres ↔ Redis round-trip; pydantic serialises them as strings.\n\n``ltv`` and ``distance_to_liquidation_pct`` are *fractions* (0.40 = 40%),\nnot percentages — keeps the math monotonic and avoids the off-by-100\nfoot-gun across modules."},"ConfigSnapshotResponse":{"properties":{"core":{"additionalProperties":true,"type":"object","title":"Core"},"venues":{"additionalProperties":true,"type":"object","title":"Venues"},"risk_profiles":{"additionalProperties":true,"type":"object","title":"Risk Profiles"},"guardian":{"additionalProperties":true,"type":"object","title":"Guardian"},"notifications":{"additionalProperties":true,"type":"object","title":"Notifications"},"polled_at":{"type":"string","format":"date-time","title":"Polled At"}},"additionalProperties":false,"type":"object","required":["core","venues","risk_profiles","guardian","notifications","polled_at"],"title":"ConfigSnapshotResponse"},"DatasetEntry":{"properties":{"path":{"type":"string","title":"Path"},"name":{"type":"string","title":"Name"},"size_bytes":{"type":"integer","title":"Size Bytes"}},"additionalProperties":false,"type":"object","required":["path","name","size_bytes"],"title":"DatasetEntry"},"DatasetsResponse":{"properties":{"rows":{"items":{"$ref":"#/components/schemas/DatasetEntry"},"type":"array","title":"Rows"},"polled_at":{"type":"string","format":"date-time","title":"Polled At"}},"additionalProperties":false,"type":"object","required":["rows","polled_at"],"title":"DatasetsResponse"},"EquityResponse":{"properties":{"strategy_name":{"type":"string","title":"Strategy Name"},"cash":{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$","title":"Cash"},"position_value":{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$","title":"Position Value","description":"Sum of qty * mark_price across open positions for this strategy."},"equity":{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$","title":"Equity","description":"cash + position_value"},"polled_at":{"type":"string","format":"date-time","title":"Polled At"}},"additionalProperties":false,"type":"object","required":["strategy_name","cash","position_value","equity","polled_at"],"title":"EquityResponse"},"GenericResponse":{"properties":{"sent":{"type":"boolean","title":"Sent","default":true}},"additionalProperties":false,"type":"object","title":"GenericResponse"},"GuardianEventResponse":{"properties":{"ts":{"type":"string","format":"date-time","title":"Ts"},"tier":{"type":"integer","title":"Tier"},"action":{"type":"string","title":"Action"},"ltv_before":{"anyOf":[{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"},{"type":"null"}],"title":"Ltv Before"},"ltv_after":{"anyOf":[{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"},{"type":"null"}],"title":"Ltv After"},"details":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Details"}},"additionalProperties":false,"type":"object","required":["ts","tier","action","ltv_before","ltv_after","details"],"title":"GuardianEventResponse","description":"One row from ``guardian_events``. Numeric fields are str-encoded\nDecimals so the JSON survives the round-trip without float coercion."},"HTTPValidationError":{"properties":{"detail":{"items":{"$ref":"#/components/schemas/ValidationError"},"type":"array","title":"Detail"}},"type":"object","title":"HTTPValidationError"},"HealthResponse":{"properties":{"status":{"type":"string","title":"Status"},"version":{"type":"string","title":"Version"},"timestamp":{"type":"string","format":"date-time","title":"Timestamp"}},"type":"object","required":["status","version","timestamp"],"title":"HealthResponse"},"IngestionHealthResponse":{"properties":{"venues":{"items":{"$ref":"#/components/schemas/VenueHealthRow"},"type":"array","title":"Venues"},"polled_at":{"type":"string","format":"date-time","title":"Polled At"},"window_seconds":{"type":"integer","title":"Window Seconds","description":"Width of the events_5min counter, in seconds (default 300)"}},"additionalProperties":false,"type":"object","required":["venues","polled_at","window_seconds"],"title":"IngestionHealthResponse","description":"Top-level wrapper. ``polled_at`` lets the UI show a \"last refreshed\nXs ago\" timestamp without trusting client-side clocks."},"KillStatusResponse":{"properties":{"halt":{"type":"boolean","title":"Halt","description":"exec.halt flag set in Redis"},"halt_reason":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Halt Reason","description":"Stored value of exec.halt, if set"},"flat":{"type":"boolean","title":"Flat","description":"exec.flat flag set in Redis"},"flat_reason":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Flat Reason","description":"Stored value of exec.flat, if set"},"polled_at":{"type":"string","format":"date-time","title":"Polled At"}},"additionalProperties":false,"type":"object","required":["halt","flat","polled_at"],"title":"KillStatusResponse"},"KillSwitchAck":{"properties":{"ok":{"type":"boolean","title":"Ok"},"halt":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Halt"},"flat":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Flat"}},"additionalProperties":false,"type":"object","required":["ok"],"title":"KillSwitchAck"},"KillSwitchRequest":{"properties":{"on":{"type":"boolean","title":"On","description":"True = set the flag, False = clear it"}},"additionalProperties":false,"type":"object","required":["on"],"title":"KillSwitchRequest"},"LaunchableStrategy":{"properties":{"name":{"type":"string","title":"Name"},"summary":{"type":"string","title":"Summary"}},"additionalProperties":false,"type":"object","required":["name","summary"],"title":"LaunchableStrategy"},"MagicLinkRequest":{"properties":{"email":{"type":"string","format":"email","title":"Email"}},"additionalProperties":false,"type":"object","required":["email"],"title":"MagicLinkRequest"},"MeResponse":{"properties":{"email":{"type":"string","title":"Email"},"totp_enabled":{"type":"boolean","title":"Totp Enabled"}},"additionalProperties":false,"type":"object","required":["email","totp_enabled"],"title":"MeResponse"},"OhlcvResponse":{"properties":{"venue":{"type":"string","title":"Venue"},"symbol":{"type":"string","title":"Symbol"},"interval":{"type":"string","title":"Interval"},"rows":{"items":{"$ref":"#/components/schemas/OhlcvRow"},"type":"array","title":"Rows","description":"Ascending by ts; oldest first."},"polled_at":{"type":"string","format":"date-time","title":"Polled At"}},"additionalProperties":false,"type":"object","required":["venue","symbol","interval","rows","polled_at"],"title":"OhlcvResponse"},"OhlcvRow":{"properties":{"ts":{"type":"string","format":"date-time","title":"Ts"},"o":{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$","title":"O"},"h":{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$","title":"H"},"l":{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$","title":"L"},"c":{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$","title":"C"},"v":{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$","title":"V"}},"additionalProperties":false,"type":"object","required":["ts","o","h","l","c","v"],"title":"OhlcvRow"},"PaperFillRow":{"properties":{"ts":{"type":"string","format":"date-time","title":"Ts"},"run_id":{"type":"string","title":"Run Id"},"strategy_name":{"type":"string","title":"Strategy Name"},"venue":{"type":"string","title":"Venue"},"symbol":{"type":"string","title":"Symbol"},"side":{"type":"string","title":"Side"},"qty":{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$","title":"Qty"},"price":{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$","title":"Price"},"fee":{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$","title":"Fee"}},"additionalProperties":false,"type":"object","required":["ts","run_id","strategy_name","venue","symbol","side","qty","price","fee"],"title":"PaperFillRow"},"PaperFillsResponse":{"properties":{"rows":{"items":{"$ref":"#/components/schemas/PaperFillRow"},"type":"array","title":"Rows","description":"Most-recent fills first."},"polled_at":{"type":"string","format":"date-time","title":"Polled At"}},"additionalProperties":false,"type":"object","required":["rows","polled_at"],"title":"PaperFillsResponse"},"PaperSnapshotRow":{"properties":{"ts":{"type":"string","format":"date-time","title":"Ts"},"run_id":{"type":"string","title":"Run Id"},"strategy_name":{"type":"string","title":"Strategy Name"},"venue":{"type":"string","title":"Venue"},"symbol":{"type":"string","title":"Symbol"},"cash":{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$","title":"Cash"},"position_qty":{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$","title":"Position Qty"},"mark_price":{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$","title":"Mark Price"},"equity":{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$","title":"Equity","description":"cash + position_qty * mark_price"}},"additionalProperties":false,"type":"object","required":["ts","run_id","strategy_name","venue","symbol","cash","position_qty","mark_price","equity"],"title":"PaperSnapshotRow"},"PaperSnapshotsResponse":{"properties":{"rows":{"items":{"$ref":"#/components/schemas/PaperSnapshotRow"},"type":"array","title":"Rows","description":"Ascending by ts; oldest first."},"polled_at":{"type":"string","format":"date-time","title":"Polled At"}},"additionalProperties":false,"type":"object","required":["rows","polled_at"],"title":"PaperSnapshotsResponse"},"PositionRow":{"properties":{"strategy_name":{"type":"string","title":"Strategy Name"},"venue":{"type":"string","title":"Venue"},"symbol":{"type":"string","title":"Symbol"},"qty":{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$","title":"Qty"},"avg_entry":{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$","title":"Avg Entry"},"last_fill_ts":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Last Fill Ts"}},"additionalProperties":false,"type":"object","required":["strategy_name","venue","symbol","qty","avg_entry","last_fill_ts"],"title":"PositionRow"},"PositionsResponse":{"properties":{"rows":{"items":{"$ref":"#/components/schemas/PositionRow"},"type":"array","title":"Rows","description":"Open positions (zero-qty omitted)."},"polled_at":{"type":"string","format":"date-time","title":"Polled At"}},"additionalProperties":false,"type":"object","required":["rows","polled_at"],"title":"PositionsResponse"},"ProfileRow":{"properties":{"name":{"type":"string","title":"Name"},"max_strategy_drawdown_30d":{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$","title":"Max Strategy Drawdown 30D"},"per_trade_risk_pct":{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$","title":"Per Trade Risk Pct"},"kelly_fraction":{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$","title":"Kelly Fraction"},"max_concurrent_positions":{"type":"integer","title":"Max Concurrent Positions"},"portfolio_drawdown_hardstop":{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$","title":"Portfolio Drawdown Hardstop"},"max_single_asset_exposure":{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$","title":"Max Single Asset Exposure"},"max_venue_concentration":{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$","title":"Max Venue Concentration"}},"additionalProperties":false,"type":"object","required":["name","max_strategy_drawdown_30d","per_trade_risk_pct","kelly_fraction","max_concurrent_positions","portfolio_drawdown_hardstop","max_single_asset_exposure","max_venue_concentration"],"title":"ProfileRow"},"ProfilesResponse":{"properties":{"profiles":{"items":{"$ref":"#/components/schemas/ProfileRow"},"type":"array","title":"Profiles","description":"Sorted conservative→aggressive."},"polled_at":{"type":"string","format":"date-time","title":"Polled At"}},"additionalProperties":false,"type":"object","required":["profiles","polled_at"],"title":"ProfilesResponse"},"RecoveryUseRequest":{"properties":{"code":{"type":"string","title":"Code"}},"additionalProperties":false,"type":"object","required":["code"],"title":"RecoveryUseRequest"},"RunAck":{"properties":{"run_id":{"type":"integer","title":"Run Id"},"status":{"type":"string","title":"Status"}},"additionalProperties":false,"type":"object","required":["run_id","status"],"title":"RunAck"},"RunBacktestRequest":{"properties":{"strategy":{"type":"string","title":"Strategy"},"parquet_path":{"type":"string","title":"Parquet Path"},"symbol":{"type":"string","title":"Symbol"},"initial_cash":{"type":"string","title":"Initial Cash","description":"Decimal string — kept as str for precision.","default":"100000"},"bars_per_year":{"type":"integer","maximum":525600.0,"minimum":1.0,"title":"Bars Per Year","description":"Annualisation factor (252 daily, 8760 hourly, 365 crypto-daily, 525600 minute).","default":252}},"additionalProperties":false,"type":"object","required":["strategy","parquet_path","symbol"],"title":"RunBacktestRequest"},"RunDetail":{"properties":{"id":{"type":"integer","title":"Id"},"ts":{"type":"string","format":"date-time","title":"Ts"},"strategy_name":{"type":"string","title":"Strategy Name"},"dataset_id":{"type":"string","title":"Dataset Id"},"params":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Params"},"metrics":{"additionalProperties":true,"type":"object","title":"Metrics"},"equity_curve":{"anyOf":[{"items":{"additionalProperties":true,"type":"object"},"type":"array"},{"type":"null"}],"title":"Equity Curve"},"gate_passed":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Gate Passed"},"gate_failures":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Gate Failures"},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes"},"status":{"type":"string","title":"Status"},"started_ts":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Started Ts"},"completed_ts":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Completed Ts"},"polled_at":{"type":"string","format":"date-time","title":"Polled At"}},"additionalProperties":false,"type":"object","required":["id","ts","strategy_name","dataset_id","params","metrics","equity_curve","gate_passed","gate_failures","notes","status","started_ts","completed_ts","polled_at"],"title":"RunDetail"},"RunSummary":{"properties":{"id":{"type":"integer","title":"Id"},"ts":{"type":"string","format":"date-time","title":"Ts"},"strategy_name":{"type":"string","title":"Strategy Name"},"dataset_id":{"type":"string","title":"Dataset Id"},"metrics":{"additionalProperties":true,"type":"object","title":"Metrics"},"gate_passed":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Gate Passed"},"gate_failures":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Gate Failures"},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes"},"status":{"type":"string","title":"Status"},"started_ts":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Started Ts"},"completed_ts":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Completed Ts"}},"additionalProperties":false,"type":"object","required":["id","ts","strategy_name","dataset_id","metrics","gate_passed","gate_failures","notes","status","started_ts","completed_ts"],"title":"RunSummary"},"RunsResponse":{"properties":{"rows":{"items":{"$ref":"#/components/schemas/RunSummary"},"type":"array","title":"Rows","description":"Most-recent-first."},"polled_at":{"type":"string","format":"date-time","title":"Polled At"}},"additionalProperties":false,"type":"object","required":["rows","polled_at"],"title":"RunsResponse"},"SessionResponse":{"properties":{"token":{"type":"string","title":"Token"},"expires_at":{"type":"string","format":"date-time","title":"Expires At"}},"additionalProperties":false,"type":"object","required":["token","expires_at"],"title":"SessionResponse"},"SnapshotResponse":{"properties":{"strategy_name":{"type":"string","title":"Strategy Name"},"profile":{"type":"string","title":"Profile","description":"Risk profile assigned to this strategy."},"cash":{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$","title":"Cash"},"position_qty":{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$","title":"Position Qty"},"mark_price":{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$","title":"Mark Price"},"equity":{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$","title":"Equity"},"strategy_drawdown_30d":{"anyOf":[{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"},{"type":"null"}],"title":"Strategy Drawdown 30D"},"open_position_count":{"type":"integer","title":"Open Position Count"},"polled_at":{"type":"string","format":"date-time","title":"Polled At"}},"additionalProperties":false,"type":"object","required":["strategy_name","profile","cash","position_qty","mark_price","equity","open_position_count","polled_at"],"title":"SnapshotResponse"},"StrategiesResponse":{"properties":{"rows":{"items":{"$ref":"#/components/schemas/LaunchableStrategy"},"type":"array","title":"Rows"},"polled_at":{"type":"string","format":"date-time","title":"Polled At"}},"additionalProperties":false,"type":"object","required":["rows","polled_at"],"title":"StrategiesResponse"},"StrategyStatePatch":{"properties":{"enabled":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Enabled"},"paper_shadow_state":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Paper Shadow State"},"risk_profile":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Risk Profile"}},"additionalProperties":false,"type":"object","title":"StrategyStatePatch"},"StrategyStateResponse":{"properties":{"rows":{"items":{"$ref":"#/components/schemas/StrategyStateRow"},"type":"array","title":"Rows"},"polled_at":{"type":"string","format":"date-time","title":"Polled At"}},"additionalProperties":false,"type":"object","required":["rows","polled_at"],"title":"StrategyStateResponse"},"StrategyStateRow":{"properties":{"name":{"type":"string","title":"Name"},"enabled":{"type":"boolean","title":"Enabled"},"paper_shadow_state":{"type":"string","title":"Paper Shadow State"},"risk_profile":{"type":"string","title":"Risk Profile"},"updated_at":{"type":"string","format":"date-time","title":"Updated At"},"updated_by":{"type":"string","title":"Updated By"}},"additionalProperties":false,"type":"object","required":["name","enabled","paper_shadow_state","risk_profile","updated_at","updated_by"],"title":"StrategyStateRow"},"TaxLotMethod":{"type":"string","enum":["hcf","fifo","lifo"],"title":"TaxLotMethod","description":"Lot-selection method for matching SELLs to BUYs.\n\nHCF (highest-cost-first) is the §11 default — minimises realised\ngains by closing out the most-expensive lots first, useful for\nDutch reporting and for general tax-loss harvesting.\n\nFIFO closes oldest lots first; LIFO closes newest lots first.\nAll three are configurable per export."},"TickerRow":{"properties":{"venue":{"type":"string","title":"Venue"},"symbol":{"type":"string","title":"Symbol"},"ts":{"type":"string","format":"date-time","title":"Ts"},"age_seconds":{"type":"integer","title":"Age Seconds"},"bid":{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$","title":"Bid"},"ask":{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$","title":"Ask"},"last":{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$","title":"Last"},"spread_bps":{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$","title":"Spread Bps","description":"((ask - bid) / mid) * 10000, in basis points. Useful at-a-glance tightness check across venues — Bitvavo BTC-EUR typically 1-3 bps, Kraken 0.5-2 bps."}},"additionalProperties":false,"type":"object","required":["venue","symbol","ts","age_seconds","bid","ask","last","spread_bps"],"title":"TickerRow"},"TickersResponse":{"properties":{"rows":{"items":{"$ref":"#/components/schemas/TickerRow"},"type":"array","title":"Rows"},"polled_at":{"type":"string","format":"date-time","title":"Polled At"}},"additionalProperties":false,"type":"object","required":["rows","polled_at"],"title":"TickersResponse"},"TotpSetupConfirmRequest":{"properties":{"code":{"type":"string","maxLength":6,"minLength":6,"title":"Code"}},"additionalProperties":false,"type":"object","required":["code"],"title":"TotpSetupConfirmRequest"},"TotpSetupConfirmResponse":{"properties":{"recovery_codes":{"items":{"type":"string"},"type":"array","title":"Recovery Codes","description":"Shown ONCE — operator must save them or lose them."}},"additionalProperties":false,"type":"object","required":["recovery_codes"],"title":"TotpSetupConfirmResponse"},"TotpSetupStartResponse":{"properties":{"secret":{"type":"string","title":"Secret"},"provisioning_uri":{"type":"string","title":"Provisioning Uri"}},"additionalProperties":false,"type":"object","required":["secret","provisioning_uri"],"title":"TotpSetupStartResponse"},"TotpVerifyRequest":{"properties":{"code":{"type":"string","maxLength":6,"minLength":6,"title":"Code"}},"additionalProperties":false,"type":"object","required":["code"],"title":"TotpVerifyRequest"},"ValidationError":{"properties":{"loc":{"items":{"anyOf":[{"type":"string"},{"type":"integer"}]},"type":"array","title":"Location"},"msg":{"type":"string","title":"Message"},"type":{"type":"string","title":"Error Type"},"input":{"title":"Input"},"ctx":{"type":"object","title":"Context"}},"type":"object","required":["loc","msg","type"],"title":"ValidationError"},"VenueHealthRow":{"properties":{"venue":{"type":"string","title":"Venue"},"symbol":{"type":"string","title":"Symbol"},"last_seen":{"type":"string","format":"date-time","title":"Last Seen"},"age_seconds":{"type":"integer","title":"Age Seconds"},"events_5min":{"type":"integer","title":"Events 5Min"},"last_price":{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$","title":"Last Price"}},"additionalProperties":false,"type":"object","required":["venue","symbol","last_seen","age_seconds","events_5min","last_price"],"title":"VenueHealthRow"}}}}