diff --git a/docs/superpowers/plans/2026-07-02-runtime-tunable-risk-controls.md b/docs/superpowers/plans/2026-07-02-runtime-tunable-risk-controls.md new file mode 100644 index 0000000..a79b295 --- /dev/null +++ b/docs/superpowers/plans/2026-07-02-runtime-tunable-risk-controls.md @@ -0,0 +1,745 @@ +# Runtime-tunable Risk Controls — Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Make Capital's five global risk limits (stop-loss, take-profit, order-size cap, daily-loss limit, drawdown kill switch) editable at runtime from the Settings UI, applied on the next engine tick, without an engine restart. + +**Architecture:** The `RiskManager` enforcement math in `engine/trading/risk.py` is unchanged. Limits move from boot-time env config into the runtime `appsettings` key-value store (no DB migration — the store is generic KV). The engine builds a fresh `RiskManager` from the store each tick (same store→engine flow as `ai_action_mode`). A typed settings endpoint + a "Risk controls" card on the Settings page read/write them. Limits default to `0`/disabled; the UI pre-fills recommended values (no behavior change until saved). Getters fall back to the env `risk_*` value so existing env-configured deployments keep working. + +**Tech Stack:** Python 3.12, SQLModel, FastAPI, Pydantic; React 19 + TypeScript + Vite; openapi-typescript for the client types; pytest. + +**Spec:** `docs/superpowers/specs/2026-07-02-runtime-tunable-risk-controls-design.md` + +**Conventions (verified in the codebase):** +- Store getters/setters use `get_setting`/`set_setting` + `_decimal_setting(session, key, default)` (`engine/appsettings/store.py`). +- API: per-setting `*Update` Pydantic model + `@router.put("/", response_model=SettingsRead)` that calls the store setter(s), `record_audit(...)`, then returns `_read(session)`. Deps: `AdminUser`, `SessionDep`. +- Web client: `const { data, error } = await api.PUT("/api/settings/", { body }); if (error) ...; return data;` (`web/src/lib/api/settings.ts`). +- Tests use the `session` and `client`/`login` fixtures in `engine/tests/conftest.py`. + +--- + +## Task 1: Risk-limit getters/setters in the settings store + +**Files:** +- Modify: `engine/appsettings/store.py` +- Test: `engine/tests/test_appsettings.py` + +- [ ] **Step 1: Write the failing tests** + +Add to `engine/tests/test_appsettings.py` (imports at top: extend the existing `from appsettings.store import (...)` block with the five getters/setters): + +```python +def test_risk_limits_default_to_zero(session: Session) -> None: + from appsettings.store import ( + get_risk_stop_loss_pct, + get_risk_take_profit_pct, + get_risk_max_drawdown_pct, + get_risk_daily_loss_limit, + get_risk_max_position_notional, + ) + assert get_risk_stop_loss_pct(session) == Decimal(0) + assert get_risk_take_profit_pct(session) == Decimal(0) + assert get_risk_max_drawdown_pct(session) == Decimal(0) + assert get_risk_daily_loss_limit(session) == Decimal(0) + assert get_risk_max_position_notional(session) == Decimal(0) + + +def test_risk_limit_round_trip(session: Session) -> None: + from appsettings.store import get_risk_stop_loss_pct, set_risk_stop_loss_pct + set_risk_stop_loss_pct(session, Decimal("5")) + assert get_risk_stop_loss_pct(session) == Decimal("5") + + +def test_risk_getter_falls_back_to_env_default_when_unset(session: Session) -> None: + from appsettings.store import get_risk_stop_loss_pct + # No stored value → the caller-supplied default (env fallback) is returned. + assert get_risk_stop_loss_pct(session, Decimal("3")) == Decimal("3") + + +def test_risk_explicit_zero_overrides_env_default(session: Session) -> None: + from appsettings.store import get_risk_stop_loss_pct, set_risk_stop_loss_pct + set_risk_stop_loss_pct(session, Decimal("0")) # operator deliberately disabled it + assert get_risk_stop_loss_pct(session, Decimal("3")) == Decimal("0") +``` + +- [ ] **Step 2: Run the tests to verify they fail** + +Run: `cd engine && python -m pytest tests/test_appsettings.py -k risk -v` +Expected: FAIL — `ImportError: cannot import name 'get_risk_stop_loss_pct'`. + +- [ ] **Step 3: Add the key constants and getters/setters** + +In `engine/appsettings/store.py`, add the key constants alongside the existing ones (near `_AI_SPEND_CAP = "ai_spend_cap_usd"`): + +```python +_RISK_STOP_LOSS = "risk_stop_loss_pct" +_RISK_TAKE_PROFIT = "risk_take_profit_pct" +_RISK_MAX_DRAWDOWN = "risk_max_drawdown_pct" +_RISK_DAILY_LOSS = "risk_daily_loss_limit" +_RISK_MAX_NOTIONAL = "risk_max_position_notional" +``` + +Add the getters/setters near the other `_decimal_setting` helpers (e.g. below `set_ai_spend_cap`). Each getter takes a `default` so callers can supply the env fallback; `_decimal_setting` returns `default` only when the key is unset (an explicitly-stored `"0"` is returned as `Decimal(0)`): + +```python +def get_risk_stop_loss_pct(session: Session, default: Decimal = Decimal(0)) -> Decimal: + """Stop-loss as a percent of a position's entry value (0 = disabled).""" + return _decimal_setting(session, _RISK_STOP_LOSS, default) + + +def set_risk_stop_loss_pct(session: Session, pct: Decimal) -> None: + set_setting(session, _RISK_STOP_LOSS, str(pct)) + + +def get_risk_take_profit_pct(session: Session, default: Decimal = Decimal(0)) -> Decimal: + """Take-profit as a percent of a position's entry value (0 = disabled).""" + return _decimal_setting(session, _RISK_TAKE_PROFIT, default) + + +def set_risk_take_profit_pct(session: Session, pct: Decimal) -> None: + set_setting(session, _RISK_TAKE_PROFIT, str(pct)) + + +def get_risk_max_drawdown_pct(session: Session, default: Decimal = Decimal(0)) -> Decimal: + """Kill-switch drawdown limit as a percent from the equity peak (0 = disabled).""" + return _decimal_setting(session, _RISK_MAX_DRAWDOWN, default) + + +def set_risk_max_drawdown_pct(session: Session, pct: Decimal) -> None: + set_setting(session, _RISK_MAX_DRAWDOWN, str(pct)) + + +def get_risk_daily_loss_limit(session: Session, default: Decimal = Decimal(0)) -> Decimal: + """Kill-switch daily realized-loss limit in quote currency (0 = disabled).""" + return _decimal_setting(session, _RISK_DAILY_LOSS, default) + + +def set_risk_daily_loss_limit(session: Session, amount: Decimal) -> None: + set_setting(session, _RISK_DAILY_LOSS, str(amount)) + + +def get_risk_max_position_notional(session: Session, default: Decimal = Decimal(0)) -> Decimal: + """Per-order notional cap in quote currency (0 = disabled).""" + return _decimal_setting(session, _RISK_MAX_NOTIONAL, default) + + +def set_risk_max_position_notional(session: Session, amount: Decimal) -> None: + set_setting(session, _RISK_MAX_NOTIONAL, str(amount)) +``` + +- [ ] **Step 4: Run the tests to verify they pass** + +Run: `cd engine && python -m pytest tests/test_appsettings.py -k risk -v` +Expected: PASS (4 tests). + +- [ ] **Step 5: Commit** + +```bash +git add engine/appsettings/store.py engine/tests/test_appsettings.py +git commit -m "feat(risk): risk-limit getters/setters in the settings store" +``` + +--- + +## Task 2: `RiskManager.from_store` + +**Files:** +- Modify: `engine/trading/risk.py` +- Test: `engine/tests/test_risk.py` + +- [ ] **Step 1: Write the failing tests** + +Add to `engine/tests/test_risk.py` (it already imports `RiskManager`, `Decimal`, `Session`): + +```python +def test_from_store_reads_stored_limits(session: Session) -> None: + from appsettings.store import set_risk_stop_loss_pct, set_risk_max_position_notional + set_risk_stop_loss_pct(session, Decimal("5")) + set_risk_max_position_notional(session, Decimal("1000")) + rm = RiskManager.from_store(session) + assert rm.stop_loss_pct == Decimal("5") + assert rm.max_position_notional == Decimal("1000") + assert rm.take_profit_pct == Decimal(0) # unset → disabled + + +def test_from_store_falls_back_to_env_settings(session: Session) -> None: + from config import Settings + env = Settings(risk_stop_loss_pct=Decimal("7")) # nothing stored + rm = RiskManager.from_store(session, env) + assert rm.stop_loss_pct == Decimal("7") +``` + +- [ ] **Step 2: Run the tests to verify they fail** + +Run: `cd engine && python -m pytest tests/test_risk.py -k from_store -v` +Expected: FAIL — `AttributeError: type object 'RiskManager' has no attribute 'from_store'`. + +- [ ] **Step 3: Implement `from_store`** + +In `engine/trading/risk.py`, add the import of the env singleton near the existing `from config import Settings`: + +```python +from config import Settings, settings as _env_settings +``` + +Add the classmethod directly below the existing `from_settings` classmethod: + +```python + @classmethod + def from_store( + cls, session: Session, settings: Settings | None = None + ) -> "RiskManager": + """Build from the runtime settings store, falling back to env config + for any limit the operator has not set through the UI.""" + from appsettings.store import ( + get_risk_daily_loss_limit, + get_risk_max_drawdown_pct, + get_risk_max_position_notional, + get_risk_stop_loss_pct, + get_risk_take_profit_pct, + ) + + env = settings if settings is not None else _env_settings + return cls( + max_position_notional=get_risk_max_position_notional( + session, env.risk_max_position_notional + ), + stop_loss_pct=get_risk_stop_loss_pct(session, env.risk_stop_loss_pct), + take_profit_pct=get_risk_take_profit_pct(session, env.risk_take_profit_pct), + daily_loss_limit=get_risk_daily_loss_limit(session, env.risk_daily_loss_limit), + max_drawdown_pct=get_risk_max_drawdown_pct(session, env.risk_max_drawdown_pct), + ) +``` + +(The `appsettings.store` import is inside the method to avoid any import-order coupling with the store module.) + +- [ ] **Step 4: Run the tests to verify they pass** + +Run: `cd engine && python -m pytest tests/test_risk.py -k from_store -v` +Expected: PASS (2 tests). Also run the full file to confirm no regressions: `python -m pytest tests/test_risk.py -v` → all PASS. + +- [ ] **Step 5: Commit** + +```bash +git add engine/trading/risk.py engine/tests/test_risk.py +git commit -m "feat(risk): RiskManager.from_store with env fallback" +``` + +--- + +## Task 3: Engine reads risk limits per tick + +**Files:** +- Modify: `engine/trading/engine.py` (constructor ~line 110; `_tick_strategy` ~lines 162-260) +- Test: `engine/tests/test_trading_engine.py` + +- [ ] **Step 1: Confirm the public tick method name** + +Run: `cd engine && grep -n "def tick\|def _tick_strategy\|self._risk" trading/engine.py` +Expected: a public `def tick(self)` that iterates strategies and calls `_tick_strategy`; `self._risk` used at ~line 194 (`stop_order`) and ~line 260 (`review`). Use the confirmed public method name in Step 2's test (below it is written as `eng.tick()`). + +- [ ] **Step 2: Write the failing test** + +Add to `engine/tests/test_trading_engine.py` (imports: add `from appsettings.store import set_risk_max_position_notional`; `list_positions`, `Market`, `BuyWhenFlat`, `_engine`, `factory` already exist in the file): + +```python +def test_engine_applies_store_risk_limits(factory: Any) -> None: + # BuyWhenFlat buys qty 1 at the FakeVenue price of 100 → notional 100. + # A stored per-order notional cap of 50 must clip the fill to qty 0.5. + strat = BuyWhenFlat("buyer", "BTCUSDT", market=Market.spot) + eng = _engine(factory, [strat]) + with factory() as session: + set_risk_max_position_notional(session, Decimal("50")) + session.commit() + eng.tick() + with factory() as session: + positions = list_positions(session) + assert len(positions) == 1 + assert positions[0].qty == Decimal("0.5") +``` + +- [ ] **Step 3: Run the test to verify it fails** + +Run: `cd engine && python -m pytest tests/test_trading_engine.py -k store_risk_limits -v` +Expected: FAIL — position qty is `1` (the boot-time `RiskManager()` has all limits disabled, so the order is not capped). + +- [ ] **Step 4: Refactor the engine to read per tick** + +In `engine/trading/engine.py` constructor, replace the boot-time singleton line: + +```python + self._risk = risk or RiskManager() # all limits disabled by default +``` + +with an override field (do NOT build a default here): + +```python + # Optional injected RiskManager (tests). When None, limits are read from + # the settings store on every tick so UI changes apply without a restart. + self._risk_override = risk +``` + +In `_tick_strategy`, immediately after the session is opened (`with self._session_factory() as session:`), build the per-tick manager: + +```python + risk = self._risk_override or RiskManager.from_store(session) +``` + +Then change the two use sites in `_tick_strategy` from `self._risk` to the local `risk`: + +```python + stop = risk.stop_order(position, price) +``` +```python + order = risk.review(session, order, position, price) +``` + +- [ ] **Step 5: Run the test to verify it passes** + +Run: `cd engine && python -m pytest tests/test_trading_engine.py -k store_risk_limits -v` +Expected: PASS (qty == 0.5). + +- [ ] **Step 6: Run the full engine + risk suites for regressions** + +Run: `cd engine && python -m pytest tests/test_trading_engine.py tests/test_risk.py -v` +Expected: all PASS (tests that inject `risk=RiskManager(...)` still work via `_risk_override`). + +- [ ] **Step 7: Commit** + +```bash +git add engine/trading/engine.py engine/tests/test_trading_engine.py +git commit -m "feat(risk): engine reads risk limits from the store each tick" +``` + +--- + +## Task 4: Settings API — read + update risk limits + +**Files:** +- Modify: `engine/api/settings.py` +- Test: `engine/tests/test_settings_api.py` + +- [ ] **Step 1: Write the failing tests** + +Add to `engine/tests/test_settings_api.py` (follow the file's existing pattern; it uses the `client` fixture + `login`). If the file already has an auth helper, reuse it; otherwise use the `login(client, "admin", ADMIN_PASSWORD)` helper from `conftest.py` and send the token as a Bearer header: + +```python +from conftest import ADMIN_PASSWORD, login + + +def _auth(client) -> dict[str, str]: + return {"Authorization": f"Bearer {login(client, 'admin', ADMIN_PASSWORD)}"} + + +def test_read_settings_exposes_risk_limits_defaulting_to_zero(client) -> None: + r = client.get("/api/settings", headers=_auth(client)) + assert r.status_code == 200 + body = r.json() + assert body["risk_stop_loss_pct"] == "0" + assert body["risk_max_position_notional"] == "0" + + +def test_update_risk_settings_persists_and_returns(client) -> None: + r = client.put( + "/api/settings/risk", + headers=_auth(client), + json={ + "stop_loss_pct": "5", + "take_profit_pct": "10", + "max_drawdown_pct": "15", + "daily_loss_limit": "250", + "max_position_notional": "1000", + }, + ) + assert r.status_code == 200 + assert r.json()["risk_stop_loss_pct"] == "5" + # persisted: a fresh read reflects it + r2 = client.get("/api/settings", headers=_auth(client)) + assert r2.json()["risk_daily_loss_limit"] == "250" + + +def test_update_risk_settings_rejects_negative_and_over_100(client) -> None: + r = client.put( + "/api/settings/risk", + headers=_auth(client), + json={ + "stop_loss_pct": "-1", + "take_profit_pct": "10", + "max_drawdown_pct": "15", + "daily_loss_limit": "0", + "max_position_notional": "0", + }, + ) + assert r.status_code == 422 # Pydantic validation + r = client.put( + "/api/settings/risk", + headers=_auth(client), + json={ + "stop_loss_pct": "150", # > 100% + "take_profit_pct": "10", + "max_drawdown_pct": "15", + "daily_loss_limit": "0", + "max_position_notional": "0", + }, + ) + assert r.status_code == 422 +``` + +- [ ] **Step 2: Run the tests to verify they fail** + +Run: `cd engine && python -m pytest tests/test_settings_api.py -k risk -v` +Expected: FAIL — the read has no `risk_stop_loss_pct` key and `PUT /api/settings/risk` is 404/405. + +- [ ] **Step 3: Add the read fields** + +In `engine/api/settings.py`: + +1. Ensure `Field` is imported: change `from pydantic import BaseModel` to `from pydantic import BaseModel, Field` (if `Field` is not already imported). +2. Extend the `from appsettings.store import (...)` block with the five getters and setters: + `get_risk_stop_loss_pct, get_risk_take_profit_pct, get_risk_max_drawdown_pct, get_risk_daily_loss_limit, get_risk_max_position_notional, set_risk_stop_loss_pct, set_risk_take_profit_pct, set_risk_max_drawdown_pct, set_risk_daily_loss_limit, set_risk_max_position_notional`. +3. Import the env singleton for the UI-read fallback: `from config import settings as _env_settings`. +4. Add fields to `SettingsRead` (below `polymarket_stake`): + +```python + # Global risk limits (0 = disabled). Enforced by trading/risk.py each tick. + risk_stop_loss_pct: Decimal + risk_take_profit_pct: Decimal + risk_max_drawdown_pct: Decimal + risk_daily_loss_limit: Decimal + risk_max_position_notional: Decimal +``` + +5. Populate them in `_read(session)` (inside the `SettingsRead(...)` call), passing the env values as fallbacks so the UI shows what the engine would use: + +```python + risk_stop_loss_pct=get_risk_stop_loss_pct(session, _env_settings.risk_stop_loss_pct), + risk_take_profit_pct=get_risk_take_profit_pct(session, _env_settings.risk_take_profit_pct), + risk_max_drawdown_pct=get_risk_max_drawdown_pct(session, _env_settings.risk_max_drawdown_pct), + risk_daily_loss_limit=get_risk_daily_loss_limit(session, _env_settings.risk_daily_loss_limit), + risk_max_position_notional=get_risk_max_position_notional(session, _env_settings.risk_max_position_notional), +``` + +- [ ] **Step 4: Add the update model + endpoint** + +Add the model alongside the other `*Update` models (e.g. below `PolymarketSettingsUpdate`): + +```python +class RiskSettingsUpdate(BaseModel): + stop_loss_pct: Decimal = Field(ge=0, le=100) + take_profit_pct: Decimal = Field(ge=0, le=100) + max_drawdown_pct: Decimal = Field(ge=0, le=100) + daily_loss_limit: Decimal = Field(ge=0) + max_position_notional: Decimal = Field(ge=0) +``` + +Add the endpoint alongside the other `@router.put` handlers (e.g. below `update_polymarket_settings`): + +```python +@router.put("/risk", response_model=SettingsRead) +def update_risk_settings( + body: RiskSettingsUpdate, admin: AdminUser, session: SessionDep +) -> SettingsRead: + """Set the global risk limits (0 disables a limit). + + Changes apply on the next engine tick — a stop-loss you enable may + immediately close an already-losing position. + """ + set_risk_stop_loss_pct(session, body.stop_loss_pct) + set_risk_take_profit_pct(session, body.take_profit_pct) + set_risk_max_drawdown_pct(session, body.max_drawdown_pct) + set_risk_daily_loss_limit(session, body.daily_loss_limit) + set_risk_max_position_notional(session, body.max_position_notional) + record_audit( + session, + actor=admin.username, + action="settings.risk", + detail={ + "stop_loss_pct": str(body.stop_loss_pct), + "take_profit_pct": str(body.take_profit_pct), + "max_drawdown_pct": str(body.max_drawdown_pct), + "daily_loss_limit": str(body.daily_loss_limit), + "max_position_notional": str(body.max_position_notional), + }, + ) + return _read(session) +``` + +- [ ] **Step 5: Run the tests to verify they pass** + +Run: `cd engine && python -m pytest tests/test_settings_api.py -k risk -v` +Expected: PASS (3 tests). Then run the whole engine suite: `python -m pytest -q` → all PASS. + +- [ ] **Step 6: Commit** + +```bash +git add engine/api/settings.py engine/tests/test_settings_api.py +git commit -m "feat(risk): settings API to read and update risk limits" +``` + +--- + +## Task 5: Regenerate the web API types + add the client + +**Files:** +- Regenerate: `web/openapi.json`, `web/src/lib/api/schema.d.ts` +- Modify: `web/src/lib/api/settings.ts` + +- [ ] **Step 1: Regenerate the OpenAPI schema + TS types** + +Find the FastAPI app object: `cd engine && grep -rn "FastAPI(" .` (expected in `main.py`, e.g. `app = FastAPI(...)`). Dump the spec and regenerate the client types: + +```bash +cd engine +python -c "import json, main; open('../web/openapi.json','w').write(json.dumps(main.app.openapi()))" +cd ../web +npm run gen:api +``` + +Expected: `web/openapi.json` and `web/src/lib/api/schema.d.ts` now include `RiskSettingsUpdate` and the five `risk_*` fields on `SettingsRead`. Verify: `grep -n "risk_stop_loss_pct\|RiskSettingsUpdate" web/src/lib/api/schema.d.ts` prints matches. + +- [ ] **Step 2: Add the client function** + +Append to `web/src/lib/api/settings.ts`, mirroring `updatePolymarketSettings`: + +```typescript +export async function updateRiskSettings(body: { + stop_loss_pct: string; + take_profit_pct: string; + max_drawdown_pct: string; + daily_loss_limit: string; + max_position_notional: string; +}): Promise { + const { data, error } = await api.PUT("/api/settings/risk", { body }); + if (error) throw new Error("Failed to update risk settings"); + return data; +} +``` + +- [ ] **Step 3: Typecheck** + +Run: `cd web && npm run typecheck` +Expected: exit 0 (the new client + regenerated types compile). + +- [ ] **Step 4: Commit** + +```bash +git add web/openapi.json web/src/lib/api/schema.d.ts web/src/lib/api/settings.ts +git commit -m "feat(risk): regenerate API types + risk settings client" +``` + +--- + +## Task 6: Settings UI — "Risk controls" card + +**Files:** +- Modify: `web/src/pages/Settings.tsx` + +**Recommended pre-fill values (percentages pre-fill on enable; the two absolute-currency limits use a placeholder hint, since a sane number depends on the operator's capital):** stop-loss `5`, take-profit `10`, max-drawdown `15`; daily-loss-limit and max-position-notional start blank with hint text. + +- [ ] **Step 1: Import the client + add component state** + +In `web/src/pages/Settings.tsx`, add `updateRiskSettings` to the settings-client import block (which already imports `updateAiSpendCap`, `updatePolymarketSettings`, etc.). Add state near the other setting groups (e.g. below the polymarket `pm*` state): + +```typescript + // Risk controls — value "" or "0" means the limit is disabled. + const [riskStopLoss, setRiskStopLoss] = useState(""); + const [riskTakeProfit, setRiskTakeProfit] = useState(""); + const [riskMaxDrawdown, setRiskMaxDrawdown] = useState(""); + const [riskDailyLoss, setRiskDailyLoss] = useState(""); + const [riskMaxNotional, setRiskMaxNotional] = useState(""); +``` + +- [ ] **Step 2: Hydrate the state from loaded settings** + +In the effect/handler that copies loaded `settings` into local state (where `setAiSpendCap(...)` etc. are called after `fetchSettings()`), add: + +```typescript + setRiskStopLoss(nonZero(s.risk_stop_loss_pct)); + setRiskTakeProfit(nonZero(s.risk_take_profit_pct)); + setRiskMaxDrawdown(nonZero(s.risk_max_drawdown_pct)); + setRiskDailyLoss(nonZero(s.risk_daily_loss_limit)); + setRiskMaxNotional(nonZero(s.risk_max_position_notional)); +``` + +Add this helper near the top of the module (a "0"/"0.00" limit shows as an empty, disabled field): + +```typescript +const nonZero = (v: string): string => (Number(v) > 0 ? v : ""); +``` + +- [ ] **Step 3: Add the save handler** + +Add near the other save handlers (e.g. `savePolymarket`), sending "0" for any blank (disabled) field: + +```typescript + const saveRisk = async () => { + setBusy(true); + setError(null); + try { + const next = await updateRiskSettings({ + stop_loss_pct: riskStopLoss || "0", + take_profit_pct: riskTakeProfit || "0", + max_drawdown_pct: riskMaxDrawdown || "0", + daily_loss_limit: riskDailyLoss || "0", + max_position_notional: riskMaxNotional || "0", + }); + setSettings(next); + setNotice("Risk controls saved — applied on the next engine tick."); + } catch (e) { + setError(e instanceof Error ? e.message : "Failed to save risk controls"); + } finally { + setBusy(false); + } + }; +``` + +- [ ] **Step 4: Render the card** + +Add a "Risk controls" `Card` in the settings layout (follow the existing card markup — `Card` + `SectionHeader` + rows + a `Button`). Each row is a labeled numeric `Input`; a `Toggle` arms a percentage limit by pre-filling the recommended value (blank = disabled). Use the design tokens/components already in the file: + +```tsx + + +
+
+ Changes apply on the next engine tick — a stop-loss you enable may + immediately close an already-losing position. +
+ + + + + + + +
+ +
+
+
+``` + +Add the `RiskRow` helper component at the bottom of the module (a toggle that pre-fills/clears the value, plus the numeric input): + +```tsx +function RiskRow({ + label, + hint, + unit, + value, + onChange, + recommended, + placeholder, +}: { + label: string; + hint: string; + unit: string; + value: string; + onChange: (v: string) => void; + recommended?: string; + placeholder?: string; +}) { + const enabled = Number(value) > 0; + return ( +
+ onChange(on ? (recommended ?? "") : "")} + /> +
+
{label}
+
{hint}
+
+
+ +
+
+ ); +} +``` + +Note: confirm `Toggle`'s prop names and `Input`'s `onChange`/`suffix`/`placeholder` props against `web/src/components/ui.tsx` and adjust the two lines if they differ (e.g. `Toggle` may take `label`; `Input` `onChange` is `(value: string) => void`). These components are already used elsewhere in `Settings.tsx`, so mirror an existing call site. + +- [ ] **Step 5: Typecheck + lint** + +Run: `cd web && npm run typecheck && npm run lint` +Expected: exit 0 for both (pre-existing react-refresh warnings are acceptable; no new errors). + +- [ ] **Step 6: Commit** + +```bash +git add web/src/pages/Settings.tsx +git commit -m "feat(risk): Risk controls card on the Settings page" +``` + +--- + +## Task 7: Final verification + +- [ ] **Step 1: Full engine test suite** + +Run: `cd engine && python -m pytest -q` +Expected: all PASS. + +- [ ] **Step 2: Web typecheck + lint + build** + +Run: `cd web && npm run typecheck && npm run lint && npm run build` +Expected: typecheck/lint exit 0; `vite build` succeeds. + +- [ ] **Step 3: Manual smoke (optional but recommended)** + +Start the app (`cd web && npm run dev` + the engine), open Settings → Risk controls, toggle stop-loss on (pre-fills 5), save, confirm the notice, reload, confirm the value persists. Switch to sim mode and confirm an armed limit is respected on the next tick. + +--- + +## Self-review notes (author) + +- **Spec coverage:** storage (T1), from_store + env fallback (T2), per-tick engine read + injected override preserved (T3), API read/update + validation (T4), regenerated client (T5), Settings UI card with hybrid recommended pre-fills + "applies next tick" warning (T6), tests throughout, final verification (T7). Non-goals (backtest sim, per-strategy, notifications) intentionally excluded. +- **No migration** — the store is generic KV; confirmed via `_decimal_setting`/`set_setting`. +- **Backward-compat** — getters take an env-default arg; `from_store` and `_read` both pass the env `risk_*` values, so a value shows/enforces identically whether it came from env or the store. +- **Open confirmations flagged inline:** the public tick method name (T3 S1), the FastAPI app import path for the openapi dump (T5 S1), and `Toggle`/`Input` prop names (T6 S4) — each has a concrete grep/verification step rather than an assumption. diff --git a/docs/superpowers/specs/2026-07-02-runtime-tunable-risk-controls-design.md b/docs/superpowers/specs/2026-07-02-runtime-tunable-risk-controls-design.md new file mode 100644 index 0000000..84bf77c --- /dev/null +++ b/docs/superpowers/specs/2026-07-02-runtime-tunable-risk-controls-design.md @@ -0,0 +1,141 @@ +# Design: Runtime-tunable risk controls + +**Date:** 2026-07-02 +**Status:** Approved (design) — pending implementation plan +**Scope:** Engine + web. Make the five global risk limits in `engine/trading/risk.py` +enableable and tunable at runtime from the Settings UI, without an engine restart. + +## Problem + +`engine/trading/risk.py` already implements a complete, correct `RiskManager`: +stop-loss, take-profit, per-order notional cap, daily realized-loss limit, and a +drawdown kill switch. But every limit is wired only through `config.py` env +settings (`risk_*`), read **once at engine boot** (`engine.py:110`, +`self._risk = risk or RiskManager()`), and defaults to `0` = disabled. There is +no Settings UI for them, and the backtest does not simulate them. + +Net effect: the safety net that most protects a solo operator's capital ships +**off**, and turning it on requires editing env + restarting the live engine — +so in practice it stays off. This design closes that gap for live trading. It +does **not** change the enforcement math, only how the limits are configured, +surfaced, and applied. + +## Goals + +- Risk limits are editable at runtime from the Settings page (no restart). +- Changes take effect on the next engine tick, in both sim and live modes. +- Zero surprise: nothing auto-closes until the operator explicitly enables a limit. +- Removing the "what number do I put here?" friction via recommended pre-fills. + +## Non-goals (deferred) + +- **Backtest simulation of the limits** — a strong follow-up (lets the operator + tune from the historical equity curve instead of intuition), but its own effort. +- **Per-strategy risk overrides** — the `RiskManager` stays global (engine-wide), + matching current architecture. (Per-strategy allocation + max-loss already exist + separately via `StrategyAllocation` / `_loss_cap_breached`.) +- **Kill-switch notifications** — could reuse the notifier later; out of scope now. + +## Decisions (from brainstorming) + +1. **Scope B** — runtime-editable via Settings UI (not env-only, not backtest-sim). +2. **Approach #1** — the engine reads the limits from the settings store **per + tick**, the same way `ai_action_mode` / AI spend-cap already flow store → engine. +3. **Defaults = C (hybrid)** — every limit defaults to `0` (disabled); the UI + pre-fills a recommended value next to each toggle so behavior changes only on + an explicit save. + +## Design + +### 1. Storage — settings store, no migration + +The five limits become key-value entries in the existing `appsettings` store +(generic KV `settings` table + the `_decimal_setting` pattern the AI spend cap +uses), so **no Alembic migration is required** — only new keys. + +| Store key | Unit | Recommended pre-fill | +|---|---|---| +| `risk_stop_loss_pct` | % of position entry value | 5% | +| `risk_take_profit_pct` | % of position entry value | 10% | +| `risk_max_drawdown_pct` | % from equity peak | 15% | +| `risk_daily_loss_limit` | quote currency (e.g. USDT) | hint: ~2% of equity (absolute; not auto-filled) | +| `risk_max_position_notional` | quote currency | hint: ~20% of equity (absolute; not auto-filled) | + +Add typed getters/setters to `engine/appsettings/store.py`: +`get_risk_stop_loss_pct(session)` … `set_risk_stop_loss_pct(session, value)` etc. + +**Backward-compatibility:** each getter returns the stored value when set, else +falls back to the env `settings.risk_*` value (0 unless already configured). Any +existing env-configured deployment keeps working; the store becomes the source of +truth once a value is saved through the UI. + +### 2. Engine wiring — per-tick read + +- Add `RiskManager.from_store(session)` (parallel to `from_settings`) reading the + five store getters. +- In `engine.py` `_tick_strategy` (which already opens a session), build the + manager from the store at the top of the tick rather than using a boot-time + singleton. The uses at `engine.py:194` (`stop_order`) and `:260` (`review`) + consume this per-tick instance. +- The `TradingEngine` constructor keeps an **optional injected `RiskManager`** + (`risk: RiskManager | None`). When injected (tests), use it; when `None` + (production), read from the store per tick. This keeps existing tests working. +- Enforcement math in `risk.py` is unchanged. + +### 3. Settings UI — a "Risk controls" card + +- New card on `web/src/pages/Settings.tsx`, following existing card/section and + design-system patterns. Five rows: each = label + one-line description + + a `Toggle` + a numeric input with its unit. +- Off ⇒ value `0` (disabled). Toggling on reveals the input **pre-filled with the + recommended value** (percentages) or focused with a placeholder hint (the two + absolute-currency limits). Numbers rendered mono/tabular per DESIGN.md. +- New typed settings endpoints on `engine/api/settings.py`: + `RiskSettingsRead` (GET) and `RiskSettingsUpdate` (using the same HTTP method + and shape as the existing `AiActionModeUpdate` / Polymarket settings endpoints — + match the established convention rather than introducing a new one). +- Regenerate the web API types (`npm run gen:api`) after the endpoint lands; add + a `fetchRiskSettings` / `updateRiskSettings` client in `web/src/lib/api/`. + +### 4. Safety + +- **No surprise:** all limits default to `0` / disabled; behavior changes only on save. +- **Mid-session application (documented in the UI):** changes take effect on the + next engine tick — this is existing behavior (`stop_order` runs every tick; + the kill switch checks live). The card carries an explicit copy line: + *"Applies on the next tick — a stop-loss you enable may immediately close an + already-losing position."* +- **Kill switch blocks new exposure only;** closing trades stay allowed (existing). +- **Server-side validation** in the update endpoint: reject negatives; clamp + percentages to 0–100; require notional / loss-limit ≥ 0. +- **Works in sim and live** (the checks run inside the engine tick, which runs for + both modes), so the operator can arm limits in simulation first. + +### 5. Testing + +- **Unit (engine):** store getters/setters — default, set/read round-trip, and the + env fallback; `RiskManager.from_store` maps the five fields correctly. +- **Engine integration:** a limit changed mid-run is enforced on the next tick + (set a stop-loss → a breaching open position closes; set a drawdown limit past + the current drawdown → kill switch blocks new exposure). Existing `risk.py` + enforcement tests remain the math baseline. +- **API:** `RiskSettingsUpdate` validation rejects negative / >100% values. +- **Web:** the Risk card renders, toggles pre-fill the recommended value, and save + calls the update endpoint (following existing Settings test patterns). + +## Affected files + +- `engine/trading/risk.py` — add `from_store`. +- `engine/appsettings/store.py` — five getters/setters with env fallback. +- `engine/trading/engine.py` — per-tick `RiskManager.from_store` in `_tick_strategy`; + keep optional injected override. +- `engine/api/settings.py` — `RiskSettingsRead` / `RiskSettingsUpdate` endpoints + validation. +- `web/src/pages/Settings.tsx` — Risk controls card. +- `web/src/lib/api/` — settings client additions + regenerated schema. +- Tests across the above. + +## Open items to confirm at plan time + +- Exact recommended pre-fill values (table above — starting point, operator-tunable). +- Whether the two absolute-currency limits should offer an equity-relative + suggestion computed client-side, or stay a plain hint (design assumes plain hint). diff --git a/engine/ai/signals.py b/engine/ai/signals.py index 858e56d..487d7dd 100644 --- a/engine/ai/signals.py +++ b/engine/ai/signals.py @@ -12,7 +12,6 @@ from sqlmodel import Field, Session, SQLModel, select -from config import settings from trading.executor_router import ExecutorRouter from trading.executors.base import Order from trading.models import FillSide @@ -127,7 +126,7 @@ def execute_signal( side=FillSide(signal.action), quantity=signal.quantity, ) - reviewed = RiskManager.from_settings(settings).review( + reviewed = RiskManager.from_store(session).review( session, order, position, price ) if reviewed is None: diff --git a/engine/api/orders.py b/engine/api/orders.py index 0e45346..1f69450 100644 --- a/engine/api/orders.py +++ b/engine/api/orders.py @@ -17,7 +17,6 @@ from auth.audit import record_audit from auth.deps import SessionDep, require_admin from auth.models import User -from config import settings from trading.executor_router import ExecutorRouter from trading.executors.base import ExecutionError, Fill from trading.manual_order import ManualOrderRequest, OrderBlocked, submit_manual_order @@ -50,7 +49,7 @@ def place_manual_order( body, venues=venues, executor_router=_executor_router(), - risk=RiskManager.from_settings(settings), + risk=RiskManager.from_store(session), ) except VenueError as exc: raise HTTPException( diff --git a/engine/api/settings.py b/engine/api/settings.py index 729828b..15ea9fc 100644 --- a/engine/api/settings.py +++ b/engine/api/settings.py @@ -40,6 +40,11 @@ get_research_interval_hours, get_research_symbols, get_research_writer, + get_risk_daily_loss_limit, + get_risk_max_drawdown_pct, + get_risk_max_position_notional, + get_risk_stop_loss_pct, + get_risk_take_profit_pct, llm_provider_configured, set_ai_action_mode, set_ai_settings, @@ -57,12 +62,18 @@ set_research_interval_hours, set_research_symbols, set_research_writer, + set_risk_daily_loss_limit, + set_risk_max_drawdown_pct, + set_risk_max_position_notional, + set_risk_stop_loss_pct, + set_risk_take_profit_pct, set_venue_credentials, venue_credentials_configured, ) from auth.audit import record_audit from auth.deps import SessionDep, require_admin from auth.models import User +from config import settings as _env_settings from trading.portfolio import list_positions from venues.registry import AVAILABLE_VENUES, get_venue @@ -112,6 +123,12 @@ class SettingsRead(BaseModel): polymarket_min_confidence: Decimal polymarket_screen_top: int polymarket_stake: Decimal + # Global risk limits (0 = disabled). Enforced by trading/risk.py each tick. + risk_stop_loss_pct: Decimal + risk_take_profit_pct: Decimal + risk_max_drawdown_pct: Decimal + risk_daily_loss_limit: Decimal + risk_max_position_notional: Decimal class VenueCredentialsUpdate(BaseModel): @@ -187,6 +204,17 @@ class PolymarketSettingsUpdate(BaseModel): stake: Decimal = Field(default=Decimal("100"), gt=0) +class RiskSettingsUpdate(BaseModel): + """Global risk limits in percent (SL/TP/drawdown) or quote currency + (loss limit, notional). 0 disables a limit.""" + + stop_loss_pct: Decimal = Field(ge=0, le=100) + take_profit_pct: Decimal = Field(ge=0, le=100) + max_drawdown_pct: Decimal = Field(ge=0, le=100) + daily_loss_limit: Decimal = Field(ge=0) + max_position_notional: Decimal = Field(ge=0) + + def _read(session: SessionDep) -> SettingsRead: ai = get_ai_settings(session) writer = get_research_writer(session) @@ -220,6 +248,21 @@ def _read(session: SessionDep) -> SettingsRead: polymarket_min_confidence=get_polymarket_min_confidence(session), polymarket_screen_top=get_polymarket_screen_top(session), polymarket_stake=get_polymarket_stake(session), + risk_stop_loss_pct=get_risk_stop_loss_pct( + session, _env_settings.risk_stop_loss_pct + ), + risk_take_profit_pct=get_risk_take_profit_pct( + session, _env_settings.risk_take_profit_pct + ), + risk_max_drawdown_pct=get_risk_max_drawdown_pct( + session, _env_settings.risk_max_drawdown_pct + ), + risk_daily_loss_limit=get_risk_daily_loss_limit( + session, _env_settings.risk_daily_loss_limit + ), + risk_max_position_notional=get_risk_max_position_notional( + session, _env_settings.risk_max_position_notional + ), ) @@ -443,6 +486,35 @@ def update_polymarket_settings( return _read(session) +@router.put("/risk", response_model=SettingsRead) +def update_risk_settings( + body: RiskSettingsUpdate, admin: AdminUser, session: SessionDep +) -> SettingsRead: + """Set the global risk limits (0 disables a limit). + + Changes apply on the next engine tick — a stop-loss you enable may + immediately close an already-losing position. + """ + set_risk_stop_loss_pct(session, body.stop_loss_pct) + set_risk_take_profit_pct(session, body.take_profit_pct) + set_risk_max_drawdown_pct(session, body.max_drawdown_pct) + set_risk_daily_loss_limit(session, body.daily_loss_limit) + set_risk_max_position_notional(session, body.max_position_notional) + record_audit( + session, + actor=admin.username, + action="settings.risk", + detail={ + "stop_loss_pct": str(body.stop_loss_pct), + "take_profit_pct": str(body.take_profit_pct), + "max_drawdown_pct": str(body.max_drawdown_pct), + "daily_loss_limit": str(body.daily_loss_limit), + "max_position_notional": str(body.max_position_notional), + }, + ) + return _read(session) + + @router.put("/council", response_model=SettingsRead) def update_council_settings( body: CouncilSettingsUpdate, admin: AdminUser, session: SessionDep diff --git a/engine/appsettings/store.py b/engine/appsettings/store.py index e0d4652..c2d3fee 100644 --- a/engine/appsettings/store.py +++ b/engine/appsettings/store.py @@ -35,6 +35,11 @@ class TradingMode(StrEnum): _AI_API_KEY = "ai_api_key" _AI_SPEND_CAP = "ai_spend_cap_usd" _AI_ACTION_MODE = "ai_action_mode" +_RISK_STOP_LOSS = "risk_stop_loss_pct" +_RISK_TAKE_PROFIT = "risk_take_profit_pct" +_RISK_MAX_DRAWDOWN = "risk_max_drawdown_pct" +_RISK_DAILY_LOSS = "risk_daily_loss_limit" +_RISK_MAX_NOTIONAL = "risk_max_position_notional" #: How an AI strategy's decision is applied. `notify` (the default) surfaces it #: for operator confirmation; `auto` executes it straight through the risk gate. @@ -204,6 +209,59 @@ def set_ai_spend_cap(session: Session, cap: Decimal) -> None: set_setting(session, _AI_SPEND_CAP, str(cap)) +# -- risk limits ---------------------------------------------------------------- +# Runtime-editable risk controls. Each getter takes a `default` so callers can +# supply the env fallback; `_decimal_setting` returns `default` only when the +# key is unset — an explicitly-stored `"0"` is returned as `Decimal(0)`. + + +def get_risk_stop_loss_pct(session: Session, default: Decimal = Decimal(0)) -> Decimal: + """Stop-loss as a percent of a position's entry value (0 = disabled).""" + return _decimal_setting(session, _RISK_STOP_LOSS, default) + + +def set_risk_stop_loss_pct(session: Session, pct: Decimal) -> None: + set_setting(session, _RISK_STOP_LOSS, str(pct)) + + +def get_risk_take_profit_pct(session: Session, default: Decimal = Decimal(0)) -> Decimal: + """Take-profit as a percent of a position's entry value (0 = disabled).""" + return _decimal_setting(session, _RISK_TAKE_PROFIT, default) + + +def set_risk_take_profit_pct(session: Session, pct: Decimal) -> None: + set_setting(session, _RISK_TAKE_PROFIT, str(pct)) + + +def get_risk_max_drawdown_pct(session: Session, default: Decimal = Decimal(0)) -> Decimal: + """Kill-switch drawdown limit as a percent from the equity peak (0 = disabled).""" + return _decimal_setting(session, _RISK_MAX_DRAWDOWN, default) + + +def set_risk_max_drawdown_pct(session: Session, pct: Decimal) -> None: + set_setting(session, _RISK_MAX_DRAWDOWN, str(pct)) + + +def get_risk_daily_loss_limit(session: Session, default: Decimal = Decimal(0)) -> Decimal: + """Kill-switch daily realized-loss limit in quote currency (0 = disabled).""" + return _decimal_setting(session, _RISK_DAILY_LOSS, default) + + +def set_risk_daily_loss_limit(session: Session, amount: Decimal) -> None: + set_setting(session, _RISK_DAILY_LOSS, str(amount)) + + +def get_risk_max_position_notional( + session: Session, default: Decimal = Decimal(0) +) -> Decimal: + """Per-order notional cap in quote currency (0 = disabled).""" + return _decimal_setting(session, _RISK_MAX_NOTIONAL, default) + + +def set_risk_max_position_notional(session: Session, amount: Decimal) -> None: + set_setting(session, _RISK_MAX_NOTIONAL, str(amount)) + + # -- per-provider LLM credentials --------------------------------------------- # Each LLM provider stores its own credentials, so a Claude strategy and a # local-Ollama strategy can run side by side. Keys: `llm:{provider}:api_key` diff --git a/engine/main.py b/engine/main.py index 3328ed1..2547485 100644 --- a/engine/main.py +++ b/engine/main.py @@ -42,7 +42,6 @@ from strategies.builtin import all_strategies_with_instances, seed_allocations from trading.engine import TradingEngine from trading.executor_router import ExecutorRouter -from trading.risk import RiskManager setup_logging(settings.log_level) log = logging.getLogger("capital") @@ -89,7 +88,6 @@ def session_factory() -> Session: session_factory=session_factory, router=ExecutorRouter(), strategies=strategies, - risk=RiskManager.from_settings(settings), notifier=TelegramNotifier.from_settings(settings), retention_candle_days=settings.retention_candle_days, retention_equity_days=settings.retention_equity_days, diff --git a/engine/tests/test_ai_signal.py b/engine/tests/test_ai_signal.py index 083a832..2366ddc 100644 --- a/engine/tests/test_ai_signal.py +++ b/engine/tests/test_ai_signal.py @@ -168,3 +168,25 @@ def test_dismiss_signal(signal_client: TestClient, session: Session) -> None: def test_list_signals_requires_auth(signal_client: TestClient) -> None: assert signal_client.get("/api/ai/signals").status_code == 401 + + +def test_confirm_honors_stored_notional_cap( + signal_client: TestClient, session: Session +) -> None: + """A UI-set notional cap must apply to AI-signal execution (via the store). + + FakeVenue prices at 100; a 10 notional cap clips the 0.5-qty signal to 0.1. + With the old env-only RiskManager (cap defaults 0/disabled) the executed + trade would be the full 0.5, so this asserts the stored limit is honored. + """ + from appsettings.store import set_risk_max_position_notional + + set_risk_max_position_notional(session, Decimal("10")) + sig = _pending(session) + resp = signal_client.post( + f"/api/ai/signals/{sig.id}/confirm", headers=_auth(signal_client) + ) + assert resp.status_code == 200, resp.text + trades = session.exec(select(Trade)).all() + assert len(trades) == 1 + assert trades[0].quantity == Decimal("0.1") # 10 / price(100), was 0.5 diff --git a/engine/tests/test_appsettings.py b/engine/tests/test_appsettings.py index a3f34ac..072639f 100644 --- a/engine/tests/test_appsettings.py +++ b/engine/tests/test_appsettings.py @@ -1,5 +1,7 @@ """Tests for runtime settings — encryption and the settings store.""" +from decimal import Decimal + import pytest from sqlmodel import Session, select @@ -11,12 +13,22 @@ get_binance_keys, get_llm_credentials, get_mode, + get_risk_daily_loss_limit, + get_risk_max_drawdown_pct, + get_risk_max_position_notional, + get_risk_stop_loss_pct, + get_risk_take_profit_pct, get_strategy_ai_config, get_venue_credentials, llm_provider_configured, set_binance_keys, set_llm_credentials, set_mode, + set_risk_daily_loss_limit, + set_risk_max_drawdown_pct, + set_risk_max_position_notional, + set_risk_stop_loss_pct, + set_risk_take_profit_pct, set_strategy_ai_config, set_venue_credentials, venue_credentials_configured, @@ -128,3 +140,35 @@ def test_strategy_ai_config_round_trip(session: Session) -> None: set_strategy_ai_config(session, "AI BTC", provider="ollama", model="qwen2.5") cfg = get_strategy_ai_config(session, "AI BTC") assert cfg == {"provider": "ollama", "model": "qwen2.5"} + + +def test_risk_limits_default_to_zero(session: Session) -> None: + assert get_risk_stop_loss_pct(session) == Decimal(0) + assert get_risk_take_profit_pct(session) == Decimal(0) + assert get_risk_max_drawdown_pct(session) == Decimal(0) + assert get_risk_daily_loss_limit(session) == Decimal(0) + assert get_risk_max_position_notional(session) == Decimal(0) + + +@pytest.mark.parametrize( + ("getter", "setter"), + [ + (get_risk_stop_loss_pct, set_risk_stop_loss_pct), + (get_risk_take_profit_pct, set_risk_take_profit_pct), + (get_risk_max_drawdown_pct, set_risk_max_drawdown_pct), + (get_risk_daily_loss_limit, set_risk_daily_loss_limit), + (get_risk_max_position_notional, set_risk_max_position_notional), + ], +) +def test_risk_limit_round_trip(getter, setter, session: Session) -> None: + setter(session, Decimal("7")) + assert getter(session) == Decimal("7") + + +def test_risk_getter_falls_back_to_env_default_when_unset(session: Session) -> None: + assert get_risk_stop_loss_pct(session, Decimal("3")) == Decimal("3") + + +def test_risk_explicit_zero_overrides_env_default(session: Session) -> None: + set_risk_stop_loss_pct(session, Decimal("0")) + assert get_risk_stop_loss_pct(session, Decimal("3")) == Decimal("0") diff --git a/engine/tests/test_orders_api.py b/engine/tests/test_orders_api.py index ec12aca..0a0c8ca 100644 --- a/engine/tests/test_orders_api.py +++ b/engine/tests/test_orders_api.py @@ -96,3 +96,25 @@ def test_manual_order_rejects_zero_quantity(orders_client: TestClient) -> None: headers=_auth(orders_client), ) assert resp.status_code == 422 # quantity must be > 0 + + +def test_manual_order_honors_stored_notional_cap( + orders_client: TestClient, session: Session +) -> None: + """A UI-set notional cap must apply to manual orders (via the store). + + FakeVenue prices at 100; a 10 notional cap clips a 0.5-qty order to 0.1. + With the old env-only RiskManager (cap defaults 0/disabled) the fill would + be the full 0.5, so this asserts the stored limit is actually honored. + """ + from appsettings.store import set_risk_max_position_notional + + set_risk_max_position_notional(session, Decimal("10")) + resp = orders_client.post( + "/api/orders/manual", json=_order(quantity="0.5"), headers=_auth(orders_client) + ) + assert resp.status_code == 200, resp.text + assert Decimal(resp.json()["quantity"]) == Decimal("0.1") # 10 / price(100) + trades = session.exec(select(Trade)).all() + assert len(trades) == 1 + assert trades[0].quantity == Decimal("0.1") diff --git a/engine/tests/test_risk.py b/engine/tests/test_risk.py index ccea762..0d6004a 100644 --- a/engine/tests/test_risk.py +++ b/engine/tests/test_risk.py @@ -29,6 +29,34 @@ def _order(side: FillSide, qty: str) -> Order: return Order(strategy="S", market="spot", symbol="BTCUSDT", side=side, quantity=Decimal(qty)) +# --- construction from stores --------------------------------------------- + +def test_from_store_reads_stored_limits(session: Session) -> None: + from appsettings.store import set_risk_max_position_notional, set_risk_stop_loss_pct + from config import Settings + set_risk_stop_loss_pct(session, Decimal("5")) + set_risk_max_position_notional(session, Decimal("1000")) + rm = RiskManager.from_store(session, Settings()) + assert rm.stop_loss_pct == Decimal("5") + assert rm.max_position_notional == Decimal("1000") + assert rm.take_profit_pct == Decimal(0) # unset → disabled + + +def test_from_store_falls_back_to_env_settings(session: Session) -> None: + from config import Settings + env = Settings(risk_stop_loss_pct=Decimal("7")) # nothing stored + rm = RiskManager.from_store(session, env) + assert rm.stop_loss_pct == Decimal("7") + + +def test_from_store_prefers_stored_over_env(session: Session) -> None: + from appsettings.store import set_risk_stop_loss_pct + from config import Settings + set_risk_stop_loss_pct(session, Decimal("5")) + rm = RiskManager.from_store(session, Settings(risk_stop_loss_pct=Decimal("7"))) + assert rm.stop_loss_pct == Decimal("5") # stored wins over env + + # --- stop-loss / take-profit ---------------------------------------------- def test_stop_loss_closes_a_losing_long() -> None: diff --git a/engine/tests/test_settings_api.py b/engine/tests/test_settings_api.py index d36fc0f..b954cc6 100644 --- a/engine/tests/test_settings_api.py +++ b/engine/tests/test_settings_api.py @@ -193,6 +193,70 @@ def test_ai_spend_cap_rejects_negative(settings_client: TestClient) -> None: assert resp.status_code == 422 +def test_read_settings_exposes_risk_limits_defaulting_to_zero( + settings_client: TestClient, +) -> None: + r = settings_client.get("/api/settings", headers=_auth(settings_client)) + assert r.status_code == 200 + body = r.json() + assert body["risk_stop_loss_pct"] == "0" + assert body["risk_max_position_notional"] == "0" + + +def test_update_risk_settings_persists_and_returns(settings_client: TestClient) -> None: + headers = _auth(settings_client) + r = settings_client.put( + "/api/settings/risk", + headers=headers, + json={ + "stop_loss_pct": "5", + "take_profit_pct": "10", + "max_drawdown_pct": "15", + "daily_loss_limit": "250", + "max_position_notional": "1000", + }, + ) + assert r.status_code == 200 + assert r.json()["risk_stop_loss_pct"] == "5" + r2 = settings_client.get("/api/settings", headers=headers) + body = r2.json() + assert body["risk_stop_loss_pct"] == "5" + assert body["risk_take_profit_pct"] == "10" + assert body["risk_max_drawdown_pct"] == "15" + assert body["risk_daily_loss_limit"] == "250" + assert body["risk_max_position_notional"] == "1000" + + +def test_update_risk_settings_rejects_negative_and_over_100( + settings_client: TestClient, +) -> None: + headers = _auth(settings_client) + r = settings_client.put( + "/api/settings/risk", + headers=headers, + json={ + "stop_loss_pct": "-1", + "take_profit_pct": "10", + "max_drawdown_pct": "15", + "daily_loss_limit": "0", + "max_position_notional": "0", + }, + ) + assert r.status_code == 422 + r = settings_client.put( + "/api/settings/risk", + headers=headers, + json={ + "stop_loss_pct": "150", + "take_profit_pct": "10", + "max_drawdown_pct": "15", + "daily_loss_limit": "0", + "max_position_notional": "0", + }, + ) + assert r.status_code == 422 + + def test_venue_credentials_audited_without_values( settings_client: TestClient, session: Session ) -> None: diff --git a/engine/tests/test_trading_engine.py b/engine/tests/test_trading_engine.py index 3944ec9..e185f93 100644 --- a/engine/tests/test_trading_engine.py +++ b/engine/tests/test_trading_engine.py @@ -9,12 +9,14 @@ from sqlmodel import Session, SQLModel, create_engine, select from sqlmodel.pool import StaticPool +from appsettings.store import set_risk_max_position_notional from exchange.client import Market from strategies.base import BaseStrategy, StrategyContext from trading.engine import TradingEngine from trading.executors.base import Order from trading.models import FillSide, PositionSide, Trade from trading.portfolio import list_positions, set_allocation +from trading.risk import RiskManager from trading.venue_router import VenueRouter from venues.base import Instrument, OrderResult, Venue, VenueCandle @@ -103,6 +105,7 @@ def _engine( strategies: list[BaseStrategy], *, ai_resolver: Any = None, + risk: Any = None, ) -> TradingEngine: # Mirror startup seeding — every strategy gets a capital budget so the # allocation enforcer admits its orders. @@ -113,6 +116,8 @@ def _engine( kwargs: dict[str, Any] = {} if ai_resolver is not None: kwargs["ai_resolver"] = ai_resolver + if risk is not None: + kwargs["risk"] = risk return TradingEngine( session_factory=factory, venue_router=VenueRouter(builder=lambda *_: venue), @@ -121,6 +126,36 @@ def _engine( ) +def test_engine_applies_store_risk_limits(factory: Any) -> None: + # BuyWhenFlat buys qty 1 at the FakeVenue price of 100 → notional 100. + # A stored per-order notional cap of 50 must clip the fill to qty 0.5. + strat = BuyWhenFlat("buyer", "BTCUSDT", market=Market.spot) + eng = _engine(factory, [strat]) + with factory() as session: + set_risk_max_position_notional(session, Decimal("50")) + session.commit() + eng.tick() + with factory() as session: + positions = list_positions(session) + assert len(positions) == 1 + assert positions[0].qty == Decimal("0.5") + + +def test_injected_risk_override_wins_over_store(factory: Any) -> None: + # The store says cap 50 (would clip to qty 0.5); the injected override caps + # at 20 (→ qty 0.2). The override must win, proving the tests/embedding path + # still bypasses the store even though production leaves it None. + strat = BuyWhenFlat("buyer", "BTCUSDT", market=Market.spot) + with factory() as session: + set_risk_max_position_notional(session, Decimal("50")) + session.commit() + eng = _engine(factory, [strat], risk=RiskManager(max_position_notional=Decimal("20"))) + eng.tick() + with factory() as session: + positions = list_positions(session) + assert positions[0].qty == Decimal("0.2") # override (20), not store (50) + + def test_tick_executes_strategy_order(db_engine: Any, factory: Any) -> None: strat = BuyWhenFlat("MA Cross", "BTCUSDT", market=Market.spot) eng = _engine(factory, [strat]) diff --git a/engine/trading/engine.py b/engine/trading/engine.py index 0340606..2fab267 100644 --- a/engine/trading/engine.py +++ b/engine/trading/engine.py @@ -107,7 +107,10 @@ def __init__( self._router = router or ExecutorRouter() # Resolves an AI strategy's (provider, model) from its stored config. self._ai_resolver = ai_resolver - self._risk = risk or RiskManager() # all limits disabled by default + # Injected RiskManager override (tests / embedding). Production leaves this + # None so limits are read from the settings store on every tick, applying + # UI changes without a restart. + self._risk_override = risk self._notifier = notifier or TelegramNotifier() # disabled by default self._strategies: list[BaseStrategy] = list(strategies or []) self._tick_seconds = tick_seconds @@ -161,6 +164,7 @@ def tick(self) -> None: def _tick_strategy(self, strat: BaseStrategy) -> None: with self._session_factory() as session: + risk = self._risk_override or RiskManager.from_store(session) candles = refresh_venue_candles( session, self._venue_router.resolve(session, venue=strat.venue), @@ -191,7 +195,7 @@ def _tick_strategy(self, strat: BaseStrategy) -> None: # Risk: force-close a position that breached its stop-loss or # take-profit. This runs regardless of lifecycle state — a stop is # a safety net, not a strategy-driven entry. - stop = self._risk.stop_order(position, price) + stop = risk.stop_order(position, price) if stop is not None: executor.execute(session, stop, reference_price=price) log.info("risk: stopped out %r position on %s", strat.name, strat.symbol) @@ -257,7 +261,7 @@ def _tick_strategy(self, strat: BaseStrategy) -> None: log.info("strategy %r order rejected — allocation exhausted", strat.name) return # Risk: order sizing cap + kill switch (blocks new exposure only). - order = self._risk.review(session, order, position, price) + order = risk.review(session, order, position, price) if order is None: log.info("strategy %r order blocked by risk manager", strat.name) return diff --git a/engine/trading/risk.py b/engine/trading/risk.py index b3d554a..0e995d1 100644 --- a/engine/trading/risk.py +++ b/engine/trading/risk.py @@ -19,7 +19,15 @@ from sqlmodel import Session, select +from appsettings.store import ( + get_risk_daily_loss_limit, + get_risk_max_drawdown_pct, + get_risk_max_position_notional, + get_risk_stop_loss_pct, + get_risk_take_profit_pct, +) from config import Settings +from config import settings as _env_settings from trading.executors.base import Order from trading.models import EquitySnapshot, FillSide, Position, PositionSide, Trade from trading.portfolio import unrealized_pnl @@ -54,6 +62,23 @@ def from_settings(cls, settings: Settings) -> "RiskManager": max_drawdown_pct=settings.risk_max_drawdown_pct, ) + @classmethod + def from_store( + cls, session: Session, settings: Settings | None = None + ) -> "RiskManager": + """Build from the runtime settings store, falling back to env config + for any limit the operator has not set through the UI.""" + env = settings if settings is not None else _env_settings + return cls( + max_position_notional=get_risk_max_position_notional( + session, env.risk_max_position_notional + ), + stop_loss_pct=get_risk_stop_loss_pct(session, env.risk_stop_loss_pct), + take_profit_pct=get_risk_take_profit_pct(session, env.risk_take_profit_pct), + daily_loss_limit=get_risk_daily_loss_limit(session, env.risk_daily_loss_limit), + max_drawdown_pct=get_risk_max_drawdown_pct(session, env.risk_max_drawdown_pct), + ) + # -- stop-loss / take-profit --------------------------------------------- def stop_order(self, position: Position, mark_price: Decimal) -> Order | None: diff --git a/web/openapi.json b/web/openapi.json index f815d10..b98de92 100644 --- a/web/openapi.json +++ b/web/openapi.json @@ -1,3966 +1,3053 @@ { - "components": { - "schemas": { - "AISignal": { - "description": "One AI decision surfaced to the operator for confirmation.", - "properties": { - "action": { - "maxLength": 8, - "title": "Action", - "type": "string" - }, - "confidence": { - "default": "0", - "pattern": "^(?!^[-+.]*$)[+-]?0*(?:\\d{0,18}|(?=[\\d.]{1,29}0*$)\\d{0,18}\\.\\d{0,10}0*$)", - "title": "Confidence", - "type": "string" - }, - "created_at": { - "format": "date-time", - "title": "Created At", - "type": "string" - }, - "id": { - "anyOf": [ - { - "type": "integer" - }, - { - "type": "null" + "openapi": "3.1.0", + "info": { + "title": "Capital Engine", + "summary": "Self-hosted automated Binance trading engine.", + "version": "0.1.0" + }, + "paths": { + "/api/auth/login": { + "post": { + "tags": [ + "auth" + ], + "summary": "Login", + "description": "Exchange operator credentials for an access + refresh token pair.\n\nRate-limited: repeated failures lock the username out (brute-force guard).", + "operationId": "login_api_auth_login_post", + "requestBody": { + "content": { + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/Body_login_api_auth_login_post" } - ], - "title": "Id" - }, - "market": { - "default": "spot", - "maxLength": 8, - "title": "Market", - "type": "string" - }, - "quantity": { - "default": "0", - "pattern": "^(?!^[-+.]*$)[+-]?0*(?:\\d{0,16}|(?=[\\d.]{1,25}0*$)\\d{0,16}\\.\\d{0,8}0*$)", - "title": "Quantity", - "type": "string" - }, - "reasoning": { - "default": "", - "title": "Reasoning", - "type": "string" - }, - "reference_price": { - "default": "0", - "pattern": "^(?!^[-+.]*$)[+-]?0*(?:\\d{0,16}|(?=[\\d.]{1,25}0*$)\\d{0,16}\\.\\d{0,8}0*$)", - "title": "Reference Price", - "type": "string" + } }, - "status": { - "default": "pending", - "maxLength": 12, - "title": "Status", - "type": "string" + "required": true + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TokenPair" + } + } + } }, - "strategy": { - "maxLength": 64, - "title": "Strategy", - "type": "string" + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/api/auth/refresh": { + "post": { + "tags": [ + "auth" + ], + "summary": "Refresh", + "description": "Issue a fresh token pair from a valid refresh token.", + "operationId": "refresh_api_auth_refresh_post", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RefreshRequest" + } + } }, - "symbol": { - "maxLength": 80, - "title": "Symbol", - "type": "string" + "required": true + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TokenPair" + } + } + } }, - "venue": { - "default": "binance", - "maxLength": 24, - "title": "Venue", - "type": "string" + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } } - }, - "required": [ - "strategy", - "symbol", - "action", - "created_at" + } + } + }, + "/api/auth/me": { + "get": { + "tags": [ + "auth" ], - "title": "AISignal", - "type": "object" - }, - "ActiveVenueUpdate": { - "properties": { - "venue": { - "title": "Venue", - "type": "string" + "summary": "Me", + "description": "Return the currently authenticated operator.", + "operationId": "me_api_auth_me_get", + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UserInfo" + } + } + } } }, - "required": [ - "venue" + "security": [ + { + "OAuth2PasswordBearer": [] + } + ] + } + }, + "/api/users": { + "get": { + "tags": [ + "users" ], - "title": "ActiveVenueUpdate", - "type": "object" - }, - "AiActionModeUpdate": { - "properties": { - "mode": { - "description": "notify | auto", - "title": "Mode", - "type": "string" + "summary": "List Users", + "operationId": "list_users_api_users_get", + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "items": { + "$ref": "#/components/schemas/UserRead" + }, + "type": "array", + "title": "Response List Users Api Users Get" + } + } + } } }, - "required": [ - "mode" - ], - "title": "AiActionModeUpdate", - "type": "object" + "security": [ + { + "OAuth2PasswordBearer": [] + } + ] }, - "AiModelUpdate": { - "properties": { - "model": { - "default": "", - "maxLength": 64, - "title": "Model", - "type": "string" + "post": { + "tags": [ + "users" + ], + "summary": "Create User", + "operationId": "create_user_api_users_post", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UserCreate" + } + } }, - "provider": { - "maxLength": 16, - "minLength": 1, - "title": "Provider", - "type": "string" + "required": true + }, + "responses": { + "201": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UserRead" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } } }, - "required": [ - "provider" + "security": [ + { + "OAuth2PasswordBearer": [] + } + ] + } + }, + "/api/users/{user_id}": { + "patch": { + "tags": [ + "users" ], - "title": "AiModelUpdate", - "type": "object" - }, - "AiSettingsUpdate": { - "properties": { - "api_key": { - "default": "", - "maxLength": 256, - "title": "Api Key", - "type": "string" - }, - "base_url": { - "default": "", - "maxLength": 256, - "title": "Base Url", - "type": "string" - }, - "model": { - "default": "", - "maxLength": 64, - "title": "Model", - "type": "string" - }, - "provider": { - "maxLength": 32, - "minLength": 1, - "title": "Provider", - "type": "string" + "summary": "Update User", + "operationId": "update_user_api_users__user_id__patch", + "security": [ + { + "OAuth2PasswordBearer": [] } - }, - "required": [ - "provider" ], - "title": "AiSettingsUpdate", - "type": "object" - }, - "AiSpendCapUpdate": { - "properties": { - "cap": { - "anyOf": [ - { - "minimum": 0.0, - "type": "number" - }, - { - "pattern": "^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$", - "type": "string" + "parameters": [ + { + "name": "user_id", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "title": "User Id" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UserUpdate" } - ], - "title": "Cap" + } } }, - "required": [ - "cap" - ], - "title": "AiSpendCapUpdate", - "type": "object" - }, - "AllocationUpdate": { - "properties": { - "allocated": { - "anyOf": [ - { - "minimum": 0.0, - "type": "number" - }, - { - "pattern": "^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$", - "type": "string" + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UserRead" + } } - ], - "title": "Allocated" + } }, - "max_loss": { - "anyOf": [ - { - "minimum": 0.0, - "type": "number" - }, - { - "pattern": "^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$", - "type": "string" - }, - { - "type": "null" + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } } - ], - "title": "Max Loss" + } } - }, - "required": [ - "allocated" + } + } + }, + "/api/users/{user_id}/password": { + "post": { + "tags": [ + "users" ], - "title": "AllocationUpdate", - "type": "object" - }, - "AnalyzeRequest": { - "properties": { - "task": { - "maxLength": 4000, - "minLength": 1, - "title": "Task", - "type": "string" + "summary": "Reset Password", + "operationId": "reset_password_api_users__user_id__password_post", + "security": [ + { + "OAuth2PasswordBearer": [] } - }, - "required": [ - "task" ], - "title": "AnalyzeRequest", - "type": "object" - }, - "ApiTokenCreate": { - "properties": { - "name": { - "maxLength": 64, - "minLength": 1, - "title": "Name", - "type": "string" - }, - "role": { - "$ref": "#/components/schemas/Role", - "default": "user" + "parameters": [ + { + "name": "user_id", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "title": "User Id" + } } - }, - "required": [ - "name" ], - "title": "ApiTokenCreate", - "type": "object" - }, - "ApiTokenCreated": { - "properties": { - "created_at": { - "format": "date-time", - "title": "Created At", - "type": "string" - }, - "id": { - "title": "Id", - "type": "integer" - }, - "last_used_at": { - "anyOf": [ - { - "format": "date-time", - "type": "string" - }, - { - "type": "null" + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PasswordReset" } - ], - "title": "Last Used At" - }, - "name": { - "title": "Name", - "type": "string" - }, - "revoked": { - "title": "Revoked", - "type": "boolean" - }, - "role": { - "title": "Role", - "type": "string" - }, - "token": { - "title": "Token", - "type": "string" + } } }, - "required": [ - "id", - "name", - "role", - "revoked", - "created_at", - "last_used_at", - "token" - ], - "title": "ApiTokenCreated", - "type": "object" - }, - "ApiTokenRead": { - "properties": { - "created_at": { - "format": "date-time", - "title": "Created At", - "type": "string" - }, - "id": { - "title": "Id", - "type": "integer" + "responses": { + "204": { + "description": "Successful Response" }, - "last_used_at": { - "anyOf": [ - { - "format": "date-time", - "type": "string" - }, - { - "type": "null" + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } } - ], - "title": "Last Used At" - }, - "name": { - "title": "Name", - "type": "string" - }, - "revoked": { - "title": "Revoked", - "type": "boolean" - }, - "role": { - "title": "Role", - "type": "string" + } } - }, - "required": [ - "id", - "name", - "role", - "revoked", - "created_at", - "last_used_at" + } + } + }, + "/api/market/tickers": { + "get": { + "tags": [ + "market" ], - "title": "ApiTokenRead", - "type": "object" - }, - "AuditLog": { - "description": "Append-only record of every config-changing action \u2014 who, when, what.\n\nSurfaced read-only on the History page (Phase 6). Write endpoints record\nentries here via `auth.audit.record_audit`.", - "properties": { - "action": { - "maxLength": 64, - "title": "Action", - "type": "string" + "summary": "List Tickers", + "description": "Latest 24h tickers \u2014 from the live snapshot, or REST as a fallback.", + "operationId": "list_tickers_api_market_tickers_get", + "security": [ + { + "OAuth2PasswordBearer": [] + } + ], + "parameters": [ + { + "name": "market", + "in": "query", + "required": false, + "schema": { + "$ref": "#/components/schemas/Market", + "default": "spot" + } + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Ticker" + }, + "title": "Response List Tickers Api Market Tickers Get" + } + } + } }, - "actor": { - "maxLength": 64, - "title": "Actor", - "type": "string" - }, - "created_at": { - "format": "date-time", - "title": "Created At", - "type": "string" - }, - "detail": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ], - "title": "Detail" - }, - "id": { - "anyOf": [ - { - "type": "integer" - }, - { - "type": "null" - } - ], - "title": "Id" - }, - "target": { - "anyOf": [ - { - "maxLength": 128, - "type": "string" - }, - { - "type": "null" + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } } - ], - "title": "Target" + } } - }, - "required": [ - "actor", - "action" + } + } + }, + "/api/market/klines": { + "get": { + "tags": [ + "market" ], - "title": "AuditLog", - "type": "object" - }, - "BacktestMetricsRead": { - "properties": { - "losses": { - "title": "Losses", - "type": "integer" - }, - "max_drawdown_pct": { - "pattern": "^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$", - "title": "Max Drawdown Pct", - "type": "string" - }, - "sharpe": { - "pattern": "^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$", - "title": "Sharpe", - "type": "string" - }, - "total_return_pct": { - "pattern": "^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$", - "title": "Total Return Pct", - "type": "string" + "summary": "Get Klines", + "description": "Candle history \u2014 served from the cache, refreshed from the active venue.", + "operationId": "get_klines_api_market_klines_get", + "security": [ + { + "OAuth2PasswordBearer": [] + } + ], + "parameters": [ + { + "name": "symbol", + "in": "query", + "required": true, + "schema": { + "type": "string", + "minLength": 3, + "maxLength": 24, + "title": "Symbol" + } }, - "trades": { - "title": "Trades", - "type": "integer" + { + "name": "interval", + "in": "query", + "required": false, + "schema": { + "type": "string", + "default": "1h", + "title": "Interval" + } }, - "win_rate_pct": { - "pattern": "^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$", - "title": "Win Rate Pct", - "type": "string" + { + "name": "market", + "in": "query", + "required": false, + "schema": { + "$ref": "#/components/schemas/Market", + "default": "spot" + } }, - "wins": { - "title": "Wins", - "type": "integer" + { + "name": "limit", + "in": "query", + "required": false, + "schema": { + "type": "integer", + "maximum": 1000, + "minimum": 1, + "default": 200, + "title": "Limit" + } } - }, - "required": [ - "total_return_pct", - "win_rate_pct", - "max_drawdown_pct", - "sharpe", - "trades", - "wins", - "losses" ], - "title": "BacktestMetricsRead", - "type": "object" - }, - "BacktestRequest": { - "properties": { - "end": { - "anyOf": [ - { - "format": "date-time", - "type": "string" - }, - { - "type": "null" - } - ], - "title": "End" - }, - "fee_rate": { - "anyOf": [ - { - "minimum": 0.0, - "type": "number" - }, - { - "pattern": "^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$", - "type": "string" + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Candle" + }, + "title": "Response Get Klines Api Market Klines Get" + } } - ], - "default": "0.001", - "title": "Fee Rate" + } }, - "funding_rate": { - "anyOf": [ - { - "minimum": 0.0, - "type": "number" - }, - { - "pattern": "^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$", - "type": "string" + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } } - ], - "default": "0", - "title": "Funding Rate" - }, - "initial_capital": { - "anyOf": [ - { - "exclusiveMinimum": 0.0, - "type": "number" - }, - { - "pattern": "^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$", - "type": "string" + } + } + } + } + }, + "/api/market/funding": { + "get": { + "tags": [ + "market" + ], + "summary": "Get Funding", + "description": "Current futures funding rate for a symbol.", + "operationId": "get_funding_api_market_funding_get", + "security": [ + { + "OAuth2PasswordBearer": [] + } + ], + "parameters": [ + { + "name": "symbol", + "in": "query", + "required": true, + "schema": { + "type": "string", + "title": "Symbol" + } + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FundingRate" + } } - ], - "default": "10000", - "title": "Initial Capital" + } }, - "slippage_bps": { - "anyOf": [ - { - "minimum": 0.0, - "type": "number" - }, - { - "pattern": "^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$", - "type": "string" + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } } - ], - "default": "2", - "title": "Slippage Bps" - }, - "start": { - "format": "date-time", - "title": "Start", - "type": "string" - }, - "strategy": { - "title": "Strategy", - "type": "string" + } } - }, - "required": [ - "strategy", - "start" + } + } + }, + "/api/market/orderbook": { + "get": { + "tags": [ + "market" ], - "title": "BacktestRequest", - "type": "object" - }, - "BacktestResponse": { - "properties": { - "candles": { - "title": "Candles", - "type": "integer" - }, - "equity_curve": { - "items": { - "$ref": "#/components/schemas/EquityPoint" - }, - "title": "Equity Curve", - "type": "array" - }, - "final_equity": { - "pattern": "^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$", - "title": "Final Equity", - "type": "string" - }, - "initial_capital": { - "pattern": "^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$", - "title": "Initial Capital", - "type": "string" - }, - "interval": { - "title": "Interval", - "type": "string" - }, - "metrics": { - "$ref": "#/components/schemas/BacktestMetricsRead" - }, - "net_pnl": { - "pattern": "^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$", - "title": "Net Pnl", - "type": "string" - }, - "strategy": { - "title": "Strategy", - "type": "string" - }, - "symbol": { - "title": "Symbol", - "type": "string" - }, - "total_fees": { - "pattern": "^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$", - "title": "Total Fees", - "type": "string" - }, - "trades": { - "items": { - "$ref": "#/components/schemas/BacktestTradeRead" - }, - "title": "Trades", - "type": "array" + "summary": "Get Orderbook", + "description": "Order-book depth for a symbol.", + "operationId": "get_orderbook_api_market_orderbook_get", + "security": [ + { + "OAuth2PasswordBearer": [] } - }, - "required": [ - "strategy", - "symbol", - "interval", - "initial_capital", - "final_equity", - "net_pnl", - "total_fees", - "candles", - "equity_curve", - "trades", - "metrics" ], - "title": "BacktestResponse", - "type": "object" - }, - "BacktestTradeRead": { - "properties": { - "fee": { - "pattern": "^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$", - "title": "Fee", - "type": "string" - }, - "price": { - "pattern": "^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$", - "title": "Price", - "type": "string" - }, - "quantity": { - "pattern": "^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$", - "title": "Quantity", - "type": "string" - }, - "realized_pnl": { - "pattern": "^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$", - "title": "Realized Pnl", - "type": "string" + "parameters": [ + { + "name": "symbol", + "in": "query", + "required": true, + "schema": { + "type": "string", + "title": "Symbol" + } }, - "side": { - "title": "Side", - "type": "string" + { + "name": "market", + "in": "query", + "required": false, + "schema": { + "$ref": "#/components/schemas/Market", + "default": "spot" + } }, - "time": { - "format": "date-time", - "title": "Time", - "type": "string" + { + "name": "limit", + "in": "query", + "required": false, + "schema": { + "type": "integer", + "maximum": 100, + "minimum": 5, + "default": 20, + "title": "Limit" + } } - }, - "required": [ - "time", - "side", - "quantity", - "price", - "fee", - "realized_pnl" ], - "title": "BacktestTradeRead", - "type": "object" - }, - "BinanceKeysUpdate": { - "properties": { - "api_key": { - "maxLength": 256, - "minLength": 1, - "title": "Api Key", - "type": "string" + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/OrderBook" + } + } + } }, - "api_secret": { - "maxLength": 256, - "minLength": 1, - "title": "Api Secret", - "type": "string" + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/api/market/latency": { + "get": { + "tags": [ + "market" + ], + "summary": "Feed Latency", + "description": "How delayed the price data is, per feed (ws event time + REST RTT).", + "operationId": "feed_latency_api_market_latency_get", + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/LatencyRead" + } + } + } } }, - "required": [ - "api_key", - "api_secret" + "security": [ + { + "OAuth2PasswordBearer": [] + } + ] + } + }, + "/api/orders/manual": { + "post": { + "tags": [ + "orders" ], - "title": "BinanceKeysUpdate", - "type": "object" - }, - "Body_login_api_auth_login_post": { - "properties": { - "client_id": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" + "summary": "Place Manual Order", + "description": "Place a one-off order, risk-checked and recorded as `manual`.", + "operationId": "place_manual_order_api_orders_manual_post", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ManualOrderRequest" } - ], - "title": "Client Id" + } }, - "client_secret": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" + "required": true + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Fill" + } } - ], - "format": "password", - "title": "Client Secret" + } }, - "grant_type": { - "anyOf": [ - { - "pattern": "^password$", - "type": "string" - }, - { - "type": "null" + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } } - ], - "title": "Grant Type" - }, - "password": { - "format": "password", - "title": "Password", - "type": "string" - }, - "scope": { - "default": "", - "title": "Scope", - "type": "string" - }, - "username": { - "title": "Username", - "type": "string" + } } }, - "required": [ - "username", - "password" + "security": [ + { + "OAuth2PasswordBearer": [] + } + ] + } + }, + "/api/portfolio/summary": { + "get": { + "tags": [ + "portfolio" ], - "title": "Body_login_api_auth_login_post", - "type": "object" - }, - "Candle": { - "description": "A single OHLCV bar for one symbol/interval/market.", - "properties": { - "close": { - "pattern": "^(?!^[-+.]*$)[+-]?0*(?:\\d{0,16}|(?=[\\d.]{1,25}0*$)\\d{0,16}\\.\\d{0,8}0*$)", - "title": "Close", - "type": "string" - }, - "close_time": { - "format": "date-time", - "title": "Close Time", - "type": "string" - }, - "high": { - "pattern": "^(?!^[-+.]*$)[+-]?0*(?:\\d{0,16}|(?=[\\d.]{1,25}0*$)\\d{0,16}\\.\\d{0,8}0*$)", - "title": "High", - "type": "string" - }, - "id": { - "anyOf": [ - { - "type": "integer" - }, - { - "type": "null" + "summary": "Summary", + "description": "Aggregate accounting \u2014 equity, PnL, fees, allocated vs idle capital.", + "operationId": "summary_api_portfolio_summary_get", + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PortfolioSummary" + } } - ], - "title": "Id" - }, - "interval": { - "maxLength": 8, - "title": "Interval", - "type": "string" - }, - "low": { - "pattern": "^(?!^[-+.]*$)[+-]?0*(?:\\d{0,16}|(?=[\\d.]{1,25}0*$)\\d{0,16}\\.\\d{0,8}0*$)", - "title": "Low", - "type": "string" - }, - "market": { - "maxLength": 8, - "title": "Market", - "type": "string" - }, - "open": { - "pattern": "^(?!^[-+.]*$)[+-]?0*(?:\\d{0,16}|(?=[\\d.]{1,25}0*$)\\d{0,16}\\.\\d{0,8}0*$)", - "title": "Open", - "type": "string" - }, - "open_time": { - "format": "date-time", - "title": "Open Time", - "type": "string" - }, - "symbol": { - "maxLength": 80, - "title": "Symbol", - "type": "string" - }, - "volume": { - "pattern": "^(?!^[-+.]*$)[+-]?0*(?:\\d{0,16}|(?=[\\d.]{1,25}0*$)\\d{0,16}\\.\\d{0,8}0*$)", - "title": "Volume", - "type": "string" + } } }, - "required": [ - "market", - "symbol", - "interval", - "open_time", - "open", - "high", - "low", - "close", - "volume", - "close_time" - ], - "title": "Candle", - "type": "object" - }, - "CloseResult": { - "properties": { - "closed": { - "title": "Closed", - "type": "integer" + "security": [ + { + "OAuth2PasswordBearer": [] } - }, - "required": [ - "closed" + ] + } + }, + "/api/portfolio/equity": { + "get": { + "tags": [ + "portfolio" ], - "title": "CloseResult", - "type": "object" - }, - "CompareCellRead": { - "properties": { - "equity_sparkline": { - "items": { - "type": "number" - }, - "title": "Equity Sparkline", - "type": "array" - }, - "error": { - "title": "Error", - "type": "string" - }, - "final_equity": { - "pattern": "^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$", - "title": "Final Equity", - "type": "string" - }, - "max_drawdown_pct": { - "pattern": "^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$", - "title": "Max Drawdown Pct", - "type": "string" - }, - "net_pnl": { - "pattern": "^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$", - "title": "Net Pnl", - "type": "string" - }, - "params": { - "additionalProperties": true, - "title": "Params", - "type": "object" - }, - "return_pct": { - "pattern": "^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$", - "title": "Return Pct", - "type": "string" - }, - "sharpe": { - "pattern": "^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$", - "title": "Sharpe", - "type": "string" - }, - "symbol": { - "title": "Symbol", - "type": "string" - }, - "trades": { - "title": "Trades", - "type": "integer" - }, - "type": { - "title": "Type", - "type": "string" - }, - "win_rate_pct": { - "pattern": "^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$", - "title": "Win Rate Pct", - "type": "string" + "summary": "Equity", + "description": "Equity-curve history, oldest-first.", + "operationId": "equity_api_portfolio_equity_get", + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "items": { + "$ref": "#/components/schemas/EquitySnapshot" + }, + "type": "array", + "title": "Response Equity Api Portfolio Equity Get" + } + } + } } }, - "required": [ - "type", - "symbol", - "params", - "return_pct", - "max_drawdown_pct", - "sharpe", - "win_rate_pct", - "trades", - "net_pnl", - "final_equity", - "equity_sparkline", - "error" + "security": [ + { + "OAuth2PasswordBearer": [] + } + ] + } + }, + "/api/portfolio/positions": { + "get": { + "tags": [ + "portfolio" ], - "title": "CompareCellRead", - "type": "object" - }, - "CompareRequest": { - "description": "A grid request \u2014 either pivot is just a different shape of the same.", - "properties": { - "capital": { - "anyOf": [ - { - "exclusiveMinimum": 0.0, - "type": "number" - }, - { - "pattern": "^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$", - "type": "string" + "summary": "Positions", + "description": "Currently open positions across all strategies.", + "operationId": "positions_api_portfolio_positions_get", + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "items": { + "$ref": "#/components/schemas/Position" + }, + "type": "array", + "title": "Response Positions Api Portfolio Positions Get" + } } - ], - "default": "10000", - "title": "Capital" - }, - "days": { - "default": 90, - "maximum": 365.0, - "minimum": 7.0, - "title": "Days", - "type": "integer" - }, - "symbols": { - "items": { - "type": "string" - }, - "maxItems": 8, - "minItems": 1, - "title": "Symbols", - "type": "array" - }, - "timeframe": { - "default": "1h", - "maxLength": 8, - "title": "Timeframe", - "type": "string" - }, - "types": { - "items": { - "type": "string" - }, - "maxItems": 6, - "title": "Types", - "type": "array" + } } }, - "required": [ - "symbols" + "security": [ + { + "OAuth2PasswordBearer": [] + } + ] + } + }, + "/api/portfolio/trades": { + "get": { + "tags": [ + "portfolio" ], - "title": "CompareRequest", - "type": "object" - }, - "CostBucket": { - "description": "One aggregation bucket \u2014 by model, purpose or day.", - "properties": { - "calls": { - "title": "Calls", - "type": "integer" - }, - "cost_usd": { - "pattern": "^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$", - "title": "Cost Usd", - "type": "string" - }, - "input_tokens": { - "title": "Input Tokens", - "type": "integer" - }, - "key": { - "title": "Key", - "type": "string" - }, - "output_tokens": { - "title": "Output Tokens", - "type": "integer" + "summary": "Trades", + "description": "Most-recent executed trades.", + "operationId": "trades_api_portfolio_trades_get", + "security": [ + { + "OAuth2PasswordBearer": [] } - }, - "required": [ - "key", - "calls", - "input_tokens", - "output_tokens", - "cost_usd" ], - "title": "CostBucket", - "type": "object" - }, - "CostsRead": { - "description": "Trading-cost breakdown, the fee-rate reference, and today's LLM spend.", - "properties": { - "fee_pct_of_volume": { - "pattern": "^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$", - "title": "Fee Pct Of Volume", - "type": "string" + "parameters": [ + { + "name": "limit", + "in": "query", + "required": false, + "schema": { + "type": "integer", + "default": 50, + "title": "Limit" + } + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Trade" + }, + "title": "Response Trades Api Portfolio Trades Get" + } + } + } }, - "fees_by_market": { - "additionalProperties": { - "pattern": "^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$", - "type": "string" - }, - "title": "Fees By Market", - "type": "object" - }, - "llm_spend_today": { - "pattern": "^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$", - "title": "Llm Spend Today", - "type": "string" - }, - "total_fees": { - "pattern": "^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$", - "title": "Total Fees", - "type": "string" - }, - "traded_volume": { - "pattern": "^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$", - "title": "Traded Volume", - "type": "string" - }, - "venue_fee_rates": { - "additionalProperties": { - "pattern": "^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$", - "type": "string" - }, - "title": "Venue Fee Rates", - "type": "object" + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } } - }, - "required": [ - "total_fees", - "fees_by_market", - "traded_volume", - "fee_pct_of_volume", - "venue_fee_rates", - "llm_spend_today" + } + } + }, + "/api/portfolio/costs": { + "get": { + "tags": [ + "portfolio" ], - "title": "CostsRead", - "type": "object" - }, - "CostsSummary": { - "description": "Headline spend + the breakdowns the Costs screen renders.", - "properties": { - "by_day": { - "items": { - "$ref": "#/components/schemas/CostBucket" - }, - "title": "By Day", - "type": "array" - }, - "by_model": { - "items": { - "$ref": "#/components/schemas/CostBucket" - }, - "title": "By Model", - "type": "array" - }, - "by_purpose": { - "items": { - "$ref": "#/components/schemas/CostBucket" - }, - "title": "By Purpose", - "type": "array" - }, - "daily_cap_usd": { - "pattern": "^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$", - "title": "Daily Cap Usd", - "type": "string" - }, - "last_30d_usd": { - "pattern": "^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$", - "title": "Last 30D Usd", - "type": "string" - }, - "last_7d_usd": { - "pattern": "^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$", - "title": "Last 7D Usd", - "type": "string" - }, - "today_usd": { - "pattern": "^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$", - "title": "Today Usd", - "type": "string" + "summary": "Costs", + "description": "Cost visibility \u2014 trading fees by market, fee rates, and LLM spend.", + "operationId": "costs_api_portfolio_costs_get", + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CostsRead" + } + } + } } }, - "required": [ - "today_usd", - "last_7d_usd", - "last_30d_usd", - "daily_cap_usd", - "by_model", - "by_purpose", - "by_day" + "security": [ + { + "OAuth2PasswordBearer": [] + } + ] + } + }, + "/api/strategies": { + "get": { + "tags": [ + "strategies" ], - "title": "CostsSummary", - "type": "object" - }, - "CouncilMember": { - "properties": { - "model": { - "default": "", - "maxLength": 64, - "title": "Model", - "type": "string" - }, - "provider": { - "maxLength": 32, - "minLength": 1, - "title": "Provider", - "type": "string" + "summary": "List Strategies", + "description": "Every registered strategy with its allocation, state and PnL.", + "operationId": "list_strategies_api_strategies_get", + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "items": { + "$ref": "#/components/schemas/StrategyRead" + }, + "type": "array", + "title": "Response List Strategies Api Strategies Get" + } + } + } } }, - "required": [ - "provider" - ], - "title": "CouncilMember", - "type": "object" + "security": [ + { + "OAuth2PasswordBearer": [] + } + ] }, - "CouncilReviewRead": { - "description": "A council verdict plus every member's vote.", - "properties": { - "created_at": { - "format": "date-time", - "title": "Created At", - "type": "string" - }, - "id": { - "title": "Id", - "type": "integer" - }, - "quorum_met": { - "title": "Quorum Met", - "type": "boolean" - }, - "report_id": { - "title": "Report Id", - "type": "integer" - }, - "strategy_brief": { - "title": "Strategy Brief", - "type": "string" - }, - "verdict": { - "title": "Verdict", - "type": "string" + "post": { + "tags": [ + "strategies" + ], + "summary": "Create Instance", + "description": "Create a strategy instance: any registered type on any chosen coin.", + "operationId": "create_instance_api_strategies_post", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InstanceCreate" + } + } }, - "votes": { - "items": { - "$ref": "#/components/schemas/CouncilVoteRead" - }, - "title": "Votes", - "type": "array" + "required": true + }, + "responses": { + "201": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StrategyRead" + } + } + } }, - "weighted_score": { - "pattern": "^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$", - "title": "Weighted Score", - "type": "string" + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } } }, - "required": [ - "id", - "report_id", - "verdict", - "weighted_score", - "quorum_met", - "strategy_brief", - "created_at", - "votes" + "security": [ + { + "OAuth2PasswordBearer": [] + } + ] + } + }, + "/api/strategies/types": { + "get": { + "tags": [ + "strategies" ], - "title": "CouncilReviewRead", - "type": "object" - }, - "CouncilSettingsUpdate": { - "description": "Council configuration \u2014 an empty member list disables the council.", - "properties": { - "members": { - "items": { - "$ref": "#/components/schemas/CouncilMember" - }, - "maxItems": 8, - "title": "Members", - "type": "array" - }, - "quorum": { - "anyOf": [ - { - "exclusiveMinimum": 0.0, - "maximum": 1.0, - "type": "number" - }, - { - "pattern": "^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$", - "type": "string" + "summary": "List Strategy Types", + "description": "Every pickable strategy type with its typed parameter schema.", + "operationId": "list_strategy_types_api_strategies_types_get", + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "items": { + "$ref": "#/components/schemas/StrategyTypeRead" + }, + "type": "array", + "title": "Response List Strategy Types Api Strategies Types Get" + } } - ], - "default": "0.5", - "title": "Quorum" + } } }, - "required": [ - "members" + "security": [ + { + "OAuth2PasswordBearer": [] + } + ] + } + }, + "/api/strategies/{name}": { + "delete": { + "tags": [ + "strategies" ], - "title": "CouncilSettingsUpdate", - "type": "object" - }, - "CouncilVoteRead": { - "properties": { - "action": { - "title": "Action", - "type": "string" - }, - "confidence": { - "pattern": "^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$", - "title": "Confidence", - "type": "string" - }, - "model": { - "title": "Model", - "type": "string" - }, - "provider": { - "title": "Provider", - "type": "string" - }, - "reasoning": { - "title": "Reasoning", - "type": "string" + "summary": "Delete Instance", + "description": "Delete an instance-backed strategy. Built-ins cannot be deleted.\n\nRefused while the strategy holds an open position \u2014 close it first.", + "operationId": "delete_instance_api_strategies__name__delete", + "security": [ + { + "OAuth2PasswordBearer": [] } - }, - "required": [ - "provider", - "model", - "action", - "confidence", - "reasoning" ], - "title": "CouncilVoteRead", - "type": "object" - }, - "Decision": { - "description": "A structured trading decision parsed from an LLM response.", - "properties": { - "action": { - "$ref": "#/components/schemas/DecisionAction" - }, - "confidence": { - "pattern": "^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$", - "title": "Confidence", - "type": "string" - }, - "reasoning": { - "default": "", - "title": "Reasoning", - "type": "string" + "parameters": [ + { + "name": "name", + "in": "path", + "required": true, + "schema": { + "type": "string", + "title": "Name" + } } - }, - "required": [ - "action", - "confidence" ], - "title": "Decision", - "type": "object" - }, - "DecisionAction": { - "enum": [ - "buy", - "sell", - "hold" + "responses": { + "204": { + "description": "Successful Response" + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/api/strategies/{name}/allocation": { + "patch": { + "tags": [ + "strategies" ], - "title": "DecisionAction", - "type": "string" - }, - "EnabledUpdate": { - "properties": { - "enabled": { - "title": "Enabled", - "type": "boolean" + "summary": "Update Allocation", + "description": "Set a strategy's capital budget; the engine caps its exposure to it.", + "operationId": "update_allocation_api_strategies__name__allocation_patch", + "security": [ + { + "OAuth2PasswordBearer": [] } - }, - "required": [ - "enabled" ], - "title": "EnabledUpdate", - "type": "object" - }, - "EquityPoint": { - "properties": { - "equity": { - "pattern": "^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$", - "title": "Equity", - "type": "string" - }, - "time": { - "format": "date-time", - "title": "Time", - "type": "string" + "parameters": [ + { + "name": "name", + "in": "path", + "required": true, + "schema": { + "type": "string", + "title": "Name" + } } - }, - "required": [ - "time", - "equity" ], - "title": "EquityPoint", - "type": "object" - }, - "EquitySnapshot": { - "description": "A point-in-time snapshot of portfolio equity \u2014 powers the equity curve.", - "properties": { - "equity": { - "pattern": "^(?!^[-+.]*$)[+-]?0*(?:\\d{0,18}|(?=[\\d.]{1,29}0*$)\\d{0,18}\\.\\d{0,10}0*$)", - "title": "Equity", - "type": "string" - }, - "fees": { - "pattern": "^(?!^[-+.]*$)[+-]?0*(?:\\d{0,18}|(?=[\\d.]{1,29}0*$)\\d{0,18}\\.\\d{0,10}0*$)", - "title": "Fees", - "type": "string" - }, - "id": { - "anyOf": [ - { - "type": "integer" - }, - { - "type": "null" + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AllocationUpdate" } - ], - "title": "Id" - }, - "net_pnl": { - "pattern": "^(?!^[-+.]*$)[+-]?0*(?:\\d{0,18}|(?=[\\d.]{1,29}0*$)\\d{0,18}\\.\\d{0,10}0*$)", - "title": "Net Pnl", - "type": "string" - }, - "realized_pnl": { - "pattern": "^(?!^[-+.]*$)[+-]?0*(?:\\d{0,18}|(?=[\\d.]{1,29}0*$)\\d{0,18}\\.\\d{0,10}0*$)", - "title": "Realized Pnl", - "type": "string" - }, - "ts": { - "format": "date-time", - "title": "Ts", - "type": "string" - }, - "unrealized_pnl": { - "pattern": "^(?!^[-+.]*$)[+-]?0*(?:\\d{0,18}|(?=[\\d.]{1,29}0*$)\\d{0,18}\\.\\d{0,10}0*$)", - "title": "Unrealized Pnl", - "type": "string" + } } }, - "required": [ - "ts", - "equity", - "realized_pnl", - "unrealized_pnl", - "fees", - "net_pnl" - ], - "title": "EquitySnapshot", - "type": "object" - }, - "Fill": { - "description": "The result of executing an `Order`.", - "properties": { - "fee": { - "pattern": "^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$", - "title": "Fee", - "type": "string" - }, - "market": { - "title": "Market", - "type": "string" - }, - "price": { - "pattern": "^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$", - "title": "Price", - "type": "string" - }, - "quantity": { - "pattern": "^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$", - "title": "Quantity", - "type": "string" - }, - "realized_pnl": { - "pattern": "^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$", - "title": "Realized Pnl", - "type": "string" - }, - "side": { - "$ref": "#/components/schemas/FillSide" - }, - "strategy": { - "title": "Strategy", - "type": "string" + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StrategyRead" + } + } + } }, - "symbol": { - "title": "Symbol", - "type": "string" + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } } - }, - "required": [ - "strategy", - "market", - "symbol", - "side", - "quantity", - "price", - "fee", - "realized_pnl" + } + } + }, + "/api/strategies/{name}/enabled": { + "patch": { + "tags": [ + "strategies" ], - "title": "Fill", - "type": "object" - }, - "FillSide": { - "enum": [ - "buy", - "sell" + "summary": "Update Enabled", + "description": "Enable or disable a strategy. Disabling stops new entries only.", + "operationId": "update_enabled_api_strategies__name__enabled_patch", + "security": [ + { + "OAuth2PasswordBearer": [] + } ], - "title": "FillSide", - "type": "string" - }, - "FundingRate": { - "properties": { - "funding_rate": { - "pattern": "^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$", - "title": "Funding Rate", - "type": "string" - }, - "mark_price": { - "pattern": "^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$", - "title": "Mark Price", - "type": "string" - }, - "next_funding_time": { - "format": "date-time", - "title": "Next Funding Time", - "type": "string" - }, - "symbol": { - "title": "Symbol", - "type": "string" + "parameters": [ + { + "name": "name", + "in": "path", + "required": true, + "schema": { + "type": "string", + "title": "Name" + } } - }, - "required": [ - "symbol", - "funding_rate", - "mark_price", - "next_funding_time" ], - "title": "FundingRate", - "type": "object" - }, - "GraphEdge": { - "description": "A directed, labelled relation from one node to another.", - "properties": { - "approved": { - "default": true, - "title": "Approved", - "type": "boolean" - }, - "created_at": { - "anyOf": [ - { - "format": "date-time", - "type": "string" - }, - { - "type": "null" - } - ], - "title": "Created At" - }, - "id": { - "anyOf": [ - { - "type": "integer" - }, - { - "type": "null" + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/EnabledUpdate" } - ], - "title": "Id" - }, - "origin": { - "default": "seed", - "maxLength": 8, - "title": "Origin", - "type": "string" - }, - "relation": { - "default": "related", - "maxLength": 40, - "title": "Relation", - "type": "string" - }, - "source_id": { - "title": "Source Id", - "type": "integer" - }, - "target_id": { - "title": "Target Id", - "type": "integer" - }, - "weight": { - "default": "1", - "pattern": "^(?!^[-+.]*$)[+-]?0*(?:\\d{0,4}|(?=[\\d.]{1,7}0*$)\\d{0,4}\\.\\d{0,2}0*$)", - "title": "Weight", - "type": "string" + } } }, - "required": [ - "source_id", - "target_id" - ], - "title": "GraphEdge", - "type": "object" - }, - "GraphNode": { - "description": "A node: an asset, a product, or a concept.", - "properties": { - "icon": { - "anyOf": [ - { - "maxLength": 32, - "type": "string" - }, - { - "type": "null" - } - ], - "title": "Icon" - }, - "id": { - "anyOf": [ - { - "type": "integer" - }, - { - "type": "null" + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StrategyRead" + } } - ], - "title": "Id" - }, - "kind": { - "default": "concept", - "maxLength": 16, - "title": "Kind", - "type": "string" - }, - "label": { - "maxLength": 120, - "title": "Label", - "type": "string" + } }, - "symbol": { - "anyOf": [ - { - "maxLength": 24, - "type": "string" - }, - { - "type": "null" + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } } - ], - "title": "Symbol" + } } - }, - "required": [ - "label" + } + } + }, + "/api/strategies/{name}/ai-model": { + "patch": { + "tags": [ + "strategies" ], - "title": "GraphNode", - "type": "object" - }, - "GraphView": { - "description": "The connections graph \u2014 nodes and the edges between them.", - "properties": { - "edges": { - "items": { - "$ref": "#/components/schemas/GraphEdge" - }, - "title": "Edges", - "type": "array" - }, - "nodes": { - "items": { - "$ref": "#/components/schemas/GraphNode" - }, - "title": "Nodes", - "type": "array" + "summary": "Update Ai Model", + "description": "Pin an AI strategy to a provider + model (Claude / OpenAI / Gemini / Ollama).", + "operationId": "update_ai_model_api_strategies__name__ai_model_patch", + "security": [ + { + "OAuth2PasswordBearer": [] } - }, - "required": [ - "nodes", - "edges" ], - "title": "GraphView", - "type": "object" - }, - "HTTPValidationError": { - "properties": { - "detail": { - "items": { - "$ref": "#/components/schemas/ValidationError" - }, - "title": "Detail", - "type": "array" + "parameters": [ + { + "name": "name", + "in": "path", + "required": true, + "schema": { + "type": "string", + "title": "Name" + } } - }, - "title": "HTTPValidationError", - "type": "object" - }, - "InstanceCreate": { - "description": "Apply a strategy type to a coin as a new named instance.", - "properties": { - "allocated": { - "anyOf": [ - { - "minimum": 0.0, - "type": "number" - }, - { - "pattern": "^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$", - "type": "string" - } - ], - "default": "10000", - "title": "Allocated" - }, - "market": { - "default": "spot", - "pattern": "^(spot|futures)$", - "title": "Market", - "type": "string" - }, - "max_loss": { - "anyOf": [ - { - "minimum": 0.0, - "type": "number" - }, - { - "pattern": "^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$", - "type": "string" + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AiModelUpdate" } - ], - "default": "0", - "title": "Max Loss" - }, - "name": { - "maxLength": 64, - "minLength": 1, - "title": "Name", - "type": "string" - }, - "params": { - "additionalProperties": { - "type": "string" - }, - "title": "Params", - "type": "object" - }, - "symbol": { - "maxLength": 80, - "minLength": 1, - "title": "Symbol", - "type": "string" - }, - "timeframe": { - "default": "1h", - "maxLength": 8, - "title": "Timeframe", - "type": "string" - }, - "type": { - "maxLength": 32, - "minLength": 1, - "title": "Type", - "type": "string" - }, - "venue": { - "default": "binance", - "maxLength": 24, - "title": "Venue", - "type": "string" + } } }, - "required": [ - "name", - "type", - "symbol" - ], - "title": "InstanceCreate", - "type": "object" - }, - "LLMUsage": { - "description": "One recorded LLM completion \u2014 tokens, estimated cost, and the decision.", - "properties": { - "action": { - "anyOf": [ - { - "maxLength": 8, - "type": "string" - }, - { - "type": "null" + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StrategyRead" + } } - ], - "title": "Action" + } }, - "confidence": { - "anyOf": [ - { - "pattern": "^(?!^[-+.]*$)[+-]?0*(?:\\d{0,18}|(?=[\\d.]{1,29}0*$)\\d{0,18}\\.\\d{0,10}0*$)", - "type": "string" - }, - { - "type": "null" + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } } - ], - "title": "Confidence" - }, - "created_at": { - "format": "date-time", - "title": "Created At", - "type": "string" - }, - "estimated_cost_usd": { - "default": "0", - "pattern": "^(?!^[-+.]*$)[+-]?0*(?:\\d{0,18}|(?=[\\d.]{1,29}0*$)\\d{0,18}\\.\\d{0,10}0*$)", - "title": "Estimated Cost Usd", - "type": "string" - }, - "id": { - "anyOf": [ - { - "type": "integer" - }, - { - "type": "null" + } + } + } + } + }, + "/api/strategies/{name}/close": { + "post": { + "tags": [ + "strategies" + ], + "summary": "Close Strategy", + "description": "Close every open position held by the strategy.", + "operationId": "close_strategy_api_strategies__name__close_post", + "security": [ + { + "OAuth2PasswordBearer": [] + } + ], + "parameters": [ + { + "name": "name", + "in": "path", + "required": true, + "schema": { + "type": "string", + "title": "Name" + } + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CloseResult" + } } - ], - "title": "Id" - }, - "input_tokens": { - "default": 0, - "title": "Input Tokens", - "type": "integer" - }, - "model": { - "maxLength": 64, - "title": "Model", - "type": "string" - }, - "output_tokens": { - "default": 0, - "title": "Output Tokens", - "type": "integer" - }, - "provider": { - "maxLength": 16, - "title": "Provider", - "type": "string" - }, - "purpose": { - "default": "analyze", - "maxLength": 16, - "title": "Purpose", - "type": "string" + } }, - "strategy": { - "anyOf": [ - { - "maxLength": 64, - "type": "string" - }, - { - "type": "null" + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } } - ], - "title": "Strategy" + } } - }, - "required": [ - "provider", - "model", - "created_at" + } + } + }, + "/api/backtest/run": { + "post": { + "tags": [ + "backtest" ], - "title": "LLMUsage", - "type": "object" - }, - "LatencyRead": { - "description": "Price-feed latency \u2014 per-feed stats plus the overall degraded flag.", - "properties": { - "degraded": { - "title": "Degraded", - "type": "boolean" + "summary": "Run", + "description": "Replay a strategy over the requested date range and return the result.", + "operationId": "run_api_backtest_run_post", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BacktestRequest" + } + } }, - "feeds": { - "items": { - "$ref": "#/components/schemas/LatencyStatsRead" - }, - "title": "Feeds", - "type": "array" + "required": true + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BacktestResponse" + } + } + } }, - "warn_ms": { - "title": "Warn Ms", - "type": "integer" + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } } }, - "required": [ - "warn_ms", - "degraded", - "feeds" + "security": [ + { + "OAuth2PasswordBearer": [] + } + ] + } + }, + "/api/settings": { + "get": { + "tags": [ + "settings" ], - "title": "LatencyRead", - "type": "object" - }, - "LatencyStatsRead": { - "description": "Rolling-window latency for one (market, symbol, kind).", - "properties": { - "current_ms": { - "title": "Current Ms", - "type": "number" - }, - "degraded": { - "title": "Degraded", - "type": "boolean" - }, - "kind": { - "title": "Kind", - "type": "string" - }, - "market": { - "title": "Market", - "type": "string" - }, - "max_ms": { - "title": "Max Ms", - "type": "number" - }, - "p50_ms": { - "title": "P50 Ms", - "type": "number" - }, - "p95_ms": { - "title": "P95 Ms", - "type": "number" - }, - "samples": { - "title": "Samples", - "type": "integer" - }, - "symbol": { - "title": "Symbol", - "type": "string" + "summary": "Read Settings", + "description": "Current trading mode and whether Binance keys are stored.", + "operationId": "read_settings_api_settings_get", + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SettingsRead" + } + } + } } }, - "required": [ - "market", - "symbol", - "kind", - "current_ms", - "p50_ms", - "p95_ms", - "max_ms", - "samples", - "degraded" + "security": [ + { + "OAuth2PasswordBearer": [] + } + ] + } + }, + "/api/settings/mode": { + "put": { + "tags": [ + "settings" ], - "title": "LatencyStatsRead", - "type": "object" - }, - "LedgerEntry": { - "description": "One paid call, newest first.", - "properties": { - "action": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" + "summary": "Update Mode", + "description": "Switch the trading mode (Sim / Testnet / Live).", + "operationId": "update_mode_api_settings_mode_put", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ModeUpdate" } - ], - "title": "Action" + } }, - "confidence": { - "anyOf": [ - { - "pattern": "^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$", - "type": "string" - }, - { - "type": "null" + "required": true + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SettingsRead" + } } - ], - "title": "Confidence" - }, - "cost_usd": { - "pattern": "^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$", - "title": "Cost Usd", - "type": "string" - }, - "created_at": { - "format": "date-time", - "title": "Created At", - "type": "string" - }, - "id": { - "title": "Id", - "type": "integer" + } }, - "input_tokens": { - "title": "Input Tokens", - "type": "integer" - }, - "model": { - "title": "Model", - "type": "string" - }, - "output_tokens": { - "title": "Output Tokens", - "type": "integer" - }, - "provider": { - "title": "Provider", - "type": "string" - }, - "purpose": { - "title": "Purpose", - "type": "string" - }, - "strategy": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } } - ], - "title": "Strategy" + } } }, - "required": [ - "id", - "created_at", - "provider", - "model", - "purpose", - "strategy", - "input_tokens", - "output_tokens", - "cost_usd", - "action", - "confidence" - ], - "title": "LedgerEntry", - "type": "object" - }, - "LedgerPage": { - "properties": { - "entries": { - "items": { - "$ref": "#/components/schemas/LedgerEntry" - }, - "title": "Entries", - "type": "array" - }, - "total": { - "title": "Total", - "type": "integer" + "security": [ + { + "OAuth2PasswordBearer": [] } - }, - "required": [ - "total", - "entries" + ] + } + }, + "/api/settings/binance-keys": { + "put": { + "tags": [ + "settings" ], - "title": "LedgerPage", - "type": "object" - }, - "LlmCredentialsUpdate": { - "description": "One LLM provider's credentials \u2014 both fields optional.", - "properties": { - "api_key": { - "default": "", - "maxLength": 256, - "title": "Api Key", - "type": "string" + "summary": "Update Binance Keys", + "description": "Store the Binance API credentials (encrypted at rest).", + "operationId": "update_binance_keys_api_settings_binance_keys_put", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BinanceKeysUpdate" + } + } }, - "base_url": { - "default": "", - "maxLength": 256, - "title": "Base Url", - "type": "string" - } + "required": true }, - "title": "LlmCredentialsUpdate", - "type": "object" - }, - "LlmfitStatusRead": { - "properties": { - "error": { - "title": "Error", - "type": "string" - }, - "fits": { - "items": { - "$ref": "#/components/schemas/ModelFitRead" - }, - "title": "Fits", - "type": "array" - }, - "hardware": { - "additionalProperties": true, - "title": "Hardware", - "type": "object" - }, - "install_hint": { - "title": "Install Hint", - "type": "string" + "responses": { + "204": { + "description": "Successful Response" }, - "installed": { - "title": "Installed", - "type": "boolean" + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } } }, - "required": [ - "installed", - "install_hint", - "hardware", - "fits", - "error" - ], - "title": "LlmfitStatusRead", - "type": "object" - }, - "LocalAIRead": { - "description": "Local-model readiness \u2014 deployed (Ollama) and deployable (llmfit).", - "properties": { - "llmfit": { - "$ref": "#/components/schemas/LlmfitStatusRead" - }, - "ollama": { - "$ref": "#/components/schemas/OllamaStatusRead" + "security": [ + { + "OAuth2PasswordBearer": [] } - }, - "required": [ - "ollama", - "llmfit" + ] + } + }, + "/api/settings/venue-credentials/{venue}": { + "put": { + "tags": [ + "settings" ], - "title": "LocalAIRead", - "type": "object" - }, - "ManualOrderRequest": { - "properties": { - "market": { - "default": "spot", - "title": "Market", - "type": "string" - }, - "quantity": { - "anyOf": [ - { - "exclusiveMinimum": 0.0, - "type": "number" - }, - { - "pattern": "^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$", - "type": "string" - } - ], - "title": "Quantity" - }, - "side": { - "$ref": "#/components/schemas/FillSide" - }, - "symbol": { - "maxLength": 24, - "minLength": 3, - "title": "Symbol", - "type": "string" + "summary": "Update Venue Credentials", + "description": "Store a venue's credentials (encrypted at rest).\n\nThe submitted field names must exactly match the venue's declared\n`credential_fields`, and every value must be non-empty.", + "operationId": "update_venue_credentials_api_settings_venue_credentials__venue__put", + "security": [ + { + "OAuth2PasswordBearer": [] } - }, - "required": [ - "symbol", - "side", - "quantity" ], - "title": "ManualOrderRequest", - "type": "object" - }, - "Market": { - "enum": [ - "spot", - "futures" + "parameters": [ + { + "name": "venue", + "in": "path", + "required": true, + "schema": { + "type": "string", + "title": "Venue" + } + } ], - "title": "Market", - "type": "string" - }, - "MarketAnalysis": { - "description": "One AI pass over a market \u2014 estimated probability vs market price.", - "properties": { - "condition_id": { - "maxLength": 80, - "title": "Condition Id", - "type": "string" - }, - "confidence": { - "default": "0", - "pattern": "^(?!^[-+.]*$)[+-]?0*(?:\\d{0,16}|(?=[\\d.]{1,25}0*$)\\d{0,16}\\.\\d{0,8}0*$)", - "title": "Confidence", - "type": "string" - }, - "created_at": { - "format": "date-time", - "title": "Created At", - "type": "string" - }, - "edge": { - "default": "0", - "pattern": "^(?!^[-+.]*$)[+-]?0*(?:\\d{0,16}|(?=[\\d.]{1,25}0*$)\\d{0,16}\\.\\d{0,8}0*$)", - "title": "Edge", - "type": "string" - }, - "est_probability": { - "default": "0", - "pattern": "^(?!^[-+.]*$)[+-]?0*(?:\\d{0,16}|(?=[\\d.]{1,25}0*$)\\d{0,16}\\.\\d{0,8}0*$)", - "title": "Est Probability", - "type": "string" - }, - "id": { - "anyOf": [ - { - "type": "integer" - }, - { - "type": "null" + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/VenueCredentialsUpdate" } - ], - "title": "Id" - }, - "market_price": { - "default": "0", - "pattern": "^(?!^[-+.]*$)[+-]?0*(?:\\d{0,16}|(?=[\\d.]{1,25}0*$)\\d{0,16}\\.\\d{0,8}0*$)", - "title": "Market Price", - "type": "string" - }, - "model": { - "default": "", - "maxLength": 64, - "title": "Model", - "type": "string" - }, - "provider": { - "default": "", - "maxLength": 16, - "title": "Provider", - "type": "string" - }, - "question": { - "maxLength": 512, - "title": "Question", - "type": "string" - }, - "reasoning": { - "default": "", - "title": "Reasoning", - "type": "string" - }, - "recommendation": { - "default": "hold", - "maxLength": 8, - "title": "Recommendation", - "type": "string" + } } }, - "required": [ - "condition_id", - "question", - "created_at" - ], - "title": "MarketAnalysis", - "type": "object" - }, - "ModeUpdate": { - "properties": { - "confirm": { - "default": false, - "title": "Confirm", - "type": "boolean" + "responses": { + "204": { + "description": "Successful Response" }, - "mode": { - "$ref": "#/components/schemas/TradingMode" + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } } - }, - "required": [ - "mode" + } + } + }, + "/api/settings/llm-credentials/{provider}": { + "put": { + "tags": [ + "settings" ], - "title": "ModeUpdate", - "type": "object" - }, - "ModelFitRead": { - "properties": { - "est_speed": { - "title": "Est Speed", - "type": "string" - }, - "fit": { - "title": "Fit", - "type": "string" - }, - "memory_gb": { - "title": "Memory Gb", - "type": "string" - }, - "model": { - "title": "Model", - "type": "string" - }, - "quantization": { - "title": "Quantization", - "type": "string" + "summary": "Update Llm Credentials", + "description": "Store one LLM provider's API key and/or base URL (encrypted at rest).", + "operationId": "update_llm_credentials_api_settings_llm_credentials__provider__put", + "security": [ + { + "OAuth2PasswordBearer": [] } - }, - "required": [ - "model", - "quantization", - "fit", - "est_speed", - "memory_gb" ], - "title": "ModelFitRead", - "type": "object" - }, - "ModelUsage": { - "description": "Per-model rollup of LLM activity \u2014 'what each model did'.", - "properties": { - "buys": { - "title": "Buys", - "type": "integer" - }, - "decisions": { - "title": "Decisions", - "type": "integer" - }, - "holds": { - "title": "Holds", - "type": "integer" - }, - "model": { - "title": "Model", - "type": "string" - }, - "provider": { - "title": "Provider", - "type": "string" - }, - "sells": { - "title": "Sells", - "type": "integer" - }, - "total_cost_usd": { - "pattern": "^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$", - "title": "Total Cost Usd", - "type": "string" + "parameters": [ + { + "name": "provider", + "in": "path", + "required": true, + "schema": { + "type": "string", + "title": "Provider" + } } - }, - "required": [ - "provider", - "model", - "decisions", - "buys", - "sells", - "holds", - "total_cost_usd" ], - "title": "ModelUsage", - "type": "object" - }, - "NewsItem": { - "description": "A single news headline pulled from an RSS feed.", - "properties": { - "category": { - "default": "world", - "maxLength": 16, - "title": "Category", - "type": "string" - }, - "fetched_at": { - "format": "date-time", - "title": "Fetched At", - "type": "string" - }, - "id": { - "anyOf": [ - { - "type": "integer" - }, - { - "type": "null" - } - ], - "title": "Id" - }, - "published_at": { - "anyOf": [ - { - "format": "date-time", - "type": "string" - }, - { - "type": "null" + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/LlmCredentialsUpdate" } - ], - "title": "Published At" - }, - "sentiment": { - "anyOf": [ - { - "maxLength": 16, - "type": "string" - }, - { - "type": "null" + } + } + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SettingsRead" + } } - ], - "title": "Sentiment" - }, - "source": { - "maxLength": 64, - "title": "Source", - "type": "string" - }, - "summary": { - "default": "", - "title": "Summary", - "type": "string" + } }, - "symbol": { - "anyOf": [ - { - "maxLength": 24, - "type": "string" - }, - { - "type": "null" + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } } - ], - "title": "Symbol" - }, - "title": { - "maxLength": 512, - "title": "Title", - "type": "string" - }, - "url": { - "maxLength": 1024, - "title": "Url", - "type": "string" + } } - }, - "required": [ - "source", - "title", - "url", - "fetched_at" + } + } + }, + "/api/settings/ai-spend-cap": { + "put": { + "tags": [ + "settings" ], - "title": "NewsItem", - "type": "object" - }, - "NodeCreate": { - "properties": { - "icon": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" + "summary": "Update Ai Spend Cap", + "description": "Set the daily LLM spend cap \u2014 AI strategies pause once it is reached.", + "operationId": "update_ai_spend_cap_api_settings_ai_spend_cap_put", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AiSpendCapUpdate" } - ], - "title": "Icon" - }, - "kind": { - "default": "concept", - "title": "Kind", - "type": "string" + } }, - "label": { - "maxLength": 120, - "minLength": 1, - "title": "Label", - "type": "string" + "required": true + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SettingsRead" + } + } + } }, - "symbol": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } } - ], - "title": "Symbol" + } } }, - "required": [ - "label" + "security": [ + { + "OAuth2PasswordBearer": [] + } + ] + } + }, + "/api/settings/ai": { + "put": { + "tags": [ + "settings" ], - "title": "NodeCreate", - "type": "object" - }, - "OllamaModelRead": { - "properties": { - "name": { - "title": "Name", - "type": "string" - }, - "parameter_size": { - "title": "Parameter Size", - "type": "string" + "summary": "Update Ai Settings", + "description": "Configure the AI provider. The API key is encrypted at rest.", + "operationId": "update_ai_settings_api_settings_ai_put", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AiSettingsUpdate" + } + } }, - "quantization": { - "title": "Quantization", - "type": "string" + "required": true + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SettingsRead" + } + } + } }, - "size_bytes": { - "title": "Size Bytes", - "type": "integer" + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } } }, - "required": [ - "name", - "size_bytes", - "parameter_size", - "quantization" + "security": [ + { + "OAuth2PasswordBearer": [] + } + ] + } + }, + "/api/settings/research": { + "put": { + "tags": [ + "settings" ], - "title": "OllamaModelRead", - "type": "object" - }, - "OllamaStatusRead": { - "properties": { - "base_url": { - "title": "Base Url", - "type": "string" - }, - "models": { - "items": { - "$ref": "#/components/schemas/OllamaModelRead" - }, - "title": "Models", - "type": "array" + "summary": "Update Research Settings", + "description": "Configure research reports and the news refresh interval.\n\nScheduler-interval changes take effect on the next engine restart.", + "operationId": "update_research_settings_api_settings_research_put", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ResearchSettingsUpdate" + } + } }, - "reachable": { - "title": "Reachable", - "type": "boolean" + "required": true + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SettingsRead" + } + } + } }, - "version": { - "title": "Version", - "type": "string" + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } } }, - "required": [ - "reachable", - "base_url", - "version", - "models" + "security": [ + { + "OAuth2PasswordBearer": [] + } + ] + } + }, + "/api/settings/polymarket": { + "put": { + "tags": [ + "settings" ], - "title": "OllamaStatusRead", - "type": "object" - }, - "OrderBook": { - "properties": { - "asks": { - "items": { - "$ref": "#/components/schemas/OrderBookLevel" - }, - "title": "Asks", - "type": "array" + "summary": "Update Polymarket Settings", + "description": "Configure Polymarket discovery and AI bet screening.\n\nScheduler-interval changes take effect on the next engine restart.", + "operationId": "update_polymarket_settings_api_settings_polymarket_put", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PolymarketSettingsUpdate" + } + } }, - "bids": { - "items": { - "$ref": "#/components/schemas/OrderBookLevel" - }, - "title": "Bids", - "type": "array" + "required": true + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SettingsRead" + } + } + } }, - "symbol": { - "title": "Symbol", - "type": "string" + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } } }, - "required": [ - "symbol", - "bids", - "asks" + "security": [ + { + "OAuth2PasswordBearer": [] + } + ] + } + }, + "/api/settings/risk": { + "put": { + "tags": [ + "settings" ], - "title": "OrderBook", - "type": "object" - }, - "OrderBookLevel": { - "properties": { - "price": { - "pattern": "^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$", - "title": "Price", - "type": "string" + "summary": "Update Risk Settings", + "description": "Set the global risk limits (0 disables a limit).\n\nChanges apply on the next engine tick \u2014 a stop-loss you enable may\nimmediately close an already-losing position.", + "operationId": "update_risk_settings_api_settings_risk_put", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RiskSettingsUpdate" + } + } }, - "qty": { - "pattern": "^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$", - "title": "Qty", - "type": "string" + "required": true + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SettingsRead" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } } }, - "required": [ - "price", - "qty" + "security": [ + { + "OAuth2PasswordBearer": [] + } + ] + } + }, + "/api/settings/council": { + "put": { + "tags": [ + "settings" ], - "title": "OrderBookLevel", - "type": "object" - }, - "ParamSpecRead": { - "properties": { - "default": { - "title": "Default", - "type": "string" - }, - "label": { - "title": "Label", - "type": "string" - }, - "max": { - "title": "Max", - "type": "string" - }, - "min": { - "title": "Min", - "type": "string" + "summary": "Update Council Settings", + "description": "Configure the AI council members and quorum.", + "operationId": "update_council_settings_api_settings_council_put", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CouncilSettingsUpdate" + } + } }, - "name": { - "title": "Name", - "type": "string" + "required": true + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SettingsRead" + } + } + } }, - "type": { - "title": "Type", - "type": "string" + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } } }, - "required": [ - "name", - "type", - "default", - "min", - "max", - "label" - ], - "title": "ParamSpecRead", - "type": "object" - }, - "PasswordReset": { - "properties": { - "password": { - "maxLength": 128, - "minLength": 8, - "title": "Password", - "type": "string" + "security": [ + { + "OAuth2PasswordBearer": [] } - }, - "required": [ - "password" + ] + } + }, + "/api/settings/ai-action-mode": { + "put": { + "tags": [ + "settings" ], - "title": "PasswordReset", - "type": "object" - }, - "PolymarketSettingsUpdate": { - "description": "Polymarket configuration \u2014 discovery/screening cadence and bars.", - "properties": { - "edge_threshold": { - "anyOf": [ - { - "maximum": 1.0, - "minimum": 0.0, - "type": "number" - }, - { - "pattern": "^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$", - "type": "string" + "summary": "Update Ai Action Mode", + "description": "Set how AI decisions are applied: `notify` (confirm) or `auto`.", + "operationId": "update_ai_action_mode_api_settings_ai_action_mode_put", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AiActionModeUpdate" } - ], - "default": "0.05", - "title": "Edge Threshold" + } }, - "min_confidence": { - "anyOf": [ - { - "maximum": 1.0, - "minimum": 0.0, - "type": "number" - }, - { - "pattern": "^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$", - "type": "string" + "required": true + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SettingsRead" + } } - ], - "default": "0.6", - "title": "Min Confidence" - }, - "refresh_hours": { - "default": 6, - "maximum": 48.0, - "minimum": 1.0, - "title": "Refresh Hours", - "type": "integer" - }, - "research_hours": { - "default": 6, - "maximum": 168.0, - "minimum": 1.0, - "title": "Research Hours", - "type": "integer" - }, - "screen_top": { - "default": 10, - "maximum": 50.0, - "minimum": 0.0, - "title": "Screen Top", - "type": "integer" + } }, - "stake": { - "anyOf": [ - { - "exclusiveMinimum": 0.0, - "type": "number" - }, - { - "pattern": "^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$", - "type": "string" + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } } - ], - "default": "100", - "title": "Stake" + } } }, - "title": "PolymarketSettingsUpdate", - "type": "object" - }, - "PortfolioSummary": { - "properties": { - "deployed_capital": { - "pattern": "^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$", - "title": "Deployed Capital", - "type": "string" - }, - "equity": { - "pattern": "^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$", - "title": "Equity", - "type": "string" - }, - "idle_capital": { - "pattern": "^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$", - "title": "Idle Capital", - "type": "string" - }, - "net_pnl": { - "pattern": "^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$", - "title": "Net Pnl", - "type": "string" - }, - "open_positions": { - "title": "Open Positions", - "type": "integer" - }, - "realized_pnl": { - "pattern": "^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$", - "title": "Realized Pnl", - "type": "string" - }, - "strategies": { - "items": { - "$ref": "#/components/schemas/StrategySummary" - }, - "title": "Strategies", - "type": "array" - }, - "total_allocated": { - "pattern": "^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$", - "title": "Total Allocated", - "type": "string" - }, - "total_fees": { - "pattern": "^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$", - "title": "Total Fees", - "type": "string" - }, - "unrealized_pnl": { - "pattern": "^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$", - "title": "Unrealized Pnl", - "type": "string" + "security": [ + { + "OAuth2PasswordBearer": [] + } + ] + } + }, + "/api/system/watchdog": { + "get": { + "tags": [ + "system" + ], + "summary": "Get Watchdog", + "description": "Engine liveness \u2014 heartbeat age and whether an alert is warranted.", + "operationId": "get_watchdog_api_system_watchdog_get", + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/WatchdogStatus" + } + } + } } }, - "required": [ - "total_allocated", - "realized_pnl", - "unrealized_pnl", - "total_fees", - "net_pnl", - "equity", - "deployed_capital", - "idle_capital", - "open_positions", - "strategies" + "security": [ + { + "OAuth2PasswordBearer": [] + } + ] + } + }, + "/api/history/trades": { + "get": { + "tags": [ + "history" ], - "title": "PortfolioSummary", - "type": "object" - }, - "Position": { - "description": "One strategy's position in one symbol/market \u2014 the attribution row.\n\n`qty` is always >= 0; `side` gives the direction. `realized_pnl` and\n`fees_paid` accumulate over the position's lifetime (the row is kept after\na position goes flat so cumulative PnL is preserved).", - "properties": { - "entry_price": { - "default": "0", - "pattern": "^(?!^[-+.]*$)[+-]?0*(?:\\d{0,18}|(?=[\\d.]{1,29}0*$)\\d{0,18}\\.\\d{0,10}0*$)", - "title": "Entry Price", - "type": "string" + "summary": "Trades", + "description": "The transaction log, newest-first, optionally filtered by date range.", + "operationId": "trades_api_history_trades_get", + "security": [ + { + "OAuth2PasswordBearer": [] + } + ], + "parameters": [ + { + "name": "start", + "in": "query", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string", + "format": "date-time" + }, + { + "type": "null" + } + ], + "title": "Start" + } }, - "fees_paid": { - "default": "0", - "pattern": "^(?!^[-+.]*$)[+-]?0*(?:\\d{0,18}|(?=[\\d.]{1,29}0*$)\\d{0,18}\\.\\d{0,10}0*$)", - "title": "Fees Paid", - "type": "string" + { + "name": "end", + "in": "query", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string", + "format": "date-time" + }, + { + "type": "null" + } + ], + "title": "End" + } }, - "id": { - "anyOf": [ - { - "type": "integer" - }, - { - "type": "null" + { + "name": "limit", + "in": "query", + "required": false, + "schema": { + "type": "integer", + "default": 500, + "title": "Limit" + } + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Trade" + }, + "title": "Response Trades Api History Trades Get" + } } - ], - "title": "Id" - }, - "market": { - "maxLength": 8, - "title": "Market", - "type": "string" + } }, - "opened_at": { - "anyOf": [ - { - "format": "date-time", - "type": "string" - }, - { - "type": "null" + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } } - ], - "title": "Opened At" - }, - "qty": { - "default": "0", - "pattern": "^(?!^[-+.]*$)[+-]?0*(?:\\d{0,18}|(?=[\\d.]{1,29}0*$)\\d{0,18}\\.\\d{0,10}0*$)", - "title": "Qty", - "type": "string" - }, - "realized_pnl": { - "default": "0", - "pattern": "^(?!^[-+.]*$)[+-]?0*(?:\\d{0,18}|(?=[\\d.]{1,29}0*$)\\d{0,18}\\.\\d{0,10}0*$)", - "title": "Realized Pnl", - "type": "string" - }, - "side": { - "default": "flat", - "maxLength": 8, - "title": "Side", - "type": "string" - }, - "strategy": { - "maxLength": 64, - "title": "Strategy", - "type": "string" - }, - "symbol": { - "maxLength": 80, - "title": "Symbol", - "type": "string" + } + } + } + } + }, + "/api/history/audit": { + "get": { + "tags": [ + "history" + ], + "summary": "Audit", + "description": "The audit log of config-changing actions, newest-first.", + "operationId": "audit_api_history_audit_get", + "security": [ + { + "OAuth2PasswordBearer": [] + } + ], + "parameters": [ + { + "name": "limit", + "in": "query", + "required": false, + "schema": { + "type": "integer", + "default": 200, + "title": "Limit" + } + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/AuditLog" + }, + "title": "Response Audit Api History Audit Get" + } + } + } }, - "updated_at": { - "anyOf": [ - { - "format": "date-time", - "type": "string" - }, - { - "type": "null" + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } } - ], - "title": "Updated At" + } } - }, - "required": [ - "strategy", - "market", - "symbol" + } + } + }, + "/api/history/trades.csv": { + "get": { + "tags": [ + "history" ], - "title": "Position", - "type": "object" - }, - "PredictionMarket": { - "description": "One Polymarket market, upserted from the Gamma API by `condition_id`.", - "properties": { - "category": { - "default": "", - "maxLength": 64, - "title": "Category", - "type": "string" - }, - "condition_id": { - "maxLength": 80, - "title": "Condition Id", - "type": "string" + "summary": "Trades Csv", + "description": "The transaction log as a CSV download (oldest-first) for tax/reporting.", + "operationId": "trades_csv_api_history_trades_csv_get", + "security": [ + { + "OAuth2PasswordBearer": [] + } + ], + "parameters": [ + { + "name": "start", + "in": "query", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string", + "format": "date-time" + }, + { + "type": "null" + } + ], + "title": "Start" + } }, - "end_date": { - "anyOf": [ - { - "format": "date-time", - "type": "string" - }, - { - "type": "null" + { + "name": "end", + "in": "query", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string", + "format": "date-time" + }, + { + "type": "null" + } + ], + "title": "End" + } + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": {} } - ], - "title": "End Date" - }, - "fetched_at": { - "format": "date-time", - "title": "Fetched At", - "type": "string" + } }, - "id": { - "anyOf": [ - { - "type": "integer" - }, - { - "type": "null" + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } } - ], - "title": "Id" - }, - "liquidity": { - "default": "0", - "pattern": "^(?!^[-+.]*$)[+-]?0*(?:\\d{0,16}|(?=[\\d.]{1,25}0*$)\\d{0,16}\\.\\d{0,8}0*$)", - "title": "Liquidity", - "type": "string" - }, - "no_token_id": { - "default": "", - "maxLength": 80, - "title": "No Token Id", - "type": "string" - }, - "outcomes": { - "default": "[]", - "title": "Outcomes", - "type": "string" - }, - "question": { - "maxLength": 512, - "title": "Question", - "type": "string" - }, - "resolved_outcome": { - "default": "", - "maxLength": 64, - "title": "Resolved Outcome", - "type": "string" - }, - "slug": { - "default": "", - "maxLength": 256, - "title": "Slug", - "type": "string" - }, - "status": { - "default": "active", - "maxLength": 12, - "title": "Status", - "type": "string" - }, - "volume_24h": { - "default": "0", - "pattern": "^(?!^[-+.]*$)[+-]?0*(?:\\d{0,16}|(?=[\\d.]{1,25}0*$)\\d{0,16}\\.\\d{0,8}0*$)", - "title": "Volume 24H", - "type": "string" - }, - "watched": { - "default": false, - "title": "Watched", - "type": "boolean" - }, - "yes_price": { - "default": "0", - "pattern": "^(?!^[-+.]*$)[+-]?0*(?:\\d{0,16}|(?=[\\d.]{1,25}0*$)\\d{0,16}\\.\\d{0,8}0*$)", - "title": "Yes Price", - "type": "string" - }, - "yes_token_id": { - "default": "", - "maxLength": 80, - "title": "Yes Token Id", - "type": "string" + } } - }, - "required": [ - "condition_id", - "question", - "fetched_at" + } + } + }, + "/api/ai/analyze": { + "post": { + "tags": [ + "ai" ], - "title": "PredictionMarket", - "type": "object" - }, - "RecommendRequest": { - "properties": { - "capital": { - "anyOf": [ - { - "exclusiveMinimum": 0.0, - "type": "number" - }, - { - "pattern": "^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$", - "type": "string" + "summary": "Analyze And Decide", + "description": "Run a free-form analyze-and-decide task through the configured LLM.", + "operationId": "analyze_and_decide_api_ai_analyze_post", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AnalyzeRequest" } - ], - "default": "10000", - "title": "Capital" + } }, - "days": { - "default": 90, - "maximum": 365.0, - "minimum": 7.0, - "title": "Days", - "type": "integer" - }, - "symbol": { - "maxLength": 24, - "minLength": 1, - "title": "Symbol", - "type": "string" - }, - "timeframe": { - "default": "1h", - "maxLength": 8, - "title": "Timeframe", - "type": "string" - } + "required": true }, - "required": [ - "symbol" - ], - "title": "RecommendRequest", - "type": "object" - }, - "RecommendResponse": { - "description": "The AI's pick, backtested so it ranks alongside the grid cells.", - "properties": { - "cell": { - "$ref": "#/components/schemas/CompareCellRead" - }, - "params": { - "additionalProperties": true, - "title": "Params", - "type": "object" - }, - "reasoning": { - "title": "Reasoning", - "type": "string" + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Decision" + } + } + } }, - "strategy_type": { - "title": "Strategy Type", - "type": "string" + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } } }, - "required": [ - "strategy_type", - "params", - "reasoning", - "cell" + "security": [ + { + "OAuth2PasswordBearer": [] + } + ] + } + }, + "/api/ai/models": { + "get": { + "tags": [ + "ai" ], - "title": "RecommendResponse", - "type": "object" - }, - "RefreshRequest": { - "properties": { - "refresh_token": { - "title": "Refresh Token", - "type": "string" + "summary": "Model Performance", + "description": "Per-model rollup \u2014 decisions, action mix and total cost for each model.", + "operationId": "model_performance_api_ai_models_get", + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "items": { + "$ref": "#/components/schemas/ModelUsage" + }, + "type": "array", + "title": "Response Model Performance Api Ai Models Get" + } + } + } } }, - "required": [ - "refresh_token" + "security": [ + { + "OAuth2PasswordBearer": [] + } + ] + } + }, + "/api/ai/decisions": { + "get": { + "tags": [ + "ai" ], - "title": "RefreshRequest", - "type": "object" - }, - "ResearchReportRead": { - "description": "One report \u2014 `sections` is the parsed schema-formatted content.", - "properties": { - "created_at": { - "format": "date-time", - "title": "Created At", - "type": "string" - }, - "error": { - "title": "Error", - "type": "string" - }, - "id": { - "title": "Id", - "type": "integer" - }, - "model": { - "title": "Model", - "type": "string" - }, - "provider": { - "title": "Provider", - "type": "string" - }, - "schema_version": { - "title": "Schema Version", - "type": "integer" - }, - "sections": { - "additionalProperties": true, - "title": "Sections", - "type": "object" - }, - "status": { - "title": "Status", - "type": "string" - }, - "symbol": { - "title": "Symbol", - "type": "string" + "summary": "Decision Log", + "description": "The recent decision log \u2014 what each model decided, newest first.", + "operationId": "decision_log_api_ai_decisions_get", + "security": [ + { + "OAuth2PasswordBearer": [] } - }, - "required": [ - "id", - "symbol", - "schema_version", - "status", - "provider", - "model", - "error", - "created_at", - "sections" ], - "title": "ResearchReportRead", - "type": "object" - }, - "ResearchRun": { - "description": "Manual trigger \u2014 one symbol, or blank for every watched symbol.", - "properties": { - "symbol": { - "default": "", - "maxLength": 24, - "title": "Symbol", - "type": "string" + "parameters": [ + { + "name": "limit", + "in": "query", + "required": false, + "schema": { + "type": "integer", + "default": 50, + "title": "Limit" + } } - }, - "title": "ResearchRun", - "type": "object" - }, - "ResearchSettingsUpdate": { - "description": "Research configuration \u2014 watched symbols, interval and writer LLM.", - "properties": { - "interval_hours": { - "default": 12, - "maximum": 168.0, - "minimum": 1.0, - "title": "Interval Hours", - "type": "integer" - }, - "news_interval_hours": { - "anyOf": [ - { - "maximum": 48.0, - "minimum": 1.0, - "type": "integer" - }, - { - "type": "null" + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/LLMUsage" + }, + "title": "Response Decision Log Api Ai Decisions Get" + } } - ], - "title": "News Interval Hours" - }, - "symbols": { - "items": { - "type": "string" - }, - "maxItems": 20, - "minItems": 1, - "title": "Symbols", - "type": "array" - }, - "writer_model": { - "default": "", - "maxLength": 64, - "title": "Writer Model", - "type": "string" + } }, - "writer_provider": { - "maxLength": 32, - "minLength": 1, - "title": "Writer Provider", - "type": "string" + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } } - }, - "required": [ - "symbols", - "writer_provider" + } + } + }, + "/api/ai/signals": { + "get": { + "tags": [ + "ai" ], - "title": "ResearchSettingsUpdate", - "type": "object" - }, - "Role": { - "description": "Operator role. `admin` has full access; `user` is restricted\n(see plan: Authentication & Roles).", - "enum": [ - "admin", - "user" + "summary": "List Signals", + "description": "Recent AI signals (notify-mode decisions), newest first.", + "operationId": "list_signals_api_ai_signals_get", + "security": [ + { + "OAuth2PasswordBearer": [] + } ], - "title": "Role", - "type": "string" - }, - "SettingsRead": { - "properties": { - "ai_action_mode": { - "title": "Ai Action Mode", - "type": "string" - }, - "ai_base_url": { - "title": "Ai Base Url", - "type": "string" - }, - "ai_key_configured": { - "title": "Ai Key Configured", - "type": "boolean" - }, - "ai_model": { - "title": "Ai Model", - "type": "string" - }, - "ai_provider": { - "title": "Ai Provider", - "type": "string" + "parameters": [ + { + "name": "status_filter", + "in": "query", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Status Filter" + } }, - "ai_spend_cap": { - "pattern": "^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$", - "title": "Ai Spend Cap", - "type": "string" - }, - "ai_spend_today": { - "pattern": "^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$", - "title": "Ai Spend Today", - "type": "string" - }, - "binance_keys_configured": { - "title": "Binance Keys Configured", - "type": "boolean" - }, - "council_members": { - "items": { - "additionalProperties": { - "type": "string" - }, - "type": "object" - }, - "title": "Council Members", - "type": "array" - }, - "council_quorum": { - "pattern": "^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$", - "title": "Council Quorum", - "type": "string" - }, - "llm_providers_configured": { - "additionalProperties": { - "type": "boolean" - }, - "title": "Llm Providers Configured", - "type": "object" - }, - "mode": { - "$ref": "#/components/schemas/TradingMode" - }, - "news_interval_hours": { - "anyOf": [ - { - "type": "integer" - }, - { - "type": "null" + { + "name": "limit", + "in": "query", + "required": false, + "schema": { + "type": "integer", + "default": 50, + "title": "Limit" + } + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/AISignal" + }, + "title": "Response List Signals Api Ai Signals Get" + } } - ], - "title": "News Interval Hours" - }, - "polymarket_edge_threshold": { - "pattern": "^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$", - "title": "Polymarket Edge Threshold", - "type": "string" - }, - "polymarket_min_confidence": { - "pattern": "^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$", - "title": "Polymarket Min Confidence", - "type": "string" - }, - "polymarket_refresh_hours": { - "title": "Polymarket Refresh Hours", - "type": "integer" - }, - "polymarket_research_hours": { - "title": "Polymarket Research Hours", - "type": "integer" - }, - "polymarket_screen_top": { - "title": "Polymarket Screen Top", - "type": "integer" - }, - "polymarket_stake": { - "pattern": "^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$", - "title": "Polymarket Stake", - "type": "string" - }, - "research_interval_hours": { - "title": "Research Interval Hours", - "type": "integer" + } }, - "research_symbols": { - "items": { - "type": "string" - }, - "title": "Research Symbols", - "type": "array" + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/api/ai/signals/{signal_id}/confirm": { + "post": { + "tags": [ + "ai" + ], + "summary": "Confirm Signal", + "description": "Execute a pending AI signal through the same risk + executor path.\n\nRe-prices at confirmation and re-runs the risk gate, so a stale or\nrisk-blocked signal cannot slip through. Marks the signal `executed`.", + "operationId": "confirm_signal_api_ai_signals__signal_id__confirm_post", + "security": [ + { + "OAuth2PasswordBearer": [] + } + ], + "parameters": [ + { + "name": "signal_id", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "title": "Signal Id" + } + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AISignal" + } + } + } }, - "research_writer_model": { - "title": "Research Writer Model", - "type": "string" + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/api/ai/signals/{signal_id}/dismiss": { + "post": { + "tags": [ + "ai" + ], + "summary": "Dismiss Signal", + "description": "Dismiss a pending AI signal without executing it.", + "operationId": "dismiss_signal_api_ai_signals__signal_id__dismiss_post", + "security": [ + { + "OAuth2PasswordBearer": [] + } + ], + "parameters": [ + { + "name": "signal_id", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "title": "Signal Id" + } + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AISignal" + } + } + } }, - "research_writer_provider": { - "title": "Research Writer Provider", - "type": "string" + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/api/ai/local": { + "get": { + "tags": [ + "ai" + ], + "summary": "Local Models", + "description": "Whether local models are deployed (Ollama) or deployable (llmfit).\n\nThe snapshot is cached for ~5 minutes; `refresh=true` re-probes now.", + "operationId": "local_models_api_ai_local_get", + "security": [ + { + "OAuth2PasswordBearer": [] + } + ], + "parameters": [ + { + "name": "refresh", + "in": "query", + "required": false, + "schema": { + "type": "boolean", + "default": false, + "title": "Refresh" + } + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/LocalAIRead" + } + } + } }, - "venue_credentials_configured": { - "additionalProperties": { - "type": "boolean" - }, - "title": "Venue Credentials Configured", - "type": "object" + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } } - }, - "required": [ - "mode", - "binance_keys_configured", - "venue_credentials_configured", - "ai_provider", - "ai_model", - "ai_base_url", - "ai_key_configured", - "ai_spend_cap", - "ai_spend_today", - "ai_action_mode", - "llm_providers_configured", - "research_symbols", - "research_interval_hours", - "research_writer_provider", - "research_writer_model", - "news_interval_hours", - "council_members", - "council_quorum", - "polymarket_refresh_hours", - "polymarket_research_hours", - "polymarket_edge_threshold", - "polymarket_min_confidence", - "polymarket_screen_top", - "polymarket_stake" + } + } + }, + "/api/tokens": { + "get": { + "tags": [ + "tokens" ], - "title": "SettingsRead", - "type": "object" + "summary": "List Tokens", + "description": "Every API token, newest-first. The secret is never returned.", + "operationId": "list_tokens_api_tokens_get", + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "items": { + "$ref": "#/components/schemas/ApiTokenRead" + }, + "type": "array", + "title": "Response List Tokens Api Tokens Get" + } + } + } + } + }, + "security": [ + { + "OAuth2PasswordBearer": [] + } + ] }, - "StrategyRead": { - "description": "A strategy's identity, lifecycle state and accounting summary.", - "properties": { - "ai_model": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" + "post": { + "tags": [ + "tokens" + ], + "summary": "Create Token", + "description": "Create a token. The plaintext is in the response and shown only here.", + "operationId": "create_token_api_tokens_post", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiTokenCreate" } - ], - "title": "Ai Model" + } }, - "ai_provider": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" + "required": true + }, + "responses": { + "201": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiTokenCreated" + } } - ], - "title": "Ai Provider" - }, - "allocated": { - "pattern": "^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$", - "title": "Allocated", - "type": "string" - }, - "enabled": { - "title": "Enabled", - "type": "boolean" - }, - "fees": { - "pattern": "^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$", - "title": "Fees", - "type": "string" - }, - "is_instance": { - "default": false, - "title": "Is Instance", - "type": "boolean" - }, - "kind": { - "title": "Kind", - "type": "string" - }, - "market": { - "title": "Market", - "type": "string" - }, - "max_loss": { - "pattern": "^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$", - "title": "Max Loss", - "type": "string" - }, - "name": { - "title": "Name", - "type": "string" - }, - "net_pnl": { - "pattern": "^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$", - "title": "Net Pnl", - "type": "string" - }, - "open_positions": { - "title": "Open Positions", - "type": "integer" - }, - "realized_pnl": { - "pattern": "^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$", - "title": "Realized Pnl", - "type": "string" - }, - "symbol": { - "title": "Symbol", - "type": "string" - }, - "timeframe": { - "title": "Timeframe", - "type": "string" - }, - "unrealized_pnl": { - "pattern": "^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$", - "title": "Unrealized Pnl", - "type": "string" + } }, - "venue": { - "title": "Venue", - "type": "string" + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } } }, - "required": [ - "name", - "kind", - "symbol", - "venue", - "market", - "timeframe", - "enabled", - "allocated", - "max_loss", - "realized_pnl", - "unrealized_pnl", - "fees", - "net_pnl", - "open_positions" + "security": [ + { + "OAuth2PasswordBearer": [] + } + ] + } + }, + "/api/tokens/{token_id}": { + "delete": { + "tags": [ + "tokens" ], - "title": "StrategyRead", - "type": "object" - }, - "StrategySummary": { - "properties": { - "allocated": { - "pattern": "^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$", - "title": "Allocated", - "type": "string" - }, - "fees": { - "pattern": "^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$", - "title": "Fees", - "type": "string" - }, - "net_pnl": { - "pattern": "^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$", - "title": "Net Pnl", - "type": "string" - }, - "open_positions": { - "title": "Open Positions", - "type": "integer" - }, - "realized_pnl": { - "pattern": "^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$", - "title": "Realized Pnl", - "type": "string" - }, - "strategy": { - "title": "Strategy", - "type": "string" + "summary": "Revoke Token", + "description": "Revoke a token by id \u2014 it can no longer authenticate.", + "operationId": "revoke_token_api_tokens__token_id__delete", + "security": [ + { + "OAuth2PasswordBearer": [] + } + ], + "parameters": [ + { + "name": "token_id", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "title": "Token Id" + } + } + ], + "responses": { + "204": { + "description": "Successful Response" }, - "unrealized_pnl": { - "pattern": "^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$", - "title": "Unrealized Pnl", - "type": "string" + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/api/venues": { + "get": { + "tags": [ + "venues" + ], + "summary": "Get Venues", + "description": "Every supported venue, with the active one flagged.", + "operationId": "get_venues_api_venues_get", + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "items": { + "$ref": "#/components/schemas/VenueRead" + }, + "type": "array", + "title": "Response Get Venues Api Venues Get" + } + } + } } }, - "required": [ - "strategy", - "allocated", - "realized_pnl", - "unrealized_pnl", - "fees", - "net_pnl", - "open_positions" + "security": [ + { + "OAuth2PasswordBearer": [] + } + ] + } + }, + "/api/venues/active": { + "put": { + "tags": [ + "venues" ], - "title": "StrategySummary", - "type": "object" - }, - "StrategyTypeRead": { - "description": "One pickable strategy type and its parameter schema.", - "properties": { - "key": { - "title": "Key", - "type": "string" - }, - "label": { - "title": "Label", - "type": "string" - }, - "params": { - "items": { - "$ref": "#/components/schemas/ParamSpecRead" - }, - "title": "Params", - "type": "array" + "summary": "Set Active", + "description": "Change the active trading venue.", + "operationId": "set_active_api_venues_active_put", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ActiveVenueUpdate" + } + } }, - "timeframes": { - "items": { - "type": "string" - }, - "title": "Timeframes", - "type": "array" + "required": true + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "items": { + "$ref": "#/components/schemas/VenueRead" + }, + "type": "array", + "title": "Response Set Active Api Venues Active Put" + } + } + } }, - "venue": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } } - ], - "title": "Venue" + } } }, - "required": [ - "key", - "label", - "params", - "timeframes" + "security": [ + { + "OAuth2PasswordBearer": [] + } + ] + } + }, + "/api/news": { + "get": { + "tags": [ + "news" ], - "title": "StrategyTypeRead", - "type": "object" - }, - "Ticker": { - "properties": { - "change_pct_24h": { - "pattern": "^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$", - "title": "Change Pct 24H", - "type": "string" + "summary": "List News", + "description": "Recent headlines, newest first \u2014 filterable by `symbol` and `category`.", + "operationId": "list_news_api_news_get", + "security": [ + { + "OAuth2PasswordBearer": [] + } + ], + "parameters": [ + { + "name": "symbol", + "in": "query", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Symbol" + } }, - "price": { - "pattern": "^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$", - "title": "Price", - "type": "string" + { + "name": "category", + "in": "query", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Category" + } }, - "quote_volume_24h": { - "pattern": "^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$", - "title": "Quote Volume 24H", - "type": "string" + { + "name": "limit", + "in": "query", + "required": false, + "schema": { + "type": "integer", + "default": 50, + "title": "Limit" + } + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NewsItem" + }, + "title": "Response List News Api News Get" + } + } + } }, - "symbol": { - "title": "Symbol", - "type": "string" + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/api/news/refresh": { + "post": { + "tags": [ + "news" + ], + "summary": "Refresh News", + "description": "Fetch every configured feed now. Returns the number of new headlines.", + "operationId": "refresh_news_api_news_refresh_post", + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "additionalProperties": { + "type": "integer" + }, + "type": "object", + "title": "Response Refresh News Api News Refresh Post" + } + } + } } }, - "required": [ - "symbol", - "price", - "change_pct_24h", - "quote_volume_24h" + "security": [ + { + "OAuth2PasswordBearer": [] + } + ] + } + }, + "/api/polymarket/markets": { + "get": { + "tags": [ + "polymarket" ], - "title": "Ticker", - "type": "object" - }, - "TokenPair": { - "properties": { - "access_token": { - "title": "Access Token", - "type": "string" + "summary": "List Markets", + "description": "Discovered markets, most-traded first \u2014 filterable and searchable.", + "operationId": "list_markets_api_polymarket_markets_get", + "security": [ + { + "OAuth2PasswordBearer": [] + } + ], + "parameters": [ + { + "name": "watched", + "in": "query", + "required": false, + "schema": { + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "null" + } + ], + "title": "Watched" + } + }, + { + "name": "status_filter", + "in": "query", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": "active", + "title": "Status Filter" + } }, - "refresh_token": { - "title": "Refresh Token", - "type": "string" + { + "name": "category", + "in": "query", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Category" + } }, - "token_type": { - "default": "bearer", - "title": "Token Type", - "type": "string" + { + "name": "search", + "in": "query", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Search" + } + }, + { + "name": "limit", + "in": "query", + "required": false, + "schema": { + "type": "integer", + "default": 100, + "title": "Limit" + } } - }, - "required": [ - "access_token", - "refresh_token" ], - "title": "TokenPair", - "type": "object" - }, - "Trade": { - "description": "An executed fill \u2014 the transaction-log row, attributed to a strategy.\n\n`realized_pnl` is the PnL booked *by this fill* (0 for opening/adding fills,\nnon-zero when it reduces or closes a position).", - "properties": { - "client_order_id": { - "anyOf": [ - { - "maxLength": 40, - "type": "string" - }, - { - "type": "null" + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/PredictionMarket" + }, + "title": "Response List Markets Api Polymarket Markets Get" + } } - ], - "title": "Client Order Id" - }, - "executed_at": { - "format": "date-time", - "title": "Executed At", - "type": "string" - }, - "fee": { - "default": "0", - "pattern": "^(?!^[-+.]*$)[+-]?0*(?:\\d{0,18}|(?=[\\d.]{1,29}0*$)\\d{0,18}\\.\\d{0,10}0*$)", - "title": "Fee", - "type": "string" + } }, - "id": { - "anyOf": [ - { - "type": "integer" - }, - { - "type": "null" + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } } - ], - "title": "Id" - }, - "market": { - "maxLength": 8, - "title": "Market", - "type": "string" - }, - "mode": { - "default": "sim", - "maxLength": 8, - "title": "Mode", - "type": "string" - }, - "price": { - "pattern": "^(?!^[-+.]*$)[+-]?0*(?:\\d{0,18}|(?=[\\d.]{1,29}0*$)\\d{0,18}\\.\\d{0,10}0*$)", - "title": "Price", - "type": "string" - }, - "quantity": { - "pattern": "^(?!^[-+.]*$)[+-]?0*(?:\\d{0,18}|(?=[\\d.]{1,29}0*$)\\d{0,18}\\.\\d{0,10}0*$)", - "title": "Quantity", - "type": "string" - }, - "realized_pnl": { - "default": "0", - "pattern": "^(?!^[-+.]*$)[+-]?0*(?:\\d{0,18}|(?=[\\d.]{1,29}0*$)\\d{0,18}\\.\\d{0,10}0*$)", - "title": "Realized Pnl", - "type": "string" - }, - "side": { - "maxLength": 8, - "title": "Side", - "type": "string" - }, - "strategy": { - "maxLength": 64, - "title": "Strategy", - "type": "string" - }, - "symbol": { - "maxLength": 80, - "title": "Symbol", - "type": "string" + } } - }, - "required": [ - "strategy", - "market", - "symbol", - "side", - "quantity", - "price", - "executed_at" - ], - "title": "Trade", - "type": "object" - }, - "TradingMode": { - "description": "Where orders are routed. Sim is the safe default.", - "enum": [ - "sim", - "testnet", - "live" + } + } + }, + "/api/polymarket/markets/{condition_id}/watch": { + "put": { + "tags": [ + "polymarket" ], - "title": "TradingMode", - "type": "string" - }, - "UserCreate": { - "properties": { - "password": { - "maxLength": 128, - "minLength": 8, - "title": "Password", - "type": "string" - }, - "role": { - "$ref": "#/components/schemas/Role", - "default": "user" - }, - "username": { - "maxLength": 64, - "minLength": 2, - "title": "Username", - "type": "string" + "summary": "Update Watched", + "description": "Pin or unpin a market for scheduled AI analysis.", + "operationId": "update_watched_api_polymarket_markets__condition_id__watch_put", + "security": [ + { + "OAuth2PasswordBearer": [] } - }, - "required": [ - "username", - "password" ], - "title": "UserCreate", - "type": "object" - }, - "UserInfo": { - "properties": { - "role": { - "title": "Role", - "type": "string" - }, - "username": { - "title": "Username", - "type": "string" + "parameters": [ + { + "name": "condition_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "title": "Condition Id" + } } - }, - "required": [ - "username", - "role" ], - "title": "UserInfo", - "type": "object" - }, - "UserRead": { - "properties": { - "id": { - "title": "Id", - "type": "integer" - }, - "is_active": { - "title": "Is Active", - "type": "boolean" - }, - "role": { - "title": "Role", - "type": "string" - }, - "username": { - "title": "Username", - "type": "string" + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/WatchUpdate" + } + } } }, - "required": [ - "id", - "username", - "role", - "is_active" - ], - "title": "UserRead", - "type": "object" - }, - "UserUpdate": { - "properties": { - "is_active": { - "anyOf": [ - { - "type": "boolean" - }, - { - "type": "null" + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PredictionMarket" + } } - ], - "title": "Is Active" + } }, - "role": { - "anyOf": [ - { - "$ref": "#/components/schemas/Role" - }, - { - "type": "null" + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/api/polymarket/refresh": { + "post": { + "tags": [ + "polymarket" + ], + "summary": "Refresh Markets", + "description": "Pull the latest top markets from the Gamma API now.", + "operationId": "refresh_markets_api_polymarket_refresh_post", + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "additionalProperties": { + "type": "integer" + }, + "type": "object", + "title": "Response Refresh Markets Api Polymarket Refresh Post" + } } - ] + } } }, - "title": "UserUpdate", - "type": "object" - }, - "ValidationError": { - "properties": { - "ctx": { - "title": "Context", - "type": "object" - }, - "input": { - "title": "Input" - }, - "loc": { - "items": { + "security": [ + { + "OAuth2PasswordBearer": [] + } + ] + } + }, + "/api/polymarket/analyses": { + "get": { + "tags": [ + "polymarket" + ], + "summary": "List Analyses", + "description": "Recent AI bet analyses, newest first \u2014 optionally for one market.", + "operationId": "list_analyses_api_polymarket_analyses_get", + "security": [ + { + "OAuth2PasswordBearer": [] + } + ], + "parameters": [ + { + "name": "condition_id", + "in": "query", + "required": false, + "schema": { "anyOf": [ { "type": "string" }, { - "type": "integer" + "type": "null" } - ] - }, - "title": "Location", - "type": "array" - }, - "msg": { - "title": "Message", - "type": "string" + ], + "title": "Condition Id" + } }, - "type": { - "title": "Error Type", - "type": "string" + { + "name": "limit", + "in": "query", + "required": false, + "schema": { + "type": "integer", + "default": 50, + "title": "Limit" + } } - }, - "required": [ - "loc", - "msg", - "type" ], - "title": "ValidationError", - "type": "object" - }, - "VenueCredentialsUpdate": { - "description": "A venue's credential fields \u2014 names validated against the catalogue.", - "properties": { - "fields": { - "additionalProperties": { - "type": "string" - }, - "minProperties": 1, - "title": "Fields", - "type": "object" + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/MarketAnalysis" + }, + "title": "Response List Analyses Api Polymarket Analyses Get" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } } - }, - "required": [ - "fields" + } + } + }, + "/api/polymarket/markets/{condition_id}/analyze": { + "post": { + "tags": [ + "polymarket" ], - "title": "VenueCredentialsUpdate", - "type": "object" - }, - "VenueRead": { - "properties": { - "active": { - "title": "Active", - "type": "boolean" - }, - "asset_class": { - "title": "Asset Class", - "type": "string" - }, - "credential_fields": { - "items": { - "type": "string" - }, - "title": "Credential Fields", - "type": "array" - }, - "name": { - "title": "Name", - "type": "string" - }, - "supports_sandbox": { - "title": "Supports Sandbox", - "type": "boolean" + "summary": "Analyze Now", + "description": "Run one AI analysis for a market immediately (a paid LLM call).", + "operationId": "analyze_now_api_polymarket_markets__condition_id__analyze_post", + "security": [ + { + "OAuth2PasswordBearer": [] } - }, - "required": [ - "name", - "asset_class", - "supports_sandbox", - "active", - "credential_fields" ], - "title": "VenueRead", - "type": "object" - }, - "WatchUpdate": { - "properties": { - "watched": { - "title": "Watched", - "type": "boolean" + "parameters": [ + { + "name": "condition_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "title": "Condition Id" + } } - }, - "required": [ - "watched" ], - "title": "WatchUpdate", - "type": "object" - }, - "WatchdogStatus": { - "description": "Engine liveness assessed from the heartbeat and open-position count.", - "properties": { - "age_seconds": { - "anyOf": [ - { - "type": "number" - }, - { - "type": "null" + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/MarketAnalysis" + } } - ], - "title": "Age Seconds" - }, - "alert": { - "title": "Alert", - "type": "boolean" - }, - "alive": { - "title": "Alive", - "type": "boolean" + } }, - "last_beat": { - "anyOf": [ - { - "format": "date-time", - "type": "string" - }, - { - "type": "null" + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } } - ], - "title": "Last Beat" - }, - "open_positions": { - "title": "Open Positions", - "type": "integer" - }, - "stale": { - "title": "Stale", - "type": "boolean" + } } - }, - "required": [ - "alive", - "stale", - "alert", - "last_beat", - "age_seconds", - "open_positions" - ], - "title": "WatchdogStatus", - "type": "object" + } } }, - "securitySchemes": { - "OAuth2PasswordBearer": { - "flows": { - "password": { - "scopes": {}, - "tokenUrl": "/api/auth/login" + "/api/connections/graph": { + "get": { + "tags": [ + "connections" + ], + "summary": "Get Graph", + "description": "The full graph. Pending (AI-suggested) edges are opt-in.", + "operationId": "get_graph_api_connections_graph_get", + "security": [ + { + "OAuth2PasswordBearer": [] + } + ], + "parameters": [ + { + "name": "include_pending", + "in": "query", + "required": false, + "schema": { + "type": "boolean", + "default": false, + "title": "Include Pending" + } + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GraphView" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } } - }, - "type": "oauth2" + } } - } - }, - "info": { - "summary": "Self-hosted automated Binance trading engine.", - "title": "Capital Engine", - "version": "0.1.0" - }, - "openapi": "3.1.0", - "paths": { - "/api/ai/analyze": { + }, + "/api/connections/nodes": { "post": { - "description": "Run a free-form analyze-and-decide task through the configured LLM.", - "operationId": "analyze_and_decide_api_ai_analyze_post", + "tags": [ + "connections" + ], + "summary": "Create Node", + "description": "Create a node (or return the existing one with the same label).", + "operationId": "create_node_api_connections_nodes_post", "requestBody": { "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/AnalyzeRequest" + "$ref": "#/components/schemas/NodeCreate" } } }, @@ -3968,178 +3055,191 @@ }, "responses": { "200": { + "description": "Successful Response", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/Decision" + "$ref": "#/components/schemas/GraphNode" } } - }, - "description": "Successful Response" + } }, "422": { + "description": "Validation Error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/HTTPValidationError" } } - }, - "description": "Validation Error" + } } }, "security": [ { "OAuth2PasswordBearer": [] } - ], - "summary": "Analyze And Decide", - "tags": [ - "ai" ] } }, - "/api/ai/decisions": { - "get": { - "description": "The recent decision log \u2014 what each model decided, newest first.", - "operationId": "decision_log_api_ai_decisions_get", + "/api/connections/suggest/{node_id}": { + "post": { + "tags": [ + "connections" + ], + "summary": "Suggest Connections", + "description": "Ask the LLM to suggest related entities for a node (stored pending).", + "operationId": "suggest_connections_api_connections_suggest__node_id__post", + "security": [ + { + "OAuth2PasswordBearer": [] + } + ], "parameters": [ { - "in": "query", - "name": "limit", - "required": false, + "name": "node_id", + "in": "path", + "required": true, "schema": { - "default": 50, - "title": "Limit", - "type": "integer" + "type": "integer", + "title": "Node Id" } } ], "responses": { "200": { + "description": "Successful Response", "content": { "application/json": { "schema": { + "type": "array", "items": { - "$ref": "#/components/schemas/LLMUsage" + "$ref": "#/components/schemas/GraphEdge" }, - "title": "Response Decision Log Api Ai Decisions Get", - "type": "array" + "title": "Response Suggest Connections Api Connections Suggest Node Id Post" } } - }, - "description": "Successful Response" + } }, "422": { + "description": "Validation Error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/HTTPValidationError" } } - }, - "description": "Validation Error" + } } - }, + } + } + }, + "/api/connections/edges/{edge_id}/approve": { + "post": { + "tags": [ + "connections" + ], + "summary": "Approve Edge", + "description": "Approve a pending edge so it joins the graph.", + "operationId": "approve_edge_api_connections_edges__edge_id__approve_post", "security": [ { "OAuth2PasswordBearer": [] } ], - "summary": "Decision Log", - "tags": [ - "ai" - ] - } - }, - "/api/ai/local": { - "get": { - "description": "Whether local models are deployed (Ollama) or deployable (llmfit).\n\nThe snapshot is cached for ~5 minutes; `refresh=true` re-probes now.", - "operationId": "local_models_api_ai_local_get", "parameters": [ { - "in": "query", - "name": "refresh", - "required": false, + "name": "edge_id", + "in": "path", + "required": true, "schema": { - "default": false, - "title": "Refresh", - "type": "boolean" + "type": "integer", + "title": "Edge Id" } } ], "responses": { "200": { + "description": "Successful Response", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/LocalAIRead" + "$ref": "#/components/schemas/GraphEdge" } } - }, - "description": "Successful Response" + } }, "422": { + "description": "Validation Error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/HTTPValidationError" } } - }, - "description": "Validation Error" + } } - }, + } + } + }, + "/api/connections/edges/{edge_id}": { + "delete": { + "tags": [ + "connections" + ], + "summary": "Delete Edge", + "description": "Delete an edge (reject a suggestion or prune a curated link).", + "operationId": "delete_edge_api_connections_edges__edge_id__delete", "security": [ { "OAuth2PasswordBearer": [] } ], - "summary": "Local Models", - "tags": [ - "ai" - ] - } - }, - "/api/ai/models": { - "get": { - "description": "Per-model rollup \u2014 decisions, action mix and total cost for each model.", - "operationId": "model_performance_api_ai_models_get", + "parameters": [ + { + "name": "edge_id", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "title": "Edge Id" + } + } + ], "responses": { - "200": { + "204": { + "description": "Successful Response" + }, + "422": { + "description": "Validation Error", "content": { "application/json": { "schema": { - "items": { - "$ref": "#/components/schemas/ModelUsage" - }, - "title": "Response Model Performance Api Ai Models Get", - "type": "array" + "$ref": "#/components/schemas/HTTPValidationError" } } - }, - "description": "Successful Response" + } } - }, + } + } + }, + "/api/research": { + "get": { + "tags": [ + "research" + ], + "summary": "List Reports", + "description": "Recent reports, newest first \u2014 optionally filtered by `symbol`.", + "operationId": "list_reports_api_research_get", "security": [ { "OAuth2PasswordBearer": [] } ], - "summary": "Model Performance", - "tags": [ - "ai" - ] - } - }, - "/api/ai/signals": { - "get": { - "description": "Recent AI signals (notify-mode decisions), newest first.", - "operationId": "list_signals_api_ai_signals_get", "parameters": [ { + "name": "symbol", "in": "query", - "name": "status_filter", "required": false, "schema": { "anyOf": [ @@ -4150,162 +3250,254 @@ "type": "null" } ], - "title": "Status Filter" + "title": "Symbol" } }, { - "in": "query", "name": "limit", + "in": "query", "required": false, "schema": { + "type": "integer", "default": 50, - "title": "Limit", - "type": "integer" + "title": "Limit" + } + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ResearchReportRead" + }, + "title": "Response List Reports Api Research Get" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/api/research/{report_id}": { + "get": { + "tags": [ + "research" + ], + "summary": "Get Report", + "description": "One report with its full sections.", + "operationId": "get_report_api_research__report_id__get", + "security": [ + { + "OAuth2PasswordBearer": [] + } + ], + "parameters": [ + { + "name": "report_id", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "title": "Report Id" + } + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ResearchReportRead" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } } } + } + } + }, + "/api/research/run": { + "post": { + "tags": [ + "research" ], + "summary": "Run Research", + "description": "Write a report now \u2014 for `symbol`, or every watched symbol when blank.", + "operationId": "run_research_api_research_run_post", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ResearchRun" + } + } + }, + "required": true + }, "responses": { "200": { + "description": "Successful Response", "content": { "application/json": { "schema": { "items": { - "$ref": "#/components/schemas/AISignal" + "$ref": "#/components/schemas/ResearchReportRead" }, - "title": "Response List Signals Api Ai Signals Get", - "type": "array" + "type": "array", + "title": "Response Run Research Api Research Run Post" } } - }, - "description": "Successful Response" + } }, "422": { + "description": "Validation Error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/HTTPValidationError" } } - }, - "description": "Validation Error" + } } }, "security": [ { "OAuth2PasswordBearer": [] } - ], - "summary": "List Signals", - "tags": [ - "ai" ] } }, - "/api/ai/signals/{signal_id}/confirm": { - "post": { - "description": "Execute a pending AI signal through the same risk + executor path.\n\nRe-prices at confirmation and re-runs the risk gate, so a stale or\nrisk-blocked signal cannot slip through. Marks the signal `executed`.", - "operationId": "confirm_signal_api_ai_signals__signal_id__confirm_post", + "/api/research/{report_id}/review": { + "get": { + "tags": [ + "research" + ], + "summary": "Get Review", + "description": "The newest council review for a report, with every vote.", + "operationId": "get_review_api_research__report_id__review_get", + "security": [ + { + "OAuth2PasswordBearer": [] + } + ], "parameters": [ { + "name": "report_id", "in": "path", - "name": "signal_id", "required": true, "schema": { - "title": "Signal Id", - "type": "integer" + "type": "integer", + "title": "Report Id" } } ], "responses": { "200": { + "description": "Successful Response", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/AISignal" + "$ref": "#/components/schemas/CouncilReviewRead" } } - }, - "description": "Successful Response" + } }, "422": { + "description": "Validation Error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/HTTPValidationError" } } - }, - "description": "Validation Error" + } } - }, + } + }, + "post": { + "tags": [ + "research" + ], + "summary": "Rerun Review", + "description": "Re-run the council on a report now (admin).", + "operationId": "rerun_review_api_research__report_id__review_post", "security": [ { "OAuth2PasswordBearer": [] } ], - "summary": "Confirm Signal", - "tags": [ - "ai" - ] - } - }, - "/api/ai/signals/{signal_id}/dismiss": { - "post": { - "description": "Dismiss a pending AI signal without executing it.", - "operationId": "dismiss_signal_api_ai_signals__signal_id__dismiss_post", "parameters": [ { + "name": "report_id", "in": "path", - "name": "signal_id", "required": true, "schema": { - "title": "Signal Id", - "type": "integer" + "type": "integer", + "title": "Report Id" } } ], "responses": { "200": { + "description": "Successful Response", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/AISignal" + "$ref": "#/components/schemas/CouncilReviewRead" } } - }, - "description": "Successful Response" + } }, "422": { + "description": "Validation Error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/HTTPValidationError" } } - }, - "description": "Validation Error" - } - }, - "security": [ - { - "OAuth2PasswordBearer": [] + } } - ], - "summary": "Dismiss Signal", - "tags": [ - "ai" - ] + } } }, - "/api/auth/login": { + "/api/lab/compare": { "post": { - "description": "Exchange operator credentials for an access + refresh token pair.\n\nRate-limited: repeated failures lock the username out (brute-force guard).", - "operationId": "login_api_auth_login_post", + "tags": [ + "lab" + ], + "summary": "Compare Grid", + "description": "Backtest every (type \u00d7 symbol) cell over the same recent range.", + "operationId": "compare_grid_api_lab_compare_post", "requestBody": { "content": { - "application/x-www-form-urlencoded": { + "application/json": { "schema": { - "$ref": "#/components/schemas/Body_login_api_auth_login_post" + "$ref": "#/components/schemas/CompareRequest" } } }, @@ -4313,110 +3505,50 @@ }, "responses": { "200": { + "description": "Successful Response", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/TokenPair" + "items": { + "$ref": "#/components/schemas/CompareCellRead" + }, + "type": "array", + "title": "Response Compare Grid Api Lab Compare Post" } } - }, - "description": "Successful Response" + } }, "422": { + "description": "Validation Error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/HTTPValidationError" } } - }, - "description": "Validation Error" - } - }, - "summary": "Login", - "tags": [ - "auth" - ] - } - }, - "/api/auth/me": { - "get": { - "description": "Return the currently authenticated operator.", - "operationId": "me_api_auth_me_get", - "responses": { - "200": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/UserInfo" - } - } - }, - "description": "Successful Response" + } } }, "security": [ { "OAuth2PasswordBearer": [] } - ], - "summary": "Me", - "tags": [ - "auth" ] } }, - "/api/auth/refresh": { + "/api/lab/recommend": { "post": { - "description": "Issue a fresh token pair from a valid refresh token.", - "operationId": "refresh_api_auth_refresh_post", - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/RefreshRequest" - } - } - }, - "required": true - }, - "responses": { - "200": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/TokenPair" - } - } - }, - "description": "Successful Response" - }, - "422": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - "description": "Validation Error" - } - }, - "summary": "Refresh", "tags": [ - "auth" - ] - } - }, - "/api/backtest/run": { - "post": { - "description": "Replay a strategy over the requested date range and return the result.", - "operationId": "run_api_backtest_run_post", + "lab" + ], + "summary": "Recommend", + "description": "Ask the configured LLM to pick a strategy for the coin, then backtest it.", + "operationId": "recommend_api_lab_recommend_post", "requestBody": { "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/BacktestRequest" + "$ref": "#/components/schemas/RecommendRequest" } } }, @@ -4424,3190 +3556,4217 @@ }, "responses": { "200": { + "description": "Successful Response", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/BacktestResponse" + "$ref": "#/components/schemas/RecommendResponse" } } - }, - "description": "Successful Response" + } }, "422": { + "description": "Validation Error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/HTTPValidationError" } } - }, - "description": "Validation Error" + } } }, "security": [ { "OAuth2PasswordBearer": [] } - ], - "summary": "Run", - "tags": [ - "backtest" ] } }, - "/api/connections/edges/{edge_id}": { - "delete": { - "description": "Delete an edge (reject a suggestion or prune a curated link).", - "operationId": "delete_edge_api_connections_edges__edge_id__delete", - "parameters": [ - { - "in": "path", - "name": "edge_id", - "required": true, - "schema": { - "title": "Edge Id", - "type": "integer" - } - } + "/api/costs/summary": { + "get": { + "tags": [ + "costs" ], + "summary": "Costs Summary", + "description": "Spend totals and breakdowns over the last 30 days.", + "operationId": "costs_summary_api_costs_summary_get", "responses": { - "204": { - "description": "Successful Response" - }, - "422": { + "200": { + "description": "Successful Response", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/HTTPValidationError" + "$ref": "#/components/schemas/CostsSummary" } } - }, - "description": "Validation Error" + } } }, "security": [ { "OAuth2PasswordBearer": [] } - ], - "summary": "Delete Edge", - "tags": [ - "connections" ] } }, - "/api/connections/edges/{edge_id}/approve": { - "post": { - "description": "Approve a pending edge so it joins the graph.", - "operationId": "approve_edge_api_connections_edges__edge_id__approve_post", - "parameters": [ - { - "in": "path", - "name": "edge_id", - "required": true, - "schema": { - "title": "Edge Id", - "type": "integer" - } - } - ], - "responses": { - "200": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/GraphEdge" - } - } - }, - "description": "Successful Response" - }, - "422": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - "description": "Validation Error" - } - }, + "/api/costs/ledger": { + "get": { + "tags": [ + "costs" + ], + "summary": "Costs Ledger", + "description": "The per-call ledger, newest first \u2014 filterable by purpose.", + "operationId": "costs_ledger_api_costs_ledger_get", "security": [ { "OAuth2PasswordBearer": [] } ], - "summary": "Approve Edge", - "tags": [ - "connections" - ] - } - }, - "/api/connections/graph": { - "get": { - "description": "The full graph. Pending (AI-suggested) edges are opt-in.", - "operationId": "get_graph_api_connections_graph_get", "parameters": [ { + "name": "limit", "in": "query", - "name": "include_pending", "required": false, "schema": { - "default": false, - "title": "Include Pending", - "type": "boolean" + "type": "integer", + "default": 50, + "title": "Limit" + } + }, + { + "name": "offset", + "in": "query", + "required": false, + "schema": { + "type": "integer", + "default": 0, + "title": "Offset" + } + }, + { + "name": "purpose", + "in": "query", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Purpose" } } ], "responses": { "200": { + "description": "Successful Response", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/GraphView" + "$ref": "#/components/schemas/LedgerPage" } } - }, - "description": "Successful Response" + } }, "422": { + "description": "Validation Error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/HTTPValidationError" } } - }, - "description": "Validation Error" - } - }, - "security": [ - { - "OAuth2PasswordBearer": [] + } } - ], - "summary": "Get Graph", - "tags": [ - "connections" - ] + } } }, - "/api/connections/nodes": { - "post": { - "description": "Create a node (or return the existing one with the same label).", - "operationId": "create_node_api_connections_nodes_post", - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/NodeCreate" - } - } - }, - "required": true - }, + "/health": { + "get": { + "tags": [ + "system" + ], + "summary": "Health", + "description": "Liveness probe \u2014 used by Docker, CI and the deploy script.", + "operationId": "health_health_get", "responses": { "200": { + "description": "Successful Response", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/GraphNode" + "additionalProperties": { + "type": "string" + }, + "type": "object", + "title": "Response Health Health Get" } } - }, - "description": "Successful Response" - }, - "422": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } + } + } + } + } + } + }, + "components": { + "schemas": { + "AISignal": { + "properties": { + "id": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" } - }, - "description": "Validation Error" + ], + "title": "Id" + }, + "strategy": { + "type": "string", + "maxLength": 64, + "title": "Strategy" + }, + "symbol": { + "type": "string", + "maxLength": 80, + "title": "Symbol" + }, + "venue": { + "type": "string", + "maxLength": 24, + "title": "Venue", + "default": "binance" + }, + "market": { + "type": "string", + "maxLength": 8, + "title": "Market", + "default": "spot" + }, + "action": { + "type": "string", + "maxLength": 8, + "title": "Action" + }, + "confidence": { + "type": "string", + "pattern": "^(?!^[-+.]*$)[+-]?0*(?:\\d{0,18}|(?=[\\d.]{1,29}0*$)\\d{0,18}\\.\\d{0,10}0*$)", + "title": "Confidence", + "default": "0" + }, + "reasoning": { + "type": "string", + "title": "Reasoning", + "default": "" + }, + "reference_price": { + "type": "string", + "pattern": "^(?!^[-+.]*$)[+-]?0*(?:\\d{0,16}|(?=[\\d.]{1,25}0*$)\\d{0,16}\\.\\d{0,8}0*$)", + "title": "Reference Price", + "default": "0" + }, + "quantity": { + "type": "string", + "pattern": "^(?!^[-+.]*$)[+-]?0*(?:\\d{0,16}|(?=[\\d.]{1,25}0*$)\\d{0,16}\\.\\d{0,8}0*$)", + "title": "Quantity", + "default": "0" + }, + "status": { + "type": "string", + "maxLength": 12, + "title": "Status", + "default": "pending" + }, + "created_at": { + "type": "string", + "format": "date-time", + "title": "Created At" } }, - "security": [ - { - "OAuth2PasswordBearer": [] + "type": "object", + "required": [ + "strategy", + "symbol", + "action", + "created_at" + ], + "title": "AISignal", + "description": "One AI decision surfaced to the operator for confirmation." + }, + "ActiveVenueUpdate": { + "properties": { + "venue": { + "type": "string", + "title": "Venue" } + }, + "type": "object", + "required": [ + "venue" ], - "summary": "Create Node", - "tags": [ - "connections" - ] - } - }, - "/api/connections/suggest/{node_id}": { - "post": { - "description": "Ask the LLM to suggest related entities for a node (stored pending).", - "operationId": "suggest_connections_api_connections_suggest__node_id__post", - "parameters": [ - { - "in": "path", - "name": "node_id", - "required": true, - "schema": { - "title": "Node Id", - "type": "integer" - } + "title": "ActiveVenueUpdate" + }, + "AiActionModeUpdate": { + "properties": { + "mode": { + "type": "string", + "title": "Mode", + "description": "notify | auto" + } + }, + "type": "object", + "required": [ + "mode" + ], + "title": "AiActionModeUpdate" + }, + "AiModelUpdate": { + "properties": { + "provider": { + "type": "string", + "maxLength": 16, + "minLength": 1, + "title": "Provider" + }, + "model": { + "type": "string", + "maxLength": 64, + "title": "Model", + "default": "" + } + }, + "type": "object", + "required": [ + "provider" + ], + "title": "AiModelUpdate" + }, + "AiSettingsUpdate": { + "properties": { + "provider": { + "type": "string", + "maxLength": 32, + "minLength": 1, + "title": "Provider" + }, + "model": { + "type": "string", + "maxLength": 64, + "title": "Model", + "default": "" + }, + "base_url": { + "type": "string", + "maxLength": 256, + "title": "Base Url", + "default": "" + }, + "api_key": { + "type": "string", + "maxLength": 256, + "title": "Api Key", + "default": "" + } + }, + "type": "object", + "required": [ + "provider" + ], + "title": "AiSettingsUpdate" + }, + "AiSpendCapUpdate": { + "properties": { + "cap": { + "anyOf": [ + { + "type": "number", + "minimum": 0.0 + }, + { + "type": "string", + "pattern": "^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$" + } + ], + "title": "Cap" } + }, + "type": "object", + "required": [ + "cap" ], - "responses": { - "200": { - "content": { - "application/json": { - "schema": { - "items": { - "$ref": "#/components/schemas/GraphEdge" - }, - "title": "Response Suggest Connections Api Connections Suggest Node Id Post", - "type": "array" - } + "title": "AiSpendCapUpdate" + }, + "AllocationUpdate": { + "properties": { + "allocated": { + "anyOf": [ + { + "type": "number", + "minimum": 0.0 + }, + { + "type": "string", + "pattern": "^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$" } - }, - "description": "Successful Response" + ], + "title": "Allocated" }, - "422": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } + "max_loss": { + "anyOf": [ + { + "type": "number", + "minimum": 0.0 + }, + { + "type": "string", + "pattern": "^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$" + }, + { + "type": "null" } - }, - "description": "Validation Error" + ], + "title": "Max Loss" } }, - "security": [ - { - "OAuth2PasswordBearer": [] + "type": "object", + "required": [ + "allocated" + ], + "title": "AllocationUpdate" + }, + "AnalyzeRequest": { + "properties": { + "task": { + "type": "string", + "maxLength": 4000, + "minLength": 1, + "title": "Task" } + }, + "type": "object", + "required": [ + "task" ], - "summary": "Suggest Connections", - "tags": [ - "connections" - ] - } - }, - "/api/costs/ledger": { - "get": { - "description": "The per-call ledger, newest first \u2014 filterable by purpose.", - "operationId": "costs_ledger_api_costs_ledger_get", - "parameters": [ - { - "in": "query", - "name": "limit", - "required": false, - "schema": { - "default": 50, - "title": "Limit", - "type": "integer" - } - }, - { - "in": "query", - "name": "offset", - "required": false, - "schema": { - "default": 0, - "title": "Offset", - "type": "integer" - } + "title": "AnalyzeRequest" + }, + "ApiTokenCreate": { + "properties": { + "name": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "title": "Name" }, - { - "in": "query", - "name": "purpose", - "required": false, - "schema": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ], - "title": "Purpose" - } + "role": { + "$ref": "#/components/schemas/Role", + "default": "user" } + }, + "type": "object", + "required": [ + "name" ], - "responses": { - "200": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/LedgerPage" - } - } - }, - "description": "Successful Response" + "title": "ApiTokenCreate" + }, + "ApiTokenCreated": { + "properties": { + "id": { + "type": "integer", + "title": "Id" }, - "422": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } + "name": { + "type": "string", + "title": "Name" + }, + "role": { + "type": "string", + "title": "Role" + }, + "revoked": { + "type": "boolean", + "title": "Revoked" + }, + "created_at": { + "type": "string", + "format": "date-time", + "title": "Created At" + }, + "last_used_at": { + "anyOf": [ + { + "type": "string", + "format": "date-time" + }, + { + "type": "null" } - }, - "description": "Validation Error" + ], + "title": "Last Used At" + }, + "token": { + "type": "string", + "title": "Token" } }, - "security": [ - { - "OAuth2PasswordBearer": [] - } + "type": "object", + "required": [ + "id", + "name", + "role", + "revoked", + "created_at", + "last_used_at", + "token" ], - "summary": "Costs Ledger", - "tags": [ - "costs" - ] - } - }, - "/api/costs/summary": { - "get": { - "description": "Spend totals and breakdowns over the last 30 days.", - "operationId": "costs_summary_api_costs_summary_get", - "responses": { - "200": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/CostsSummary" - } + "title": "ApiTokenCreated" + }, + "ApiTokenRead": { + "properties": { + "id": { + "type": "integer", + "title": "Id" + }, + "name": { + "type": "string", + "title": "Name" + }, + "role": { + "type": "string", + "title": "Role" + }, + "revoked": { + "type": "boolean", + "title": "Revoked" + }, + "created_at": { + "type": "string", + "format": "date-time", + "title": "Created At" + }, + "last_used_at": { + "anyOf": [ + { + "type": "string", + "format": "date-time" + }, + { + "type": "null" } - }, - "description": "Successful Response" + ], + "title": "Last Used At" } }, - "security": [ - { - "OAuth2PasswordBearer": [] - } - ], - "summary": "Costs Summary", - "tags": [ - "costs" - ] - } - }, - "/api/history/audit": { - "get": { - "description": "The audit log of config-changing actions, newest-first.", - "operationId": "audit_api_history_audit_get", - "parameters": [ - { - "in": "query", - "name": "limit", - "required": false, - "schema": { - "default": 200, - "title": "Limit", - "type": "integer" - } - } + "type": "object", + "required": [ + "id", + "name", + "role", + "revoked", + "created_at", + "last_used_at" ], - "responses": { - "200": { - "content": { - "application/json": { - "schema": { - "items": { - "$ref": "#/components/schemas/AuditLog" - }, - "title": "Response Audit Api History Audit Get", - "type": "array" - } + "title": "ApiTokenRead" + }, + "AuditLog": { + "properties": { + "id": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], + "title": "Id" + }, + "actor": { + "type": "string", + "maxLength": 64, + "title": "Actor" + }, + "action": { + "type": "string", + "maxLength": 64, + "title": "Action" + }, + "target": { + "anyOf": [ + { + "type": "string", + "maxLength": 128 + }, + { + "type": "null" } - }, - "description": "Successful Response" + ], + "title": "Target" }, - "422": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } + "detail": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" } - }, - "description": "Validation Error" + ], + "title": "Detail" + }, + "created_at": { + "type": "string", + "format": "date-time", + "title": "Created At" } }, - "security": [ - { - "OAuth2PasswordBearer": [] - } + "type": "object", + "required": [ + "actor", + "action" ], - "summary": "Audit", - "tags": [ - "history" - ] - } - }, - "/api/history/trades": { - "get": { - "description": "The transaction log, newest-first, optionally filtered by date range.", - "operationId": "trades_api_history_trades_get", - "parameters": [ - { - "in": "query", - "name": "start", - "required": false, - "schema": { - "anyOf": [ - { - "format": "date-time", - "type": "string" - }, - { - "type": "null" - } - ], - "title": "Start" - } + "title": "AuditLog", + "description": "Append-only record of every config-changing action \u2014 who, when, what.\n\nSurfaced read-only on the History page (Phase 6). Write endpoints record\nentries here via `auth.audit.record_audit`." + }, + "BacktestMetricsRead": { + "properties": { + "total_return_pct": { + "type": "string", + "pattern": "^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$", + "title": "Total Return Pct" }, - { - "in": "query", - "name": "end", - "required": false, - "schema": { - "anyOf": [ - { - "format": "date-time", - "type": "string" - }, - { - "type": "null" - } - ], - "title": "End" - } + "win_rate_pct": { + "type": "string", + "pattern": "^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$", + "title": "Win Rate Pct" }, - { - "in": "query", - "name": "limit", - "required": false, - "schema": { - "default": 500, - "title": "Limit", - "type": "integer" - } + "max_drawdown_pct": { + "type": "string", + "pattern": "^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$", + "title": "Max Drawdown Pct" + }, + "sharpe": { + "type": "string", + "pattern": "^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$", + "title": "Sharpe" + }, + "trades": { + "type": "integer", + "title": "Trades" + }, + "wins": { + "type": "integer", + "title": "Wins" + }, + "losses": { + "type": "integer", + "title": "Losses" } + }, + "type": "object", + "required": [ + "total_return_pct", + "win_rate_pct", + "max_drawdown_pct", + "sharpe", + "trades", + "wins", + "losses" ], - "responses": { - "200": { - "content": { - "application/json": { - "schema": { - "items": { - "$ref": "#/components/schemas/Trade" - }, - "title": "Response Trades Api History Trades Get", - "type": "array" - } + "title": "BacktestMetricsRead" + }, + "BacktestRequest": { + "properties": { + "strategy": { + "type": "string", + "title": "Strategy" + }, + "start": { + "type": "string", + "format": "date-time", + "title": "Start" + }, + "end": { + "anyOf": [ + { + "type": "string", + "format": "date-time" + }, + { + "type": "null" } - }, - "description": "Successful Response" + ], + "title": "End" }, - "422": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } + "initial_capital": { + "anyOf": [ + { + "type": "number", + "exclusiveMinimum": 0.0 + }, + { + "type": "string", + "pattern": "^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$" } - }, - "description": "Validation Error" + ], + "title": "Initial Capital", + "default": "10000" + }, + "slippage_bps": { + "anyOf": [ + { + "type": "number", + "minimum": 0.0 + }, + { + "type": "string", + "pattern": "^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$" + } + ], + "title": "Slippage Bps", + "default": "2" + }, + "fee_rate": { + "anyOf": [ + { + "type": "number", + "minimum": 0.0 + }, + { + "type": "string", + "pattern": "^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$" + } + ], + "title": "Fee Rate", + "default": "0.001" + }, + "funding_rate": { + "anyOf": [ + { + "type": "number", + "minimum": 0.0 + }, + { + "type": "string", + "pattern": "^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$" + } + ], + "title": "Funding Rate", + "default": "0" } }, - "security": [ - { - "OAuth2PasswordBearer": [] - } + "type": "object", + "required": [ + "strategy", + "start" ], - "summary": "Trades", - "tags": [ - "history" - ] - } - }, - "/api/history/trades.csv": { - "get": { - "description": "The transaction log as a CSV download (oldest-first) for tax/reporting.", - "operationId": "trades_csv_api_history_trades_csv_get", - "parameters": [ - { - "in": "query", - "name": "start", - "required": false, - "schema": { - "anyOf": [ - { - "format": "date-time", - "type": "string" - }, - { - "type": "null" - } - ], - "title": "Start" - } + "title": "BacktestRequest" + }, + "BacktestResponse": { + "properties": { + "strategy": { + "type": "string", + "title": "Strategy" + }, + "symbol": { + "type": "string", + "title": "Symbol" + }, + "interval": { + "type": "string", + "title": "Interval" + }, + "initial_capital": { + "type": "string", + "pattern": "^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$", + "title": "Initial Capital" + }, + "final_equity": { + "type": "string", + "pattern": "^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$", + "title": "Final Equity" + }, + "net_pnl": { + "type": "string", + "pattern": "^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$", + "title": "Net Pnl" + }, + "total_fees": { + "type": "string", + "pattern": "^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$", + "title": "Total Fees" + }, + "candles": { + "type": "integer", + "title": "Candles" + }, + "equity_curve": { + "items": { + "$ref": "#/components/schemas/EquityPoint" + }, + "type": "array", + "title": "Equity Curve" + }, + "trades": { + "items": { + "$ref": "#/components/schemas/BacktestTradeRead" + }, + "type": "array", + "title": "Trades" }, - { - "in": "query", - "name": "end", - "required": false, - "schema": { - "anyOf": [ - { - "format": "date-time", - "type": "string" - }, - { - "type": "null" - } - ], - "title": "End" - } + "metrics": { + "$ref": "#/components/schemas/BacktestMetricsRead" } + }, + "type": "object", + "required": [ + "strategy", + "symbol", + "interval", + "initial_capital", + "final_equity", + "net_pnl", + "total_fees", + "candles", + "equity_curve", + "trades", + "metrics" ], - "responses": { - "200": { - "content": { - "application/json": { - "schema": {} - } - }, - "description": "Successful Response" + "title": "BacktestResponse" + }, + "BacktestTradeRead": { + "properties": { + "time": { + "type": "string", + "format": "date-time", + "title": "Time" }, - "422": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - "description": "Validation Error" + "side": { + "type": "string", + "title": "Side" + }, + "quantity": { + "type": "string", + "pattern": "^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$", + "title": "Quantity" + }, + "price": { + "type": "string", + "pattern": "^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$", + "title": "Price" + }, + "fee": { + "type": "string", + "pattern": "^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$", + "title": "Fee" + }, + "realized_pnl": { + "type": "string", + "pattern": "^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$", + "title": "Realized Pnl" } }, - "security": [ - { - "OAuth2PasswordBearer": [] + "type": "object", + "required": [ + "time", + "side", + "quantity", + "price", + "fee", + "realized_pnl" + ], + "title": "BacktestTradeRead" + }, + "BinanceKeysUpdate": { + "properties": { + "api_key": { + "type": "string", + "maxLength": 256, + "minLength": 1, + "title": "Api Key" + }, + "api_secret": { + "type": "string", + "maxLength": 256, + "minLength": 1, + "title": "Api Secret" } + }, + "type": "object", + "required": [ + "api_key", + "api_secret" ], - "summary": "Trades Csv", - "tags": [ - "history" - ] - } - }, - "/api/lab/compare": { - "post": { - "description": "Backtest every (type \u00d7 symbol) cell over the same recent range.", - "operationId": "compare_grid_api_lab_compare_post", - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/CompareRequest" + "title": "BinanceKeysUpdate" + }, + "Body_login_api_auth_login_post": { + "properties": { + "grant_type": { + "anyOf": [ + { + "type": "string", + "pattern": "^password$" + }, + { + "type": "null" } - } + ], + "title": "Grant Type" }, - "required": true - }, - "responses": { - "200": { - "content": { - "application/json": { - "schema": { - "items": { - "$ref": "#/components/schemas/CompareCellRead" - }, - "title": "Response Compare Grid Api Lab Compare Post", - "type": "array" - } + "username": { + "type": "string", + "title": "Username" + }, + "password": { + "type": "string", + "format": "password", + "title": "Password" + }, + "scope": { + "type": "string", + "title": "Scope", + "default": "" + }, + "client_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" } - }, - "description": "Successful Response" + ], + "title": "Client Id" }, - "422": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } + "client_secret": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" } - }, - "description": "Validation Error" + ], + "format": "password", + "title": "Client Secret" } }, - "security": [ - { - "OAuth2PasswordBearer": [] - } + "type": "object", + "required": [ + "username", + "password" ], - "summary": "Compare Grid", - "tags": [ - "lab" - ] - } - }, - "/api/lab/recommend": { - "post": { - "description": "Ask the configured LLM to pick a strategy for the coin, then backtest it.", - "operationId": "recommend_api_lab_recommend_post", - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/RecommendRequest" + "title": "Body_login_api_auth_login_post" + }, + "Candle": { + "properties": { + "id": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" } - } + ], + "title": "Id" }, - "required": true - }, - "responses": { - "200": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/RecommendResponse" - } - } - }, - "description": "Successful Response" + "market": { + "type": "string", + "maxLength": 8, + "title": "Market" }, - "422": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - "description": "Validation Error" + "symbol": { + "type": "string", + "maxLength": 80, + "title": "Symbol" + }, + "interval": { + "type": "string", + "maxLength": 8, + "title": "Interval" + }, + "open_time": { + "type": "string", + "format": "date-time", + "title": "Open Time" + }, + "open": { + "type": "string", + "pattern": "^(?!^[-+.]*$)[+-]?0*(?:\\d{0,16}|(?=[\\d.]{1,25}0*$)\\d{0,16}\\.\\d{0,8}0*$)", + "title": "Open" + }, + "high": { + "type": "string", + "pattern": "^(?!^[-+.]*$)[+-]?0*(?:\\d{0,16}|(?=[\\d.]{1,25}0*$)\\d{0,16}\\.\\d{0,8}0*$)", + "title": "High" + }, + "low": { + "type": "string", + "pattern": "^(?!^[-+.]*$)[+-]?0*(?:\\d{0,16}|(?=[\\d.]{1,25}0*$)\\d{0,16}\\.\\d{0,8}0*$)", + "title": "Low" + }, + "close": { + "type": "string", + "pattern": "^(?!^[-+.]*$)[+-]?0*(?:\\d{0,16}|(?=[\\d.]{1,25}0*$)\\d{0,16}\\.\\d{0,8}0*$)", + "title": "Close" + }, + "volume": { + "type": "string", + "pattern": "^(?!^[-+.]*$)[+-]?0*(?:\\d{0,16}|(?=[\\d.]{1,25}0*$)\\d{0,16}\\.\\d{0,8}0*$)", + "title": "Volume" + }, + "close_time": { + "type": "string", + "format": "date-time", + "title": "Close Time" } }, - "security": [ - { - "OAuth2PasswordBearer": [] - } + "type": "object", + "required": [ + "market", + "symbol", + "interval", + "open_time", + "open", + "high", + "low", + "close", + "volume", + "close_time" ], - "summary": "Recommend", - "tags": [ - "lab" - ] - } - }, - "/api/market/funding": { - "get": { - "description": "Current futures funding rate for a symbol.", - "operationId": "get_funding_api_market_funding_get", - "parameters": [ - { - "in": "query", - "name": "symbol", - "required": true, - "schema": { - "title": "Symbol", - "type": "string" - } + "title": "Candle", + "description": "A single OHLCV bar for one symbol/interval/market." + }, + "CloseResult": { + "properties": { + "closed": { + "type": "integer", + "title": "Closed" } + }, + "type": "object", + "required": [ + "closed" ], - "responses": { - "200": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/FundingRate" - } - } - }, - "description": "Successful Response" + "title": "CloseResult" + }, + "CompareCellRead": { + "properties": { + "type": { + "type": "string", + "title": "Type" }, - "422": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } + "symbol": { + "type": "string", + "title": "Symbol" + }, + "params": { + "additionalProperties": true, + "type": "object", + "title": "Params" + }, + "return_pct": { + "type": "string", + "pattern": "^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$", + "title": "Return Pct" + }, + "max_drawdown_pct": { + "type": "string", + "pattern": "^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$", + "title": "Max Drawdown Pct" + }, + "sharpe": { + "type": "string", + "pattern": "^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$", + "title": "Sharpe" + }, + "win_rate_pct": { + "type": "string", + "pattern": "^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$", + "title": "Win Rate Pct" + }, + "trades": { + "type": "integer", + "title": "Trades" + }, + "net_pnl": { + "type": "string", + "pattern": "^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$", + "title": "Net Pnl" + }, + "final_equity": { + "type": "string", + "pattern": "^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$", + "title": "Final Equity" + }, + "equity_sparkline": { + "items": { + "type": "number" }, - "description": "Validation Error" + "type": "array", + "title": "Equity Sparkline" + }, + "error": { + "type": "string", + "title": "Error" } }, - "security": [ - { - "OAuth2PasswordBearer": [] - } + "type": "object", + "required": [ + "type", + "symbol", + "params", + "return_pct", + "max_drawdown_pct", + "sharpe", + "win_rate_pct", + "trades", + "net_pnl", + "final_equity", + "equity_sparkline", + "error" ], - "summary": "Get Funding", - "tags": [ - "market" - ] - } - }, - "/api/market/klines": { - "get": { - "description": "Candle history \u2014 served from the cache, refreshed from the active venue.", - "operationId": "get_klines_api_market_klines_get", - "parameters": [ - { - "in": "query", - "name": "symbol", - "required": true, - "schema": { - "maxLength": 24, - "minLength": 3, - "title": "Symbol", + "title": "CompareCellRead" + }, + "CompareRequest": { + "properties": { + "types": { + "items": { "type": "string" - } + }, + "type": "array", + "maxItems": 6, + "title": "Types" }, - { - "in": "query", - "name": "interval", - "required": false, - "schema": { - "default": "1h", - "title": "Interval", + "symbols": { + "items": { "type": "string" - } + }, + "type": "array", + "maxItems": 8, + "minItems": 1, + "title": "Symbols" }, - { - "in": "query", - "name": "market", - "required": false, - "schema": { - "$ref": "#/components/schemas/Market", - "default": "spot" - } + "timeframe": { + "type": "string", + "maxLength": 8, + "title": "Timeframe", + "default": "1h" }, - { - "in": "query", - "name": "limit", - "required": false, - "schema": { - "default": 200, - "maximum": 1000, - "minimum": 1, - "title": "Limit", - "type": "integer" - } - } - ], - "responses": { - "200": { - "content": { - "application/json": { - "schema": { - "items": { - "$ref": "#/components/schemas/Candle" - }, - "title": "Response Get Klines Api Market Klines Get", - "type": "array" - } - } - }, - "description": "Successful Response" + "days": { + "type": "integer", + "maximum": 365.0, + "minimum": 7.0, + "title": "Days", + "default": 90 }, - "422": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - "description": "Validation Error" - } - }, - "security": [ - { - "OAuth2PasswordBearer": [] - } - ], - "summary": "Get Klines", - "tags": [ - "market" - ] - } - }, - "/api/market/latency": { - "get": { - "description": "How delayed the price data is, per feed (ws event time + REST RTT).", - "operationId": "feed_latency_api_market_latency_get", - "responses": { - "200": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/LatencyRead" - } + "capital": { + "anyOf": [ + { + "type": "number", + "exclusiveMinimum": 0.0 + }, + { + "type": "string", + "pattern": "^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$" } - }, - "description": "Successful Response" + ], + "title": "Capital", + "default": "10000" } }, - "security": [ - { - "OAuth2PasswordBearer": [] - } + "type": "object", + "required": [ + "symbols" ], - "summary": "Feed Latency", - "tags": [ - "market" - ] - } - }, - "/api/market/orderbook": { - "get": { - "description": "Order-book depth for a symbol.", - "operationId": "get_orderbook_api_market_orderbook_get", - "parameters": [ - { - "in": "query", - "name": "symbol", - "required": true, - "schema": { - "title": "Symbol", - "type": "string" - } - }, - { - "in": "query", - "name": "market", - "required": false, - "schema": { - "$ref": "#/components/schemas/Market", - "default": "spot" - } + "title": "CompareRequest", + "description": "A grid request \u2014 either pivot is just a different shape of the same." + }, + "CostBucket": { + "properties": { + "key": { + "type": "string", + "title": "Key" }, - { - "in": "query", - "name": "limit", - "required": false, - "schema": { - "default": 20, - "maximum": 100, - "minimum": 5, - "title": "Limit", - "type": "integer" - } + "calls": { + "type": "integer", + "title": "Calls" + }, + "input_tokens": { + "type": "integer", + "title": "Input Tokens" + }, + "output_tokens": { + "type": "integer", + "title": "Output Tokens" + }, + "cost_usd": { + "type": "string", + "pattern": "^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$", + "title": "Cost Usd" } + }, + "type": "object", + "required": [ + "key", + "calls", + "input_tokens", + "output_tokens", + "cost_usd" ], - "responses": { - "200": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/OrderBook" - } - } + "title": "CostBucket", + "description": "One aggregation bucket \u2014 by model, purpose or day." + }, + "CostsRead": { + "properties": { + "total_fees": { + "type": "string", + "pattern": "^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$", + "title": "Total Fees" + }, + "fees_by_market": { + "additionalProperties": { + "type": "string", + "pattern": "^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$" }, - "description": "Successful Response" + "type": "object", + "title": "Fees By Market" }, - "422": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } + "traded_volume": { + "type": "string", + "pattern": "^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$", + "title": "Traded Volume" + }, + "fee_pct_of_volume": { + "type": "string", + "pattern": "^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$", + "title": "Fee Pct Of Volume" + }, + "venue_fee_rates": { + "additionalProperties": { + "type": "string", + "pattern": "^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$" }, - "description": "Validation Error" + "type": "object", + "title": "Venue Fee Rates" + }, + "llm_spend_today": { + "type": "string", + "pattern": "^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$", + "title": "Llm Spend Today" } }, - "security": [ - { - "OAuth2PasswordBearer": [] - } - ], - "summary": "Get Orderbook", - "tags": [ - "market" - ] - } - }, - "/api/market/tickers": { - "get": { - "description": "Latest 24h tickers \u2014 from the live snapshot, or REST as a fallback.", - "operationId": "list_tickers_api_market_tickers_get", - "parameters": [ - { - "in": "query", - "name": "market", - "required": false, - "schema": { - "$ref": "#/components/schemas/Market", - "default": "spot" - } - } + "type": "object", + "required": [ + "total_fees", + "fees_by_market", + "traded_volume", + "fee_pct_of_volume", + "venue_fee_rates", + "llm_spend_today" ], - "responses": { - "200": { - "content": { - "application/json": { - "schema": { - "items": { - "$ref": "#/components/schemas/Ticker" - }, - "title": "Response List Tickers Api Market Tickers Get", - "type": "array" - } - } + "title": "CostsRead", + "description": "Trading-cost breakdown, the fee-rate reference, and today's LLM spend." + }, + "CostsSummary": { + "properties": { + "today_usd": { + "type": "string", + "pattern": "^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$", + "title": "Today Usd" + }, + "last_7d_usd": { + "type": "string", + "pattern": "^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$", + "title": "Last 7D Usd" + }, + "last_30d_usd": { + "type": "string", + "pattern": "^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$", + "title": "Last 30D Usd" + }, + "daily_cap_usd": { + "type": "string", + "pattern": "^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$", + "title": "Daily Cap Usd" + }, + "by_model": { + "items": { + "$ref": "#/components/schemas/CostBucket" }, - "description": "Successful Response" + "type": "array", + "title": "By Model" }, - "422": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } + "by_purpose": { + "items": { + "$ref": "#/components/schemas/CostBucket" + }, + "type": "array", + "title": "By Purpose" + }, + "by_day": { + "items": { + "$ref": "#/components/schemas/CostBucket" }, - "description": "Validation Error" + "type": "array", + "title": "By Day" } }, - "security": [ - { - "OAuth2PasswordBearer": [] + "type": "object", + "required": [ + "today_usd", + "last_7d_usd", + "last_30d_usd", + "daily_cap_usd", + "by_model", + "by_purpose", + "by_day" + ], + "title": "CostsSummary", + "description": "Headline spend + the breakdowns the Costs screen renders." + }, + "CouncilMember": { + "properties": { + "provider": { + "type": "string", + "maxLength": 32, + "minLength": 1, + "title": "Provider" + }, + "model": { + "type": "string", + "maxLength": 64, + "title": "Model", + "default": "" } + }, + "type": "object", + "required": [ + "provider" ], - "summary": "List Tickers", - "tags": [ - "market" - ] - } - }, - "/api/news": { - "get": { - "description": "Recent headlines, newest first \u2014 filterable by `symbol` and `category`.", - "operationId": "list_news_api_news_get", - "parameters": [ - { - "in": "query", - "name": "symbol", - "required": false, - "schema": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ], - "title": "Symbol" - } + "title": "CouncilMember" + }, + "CouncilReviewRead": { + "properties": { + "id": { + "type": "integer", + "title": "Id" }, - { - "in": "query", - "name": "category", - "required": false, - "schema": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ], - "title": "Category" - } + "report_id": { + "type": "integer", + "title": "Report Id" }, - { - "in": "query", - "name": "limit", - "required": false, - "schema": { - "default": 50, - "title": "Limit", - "type": "integer" - } + "verdict": { + "type": "string", + "title": "Verdict" + }, + "weighted_score": { + "type": "string", + "pattern": "^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$", + "title": "Weighted Score" + }, + "quorum_met": { + "type": "boolean", + "title": "Quorum Met" + }, + "strategy_brief": { + "type": "string", + "title": "Strategy Brief" + }, + "created_at": { + "type": "string", + "format": "date-time", + "title": "Created At" + }, + "votes": { + "items": { + "$ref": "#/components/schemas/CouncilVoteRead" + }, + "type": "array", + "title": "Votes" } + }, + "type": "object", + "required": [ + "id", + "report_id", + "verdict", + "weighted_score", + "quorum_met", + "strategy_brief", + "created_at", + "votes" ], - "responses": { - "200": { - "content": { - "application/json": { - "schema": { - "items": { - "$ref": "#/components/schemas/NewsItem" - }, - "title": "Response List News Api News Get", - "type": "array" - } - } + "title": "CouncilReviewRead", + "description": "A council verdict plus every member's vote." + }, + "CouncilSettingsUpdate": { + "properties": { + "members": { + "items": { + "$ref": "#/components/schemas/CouncilMember" }, - "description": "Successful Response" + "type": "array", + "maxItems": 8, + "title": "Members" }, - "422": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } + "quorum": { + "anyOf": [ + { + "type": "number", + "maximum": 1.0, + "exclusiveMinimum": 0.0 + }, + { + "type": "string", + "pattern": "^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$" } - }, - "description": "Validation Error" + ], + "title": "Quorum", + "default": "0.5" } }, - "security": [ - { - "OAuth2PasswordBearer": [] - } + "type": "object", + "required": [ + "members" ], - "summary": "List News", - "tags": [ - "news" - ] - } - }, - "/api/news/refresh": { - "post": { - "description": "Fetch every configured feed now. Returns the number of new headlines.", - "operationId": "refresh_news_api_news_refresh_post", - "responses": { - "200": { - "content": { - "application/json": { - "schema": { - "additionalProperties": { - "type": "integer" - }, - "title": "Response Refresh News Api News Refresh Post", - "type": "object" - } - } - }, - "description": "Successful Response" + "title": "CouncilSettingsUpdate", + "description": "Council configuration \u2014 an empty member list disables the council." + }, + "CouncilVoteRead": { + "properties": { + "provider": { + "type": "string", + "title": "Provider" + }, + "model": { + "type": "string", + "title": "Model" + }, + "action": { + "type": "string", + "title": "Action" + }, + "confidence": { + "type": "string", + "pattern": "^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$", + "title": "Confidence" + }, + "reasoning": { + "type": "string", + "title": "Reasoning" } }, - "security": [ - { - "OAuth2PasswordBearer": [] - } + "type": "object", + "required": [ + "provider", + "model", + "action", + "confidence", + "reasoning" ], - "summary": "Refresh News", - "tags": [ - "news" - ] - } - }, - "/api/orders/manual": { - "post": { - "description": "Place a one-off order, risk-checked and recorded as `manual`.", - "operationId": "place_manual_order_api_orders_manual_post", - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ManualOrderRequest" - } - } + "title": "CouncilVoteRead" + }, + "Decision": { + "properties": { + "action": { + "$ref": "#/components/schemas/DecisionAction" }, - "required": true - }, - "responses": { - "200": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Fill" - } - } - }, - "description": "Successful Response" + "confidence": { + "type": "string", + "pattern": "^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$", + "title": "Confidence" }, - "422": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - "description": "Validation Error" + "reasoning": { + "type": "string", + "title": "Reasoning", + "default": "" } }, - "security": [ - { - "OAuth2PasswordBearer": [] - } + "type": "object", + "required": [ + "action", + "confidence" ], - "summary": "Place Manual Order", - "tags": [ - "orders" - ] - } - }, - "/api/polymarket/analyses": { - "get": { - "description": "Recent AI bet analyses, newest first \u2014 optionally for one market.", - "operationId": "list_analyses_api_polymarket_analyses_get", - "parameters": [ - { - "in": "query", - "name": "condition_id", - "required": false, - "schema": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ], - "title": "Condition Id" - } - }, - { - "in": "query", - "name": "limit", - "required": false, - "schema": { - "default": 50, - "title": "Limit", - "type": "integer" - } + "title": "Decision", + "description": "A structured trading decision parsed from an LLM response." + }, + "DecisionAction": { + "type": "string", + "enum": [ + "buy", + "sell", + "hold" + ], + "title": "DecisionAction" + }, + "EnabledUpdate": { + "properties": { + "enabled": { + "type": "boolean", + "title": "Enabled" } + }, + "type": "object", + "required": [ + "enabled" ], - "responses": { - "200": { - "content": { - "application/json": { - "schema": { - "items": { - "$ref": "#/components/schemas/MarketAnalysis" - }, - "title": "Response List Analyses Api Polymarket Analyses Get", - "type": "array" - } - } - }, - "description": "Successful Response" + "title": "EnabledUpdate" + }, + "EquityPoint": { + "properties": { + "time": { + "type": "string", + "format": "date-time", + "title": "Time" }, - "422": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - "description": "Validation Error" + "equity": { + "type": "string", + "pattern": "^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$", + "title": "Equity" } }, - "security": [ - { - "OAuth2PasswordBearer": [] - } + "type": "object", + "required": [ + "time", + "equity" ], - "summary": "List Analyses", - "tags": [ - "polymarket" - ] - } - }, - "/api/polymarket/markets": { - "get": { - "description": "Discovered markets, most-traded first \u2014 filterable and searchable.", - "operationId": "list_markets_api_polymarket_markets_get", - "parameters": [ - { - "in": "query", - "name": "watched", - "required": false, - "schema": { - "anyOf": [ - { - "type": "boolean" - }, - { - "type": "null" - } - ], - "title": "Watched" - } + "title": "EquityPoint" + }, + "EquitySnapshot": { + "properties": { + "id": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], + "title": "Id" + }, + "ts": { + "type": "string", + "format": "date-time", + "title": "Ts" + }, + "equity": { + "type": "string", + "pattern": "^(?!^[-+.]*$)[+-]?0*(?:\\d{0,18}|(?=[\\d.]{1,29}0*$)\\d{0,18}\\.\\d{0,10}0*$)", + "title": "Equity" }, - { - "in": "query", - "name": "status_filter", - "required": false, - "schema": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ], - "default": "active", - "title": "Status Filter" - } + "realized_pnl": { + "type": "string", + "pattern": "^(?!^[-+.]*$)[+-]?0*(?:\\d{0,18}|(?=[\\d.]{1,29}0*$)\\d{0,18}\\.\\d{0,10}0*$)", + "title": "Realized Pnl" }, - { - "in": "query", - "name": "category", - "required": false, - "schema": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ], - "title": "Category" - } + "unrealized_pnl": { + "type": "string", + "pattern": "^(?!^[-+.]*$)[+-]?0*(?:\\d{0,18}|(?=[\\d.]{1,29}0*$)\\d{0,18}\\.\\d{0,10}0*$)", + "title": "Unrealized Pnl" }, - { - "in": "query", - "name": "search", - "required": false, - "schema": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ], - "title": "Search" - } + "fees": { + "type": "string", + "pattern": "^(?!^[-+.]*$)[+-]?0*(?:\\d{0,18}|(?=[\\d.]{1,29}0*$)\\d{0,18}\\.\\d{0,10}0*$)", + "title": "Fees" }, - { - "in": "query", - "name": "limit", - "required": false, - "schema": { - "default": 100, - "title": "Limit", - "type": "integer" - } + "net_pnl": { + "type": "string", + "pattern": "^(?!^[-+.]*$)[+-]?0*(?:\\d{0,18}|(?=[\\d.]{1,29}0*$)\\d{0,18}\\.\\d{0,10}0*$)", + "title": "Net Pnl" } + }, + "type": "object", + "required": [ + "ts", + "equity", + "realized_pnl", + "unrealized_pnl", + "fees", + "net_pnl" ], - "responses": { - "200": { - "content": { - "application/json": { - "schema": { - "items": { - "$ref": "#/components/schemas/PredictionMarket" - }, - "title": "Response List Markets Api Polymarket Markets Get", - "type": "array" - } - } - }, - "description": "Successful Response" + "title": "EquitySnapshot", + "description": "A point-in-time snapshot of portfolio equity \u2014 powers the equity curve." + }, + "Fill": { + "properties": { + "strategy": { + "type": "string", + "title": "Strategy" }, - "422": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - "description": "Validation Error" + "market": { + "type": "string", + "title": "Market" + }, + "symbol": { + "type": "string", + "title": "Symbol" + }, + "side": { + "$ref": "#/components/schemas/FillSide" + }, + "quantity": { + "type": "string", + "pattern": "^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$", + "title": "Quantity" + }, + "price": { + "type": "string", + "pattern": "^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$", + "title": "Price" + }, + "fee": { + "type": "string", + "pattern": "^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$", + "title": "Fee" + }, + "realized_pnl": { + "type": "string", + "pattern": "^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$", + "title": "Realized Pnl" } }, - "security": [ - { - "OAuth2PasswordBearer": [] - } + "type": "object", + "required": [ + "strategy", + "market", + "symbol", + "side", + "quantity", + "price", + "fee", + "realized_pnl" ], - "summary": "List Markets", - "tags": [ - "polymarket" - ] - } - }, - "/api/polymarket/markets/{condition_id}/analyze": { - "post": { - "description": "Run one AI analysis for a market immediately (a paid LLM call).", - "operationId": "analyze_now_api_polymarket_markets__condition_id__analyze_post", - "parameters": [ - { - "in": "path", - "name": "condition_id", - "required": true, - "schema": { - "title": "Condition Id", - "type": "string" - } + "title": "Fill", + "description": "The result of executing an `Order`." + }, + "FillSide": { + "type": "string", + "enum": [ + "buy", + "sell" + ], + "title": "FillSide" + }, + "FundingRate": { + "properties": { + "symbol": { + "type": "string", + "title": "Symbol" + }, + "funding_rate": { + "type": "string", + "pattern": "^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$", + "title": "Funding Rate" + }, + "mark_price": { + "type": "string", + "pattern": "^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$", + "title": "Mark Price" + }, + "next_funding_time": { + "type": "string", + "format": "date-time", + "title": "Next Funding Time" } + }, + "type": "object", + "required": [ + "symbol", + "funding_rate", + "mark_price", + "next_funding_time" ], - "responses": { - "200": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/MarketAnalysis" - } + "title": "FundingRate" + }, + "GraphEdge": { + "properties": { + "id": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" } - }, - "description": "Successful Response" + ], + "title": "Id" }, - "422": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } + "source_id": { + "type": "integer", + "title": "Source Id" + }, + "target_id": { + "type": "integer", + "title": "Target Id" + }, + "relation": { + "type": "string", + "maxLength": 40, + "title": "Relation", + "default": "related" + }, + "weight": { + "type": "string", + "pattern": "^(?!^[-+.]*$)[+-]?0*(?:\\d{0,4}|(?=[\\d.]{1,7}0*$)\\d{0,4}\\.\\d{0,2}0*$)", + "title": "Weight", + "default": "1" + }, + "origin": { + "type": "string", + "maxLength": 8, + "title": "Origin", + "default": "seed" + }, + "approved": { + "type": "boolean", + "title": "Approved", + "default": true + }, + "created_at": { + "anyOf": [ + { + "type": "string", + "format": "date-time" + }, + { + "type": "null" } - }, - "description": "Validation Error" + ], + "title": "Created At" } }, - "security": [ - { - "OAuth2PasswordBearer": [] - } - ], - "summary": "Analyze Now", - "tags": [ - "polymarket" - ] - } - }, - "/api/polymarket/markets/{condition_id}/watch": { - "put": { - "description": "Pin or unpin a market for scheduled AI analysis.", - "operationId": "update_watched_api_polymarket_markets__condition_id__watch_put", - "parameters": [ - { - "in": "path", - "name": "condition_id", - "required": true, - "schema": { - "title": "Condition Id", - "type": "string" - } - } + "type": "object", + "required": [ + "source_id", + "target_id" ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/WatchUpdate" + "title": "GraphEdge", + "description": "A directed, labelled relation from one node to another." + }, + "GraphNode": { + "properties": { + "id": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" } - } + ], + "title": "Id" }, - "required": true - }, - "responses": { - "200": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/PredictionMarket" - } + "label": { + "type": "string", + "maxLength": 120, + "title": "Label" + }, + "kind": { + "type": "string", + "maxLength": 16, + "title": "Kind", + "default": "concept" + }, + "symbol": { + "anyOf": [ + { + "type": "string", + "maxLength": 24 + }, + { + "type": "null" } - }, - "description": "Successful Response" + ], + "title": "Symbol" }, - "422": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } + "icon": { + "anyOf": [ + { + "type": "string", + "maxLength": 32 + }, + { + "type": "null" } - }, - "description": "Validation Error" + ], + "title": "Icon" } }, - "security": [ - { - "OAuth2PasswordBearer": [] - } + "type": "object", + "required": [ + "label" ], - "summary": "Update Watched", - "tags": [ - "polymarket" - ] - } - }, - "/api/polymarket/refresh": { - "post": { - "description": "Pull the latest top markets from the Gamma API now.", - "operationId": "refresh_markets_api_polymarket_refresh_post", - "responses": { - "200": { - "content": { - "application/json": { - "schema": { - "additionalProperties": { - "type": "integer" - }, - "title": "Response Refresh Markets Api Polymarket Refresh Post", - "type": "object" - } - } + "title": "GraphNode", + "description": "A node: an asset, a product, or a concept." + }, + "GraphView": { + "properties": { + "nodes": { + "items": { + "$ref": "#/components/schemas/GraphNode" }, - "description": "Successful Response" - } - }, - "security": [ - { - "OAuth2PasswordBearer": [] - } - ], - "summary": "Refresh Markets", - "tags": [ - "polymarket" - ] - } - }, - "/api/portfolio/costs": { - "get": { - "description": "Cost visibility \u2014 trading fees by market, fee rates, and LLM spend.", - "operationId": "costs_api_portfolio_costs_get", - "responses": { - "200": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/CostsRead" - } - } + "type": "array", + "title": "Nodes" + }, + "edges": { + "items": { + "$ref": "#/components/schemas/GraphEdge" }, - "description": "Successful Response" + "type": "array", + "title": "Edges" } }, - "security": [ - { - "OAuth2PasswordBearer": [] - } + "type": "object", + "required": [ + "nodes", + "edges" ], - "summary": "Costs", - "tags": [ - "portfolio" - ] - } - }, - "/api/portfolio/equity": { - "get": { - "description": "Equity-curve history, oldest-first.", - "operationId": "equity_api_portfolio_equity_get", - "responses": { - "200": { - "content": { - "application/json": { - "schema": { - "items": { - "$ref": "#/components/schemas/EquitySnapshot" - }, - "title": "Response Equity Api Portfolio Equity Get", - "type": "array" - } - } + "title": "GraphView", + "description": "The connections graph \u2014 nodes and the edges between them." + }, + "HTTPValidationError": { + "properties": { + "detail": { + "items": { + "$ref": "#/components/schemas/ValidationError" }, - "description": "Successful Response" + "type": "array", + "title": "Detail" } }, - "security": [ - { - "OAuth2PasswordBearer": [] - } - ], - "summary": "Equity", - "tags": [ - "portfolio" - ] - } - }, - "/api/portfolio/positions": { - "get": { - "description": "Currently open positions across all strategies.", - "operationId": "positions_api_portfolio_positions_get", - "responses": { - "200": { - "content": { - "application/json": { - "schema": { - "items": { - "$ref": "#/components/schemas/Position" - }, - "title": "Response Positions Api Portfolio Positions Get", - "type": "array" - } - } + "type": "object", + "title": "HTTPValidationError" + }, + "InstanceCreate": { + "properties": { + "name": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "title": "Name" + }, + "type": { + "type": "string", + "maxLength": 32, + "minLength": 1, + "title": "Type" + }, + "symbol": { + "type": "string", + "maxLength": 80, + "minLength": 1, + "title": "Symbol" + }, + "venue": { + "type": "string", + "maxLength": 24, + "title": "Venue", + "default": "binance" + }, + "market": { + "type": "string", + "pattern": "^(spot|futures)$", + "title": "Market", + "default": "spot" + }, + "timeframe": { + "type": "string", + "maxLength": 8, + "title": "Timeframe", + "default": "1h" + }, + "params": { + "additionalProperties": { + "type": "string" }, - "description": "Successful Response" - } - }, - "security": [ - { - "OAuth2PasswordBearer": [] - } - ], - "summary": "Positions", - "tags": [ - "portfolio" - ] - } - }, - "/api/portfolio/summary": { - "get": { - "description": "Aggregate accounting \u2014 equity, PnL, fees, allocated vs idle capital.", - "operationId": "summary_api_portfolio_summary_get", - "responses": { - "200": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/PortfolioSummary" - } + "type": "object", + "title": "Params" + }, + "allocated": { + "anyOf": [ + { + "type": "number", + "minimum": 0.0 + }, + { + "type": "string", + "pattern": "^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$" + } + ], + "title": "Allocated", + "default": "10000" + }, + "max_loss": { + "anyOf": [ + { + "type": "number", + "minimum": 0.0 + }, + { + "type": "string", + "pattern": "^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$" } - }, - "description": "Successful Response" + ], + "title": "Max Loss", + "default": "0" } }, - "security": [ - { - "OAuth2PasswordBearer": [] - } - ], - "summary": "Summary", - "tags": [ - "portfolio" - ] - } - }, - "/api/portfolio/trades": { - "get": { - "description": "Most-recent executed trades.", - "operationId": "trades_api_portfolio_trades_get", - "parameters": [ - { - "in": "query", - "name": "limit", - "required": false, - "schema": { - "default": 50, - "title": "Limit", - "type": "integer" - } - } + "type": "object", + "required": [ + "name", + "type", + "symbol" ], - "responses": { - "200": { - "content": { - "application/json": { - "schema": { - "items": { - "$ref": "#/components/schemas/Trade" - }, - "title": "Response Trades Api Portfolio Trades Get", - "type": "array" - } + "title": "InstanceCreate", + "description": "Apply a strategy type to a coin as a new named instance." + }, + "LLMUsage": { + "properties": { + "id": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" } - }, - "description": "Successful Response" + ], + "title": "Id" }, - "422": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } + "provider": { + "type": "string", + "maxLength": 16, + "title": "Provider" + }, + "model": { + "type": "string", + "maxLength": 64, + "title": "Model" + }, + "purpose": { + "type": "string", + "maxLength": 16, + "title": "Purpose", + "default": "analyze" + }, + "strategy": { + "anyOf": [ + { + "type": "string", + "maxLength": 64 + }, + { + "type": "null" } - }, - "description": "Validation Error" + ], + "title": "Strategy" + }, + "input_tokens": { + "type": "integer", + "title": "Input Tokens", + "default": 0 + }, + "output_tokens": { + "type": "integer", + "title": "Output Tokens", + "default": 0 + }, + "estimated_cost_usd": { + "type": "string", + "pattern": "^(?!^[-+.]*$)[+-]?0*(?:\\d{0,18}|(?=[\\d.]{1,29}0*$)\\d{0,18}\\.\\d{0,10}0*$)", + "title": "Estimated Cost Usd", + "default": "0" + }, + "action": { + "anyOf": [ + { + "type": "string", + "maxLength": 8 + }, + { + "type": "null" + } + ], + "title": "Action" + }, + "confidence": { + "anyOf": [ + { + "type": "string", + "pattern": "^(?!^[-+.]*$)[+-]?0*(?:\\d{0,18}|(?=[\\d.]{1,29}0*$)\\d{0,18}\\.\\d{0,10}0*$)" + }, + { + "type": "null" + } + ], + "title": "Confidence" + }, + "created_at": { + "type": "string", + "format": "date-time", + "title": "Created At" } }, - "security": [ - { - "OAuth2PasswordBearer": [] + "type": "object", + "required": [ + "provider", + "model", + "created_at" + ], + "title": "LLMUsage", + "description": "One recorded LLM completion \u2014 tokens, estimated cost, and the decision." + }, + "LatencyRead": { + "properties": { + "warn_ms": { + "type": "integer", + "title": "Warn Ms" + }, + "degraded": { + "type": "boolean", + "title": "Degraded" + }, + "feeds": { + "items": { + "$ref": "#/components/schemas/LatencyStatsRead" + }, + "type": "array", + "title": "Feeds" } + }, + "type": "object", + "required": [ + "warn_ms", + "degraded", + "feeds" ], - "summary": "Trades", - "tags": [ - "portfolio" - ] - } - }, - "/api/research": { - "get": { - "description": "Recent reports, newest first \u2014 optionally filtered by `symbol`.", - "operationId": "list_reports_api_research_get", - "parameters": [ - { - "in": "query", - "name": "symbol", - "required": false, - "schema": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ], - "title": "Symbol" - } + "title": "LatencyRead", + "description": "Price-feed latency \u2014 per-feed stats plus the overall degraded flag." + }, + "LatencyStatsRead": { + "properties": { + "market": { + "type": "string", + "title": "Market" }, - { - "in": "query", - "name": "limit", - "required": false, - "schema": { - "default": 50, - "title": "Limit", - "type": "integer" - } + "symbol": { + "type": "string", + "title": "Symbol" + }, + "kind": { + "type": "string", + "title": "Kind" + }, + "current_ms": { + "type": "number", + "title": "Current Ms" + }, + "p50_ms": { + "type": "number", + "title": "P50 Ms" + }, + "p95_ms": { + "type": "number", + "title": "P95 Ms" + }, + "max_ms": { + "type": "number", + "title": "Max Ms" + }, + "samples": { + "type": "integer", + "title": "Samples" + }, + "degraded": { + "type": "boolean", + "title": "Degraded" } + }, + "type": "object", + "required": [ + "market", + "symbol", + "kind", + "current_ms", + "p50_ms", + "p95_ms", + "max_ms", + "samples", + "degraded" ], - "responses": { - "200": { - "content": { - "application/json": { - "schema": { - "items": { - "$ref": "#/components/schemas/ResearchReportRead" - }, - "title": "Response List Reports Api Research Get", - "type": "array" - } + "title": "LatencyStatsRead", + "description": "Rolling-window latency for one (market, symbol, kind)." + }, + "LedgerEntry": { + "properties": { + "id": { + "type": "integer", + "title": "Id" + }, + "created_at": { + "type": "string", + "format": "date-time", + "title": "Created At" + }, + "provider": { + "type": "string", + "title": "Provider" + }, + "model": { + "type": "string", + "title": "Model" + }, + "purpose": { + "type": "string", + "title": "Purpose" + }, + "strategy": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Strategy" + }, + "input_tokens": { + "type": "integer", + "title": "Input Tokens" + }, + "output_tokens": { + "type": "integer", + "title": "Output Tokens" + }, + "cost_usd": { + "type": "string", + "pattern": "^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$", + "title": "Cost Usd" + }, + "action": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" } - }, - "description": "Successful Response" + ], + "title": "Action" }, - "422": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } + "confidence": { + "anyOf": [ + { + "type": "string", + "pattern": "^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$" + }, + { + "type": "null" } - }, - "description": "Validation Error" + ], + "title": "Confidence" } }, - "security": [ - { - "OAuth2PasswordBearer": [] + "type": "object", + "required": [ + "id", + "created_at", + "provider", + "model", + "purpose", + "strategy", + "input_tokens", + "output_tokens", + "cost_usd", + "action", + "confidence" + ], + "title": "LedgerEntry", + "description": "One paid call, newest first." + }, + "LedgerPage": { + "properties": { + "total": { + "type": "integer", + "title": "Total" + }, + "entries": { + "items": { + "$ref": "#/components/schemas/LedgerEntry" + }, + "type": "array", + "title": "Entries" } + }, + "type": "object", + "required": [ + "total", + "entries" ], - "summary": "List Reports", - "tags": [ - "research" - ] - } - }, - "/api/research/run": { - "post": { - "description": "Write a report now \u2014 for `symbol`, or every watched symbol when blank.", - "operationId": "run_research_api_research_run_post", - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ResearchRun" - } - } + "title": "LedgerPage" + }, + "LlmCredentialsUpdate": { + "properties": { + "api_key": { + "type": "string", + "maxLength": 256, + "title": "Api Key", + "default": "" }, - "required": true + "base_url": { + "type": "string", + "maxLength": 256, + "title": "Base Url", + "default": "" + } }, - "responses": { - "200": { - "content": { - "application/json": { - "schema": { - "items": { - "$ref": "#/components/schemas/ResearchReportRead" - }, - "title": "Response Run Research Api Research Run Post", - "type": "array" - } - } - }, - "description": "Successful Response" + "type": "object", + "title": "LlmCredentialsUpdate", + "description": "One LLM provider's credentials \u2014 both fields optional." + }, + "LlmfitStatusRead": { + "properties": { + "installed": { + "type": "boolean", + "title": "Installed" }, - "422": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } + "install_hint": { + "type": "string", + "title": "Install Hint" + }, + "hardware": { + "additionalProperties": true, + "type": "object", + "title": "Hardware" + }, + "fits": { + "items": { + "$ref": "#/components/schemas/ModelFitRead" }, - "description": "Validation Error" + "type": "array", + "title": "Fits" + }, + "error": { + "type": "string", + "title": "Error" } }, - "security": [ - { - "OAuth2PasswordBearer": [] - } + "type": "object", + "required": [ + "installed", + "install_hint", + "hardware", + "fits", + "error" ], - "summary": "Run Research", - "tags": [ - "research" - ] - } - }, - "/api/research/{report_id}": { - "get": { - "description": "One report with its full sections.", - "operationId": "get_report_api_research__report_id__get", - "parameters": [ - { - "in": "path", - "name": "report_id", - "required": true, - "schema": { - "title": "Report Id", - "type": "integer" - } + "title": "LlmfitStatusRead" + }, + "LocalAIRead": { + "properties": { + "ollama": { + "$ref": "#/components/schemas/OllamaStatusRead" + }, + "llmfit": { + "$ref": "#/components/schemas/LlmfitStatusRead" } + }, + "type": "object", + "required": [ + "ollama", + "llmfit" ], - "responses": { - "200": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ResearchReportRead" - } - } - }, - "description": "Successful Response" + "title": "LocalAIRead", + "description": "Local-model readiness \u2014 deployed (Ollama) and deployable (llmfit)." + }, + "ManualOrderRequest": { + "properties": { + "symbol": { + "type": "string", + "maxLength": 24, + "minLength": 3, + "title": "Symbol" }, - "422": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } + "side": { + "$ref": "#/components/schemas/FillSide" + }, + "quantity": { + "anyOf": [ + { + "type": "number", + "exclusiveMinimum": 0.0 + }, + { + "type": "string", + "pattern": "^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$" } - }, - "description": "Validation Error" + ], + "title": "Quantity" + }, + "market": { + "type": "string", + "title": "Market", + "default": "spot" } }, - "security": [ - { - "OAuth2PasswordBearer": [] - } + "type": "object", + "required": [ + "symbol", + "side", + "quantity" ], - "summary": "Get Report", - "tags": [ - "research" - ] - } - }, - "/api/research/{report_id}/review": { - "get": { - "description": "The newest council review for a report, with every vote.", - "operationId": "get_review_api_research__report_id__review_get", - "parameters": [ - { - "in": "path", - "name": "report_id", - "required": true, - "schema": { - "title": "Report Id", - "type": "integer" - } - } + "title": "ManualOrderRequest" + }, + "Market": { + "type": "string", + "enum": [ + "spot", + "futures" ], - "responses": { - "200": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/CouncilReviewRead" - } + "title": "Market" + }, + "MarketAnalysis": { + "properties": { + "id": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" } - }, - "description": "Successful Response" + ], + "title": "Id" + }, + "condition_id": { + "type": "string", + "maxLength": 80, + "title": "Condition Id" + }, + "question": { + "type": "string", + "maxLength": 512, + "title": "Question" + }, + "market_price": { + "type": "string", + "pattern": "^(?!^[-+.]*$)[+-]?0*(?:\\d{0,16}|(?=[\\d.]{1,25}0*$)\\d{0,16}\\.\\d{0,8}0*$)", + "title": "Market Price", + "default": "0" + }, + "est_probability": { + "type": "string", + "pattern": "^(?!^[-+.]*$)[+-]?0*(?:\\d{0,16}|(?=[\\d.]{1,25}0*$)\\d{0,16}\\.\\d{0,8}0*$)", + "title": "Est Probability", + "default": "0" }, - "422": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - "description": "Validation Error" + "edge": { + "type": "string", + "pattern": "^(?!^[-+.]*$)[+-]?0*(?:\\d{0,16}|(?=[\\d.]{1,25}0*$)\\d{0,16}\\.\\d{0,8}0*$)", + "title": "Edge", + "default": "0" + }, + "recommendation": { + "type": "string", + "maxLength": 8, + "title": "Recommendation", + "default": "hold" + }, + "confidence": { + "type": "string", + "pattern": "^(?!^[-+.]*$)[+-]?0*(?:\\d{0,16}|(?=[\\d.]{1,25}0*$)\\d{0,16}\\.\\d{0,8}0*$)", + "title": "Confidence", + "default": "0" + }, + "reasoning": { + "type": "string", + "title": "Reasoning", + "default": "" + }, + "provider": { + "type": "string", + "maxLength": 16, + "title": "Provider", + "default": "" + }, + "model": { + "type": "string", + "maxLength": 64, + "title": "Model", + "default": "" + }, + "created_at": { + "type": "string", + "format": "date-time", + "title": "Created At" } }, - "security": [ - { - "OAuth2PasswordBearer": [] - } + "type": "object", + "required": [ + "condition_id", + "question", + "created_at" ], - "summary": "Get Review", - "tags": [ - "research" - ] + "title": "MarketAnalysis", + "description": "One AI pass over a market \u2014 estimated probability vs market price." }, - "post": { - "description": "Re-run the council on a report now (admin).", - "operationId": "rerun_review_api_research__report_id__review_post", - "parameters": [ - { - "in": "path", - "name": "report_id", - "required": true, - "schema": { - "title": "Report Id", - "type": "integer" - } - } - ], - "responses": { - "200": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/CouncilReviewRead" - } - } - }, - "description": "Successful Response" + "ModeUpdate": { + "properties": { + "mode": { + "$ref": "#/components/schemas/TradingMode" }, - "422": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - "description": "Validation Error" + "confirm": { + "type": "boolean", + "title": "Confirm", + "default": false } }, - "security": [ - { - "OAuth2PasswordBearer": [] - } + "type": "object", + "required": [ + "mode" ], - "summary": "Rerun Review", - "tags": [ - "research" - ] - } - }, - "/api/settings": { - "get": { - "description": "Current trading mode and whether Binance keys are stored.", - "operationId": "read_settings_api_settings_get", - "responses": { - "200": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SettingsRead" - } - } - }, - "description": "Successful Response" + "title": "ModeUpdate" + }, + "ModelFitRead": { + "properties": { + "model": { + "type": "string", + "title": "Model" + }, + "quantization": { + "type": "string", + "title": "Quantization" + }, + "fit": { + "type": "string", + "title": "Fit" + }, + "est_speed": { + "type": "string", + "title": "Est Speed" + }, + "memory_gb": { + "type": "string", + "title": "Memory Gb" } }, - "security": [ - { - "OAuth2PasswordBearer": [] - } + "type": "object", + "required": [ + "model", + "quantization", + "fit", + "est_speed", + "memory_gb" ], - "summary": "Read Settings", - "tags": [ - "settings" - ] - } - }, - "/api/settings/ai": { - "put": { - "description": "Configure the AI provider. The API key is encrypted at rest.", - "operationId": "update_ai_settings_api_settings_ai_put", - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/AiSettingsUpdate" - } - } + "title": "ModelFitRead" + }, + "ModelUsage": { + "properties": { + "provider": { + "type": "string", + "title": "Provider" }, - "required": true - }, - "responses": { - "200": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SettingsRead" - } - } - }, - "description": "Successful Response" + "model": { + "type": "string", + "title": "Model" }, - "422": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - "description": "Validation Error" + "decisions": { + "type": "integer", + "title": "Decisions" + }, + "buys": { + "type": "integer", + "title": "Buys" + }, + "sells": { + "type": "integer", + "title": "Sells" + }, + "holds": { + "type": "integer", + "title": "Holds" + }, + "total_cost_usd": { + "type": "string", + "pattern": "^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$", + "title": "Total Cost Usd" } }, - "security": [ - { - "OAuth2PasswordBearer": [] - } + "type": "object", + "required": [ + "provider", + "model", + "decisions", + "buys", + "sells", + "holds", + "total_cost_usd" ], - "summary": "Update Ai Settings", - "tags": [ - "settings" - ] - } - }, - "/api/settings/ai-action-mode": { - "put": { - "description": "Set how AI decisions are applied: `notify` (confirm) or `auto`.", - "operationId": "update_ai_action_mode_api_settings_ai_action_mode_put", - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/AiActionModeUpdate" + "title": "ModelUsage", + "description": "Per-model rollup of LLM activity \u2014 'what each model did'." + }, + "NewsItem": { + "properties": { + "id": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" } - } + ], + "title": "Id" }, - "required": true - }, - "responses": { - "200": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SettingsRead" - } + "source": { + "type": "string", + "maxLength": 64, + "title": "Source" + }, + "title": { + "type": "string", + "maxLength": 512, + "title": "Title" + }, + "url": { + "type": "string", + "maxLength": 1024, + "title": "Url" + }, + "summary": { + "type": "string", + "title": "Summary", + "default": "" + }, + "category": { + "type": "string", + "maxLength": 16, + "title": "Category", + "default": "world" + }, + "symbol": { + "anyOf": [ + { + "type": "string", + "maxLength": 24 + }, + { + "type": "null" + } + ], + "title": "Symbol" + }, + "sentiment": { + "anyOf": [ + { + "type": "string", + "maxLength": 16 + }, + { + "type": "null" } - }, - "description": "Successful Response" + ], + "title": "Sentiment" }, - "422": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } + "published_at": { + "anyOf": [ + { + "type": "string", + "format": "date-time" + }, + { + "type": "null" } - }, - "description": "Validation Error" + ], + "title": "Published At" + }, + "fetched_at": { + "type": "string", + "format": "date-time", + "title": "Fetched At" } }, - "security": [ - { - "OAuth2PasswordBearer": [] - } + "type": "object", + "required": [ + "source", + "title", + "url", + "fetched_at" ], - "summary": "Update Ai Action Mode", - "tags": [ - "settings" - ] - } - }, - "/api/settings/ai-spend-cap": { - "put": { - "description": "Set the daily LLM spend cap \u2014 AI strategies pause once it is reached.", - "operationId": "update_ai_spend_cap_api_settings_ai_spend_cap_put", - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/AiSpendCapUpdate" - } - } + "title": "NewsItem", + "description": "A single news headline pulled from an RSS feed." + }, + "NodeCreate": { + "properties": { + "label": { + "type": "string", + "maxLength": 120, + "minLength": 1, + "title": "Label" }, - "required": true - }, - "responses": { - "200": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SettingsRead" - } + "kind": { + "type": "string", + "title": "Kind", + "default": "concept" + }, + "symbol": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" } - }, - "description": "Successful Response" + ], + "title": "Symbol" }, - "422": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } + "icon": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" } - }, - "description": "Validation Error" + ], + "title": "Icon" } }, - "security": [ - { - "OAuth2PasswordBearer": [] - } + "type": "object", + "required": [ + "label" ], - "summary": "Update Ai Spend Cap", - "tags": [ - "settings" - ] - } - }, - "/api/settings/binance-keys": { - "put": { - "description": "Store the Binance API credentials (encrypted at rest).", - "operationId": "update_binance_keys_api_settings_binance_keys_put", - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/BinanceKeysUpdate" - } - } + "title": "NodeCreate" + }, + "OllamaModelRead": { + "properties": { + "name": { + "type": "string", + "title": "Name" }, - "required": true + "size_bytes": { + "type": "integer", + "title": "Size Bytes" + }, + "parameter_size": { + "type": "string", + "title": "Parameter Size" + }, + "quantization": { + "type": "string", + "title": "Quantization" + } }, - "responses": { - "204": { - "description": "Successful Response" + "type": "object", + "required": [ + "name", + "size_bytes", + "parameter_size", + "quantization" + ], + "title": "OllamaModelRead" + }, + "OllamaStatusRead": { + "properties": { + "reachable": { + "type": "boolean", + "title": "Reachable" }, - "422": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } + "base_url": { + "type": "string", + "title": "Base Url" + }, + "version": { + "type": "string", + "title": "Version" + }, + "models": { + "items": { + "$ref": "#/components/schemas/OllamaModelRead" }, - "description": "Validation Error" + "type": "array", + "title": "Models" } }, - "security": [ - { - "OAuth2PasswordBearer": [] - } + "type": "object", + "required": [ + "reachable", + "base_url", + "version", + "models" ], - "summary": "Update Binance Keys", - "tags": [ - "settings" - ] - } - }, - "/api/settings/council": { - "put": { - "description": "Configure the AI council members and quorum.", - "operationId": "update_council_settings_api_settings_council_put", - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/CouncilSettingsUpdate" - } - } + "title": "OllamaStatusRead" + }, + "OrderBook": { + "properties": { + "symbol": { + "type": "string", + "title": "Symbol" }, - "required": true - }, - "responses": { - "200": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SettingsRead" - } - } + "bids": { + "items": { + "$ref": "#/components/schemas/OrderBookLevel" }, - "description": "Successful Response" + "type": "array", + "title": "Bids" }, - "422": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } + "asks": { + "items": { + "$ref": "#/components/schemas/OrderBookLevel" }, - "description": "Validation Error" + "type": "array", + "title": "Asks" } }, - "security": [ - { - "OAuth2PasswordBearer": [] - } - ], - "summary": "Update Council Settings", - "tags": [ - "settings" - ] - } - }, - "/api/settings/llm-credentials/{provider}": { - "put": { - "description": "Store one LLM provider's API key and/or base URL (encrypted at rest).", - "operationId": "update_llm_credentials_api_settings_llm_credentials__provider__put", - "parameters": [ - { - "in": "path", - "name": "provider", - "required": true, - "schema": { - "title": "Provider", - "type": "string" - } - } + "type": "object", + "required": [ + "symbol", + "bids", + "asks" ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/LlmCredentialsUpdate" - } - } + "title": "OrderBook" + }, + "OrderBookLevel": { + "properties": { + "price": { + "type": "string", + "pattern": "^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$", + "title": "Price" }, - "required": true + "qty": { + "type": "string", + "pattern": "^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$", + "title": "Qty" + } }, - "responses": { - "200": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SettingsRead" - } - } - }, - "description": "Successful Response" + "type": "object", + "required": [ + "price", + "qty" + ], + "title": "OrderBookLevel" + }, + "ParamSpecRead": { + "properties": { + "name": { + "type": "string", + "title": "Name" }, - "422": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - "description": "Validation Error" + "type": { + "type": "string", + "title": "Type" + }, + "default": { + "type": "string", + "title": "Default" + }, + "min": { + "type": "string", + "title": "Min" + }, + "max": { + "type": "string", + "title": "Max" + }, + "label": { + "type": "string", + "title": "Label" } }, - "security": [ - { - "OAuth2PasswordBearer": [] + "type": "object", + "required": [ + "name", + "type", + "default", + "min", + "max", + "label" + ], + "title": "ParamSpecRead" + }, + "PasswordReset": { + "properties": { + "password": { + "type": "string", + "maxLength": 128, + "minLength": 8, + "title": "Password" } + }, + "type": "object", + "required": [ + "password" ], - "summary": "Update Llm Credentials", - "tags": [ - "settings" - ] - } - }, - "/api/settings/mode": { - "put": { - "description": "Switch the trading mode (Sim / Testnet / Live).", - "operationId": "update_mode_api_settings_mode_put", - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ModeUpdate" + "title": "PasswordReset" + }, + "PolymarketSettingsUpdate": { + "properties": { + "refresh_hours": { + "type": "integer", + "maximum": 48.0, + "minimum": 1.0, + "title": "Refresh Hours", + "default": 6 + }, + "research_hours": { + "type": "integer", + "maximum": 168.0, + "minimum": 1.0, + "title": "Research Hours", + "default": 6 + }, + "edge_threshold": { + "anyOf": [ + { + "type": "number", + "maximum": 1.0, + "minimum": 0.0 + }, + { + "type": "string", + "pattern": "^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$" } - } + ], + "title": "Edge Threshold", + "default": "0.05" }, - "required": true - }, - "responses": { - "200": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SettingsRead" - } + "min_confidence": { + "anyOf": [ + { + "type": "number", + "maximum": 1.0, + "minimum": 0.0 + }, + { + "type": "string", + "pattern": "^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$" } - }, - "description": "Successful Response" + ], + "title": "Min Confidence", + "default": "0.6" }, - "422": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } + "screen_top": { + "type": "integer", + "maximum": 50.0, + "minimum": 0.0, + "title": "Screen Top", + "default": 10 + }, + "stake": { + "anyOf": [ + { + "type": "number", + "exclusiveMinimum": 0.0 + }, + { + "type": "string", + "pattern": "^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$" } - }, - "description": "Validation Error" + ], + "title": "Stake", + "default": "100" } }, - "security": [ - { - "OAuth2PasswordBearer": [] + "type": "object", + "title": "PolymarketSettingsUpdate", + "description": "Polymarket configuration \u2014 discovery/screening cadence and bars." + }, + "PortfolioSummary": { + "properties": { + "total_allocated": { + "type": "string", + "pattern": "^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$", + "title": "Total Allocated" + }, + "realized_pnl": { + "type": "string", + "pattern": "^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$", + "title": "Realized Pnl" + }, + "unrealized_pnl": { + "type": "string", + "pattern": "^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$", + "title": "Unrealized Pnl" + }, + "total_fees": { + "type": "string", + "pattern": "^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$", + "title": "Total Fees" + }, + "net_pnl": { + "type": "string", + "pattern": "^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$", + "title": "Net Pnl" + }, + "equity": { + "type": "string", + "pattern": "^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$", + "title": "Equity" + }, + "deployed_capital": { + "type": "string", + "pattern": "^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$", + "title": "Deployed Capital" + }, + "idle_capital": { + "type": "string", + "pattern": "^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$", + "title": "Idle Capital" + }, + "open_positions": { + "type": "integer", + "title": "Open Positions" + }, + "strategies": { + "items": { + "$ref": "#/components/schemas/StrategySummary" + }, + "type": "array", + "title": "Strategies" } + }, + "type": "object", + "required": [ + "total_allocated", + "realized_pnl", + "unrealized_pnl", + "total_fees", + "net_pnl", + "equity", + "deployed_capital", + "idle_capital", + "open_positions", + "strategies" ], - "summary": "Update Mode", - "tags": [ - "settings" - ] - } - }, - "/api/settings/polymarket": { - "put": { - "description": "Configure Polymarket discovery and AI bet screening.\n\nScheduler-interval changes take effect on the next engine restart.", - "operationId": "update_polymarket_settings_api_settings_polymarket_put", - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/PolymarketSettingsUpdate" + "title": "PortfolioSummary" + }, + "Position": { + "properties": { + "id": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" } - } + ], + "title": "Id" }, - "required": true - }, - "responses": { - "200": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SettingsRead" - } + "strategy": { + "type": "string", + "maxLength": 64, + "title": "Strategy" + }, + "market": { + "type": "string", + "maxLength": 8, + "title": "Market" + }, + "symbol": { + "type": "string", + "maxLength": 80, + "title": "Symbol" + }, + "side": { + "type": "string", + "maxLength": 8, + "title": "Side", + "default": "flat" + }, + "qty": { + "type": "string", + "pattern": "^(?!^[-+.]*$)[+-]?0*(?:\\d{0,18}|(?=[\\d.]{1,29}0*$)\\d{0,18}\\.\\d{0,10}0*$)", + "title": "Qty", + "default": "0" + }, + "entry_price": { + "type": "string", + "pattern": "^(?!^[-+.]*$)[+-]?0*(?:\\d{0,18}|(?=[\\d.]{1,29}0*$)\\d{0,18}\\.\\d{0,10}0*$)", + "title": "Entry Price", + "default": "0" + }, + "realized_pnl": { + "type": "string", + "pattern": "^(?!^[-+.]*$)[+-]?0*(?:\\d{0,18}|(?=[\\d.]{1,29}0*$)\\d{0,18}\\.\\d{0,10}0*$)", + "title": "Realized Pnl", + "default": "0" + }, + "fees_paid": { + "type": "string", + "pattern": "^(?!^[-+.]*$)[+-]?0*(?:\\d{0,18}|(?=[\\d.]{1,29}0*$)\\d{0,18}\\.\\d{0,10}0*$)", + "title": "Fees Paid", + "default": "0" + }, + "opened_at": { + "anyOf": [ + { + "type": "string", + "format": "date-time" + }, + { + "type": "null" } - }, - "description": "Successful Response" + ], + "title": "Opened At" }, - "422": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } + "updated_at": { + "anyOf": [ + { + "type": "string", + "format": "date-time" + }, + { + "type": "null" } - }, - "description": "Validation Error" + ], + "title": "Updated At" } }, - "security": [ - { - "OAuth2PasswordBearer": [] - } + "type": "object", + "required": [ + "strategy", + "market", + "symbol" ], - "summary": "Update Polymarket Settings", - "tags": [ - "settings" - ] - } - }, - "/api/settings/research": { - "put": { - "description": "Configure research reports and the news refresh interval.\n\nScheduler-interval changes take effect on the next engine restart.", - "operationId": "update_research_settings_api_settings_research_put", - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ResearchSettingsUpdate" + "title": "Position", + "description": "One strategy's position in one symbol/market \u2014 the attribution row.\n\n`qty` is always >= 0; `side` gives the direction. `realized_pnl` and\n`fees_paid` accumulate over the position's lifetime (the row is kept after\na position goes flat so cumulative PnL is preserved)." + }, + "PredictionMarket": { + "properties": { + "id": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" } - } + ], + "title": "Id" }, - "required": true - }, - "responses": { - "200": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SettingsRead" - } - } - }, - "description": "Successful Response" + "condition_id": { + "type": "string", + "maxLength": 80, + "title": "Condition Id" }, - "422": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - "description": "Validation Error" - } - }, - "security": [ - { - "OAuth2PasswordBearer": [] - } - ], - "summary": "Update Research Settings", - "tags": [ - "settings" - ] - } - }, - "/api/settings/venue-credentials/{venue}": { - "put": { - "description": "Store a venue's credentials (encrypted at rest).\n\nThe submitted field names must exactly match the venue's declared\n`credential_fields`, and every value must be non-empty.", - "operationId": "update_venue_credentials_api_settings_venue_credentials__venue__put", - "parameters": [ - { - "in": "path", - "name": "venue", - "required": true, - "schema": { - "title": "Venue", - "type": "string" - } - } - ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/VenueCredentialsUpdate" - } - } + "question": { + "type": "string", + "maxLength": 512, + "title": "Question" }, - "required": true - }, - "responses": { - "204": { - "description": "Successful Response" + "slug": { + "type": "string", + "maxLength": 256, + "title": "Slug", + "default": "" }, - "422": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - "description": "Validation Error" - } - }, - "security": [ - { - "OAuth2PasswordBearer": [] - } - ], - "summary": "Update Venue Credentials", - "tags": [ - "settings" - ] - } - }, - "/api/strategies": { - "get": { - "description": "Every registered strategy with its allocation, state and PnL.", - "operationId": "list_strategies_api_strategies_get", - "responses": { - "200": { - "content": { - "application/json": { - "schema": { - "items": { - "$ref": "#/components/schemas/StrategyRead" - }, - "title": "Response List Strategies Api Strategies Get", - "type": "array" - } + "category": { + "type": "string", + "maxLength": 64, + "title": "Category", + "default": "" + }, + "end_date": { + "anyOf": [ + { + "type": "string", + "format": "date-time" + }, + { + "type": "null" } - }, - "description": "Successful Response" + ], + "title": "End Date" + }, + "yes_token_id": { + "type": "string", + "maxLength": 80, + "title": "Yes Token Id", + "default": "" + }, + "no_token_id": { + "type": "string", + "maxLength": 80, + "title": "No Token Id", + "default": "" + }, + "outcomes": { + "type": "string", + "title": "Outcomes", + "default": "[]" + }, + "yes_price": { + "type": "string", + "pattern": "^(?!^[-+.]*$)[+-]?0*(?:\\d{0,16}|(?=[\\d.]{1,25}0*$)\\d{0,16}\\.\\d{0,8}0*$)", + "title": "Yes Price", + "default": "0" + }, + "volume_24h": { + "type": "string", + "pattern": "^(?!^[-+.]*$)[+-]?0*(?:\\d{0,16}|(?=[\\d.]{1,25}0*$)\\d{0,16}\\.\\d{0,8}0*$)", + "title": "Volume 24H", + "default": "0" + }, + "liquidity": { + "type": "string", + "pattern": "^(?!^[-+.]*$)[+-]?0*(?:\\d{0,16}|(?=[\\d.]{1,25}0*$)\\d{0,16}\\.\\d{0,8}0*$)", + "title": "Liquidity", + "default": "0" + }, + "status": { + "type": "string", + "maxLength": 12, + "title": "Status", + "default": "active" + }, + "resolved_outcome": { + "type": "string", + "maxLength": 64, + "title": "Resolved Outcome", + "default": "" + }, + "watched": { + "type": "boolean", + "title": "Watched", + "default": false + }, + "fetched_at": { + "type": "string", + "format": "date-time", + "title": "Fetched At" } }, - "security": [ - { - "OAuth2PasswordBearer": [] - } + "type": "object", + "required": [ + "condition_id", + "question", + "fetched_at" ], - "summary": "List Strategies", - "tags": [ - "strategies" - ] + "title": "PredictionMarket", + "description": "One Polymarket market, upserted from the Gamma API by `condition_id`." }, - "post": { - "description": "Create a strategy instance: any registered type on any chosen coin.", - "operationId": "create_instance_api_strategies_post", - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/InstanceCreate" - } - } + "RecommendRequest": { + "properties": { + "symbol": { + "type": "string", + "maxLength": 24, + "minLength": 1, + "title": "Symbol" }, - "required": true - }, - "responses": { - "201": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/StrategyRead" - } - } - }, - "description": "Successful Response" + "timeframe": { + "type": "string", + "maxLength": 8, + "title": "Timeframe", + "default": "1h" + }, + "days": { + "type": "integer", + "maximum": 365.0, + "minimum": 7.0, + "title": "Days", + "default": 90 }, - "422": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } + "capital": { + "anyOf": [ + { + "type": "number", + "exclusiveMinimum": 0.0 + }, + { + "type": "string", + "pattern": "^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$" } - }, - "description": "Validation Error" + ], + "title": "Capital", + "default": "10000" } }, - "security": [ - { - "OAuth2PasswordBearer": [] - } + "type": "object", + "required": [ + "symbol" ], - "summary": "Create Instance", - "tags": [ - "strategies" - ] - } - }, - "/api/strategies/types": { - "get": { - "description": "Every pickable strategy type with its typed parameter schema.", - "operationId": "list_strategy_types_api_strategies_types_get", - "responses": { - "200": { - "content": { - "application/json": { - "schema": { - "items": { - "$ref": "#/components/schemas/StrategyTypeRead" - }, - "title": "Response List Strategy Types Api Strategies Types Get", - "type": "array" - } - } - }, - "description": "Successful Response" + "title": "RecommendRequest" + }, + "RecommendResponse": { + "properties": { + "strategy_type": { + "type": "string", + "title": "Strategy Type" + }, + "params": { + "additionalProperties": true, + "type": "object", + "title": "Params" + }, + "reasoning": { + "type": "string", + "title": "Reasoning" + }, + "cell": { + "$ref": "#/components/schemas/CompareCellRead" } }, - "security": [ - { - "OAuth2PasswordBearer": [] - } + "type": "object", + "required": [ + "strategy_type", + "params", + "reasoning", + "cell" ], - "summary": "List Strategy Types", - "tags": [ - "strategies" - ] - } - }, - "/api/strategies/{name}": { - "delete": { - "description": "Delete an instance-backed strategy. Built-ins cannot be deleted.\n\nRefused while the strategy holds an open position \u2014 close it first.", - "operationId": "delete_instance_api_strategies__name__delete", - "parameters": [ - { - "in": "path", - "name": "name", - "required": true, - "schema": { - "title": "Name", - "type": "string" - } + "title": "RecommendResponse", + "description": "The AI's pick, backtested so it ranks alongside the grid cells." + }, + "RefreshRequest": { + "properties": { + "refresh_token": { + "type": "string", + "title": "Refresh Token" } + }, + "type": "object", + "required": [ + "refresh_token" ], - "responses": { - "204": { - "description": "Successful Response" + "title": "RefreshRequest" + }, + "ResearchReportRead": { + "properties": { + "id": { + "type": "integer", + "title": "Id" }, - "422": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - "description": "Validation Error" + "symbol": { + "type": "string", + "title": "Symbol" + }, + "schema_version": { + "type": "integer", + "title": "Schema Version" + }, + "status": { + "type": "string", + "title": "Status" + }, + "provider": { + "type": "string", + "title": "Provider" + }, + "model": { + "type": "string", + "title": "Model" + }, + "error": { + "type": "string", + "title": "Error" + }, + "created_at": { + "type": "string", + "format": "date-time", + "title": "Created At" + }, + "sections": { + "additionalProperties": true, + "type": "object", + "title": "Sections" } }, - "security": [ - { - "OAuth2PasswordBearer": [] - } + "type": "object", + "required": [ + "id", + "symbol", + "schema_version", + "status", + "provider", + "model", + "error", + "created_at", + "sections" ], - "summary": "Delete Instance", - "tags": [ - "strategies" - ] - } - }, - "/api/strategies/{name}/ai-model": { - "patch": { - "description": "Pin an AI strategy to a provider + model (Claude / OpenAI / Gemini / Ollama).", - "operationId": "update_ai_model_api_strategies__name__ai_model_patch", - "parameters": [ - { - "in": "path", - "name": "name", - "required": true, - "schema": { - "title": "Name", + "title": "ResearchReportRead", + "description": "One report \u2014 `sections` is the parsed schema-formatted content." + }, + "ResearchRun": { + "properties": { + "symbol": { + "type": "string", + "maxLength": 24, + "title": "Symbol", + "default": "" + } + }, + "type": "object", + "title": "ResearchRun", + "description": "Manual trigger \u2014 one symbol, or blank for every watched symbol." + }, + "ResearchSettingsUpdate": { + "properties": { + "symbols": { + "items": { "type": "string" - } + }, + "type": "array", + "maxItems": 20, + "minItems": 1, + "title": "Symbols" + }, + "interval_hours": { + "type": "integer", + "maximum": 168.0, + "minimum": 1.0, + "title": "Interval Hours", + "default": 12 + }, + "writer_provider": { + "type": "string", + "maxLength": 32, + "minLength": 1, + "title": "Writer Provider" + }, + "writer_model": { + "type": "string", + "maxLength": 64, + "title": "Writer Model", + "default": "" + }, + "news_interval_hours": { + "anyOf": [ + { + "type": "integer", + "maximum": 48.0, + "minimum": 1.0 + }, + { + "type": "null" + } + ], + "title": "News Interval Hours" } + }, + "type": "object", + "required": [ + "symbols", + "writer_provider" ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/AiModelUpdate" + "title": "ResearchSettingsUpdate", + "description": "Research configuration \u2014 watched symbols, interval and writer LLM." + }, + "RiskSettingsUpdate": { + "properties": { + "stop_loss_pct": { + "anyOf": [ + { + "type": "number", + "maximum": 100.0, + "minimum": 0.0 + }, + { + "type": "string", + "pattern": "^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$" } - } + ], + "title": "Stop Loss Pct" }, - "required": true - }, - "responses": { - "200": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/StrategyRead" - } + "take_profit_pct": { + "anyOf": [ + { + "type": "number", + "maximum": 100.0, + "minimum": 0.0 + }, + { + "type": "string", + "pattern": "^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$" } - }, - "description": "Successful Response" + ], + "title": "Take Profit Pct" }, - "422": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } + "max_drawdown_pct": { + "anyOf": [ + { + "type": "number", + "maximum": 100.0, + "minimum": 0.0 + }, + { + "type": "string", + "pattern": "^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$" + } + ], + "title": "Max Drawdown Pct" + }, + "daily_loss_limit": { + "anyOf": [ + { + "type": "number", + "minimum": 0.0 + }, + { + "type": "string", + "pattern": "^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$" + } + ], + "title": "Daily Loss Limit" + }, + "max_position_notional": { + "anyOf": [ + { + "type": "number", + "minimum": 0.0 + }, + { + "type": "string", + "pattern": "^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$" } - }, - "description": "Validation Error" + ], + "title": "Max Position Notional" } }, - "security": [ - { - "OAuth2PasswordBearer": [] - } + "type": "object", + "required": [ + "stop_loss_pct", + "take_profit_pct", + "max_drawdown_pct", + "daily_loss_limit", + "max_position_notional" ], - "summary": "Update Ai Model", - "tags": [ - "strategies" - ] - } - }, - "/api/strategies/{name}/allocation": { - "patch": { - "description": "Set a strategy's capital budget; the engine caps its exposure to it.", - "operationId": "update_allocation_api_strategies__name__allocation_patch", - "parameters": [ - { - "in": "path", - "name": "name", - "required": true, - "schema": { - "title": "Name", - "type": "string" - } - } + "title": "RiskSettingsUpdate", + "description": "Global risk limits in percent (SL/TP/drawdown) or quote currency\n(loss limit, notional). 0 disables a limit." + }, + "Role": { + "type": "string", + "enum": [ + "admin", + "user" ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/AllocationUpdate" - } - } + "title": "Role", + "description": "Operator role. `admin` has full access; `user` is restricted\n(see plan: Authentication & Roles)." + }, + "SettingsRead": { + "properties": { + "mode": { + "$ref": "#/components/schemas/TradingMode" }, - "required": true - }, - "responses": { - "200": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/StrategyRead" - } - } + "binance_keys_configured": { + "type": "boolean", + "title": "Binance Keys Configured" + }, + "venue_credentials_configured": { + "additionalProperties": { + "type": "boolean" }, - "description": "Successful Response" + "type": "object", + "title": "Venue Credentials Configured" }, - "422": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } + "ai_provider": { + "type": "string", + "title": "Ai Provider" + }, + "ai_model": { + "type": "string", + "title": "Ai Model" + }, + "ai_base_url": { + "type": "string", + "title": "Ai Base Url" + }, + "ai_key_configured": { + "type": "boolean", + "title": "Ai Key Configured" + }, + "ai_spend_cap": { + "type": "string", + "pattern": "^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$", + "title": "Ai Spend Cap" + }, + "ai_spend_today": { + "type": "string", + "pattern": "^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$", + "title": "Ai Spend Today" + }, + "ai_action_mode": { + "type": "string", + "title": "Ai Action Mode" + }, + "llm_providers_configured": { + "additionalProperties": { + "type": "boolean" + }, + "type": "object", + "title": "Llm Providers Configured" + }, + "research_symbols": { + "items": { + "type": "string" + }, + "type": "array", + "title": "Research Symbols" + }, + "research_interval_hours": { + "type": "integer", + "title": "Research Interval Hours" + }, + "research_writer_provider": { + "type": "string", + "title": "Research Writer Provider" + }, + "research_writer_model": { + "type": "string", + "title": "Research Writer Model" + }, + "news_interval_hours": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" } + ], + "title": "News Interval Hours" + }, + "council_members": { + "items": { + "additionalProperties": { + "type": "string" + }, + "type": "object" }, - "description": "Validation Error" + "type": "array", + "title": "Council Members" + }, + "council_quorum": { + "type": "string", + "pattern": "^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$", + "title": "Council Quorum" + }, + "polymarket_refresh_hours": { + "type": "integer", + "title": "Polymarket Refresh Hours" + }, + "polymarket_research_hours": { + "type": "integer", + "title": "Polymarket Research Hours" + }, + "polymarket_edge_threshold": { + "type": "string", + "pattern": "^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$", + "title": "Polymarket Edge Threshold" + }, + "polymarket_min_confidence": { + "type": "string", + "pattern": "^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$", + "title": "Polymarket Min Confidence" + }, + "polymarket_screen_top": { + "type": "integer", + "title": "Polymarket Screen Top" + }, + "polymarket_stake": { + "type": "string", + "pattern": "^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$", + "title": "Polymarket Stake" + }, + "risk_stop_loss_pct": { + "type": "string", + "pattern": "^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$", + "title": "Risk Stop Loss Pct" + }, + "risk_take_profit_pct": { + "type": "string", + "pattern": "^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$", + "title": "Risk Take Profit Pct" + }, + "risk_max_drawdown_pct": { + "type": "string", + "pattern": "^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$", + "title": "Risk Max Drawdown Pct" + }, + "risk_daily_loss_limit": { + "type": "string", + "pattern": "^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$", + "title": "Risk Daily Loss Limit" + }, + "risk_max_position_notional": { + "type": "string", + "pattern": "^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$", + "title": "Risk Max Position Notional" } }, - "security": [ - { - "OAuth2PasswordBearer": [] - } - ], - "summary": "Update Allocation", - "tags": [ - "strategies" - ] - } - }, - "/api/strategies/{name}/close": { - "post": { - "description": "Close every open position held by the strategy.", - "operationId": "close_strategy_api_strategies__name__close_post", - "parameters": [ - { - "in": "path", - "name": "name", - "required": true, - "schema": { - "title": "Name", - "type": "string" - } - } - ], - "responses": { - "200": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/CloseResult" - } + "type": "object", + "required": [ + "mode", + "binance_keys_configured", + "venue_credentials_configured", + "ai_provider", + "ai_model", + "ai_base_url", + "ai_key_configured", + "ai_spend_cap", + "ai_spend_today", + "ai_action_mode", + "llm_providers_configured", + "research_symbols", + "research_interval_hours", + "research_writer_provider", + "research_writer_model", + "news_interval_hours", + "council_members", + "council_quorum", + "polymarket_refresh_hours", + "polymarket_research_hours", + "polymarket_edge_threshold", + "polymarket_min_confidence", + "polymarket_screen_top", + "polymarket_stake", + "risk_stop_loss_pct", + "risk_take_profit_pct", + "risk_max_drawdown_pct", + "risk_daily_loss_limit", + "risk_max_position_notional" + ], + "title": "SettingsRead" + }, + "StrategyRead": { + "properties": { + "name": { + "type": "string", + "title": "Name" + }, + "kind": { + "type": "string", + "title": "Kind" + }, + "symbol": { + "type": "string", + "title": "Symbol" + }, + "venue": { + "type": "string", + "title": "Venue" + }, + "market": { + "type": "string", + "title": "Market" + }, + "timeframe": { + "type": "string", + "title": "Timeframe" + }, + "enabled": { + "type": "boolean", + "title": "Enabled" + }, + "allocated": { + "type": "string", + "pattern": "^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$", + "title": "Allocated" + }, + "max_loss": { + "type": "string", + "pattern": "^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$", + "title": "Max Loss" + }, + "realized_pnl": { + "type": "string", + "pattern": "^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$", + "title": "Realized Pnl" + }, + "unrealized_pnl": { + "type": "string", + "pattern": "^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$", + "title": "Unrealized Pnl" + }, + "fees": { + "type": "string", + "pattern": "^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$", + "title": "Fees" + }, + "net_pnl": { + "type": "string", + "pattern": "^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$", + "title": "Net Pnl" + }, + "open_positions": { + "type": "integer", + "title": "Open Positions" + }, + "ai_provider": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Ai Provider" + }, + "ai_model": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" } - }, - "description": "Successful Response" + ], + "title": "Ai Model" }, - "422": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - "description": "Validation Error" + "is_instance": { + "type": "boolean", + "title": "Is Instance", + "default": false } }, - "security": [ - { - "OAuth2PasswordBearer": [] - } + "type": "object", + "required": [ + "name", + "kind", + "symbol", + "venue", + "market", + "timeframe", + "enabled", + "allocated", + "max_loss", + "realized_pnl", + "unrealized_pnl", + "fees", + "net_pnl", + "open_positions" ], - "summary": "Close Strategy", - "tags": [ - "strategies" - ] - } - }, - "/api/strategies/{name}/enabled": { - "patch": { - "description": "Enable or disable a strategy. Disabling stops new entries only.", - "operationId": "update_enabled_api_strategies__name__enabled_patch", - "parameters": [ - { - "in": "path", - "name": "name", - "required": true, - "schema": { - "title": "Name", - "type": "string" - } + "title": "StrategyRead", + "description": "A strategy's identity, lifecycle state and accounting summary." + }, + "StrategySummary": { + "properties": { + "strategy": { + "type": "string", + "title": "Strategy" + }, + "allocated": { + "type": "string", + "pattern": "^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$", + "title": "Allocated" + }, + "realized_pnl": { + "type": "string", + "pattern": "^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$", + "title": "Realized Pnl" + }, + "unrealized_pnl": { + "type": "string", + "pattern": "^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$", + "title": "Unrealized Pnl" + }, + "fees": { + "type": "string", + "pattern": "^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$", + "title": "Fees" + }, + "net_pnl": { + "type": "string", + "pattern": "^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$", + "title": "Net Pnl" + }, + "open_positions": { + "type": "integer", + "title": "Open Positions" } + }, + "type": "object", + "required": [ + "strategy", + "allocated", + "realized_pnl", + "unrealized_pnl", + "fees", + "net_pnl", + "open_positions" ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/EnabledUpdate" - } - } + "title": "StrategySummary" + }, + "StrategyTypeRead": { + "properties": { + "key": { + "type": "string", + "title": "Key" }, - "required": true - }, - "responses": { - "200": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/StrategyRead" - } - } + "label": { + "type": "string", + "title": "Label" + }, + "params": { + "items": { + "$ref": "#/components/schemas/ParamSpecRead" }, - "description": "Successful Response" + "type": "array", + "title": "Params" }, - "422": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } + "timeframes": { + "items": { + "type": "string" }, - "description": "Validation Error" - } - }, - "security": [ - { - "OAuth2PasswordBearer": [] - } - ], - "summary": "Update Enabled", - "tags": [ - "strategies" - ] - } - }, - "/api/system/watchdog": { - "get": { - "description": "Engine liveness \u2014 heartbeat age and whether an alert is warranted.", - "operationId": "get_watchdog_api_system_watchdog_get", - "responses": { - "200": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/WatchdogStatus" - } + "type": "array", + "title": "Timeframes" + }, + "venue": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" } - }, - "description": "Successful Response" + ], + "title": "Venue" } }, - "security": [ - { - "OAuth2PasswordBearer": [] - } + "type": "object", + "required": [ + "key", + "label", + "params", + "timeframes" ], - "summary": "Get Watchdog", - "tags": [ - "system" - ] - } - }, - "/api/tokens": { - "get": { - "description": "Every API token, newest-first. The secret is never returned.", - "operationId": "list_tokens_api_tokens_get", - "responses": { - "200": { - "content": { - "application/json": { - "schema": { - "items": { - "$ref": "#/components/schemas/ApiTokenRead" - }, - "title": "Response List Tokens Api Tokens Get", - "type": "array" - } - } - }, - "description": "Successful Response" + "title": "StrategyTypeRead", + "description": "One pickable strategy type and its parameter schema." + }, + "Ticker": { + "properties": { + "symbol": { + "type": "string", + "title": "Symbol" + }, + "price": { + "type": "string", + "pattern": "^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$", + "title": "Price" + }, + "change_pct_24h": { + "type": "string", + "pattern": "^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$", + "title": "Change Pct 24H" + }, + "quote_volume_24h": { + "type": "string", + "pattern": "^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$", + "title": "Quote Volume 24H" } }, - "security": [ - { - "OAuth2PasswordBearer": [] - } + "type": "object", + "required": [ + "symbol", + "price", + "change_pct_24h", + "quote_volume_24h" ], - "summary": "List Tokens", - "tags": [ - "tokens" - ] + "title": "Ticker" }, - "post": { - "description": "Create a token. The plaintext is in the response and shown only here.", - "operationId": "create_token_api_tokens_post", - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ApiTokenCreate" - } - } + "TokenPair": { + "properties": { + "access_token": { + "type": "string", + "title": "Access Token" }, - "required": true + "refresh_token": { + "type": "string", + "title": "Refresh Token" + }, + "token_type": { + "type": "string", + "title": "Token Type", + "default": "bearer" + } }, - "responses": { - "201": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ApiTokenCreated" - } + "type": "object", + "required": [ + "access_token", + "refresh_token" + ], + "title": "TokenPair" + }, + "Trade": { + "properties": { + "id": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" } - }, - "description": "Successful Response" + ], + "title": "Id" }, - "422": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } + "strategy": { + "type": "string", + "maxLength": 64, + "title": "Strategy" + }, + "market": { + "type": "string", + "maxLength": 8, + "title": "Market" + }, + "symbol": { + "type": "string", + "maxLength": 80, + "title": "Symbol" + }, + "side": { + "type": "string", + "maxLength": 8, + "title": "Side" + }, + "quantity": { + "type": "string", + "pattern": "^(?!^[-+.]*$)[+-]?0*(?:\\d{0,18}|(?=[\\d.]{1,29}0*$)\\d{0,18}\\.\\d{0,10}0*$)", + "title": "Quantity" + }, + "price": { + "type": "string", + "pattern": "^(?!^[-+.]*$)[+-]?0*(?:\\d{0,18}|(?=[\\d.]{1,29}0*$)\\d{0,18}\\.\\d{0,10}0*$)", + "title": "Price" + }, + "fee": { + "type": "string", + "pattern": "^(?!^[-+.]*$)[+-]?0*(?:\\d{0,18}|(?=[\\d.]{1,29}0*$)\\d{0,18}\\.\\d{0,10}0*$)", + "title": "Fee", + "default": "0" + }, + "realized_pnl": { + "type": "string", + "pattern": "^(?!^[-+.]*$)[+-]?0*(?:\\d{0,18}|(?=[\\d.]{1,29}0*$)\\d{0,18}\\.\\d{0,10}0*$)", + "title": "Realized Pnl", + "default": "0" + }, + "mode": { + "type": "string", + "maxLength": 8, + "title": "Mode", + "default": "sim" + }, + "client_order_id": { + "anyOf": [ + { + "type": "string", + "maxLength": 40 + }, + { + "type": "null" } - }, - "description": "Validation Error" + ], + "title": "Client Order Id" + }, + "executed_at": { + "type": "string", + "format": "date-time", + "title": "Executed At" } }, - "security": [ - { - "OAuth2PasswordBearer": [] - } + "type": "object", + "required": [ + "strategy", + "market", + "symbol", + "side", + "quantity", + "price", + "executed_at" ], - "summary": "Create Token", - "tags": [ - "tokens" - ] - } - }, - "/api/tokens/{token_id}": { - "delete": { - "description": "Revoke a token by id \u2014 it can no longer authenticate.", - "operationId": "revoke_token_api_tokens__token_id__delete", - "parameters": [ - { - "in": "path", - "name": "token_id", - "required": true, - "schema": { - "title": "Token Id", - "type": "integer" - } - } + "title": "Trade", + "description": "An executed fill \u2014 the transaction-log row, attributed to a strategy.\n\n`realized_pnl` is the PnL booked *by this fill* (0 for opening/adding fills,\nnon-zero when it reduces or closes a position)." + }, + "TradingMode": { + "type": "string", + "enum": [ + "sim", + "testnet", + "live" ], - "responses": { - "204": { - "description": "Successful Response" + "title": "TradingMode", + "description": "Where orders are routed. Sim is the safe default." + }, + "UserCreate": { + "properties": { + "username": { + "type": "string", + "maxLength": 64, + "minLength": 2, + "title": "Username" }, - "422": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - "description": "Validation Error" + "password": { + "type": "string", + "maxLength": 128, + "minLength": 8, + "title": "Password" + }, + "role": { + "$ref": "#/components/schemas/Role", + "default": "user" } }, - "security": [ - { - "OAuth2PasswordBearer": [] - } + "type": "object", + "required": [ + "username", + "password" ], - "summary": "Revoke Token", - "tags": [ - "tokens" - ] - } - }, - "/api/users": { - "get": { - "operationId": "list_users_api_users_get", - "responses": { - "200": { - "content": { - "application/json": { - "schema": { - "items": { - "$ref": "#/components/schemas/UserRead" - }, - "title": "Response List Users Api Users Get", - "type": "array" - } - } - }, - "description": "Successful Response" + "title": "UserCreate" + }, + "UserInfo": { + "properties": { + "username": { + "type": "string", + "title": "Username" + }, + "role": { + "type": "string", + "title": "Role" } }, - "security": [ - { - "OAuth2PasswordBearer": [] - } + "type": "object", + "required": [ + "username", + "role" ], - "summary": "List Users", - "tags": [ - "users" - ] + "title": "UserInfo" }, - "post": { - "operationId": "create_user_api_users_post", - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/UserCreate" - } - } + "UserRead": { + "properties": { + "id": { + "type": "integer", + "title": "Id" }, - "required": true - }, - "responses": { - "201": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/UserRead" - } - } - }, - "description": "Successful Response" + "username": { + "type": "string", + "title": "Username" }, - "422": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - "description": "Validation Error" + "role": { + "type": "string", + "title": "Role" + }, + "is_active": { + "type": "boolean", + "title": "Is Active" } }, - "security": [ - { - "OAuth2PasswordBearer": [] - } - ], - "summary": "Create User", - "tags": [ - "users" - ] - } - }, - "/api/users/{user_id}": { - "patch": { - "operationId": "update_user_api_users__user_id__patch", - "parameters": [ - { - "in": "path", - "name": "user_id", - "required": true, - "schema": { - "title": "User Id", - "type": "integer" - } - } + "type": "object", + "required": [ + "id", + "username", + "role", + "is_active" ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/UserUpdate" + "title": "UserRead" + }, + "UserUpdate": { + "properties": { + "role": { + "anyOf": [ + { + "$ref": "#/components/schemas/Role" + }, + { + "type": "null" } - } + ] }, - "required": true + "is_active": { + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "null" + } + ], + "title": "Is Active" + } }, - "responses": { - "200": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/UserRead" + "type": "object", + "title": "UserUpdate" + }, + "ValidationError": { + "properties": { + "loc": { + "items": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "integer" } - } + ] }, - "description": "Successful Response" + "type": "array", + "title": "Location" }, - "422": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - "description": "Validation Error" + "msg": { + "type": "string", + "title": "Message" + }, + "type": { + "type": "string", + "title": "Error Type" + }, + "input": { + "title": "Input" + }, + "ctx": { + "type": "object", + "title": "Context" } }, - "security": [ - { - "OAuth2PasswordBearer": [] - } + "type": "object", + "required": [ + "loc", + "msg", + "type" ], - "summary": "Update User", - "tags": [ - "users" - ] - } - }, - "/api/users/{user_id}/password": { - "post": { - "operationId": "reset_password_api_users__user_id__password_post", - "parameters": [ - { - "in": "path", - "name": "user_id", - "required": true, - "schema": { - "title": "User Id", - "type": "integer" - } + "title": "ValidationError" + }, + "VenueCredentialsUpdate": { + "properties": { + "fields": { + "additionalProperties": { + "type": "string" + }, + "type": "object", + "minProperties": 1, + "title": "Fields" } + }, + "type": "object", + "required": [ + "fields" ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/PasswordReset" - } - } + "title": "VenueCredentialsUpdate", + "description": "A venue's credential fields \u2014 names validated against the catalogue." + }, + "VenueRead": { + "properties": { + "name": { + "type": "string", + "title": "Name" }, - "required": true - }, - "responses": { - "204": { - "description": "Successful Response" + "asset_class": { + "type": "string", + "title": "Asset Class" }, - "422": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } + "supports_sandbox": { + "type": "boolean", + "title": "Supports Sandbox" + }, + "active": { + "type": "boolean", + "title": "Active" + }, + "credential_fields": { + "items": { + "type": "string" }, - "description": "Validation Error" + "type": "array", + "title": "Credential Fields" } }, - "security": [ - { - "OAuth2PasswordBearer": [] - } + "type": "object", + "required": [ + "name", + "asset_class", + "supports_sandbox", + "active", + "credential_fields" ], - "summary": "Reset Password", - "tags": [ - "users" - ] - } - }, - "/api/venues": { - "get": { - "description": "Every supported venue, with the active one flagged.", - "operationId": "get_venues_api_venues_get", - "responses": { - "200": { - "content": { - "application/json": { - "schema": { - "items": { - "$ref": "#/components/schemas/VenueRead" - }, - "title": "Response Get Venues Api Venues Get", - "type": "array" - } - } - }, - "description": "Successful Response" + "title": "VenueRead" + }, + "WatchUpdate": { + "properties": { + "watched": { + "type": "boolean", + "title": "Watched" } }, - "security": [ - { - "OAuth2PasswordBearer": [] - } + "type": "object", + "required": [ + "watched" ], - "summary": "Get Venues", - "tags": [ - "venues" - ] - } - }, - "/api/venues/active": { - "put": { - "description": "Change the active trading venue.", - "operationId": "set_active_api_venues_active_put", - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ActiveVenueUpdate" - } - } + "title": "WatchUpdate" + }, + "WatchdogStatus": { + "properties": { + "alive": { + "type": "boolean", + "title": "Alive" }, - "required": true - }, - "responses": { - "200": { - "content": { - "application/json": { - "schema": { - "items": { - "$ref": "#/components/schemas/VenueRead" - }, - "title": "Response Set Active Api Venues Active Put", - "type": "array" - } + "stale": { + "type": "boolean", + "title": "Stale" + }, + "alert": { + "type": "boolean", + "title": "Alert" + }, + "last_beat": { + "anyOf": [ + { + "type": "string", + "format": "date-time" + }, + { + "type": "null" } - }, - "description": "Successful Response" + ], + "title": "Last Beat" }, - "422": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } + "age_seconds": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" } - }, - "description": "Validation Error" + ], + "title": "Age Seconds" + }, + "open_positions": { + "type": "integer", + "title": "Open Positions" } }, - "security": [ - { - "OAuth2PasswordBearer": [] - } + "type": "object", + "required": [ + "alive", + "stale", + "alert", + "last_beat", + "age_seconds", + "open_positions" ], - "summary": "Set Active", - "tags": [ - "venues" - ] + "title": "WatchdogStatus", + "description": "Engine liveness assessed from the heartbeat and open-position count." } }, - "/health": { - "get": { - "description": "Liveness probe \u2014 used by Docker, CI and the deploy script.", - "operationId": "health_health_get", - "responses": { - "200": { - "content": { - "application/json": { - "schema": { - "additionalProperties": { - "type": "string" - }, - "title": "Response Health Health Get", - "type": "object" - } - } - }, - "description": "Successful Response" + "securitySchemes": { + "OAuth2PasswordBearer": { + "type": "oauth2", + "flows": { + "password": { + "scopes": {}, + "tokenUrl": "/api/auth/login" } - }, - "summary": "Health", - "tags": [ - "system" - ] + } } } } diff --git a/web/src/lib/api/schema.d.ts b/web/src/lib/api/schema.d.ts index be09c99..36f8af3 100644 --- a/web/src/lib/api/schema.d.ts +++ b/web/src/lib/api/schema.d.ts @@ -4,7 +4,7 @@ */ export interface paths { - "/api/ai/analyze": { + "/api/auth/login": { parameters: { query?: never; header?: never; @@ -14,37 +14,39 @@ export interface paths { get?: never; put?: never; /** - * Analyze And Decide - * @description Run a free-form analyze-and-decide task through the configured LLM. + * Login + * @description Exchange operator credentials for an access + refresh token pair. + * + * Rate-limited: repeated failures lock the username out (brute-force guard). */ - post: operations["analyze_and_decide_api_ai_analyze_post"]; + post: operations["login_api_auth_login_post"]; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/api/ai/decisions": { + "/api/auth/refresh": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; + get?: never; + put?: never; /** - * Decision Log - * @description The recent decision log — what each model decided, newest first. + * Refresh + * @description Issue a fresh token pair from a valid refresh token. */ - get: operations["decision_log_api_ai_decisions_get"]; - put?: never; - post?: never; + post: operations["refresh_api_auth_refresh_post"]; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/api/ai/local": { + "/api/auth/me": { parameters: { query?: never; header?: never; @@ -52,12 +54,10 @@ export interface paths { cookie?: never; }; /** - * Local Models - * @description Whether local models are deployed (Ollama) or deployable (llmfit). - * - * The snapshot is cached for ~5 minutes; `refresh=true` re-probes now. + * Me + * @description Return the currently authenticated operator. */ - get: operations["local_models_api_ai_local_get"]; + get: operations["me_api_auth_me_get"]; put?: never; post?: never; delete?: never; @@ -66,47 +66,42 @@ export interface paths { patch?: never; trace?: never; }; - "/api/ai/models": { + "/api/users": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; - /** - * Model Performance - * @description Per-model rollup — decisions, action mix and total cost for each model. - */ - get: operations["model_performance_api_ai_models_get"]; + /** List Users */ + get: operations["list_users_api_users_get"]; put?: never; - post?: never; + /** Create User */ + post: operations["create_user_api_users_post"]; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/api/ai/signals": { + "/api/users/{user_id}": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; - /** - * List Signals - * @description Recent AI signals (notify-mode decisions), newest first. - */ - get: operations["list_signals_api_ai_signals_get"]; + get?: never; put?: never; post?: never; delete?: never; options?: never; head?: never; - patch?: never; + /** Update User */ + patch: operations["update_user_api_users__user_id__patch"]; trace?: never; }; - "/api/ai/signals/{signal_id}/confirm": { + "/api/users/{user_id}/password": { parameters: { query?: never; header?: never; @@ -115,63 +110,55 @@ export interface paths { }; get?: never; put?: never; - /** - * Confirm Signal - * @description Execute a pending AI signal through the same risk + executor path. - * - * Re-prices at confirmation and re-runs the risk gate, so a stale or - * risk-blocked signal cannot slip through. Marks the signal `executed`. - */ - post: operations["confirm_signal_api_ai_signals__signal_id__confirm_post"]; + /** Reset Password */ + post: operations["reset_password_api_users__user_id__password_post"]; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/api/ai/signals/{signal_id}/dismiss": { + "/api/market/tickers": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; - get?: never; - put?: never; /** - * Dismiss Signal - * @description Dismiss a pending AI signal without executing it. + * List Tickers + * @description Latest 24h tickers — from the live snapshot, or REST as a fallback. */ - post: operations["dismiss_signal_api_ai_signals__signal_id__dismiss_post"]; + get: operations["list_tickers_api_market_tickers_get"]; + put?: never; + post?: never; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/api/auth/login": { + "/api/market/klines": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; - get?: never; - put?: never; /** - * Login - * @description Exchange operator credentials for an access + refresh token pair. - * - * Rate-limited: repeated failures lock the username out (brute-force guard). + * Get Klines + * @description Candle history — served from the cache, refreshed from the active venue. */ - post: operations["login_api_auth_login_post"]; + get: operations["get_klines_api_market_klines_get"]; + put?: never; + post?: never; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/api/auth/me": { + "/api/market/funding": { parameters: { query?: never; header?: never; @@ -179,10 +166,10 @@ export interface paths { cookie?: never; }; /** - * Me - * @description Return the currently authenticated operator. + * Get Funding + * @description Current futures funding rate for a symbol. */ - get: operations["me_api_auth_me_get"]; + get: operations["get_funding_api_market_funding_get"]; put?: never; post?: never; delete?: never; @@ -191,47 +178,47 @@ export interface paths { patch?: never; trace?: never; }; - "/api/auth/refresh": { + "/api/market/orderbook": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; - get?: never; - put?: never; /** - * Refresh - * @description Issue a fresh token pair from a valid refresh token. + * Get Orderbook + * @description Order-book depth for a symbol. */ - post: operations["refresh_api_auth_refresh_post"]; + get: operations["get_orderbook_api_market_orderbook_get"]; + put?: never; + post?: never; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/api/backtest/run": { + "/api/market/latency": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; - get?: never; - put?: never; /** - * Run - * @description Replay a strategy over the requested date range and return the result. + * Feed Latency + * @description How delayed the price data is, per feed (ws event time + REST RTT). */ - post: operations["run_api_backtest_run_post"]; + get: operations["feed_latency_api_market_latency_get"]; + put?: never; + post?: never; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/api/connections/edges/{edge_id}": { + "/api/orders/manual": { parameters: { query?: never; header?: never; @@ -240,38 +227,38 @@ export interface paths { }; get?: never; put?: never; - post?: never; /** - * Delete Edge - * @description Delete an edge (reject a suggestion or prune a curated link). + * Place Manual Order + * @description Place a one-off order, risk-checked and recorded as `manual`. */ - delete: operations["delete_edge_api_connections_edges__edge_id__delete"]; + post: operations["place_manual_order_api_orders_manual_post"]; + delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/api/connections/edges/{edge_id}/approve": { + "/api/portfolio/summary": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; - get?: never; - put?: never; /** - * Approve Edge - * @description Approve a pending edge so it joins the graph. + * Summary + * @description Aggregate accounting — equity, PnL, fees, allocated vs idle capital. */ - post: operations["approve_edge_api_connections_edges__edge_id__approve_post"]; + get: operations["summary_api_portfolio_summary_get"]; + put?: never; + post?: never; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/api/connections/graph": { + "/api/portfolio/equity": { parameters: { query?: never; header?: never; @@ -279,10 +266,10 @@ export interface paths { cookie?: never; }; /** - * Get Graph - * @description The full graph. Pending (AI-suggested) edges are opt-in. + * Equity + * @description Equity-curve history, oldest-first. */ - get: operations["get_graph_api_connections_graph_get"]; + get: operations["equity_api_portfolio_equity_get"]; put?: never; post?: never; delete?: never; @@ -291,47 +278,47 @@ export interface paths { patch?: never; trace?: never; }; - "/api/connections/nodes": { + "/api/portfolio/positions": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; - get?: never; - put?: never; /** - * Create Node - * @description Create a node (or return the existing one with the same label). + * Positions + * @description Currently open positions across all strategies. */ - post: operations["create_node_api_connections_nodes_post"]; + get: operations["positions_api_portfolio_positions_get"]; + put?: never; + post?: never; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/api/connections/suggest/{node_id}": { + "/api/portfolio/trades": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; - get?: never; - put?: never; /** - * Suggest Connections - * @description Ask the LLM to suggest related entities for a node (stored pending). + * Trades + * @description Most-recent executed trades. */ - post: operations["suggest_connections_api_connections_suggest__node_id__post"]; + get: operations["trades_api_portfolio_trades_get"]; + put?: never; + post?: never; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/api/costs/ledger": { + "/api/portfolio/costs": { parameters: { query?: never; header?: never; @@ -339,10 +326,10 @@ export interface paths { cookie?: never; }; /** - * Costs Ledger - * @description The per-call ledger, newest first — filterable by purpose. + * Costs + * @description Cost visibility — trading fees by market, fee rates, and LLM spend. */ - get: operations["costs_ledger_api_costs_ledger_get"]; + get: operations["costs_api_portfolio_costs_get"]; put?: never; post?: never; delete?: never; @@ -351,7 +338,7 @@ export interface paths { patch?: never; trace?: never; }; - "/api/costs/summary": { + "/api/strategies": { parameters: { query?: never; header?: never; @@ -359,19 +346,23 @@ export interface paths { cookie?: never; }; /** - * Costs Summary - * @description Spend totals and breakdowns over the last 30 days. + * List Strategies + * @description Every registered strategy with its allocation, state and PnL. */ - get: operations["costs_summary_api_costs_summary_get"]; + get: operations["list_strategies_api_strategies_get"]; put?: never; - post?: never; + /** + * Create Instance + * @description Create a strategy instance: any registered type on any chosen coin. + */ + post: operations["create_instance_api_strategies_post"]; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/api/history/audit": { + "/api/strategies/types": { parameters: { query?: never; header?: never; @@ -379,10 +370,10 @@ export interface paths { cookie?: never; }; /** - * Audit - * @description The audit log of config-changing actions, newest-first. + * List Strategy Types + * @description Every pickable strategy type with its typed parameter schema. */ - get: operations["audit_api_history_audit_get"]; + get: operations["list_strategy_types_api_strategies_types_get"]; put?: never; post?: never; delete?: never; @@ -391,47 +382,49 @@ export interface paths { patch?: never; trace?: never; }; - "/api/history/trades": { + "/api/strategies/{name}": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; - /** - * Trades - * @description The transaction log, newest-first, optionally filtered by date range. - */ - get: operations["trades_api_history_trades_get"]; + get?: never; put?: never; post?: never; - delete?: never; + /** + * Delete Instance + * @description Delete an instance-backed strategy. Built-ins cannot be deleted. + * + * Refused while the strategy holds an open position — close it first. + */ + delete: operations["delete_instance_api_strategies__name__delete"]; options?: never; head?: never; patch?: never; trace?: never; }; - "/api/history/trades.csv": { + "/api/strategies/{name}/allocation": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; - /** - * Trades Csv - * @description The transaction log as a CSV download (oldest-first) for tax/reporting. - */ - get: operations["trades_csv_api_history_trades_csv_get"]; + get?: never; put?: never; post?: never; delete?: never; options?: never; head?: never; - patch?: never; + /** + * Update Allocation + * @description Set a strategy's capital budget; the engine caps its exposure to it. + */ + patch: operations["update_allocation_api_strategies__name__allocation_patch"]; trace?: never; }; - "/api/lab/compare": { + "/api/strategies/{name}/enabled": { parameters: { query?: never; header?: never; @@ -440,18 +433,18 @@ export interface paths { }; get?: never; put?: never; - /** - * Compare Grid - * @description Backtest every (type × symbol) cell over the same recent range. - */ - post: operations["compare_grid_api_lab_compare_post"]; + post?: never; delete?: never; options?: never; head?: never; - patch?: never; + /** + * Update Enabled + * @description Enable or disable a strategy. Disabling stops new entries only. + */ + patch: operations["update_enabled_api_strategies__name__enabled_patch"]; trace?: never; }; - "/api/lab/recommend": { + "/api/strategies/{name}/ai-model": { parameters: { query?: never; header?: never; @@ -460,78 +453,58 @@ export interface paths { }; get?: never; put?: never; - /** - * Recommend - * @description Ask the configured LLM to pick a strategy for the coin, then backtest it. - */ - post: operations["recommend_api_lab_recommend_post"]; + post?: never; delete?: never; options?: never; head?: never; - patch?: never; - trace?: never; - }; - "/api/market/funding": { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; /** - * Get Funding - * @description Current futures funding rate for a symbol. + * Update Ai Model + * @description Pin an AI strategy to a provider + model (Claude / OpenAI / Gemini / Ollama). */ - get: operations["get_funding_api_market_funding_get"]; - put?: never; - post?: never; - delete?: never; - options?: never; - head?: never; - patch?: never; + patch: operations["update_ai_model_api_strategies__name__ai_model_patch"]; trace?: never; }; - "/api/market/klines": { + "/api/strategies/{name}/close": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; + get?: never; + put?: never; /** - * Get Klines - * @description Candle history — served from the cache, refreshed from the active venue. + * Close Strategy + * @description Close every open position held by the strategy. */ - get: operations["get_klines_api_market_klines_get"]; - put?: never; - post?: never; + post: operations["close_strategy_api_strategies__name__close_post"]; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/api/market/latency": { + "/api/backtest/run": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; + get?: never; + put?: never; /** - * Feed Latency - * @description How delayed the price data is, per feed (ws event time + REST RTT). + * Run + * @description Replay a strategy over the requested date range and return the result. */ - get: operations["feed_latency_api_market_latency_get"]; - put?: never; - post?: never; + post: operations["run_api_backtest_run_post"]; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/api/market/orderbook": { + "/api/settings": { parameters: { query?: never; header?: never; @@ -539,10 +512,10 @@ export interface paths { cookie?: never; }; /** - * Get Orderbook - * @description Order-book depth for a symbol. + * Read Settings + * @description Current trading mode and whether Binance keys are stored. */ - get: operations["get_orderbook_api_market_orderbook_get"]; + get: operations["read_settings_api_settings_get"]; put?: never; post?: never; delete?: never; @@ -551,19 +524,19 @@ export interface paths { patch?: never; trace?: never; }; - "/api/market/tickers": { + "/api/settings/mode": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; + get?: never; /** - * List Tickers - * @description Latest 24h tickers — from the live snapshot, or REST as a fallback. + * Update Mode + * @description Switch the trading mode (Sim / Testnet / Live). */ - get: operations["list_tickers_api_market_tickers_get"]; - put?: never; + put: operations["update_mode_api_settings_mode_put"]; post?: never; delete?: never; options?: never; @@ -571,19 +544,19 @@ export interface paths { patch?: never; trace?: never; }; - "/api/news": { + "/api/settings/binance-keys": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; + get?: never; /** - * List News - * @description Recent headlines, newest first — filterable by `symbol` and `category`. + * Update Binance Keys + * @description Store the Binance API credentials (encrypted at rest). */ - get: operations["list_news_api_news_get"]; - put?: never; + put: operations["update_binance_keys_api_settings_binance_keys_put"]; post?: never; delete?: never; options?: never; @@ -591,7 +564,7 @@ export interface paths { patch?: never; trace?: never; }; - "/api/news/refresh": { + "/api/settings/venue-credentials/{venue}": { parameters: { query?: never; header?: never; @@ -599,19 +572,22 @@ export interface paths { cookie?: never; }; get?: never; - put?: never; /** - * Refresh News - * @description Fetch every configured feed now. Returns the number of new headlines. + * Update Venue Credentials + * @description Store a venue's credentials (encrypted at rest). + * + * The submitted field names must exactly match the venue's declared + * `credential_fields`, and every value must be non-empty. */ - post: operations["refresh_news_api_news_refresh_post"]; + put: operations["update_venue_credentials_api_settings_venue_credentials__venue__put"]; + post?: never; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/api/orders/manual": { + "/api/settings/llm-credentials/{provider}": { parameters: { query?: never; header?: never; @@ -619,31 +595,31 @@ export interface paths { cookie?: never; }; get?: never; - put?: never; /** - * Place Manual Order - * @description Place a one-off order, risk-checked and recorded as `manual`. + * Update Llm Credentials + * @description Store one LLM provider's API key and/or base URL (encrypted at rest). */ - post: operations["place_manual_order_api_orders_manual_post"]; + put: operations["update_llm_credentials_api_settings_llm_credentials__provider__put"]; + post?: never; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/api/polymarket/analyses": { + "/api/settings/ai-spend-cap": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; + get?: never; /** - * List Analyses - * @description Recent AI bet analyses, newest first — optionally for one market. + * Update Ai Spend Cap + * @description Set the daily LLM spend cap — AI strategies pause once it is reached. */ - get: operations["list_analyses_api_polymarket_analyses_get"]; - put?: never; + put: operations["update_ai_spend_cap_api_settings_ai_spend_cap_put"]; post?: never; delete?: never; options?: never; @@ -651,19 +627,19 @@ export interface paths { patch?: never; trace?: never; }; - "/api/polymarket/markets": { + "/api/settings/ai": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; + get?: never; /** - * List Markets - * @description Discovered markets, most-traded first — filterable and searchable. + * Update Ai Settings + * @description Configure the AI provider. The API key is encrypted at rest. */ - get: operations["list_markets_api_polymarket_markets_get"]; - put?: never; + put: operations["update_ai_settings_api_settings_ai_put"]; post?: never; delete?: never; options?: never; @@ -671,7 +647,7 @@ export interface paths { patch?: never; trace?: never; }; - "/api/polymarket/markets/{condition_id}/analyze": { + "/api/settings/research": { parameters: { query?: never; header?: never; @@ -679,19 +655,21 @@ export interface paths { cookie?: never; }; get?: never; - put?: never; /** - * Analyze Now - * @description Run one AI analysis for a market immediately (a paid LLM call). + * Update Research Settings + * @description Configure research reports and the news refresh interval. + * + * Scheduler-interval changes take effect on the next engine restart. */ - post: operations["analyze_now_api_polymarket_markets__condition_id__analyze_post"]; + put: operations["update_research_settings_api_settings_research_put"]; + post?: never; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/api/polymarket/markets/{condition_id}/watch": { + "/api/settings/polymarket": { parameters: { query?: never; header?: never; @@ -700,10 +678,12 @@ export interface paths { }; get?: never; /** - * Update Watched - * @description Pin or unpin a market for scheduled AI analysis. + * Update Polymarket Settings + * @description Configure Polymarket discovery and AI bet screening. + * + * Scheduler-interval changes take effect on the next engine restart. */ - put: operations["update_watched_api_polymarket_markets__condition_id__watch_put"]; + put: operations["update_polymarket_settings_api_settings_polymarket_put"]; post?: never; delete?: never; options?: never; @@ -711,7 +691,7 @@ export interface paths { patch?: never; trace?: never; }; - "/api/polymarket/refresh": { + "/api/settings/risk": { parameters: { query?: never; header?: never; @@ -719,31 +699,34 @@ export interface paths { cookie?: never; }; get?: never; - put?: never; /** - * Refresh Markets - * @description Pull the latest top markets from the Gamma API now. + * Update Risk Settings + * @description Set the global risk limits (0 disables a limit). + * + * Changes apply on the next engine tick — a stop-loss you enable may + * immediately close an already-losing position. */ - post: operations["refresh_markets_api_polymarket_refresh_post"]; + put: operations["update_risk_settings_api_settings_risk_put"]; + post?: never; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/api/portfolio/costs": { + "/api/settings/council": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; + get?: never; /** - * Costs - * @description Cost visibility — trading fees by market, fee rates, and LLM spend. + * Update Council Settings + * @description Configure the AI council members and quorum. */ - get: operations["costs_api_portfolio_costs_get"]; - put?: never; + put: operations["update_council_settings_api_settings_council_put"]; post?: never; delete?: never; options?: never; @@ -751,19 +734,19 @@ export interface paths { patch?: never; trace?: never; }; - "/api/portfolio/equity": { + "/api/settings/ai-action-mode": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; + get?: never; /** - * Equity - * @description Equity-curve history, oldest-first. + * Update Ai Action Mode + * @description Set how AI decisions are applied: `notify` (confirm) or `auto`. */ - get: operations["equity_api_portfolio_equity_get"]; - put?: never; + put: operations["update_ai_action_mode_api_settings_ai_action_mode_put"]; post?: never; delete?: never; options?: never; @@ -771,7 +754,7 @@ export interface paths { patch?: never; trace?: never; }; - "/api/portfolio/positions": { + "/api/system/watchdog": { parameters: { query?: never; header?: never; @@ -779,10 +762,10 @@ export interface paths { cookie?: never; }; /** - * Positions - * @description Currently open positions across all strategies. + * Get Watchdog + * @description Engine liveness — heartbeat age and whether an alert is warranted. */ - get: operations["positions_api_portfolio_positions_get"]; + get: operations["get_watchdog_api_system_watchdog_get"]; put?: never; post?: never; delete?: never; @@ -791,7 +774,7 @@ export interface paths { patch?: never; trace?: never; }; - "/api/portfolio/summary": { + "/api/history/trades": { parameters: { query?: never; header?: never; @@ -799,10 +782,10 @@ export interface paths { cookie?: never; }; /** - * Summary - * @description Aggregate accounting — equity, PnL, fees, allocated vs idle capital. + * Trades + * @description The transaction log, newest-first, optionally filtered by date range. */ - get: operations["summary_api_portfolio_summary_get"]; + get: operations["trades_api_history_trades_get"]; put?: never; post?: never; delete?: never; @@ -811,7 +794,7 @@ export interface paths { patch?: never; trace?: never; }; - "/api/portfolio/trades": { + "/api/history/audit": { parameters: { query?: never; header?: never; @@ -819,10 +802,10 @@ export interface paths { cookie?: never; }; /** - * Trades - * @description Most-recent executed trades. + * Audit + * @description The audit log of config-changing actions, newest-first. */ - get: operations["trades_api_portfolio_trades_get"]; + get: operations["audit_api_history_audit_get"]; put?: never; post?: never; delete?: never; @@ -831,7 +814,7 @@ export interface paths { patch?: never; trace?: never; }; - "/api/research": { + "/api/history/trades.csv": { parameters: { query?: never; header?: never; @@ -839,10 +822,10 @@ export interface paths { cookie?: never; }; /** - * List Reports - * @description Recent reports, newest first — optionally filtered by `symbol`. + * Trades Csv + * @description The transaction log as a CSV download (oldest-first) for tax/reporting. */ - get: operations["list_reports_api_research_get"]; + get: operations["trades_csv_api_history_trades_csv_get"]; put?: never; post?: never; delete?: never; @@ -851,7 +834,7 @@ export interface paths { patch?: never; trace?: never; }; - "/api/research/run": { + "/api/ai/analyze": { parameters: { query?: never; header?: never; @@ -861,17 +844,17 @@ export interface paths { get?: never; put?: never; /** - * Run Research - * @description Write a report now — for `symbol`, or every watched symbol when blank. + * Analyze And Decide + * @description Run a free-form analyze-and-decide task through the configured LLM. */ - post: operations["run_research_api_research_run_post"]; + post: operations["analyze_and_decide_api_ai_analyze_post"]; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/api/research/{report_id}": { + "/api/ai/models": { parameters: { query?: never; header?: never; @@ -879,10 +862,10 @@ export interface paths { cookie?: never; }; /** - * Get Report - * @description One report with its full sections. + * Model Performance + * @description Per-model rollup — decisions, action mix and total cost for each model. */ - get: operations["get_report_api_research__report_id__get"]; + get: operations["model_performance_api_ai_models_get"]; put?: never; post?: never; delete?: never; @@ -891,7 +874,7 @@ export interface paths { patch?: never; trace?: never; }; - "/api/research/{report_id}/review": { + "/api/ai/decisions": { parameters: { query?: never; header?: never; @@ -899,23 +882,19 @@ export interface paths { cookie?: never; }; /** - * Get Review - * @description The newest council review for a report, with every vote. + * Decision Log + * @description The recent decision log — what each model decided, newest first. */ - get: operations["get_review_api_research__report_id__review_get"]; + get: operations["decision_log_api_ai_decisions_get"]; put?: never; - /** - * Rerun Review - * @description Re-run the council on a report now (admin). - */ - post: operations["rerun_review_api_research__report_id__review_post"]; + post?: never; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/api/settings": { + "/api/ai/signals": { parameters: { query?: never; header?: never; @@ -923,10 +902,10 @@ export interface paths { cookie?: never; }; /** - * Read Settings - * @description Current trading mode and whether Binance keys are stored. + * List Signals + * @description Recent AI signals (notify-mode decisions), newest first. */ - get: operations["read_settings_api_settings_get"]; + get: operations["list_signals_api_ai_signals_get"]; put?: never; post?: never; delete?: never; @@ -935,7 +914,7 @@ export interface paths { patch?: never; trace?: never; }; - "/api/settings/ai": { + "/api/ai/signals/{signal_id}/confirm": { parameters: { query?: never; header?: never; @@ -943,19 +922,22 @@ export interface paths { cookie?: never; }; get?: never; + put?: never; /** - * Update Ai Settings - * @description Configure the AI provider. The API key is encrypted at rest. + * Confirm Signal + * @description Execute a pending AI signal through the same risk + executor path. + * + * Re-prices at confirmation and re-runs the risk gate, so a stale or + * risk-blocked signal cannot slip through. Marks the signal `executed`. */ - put: operations["update_ai_settings_api_settings_ai_put"]; - post?: never; + post: operations["confirm_signal_api_ai_signals__signal_id__confirm_post"]; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/api/settings/ai-action-mode": { + "/api/ai/signals/{signal_id}/dismiss": { parameters: { query?: never; header?: never; @@ -963,31 +945,33 @@ export interface paths { cookie?: never; }; get?: never; + put?: never; /** - * Update Ai Action Mode - * @description Set how AI decisions are applied: `notify` (confirm) or `auto`. + * Dismiss Signal + * @description Dismiss a pending AI signal without executing it. */ - put: operations["update_ai_action_mode_api_settings_ai_action_mode_put"]; - post?: never; + post: operations["dismiss_signal_api_ai_signals__signal_id__dismiss_post"]; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/api/settings/ai-spend-cap": { + "/api/ai/local": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; - get?: never; /** - * Update Ai Spend Cap - * @description Set the daily LLM spend cap — AI strategies pause once it is reached. + * Local Models + * @description Whether local models are deployed (Ollama) or deployable (llmfit). + * + * The snapshot is cached for ~5 minutes; `refresh=true` re-probes now. */ - put: operations["update_ai_spend_cap_api_settings_ai_spend_cap_put"]; + get: operations["local_models_api_ai_local_get"]; + put?: never; post?: never; delete?: never; options?: never; @@ -995,27 +979,31 @@ export interface paths { patch?: never; trace?: never; }; - "/api/settings/binance-keys": { + "/api/tokens": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; - get?: never; /** - * Update Binance Keys - * @description Store the Binance API credentials (encrypted at rest). + * List Tokens + * @description Every API token, newest-first. The secret is never returned. */ - put: operations["update_binance_keys_api_settings_binance_keys_put"]; - post?: never; + get: operations["list_tokens_api_tokens_get"]; + put?: never; + /** + * Create Token + * @description Create a token. The plaintext is in the response and shown only here. + */ + post: operations["create_token_api_tokens_post"]; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/api/settings/council": { + "/api/tokens/{token_id}": { parameters: { query?: never; header?: never; @@ -1023,31 +1011,31 @@ export interface paths { cookie?: never; }; get?: never; + put?: never; + post?: never; /** - * Update Council Settings - * @description Configure the AI council members and quorum. + * Revoke Token + * @description Revoke a token by id — it can no longer authenticate. */ - put: operations["update_council_settings_api_settings_council_put"]; - post?: never; - delete?: never; + delete: operations["revoke_token_api_tokens__token_id__delete"]; options?: never; head?: never; patch?: never; trace?: never; }; - "/api/settings/llm-credentials/{provider}": { + "/api/venues": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; - get?: never; /** - * Update Llm Credentials - * @description Store one LLM provider's API key and/or base URL (encrypted at rest). + * Get Venues + * @description Every supported venue, with the active one flagged. */ - put: operations["update_llm_credentials_api_settings_llm_credentials__provider__put"]; + get: operations["get_venues_api_venues_get"]; + put?: never; post?: never; delete?: never; options?: never; @@ -1055,7 +1043,7 @@ export interface paths { patch?: never; trace?: never; }; - "/api/settings/mode": { + "/api/venues/active": { parameters: { query?: never; header?: never; @@ -1064,10 +1052,10 @@ export interface paths { }; get?: never; /** - * Update Mode - * @description Switch the trading mode (Sim / Testnet / Live). + * Set Active + * @description Change the active trading venue. */ - put: operations["update_mode_api_settings_mode_put"]; + put: operations["set_active_api_venues_active_put"]; post?: never; delete?: never; options?: never; @@ -1075,21 +1063,19 @@ export interface paths { patch?: never; trace?: never; }; - "/api/settings/polymarket": { + "/api/news": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; - get?: never; /** - * Update Polymarket Settings - * @description Configure Polymarket discovery and AI bet screening. - * - * Scheduler-interval changes take effect on the next engine restart. + * List News + * @description Recent headlines, newest first — filterable by `symbol` and `category`. */ - put: operations["update_polymarket_settings_api_settings_polymarket_put"]; + get: operations["list_news_api_news_get"]; + put?: never; post?: never; delete?: never; options?: never; @@ -1097,7 +1083,7 @@ export interface paths { patch?: never; trace?: never; }; - "/api/settings/research": { + "/api/news/refresh": { parameters: { query?: never; header?: never; @@ -1105,36 +1091,31 @@ export interface paths { cookie?: never; }; get?: never; + put?: never; /** - * Update Research Settings - * @description Configure research reports and the news refresh interval. - * - * Scheduler-interval changes take effect on the next engine restart. + * Refresh News + * @description Fetch every configured feed now. Returns the number of new headlines. */ - put: operations["update_research_settings_api_settings_research_put"]; - post?: never; + post: operations["refresh_news_api_news_refresh_post"]; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/api/settings/venue-credentials/{venue}": { + "/api/polymarket/markets": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; - get?: never; /** - * Update Venue Credentials - * @description Store a venue's credentials (encrypted at rest). - * - * The submitted field names must exactly match the venue's declared - * `credential_fields`, and every value must be non-empty. + * List Markets + * @description Discovered markets, most-traded first — filterable and searchable. */ - put: operations["update_venue_credentials_api_settings_venue_credentials__venue__put"]; + get: operations["list_markets_api_polymarket_markets_get"]; + put?: never; post?: never; delete?: never; options?: never; @@ -1142,73 +1123,67 @@ export interface paths { patch?: never; trace?: never; }; - "/api/strategies": { + "/api/polymarket/markets/{condition_id}/watch": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; + get?: never; /** - * List Strategies - * @description Every registered strategy with its allocation, state and PnL. - */ - get: operations["list_strategies_api_strategies_get"]; - put?: never; - /** - * Create Instance - * @description Create a strategy instance: any registered type on any chosen coin. + * Update Watched + * @description Pin or unpin a market for scheduled AI analysis. */ - post: operations["create_instance_api_strategies_post"]; + put: operations["update_watched_api_polymarket_markets__condition_id__watch_put"]; + post?: never; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/api/strategies/types": { + "/api/polymarket/refresh": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; + get?: never; + put?: never; /** - * List Strategy Types - * @description Every pickable strategy type with its typed parameter schema. + * Refresh Markets + * @description Pull the latest top markets from the Gamma API now. */ - get: operations["list_strategy_types_api_strategies_types_get"]; - put?: never; - post?: never; + post: operations["refresh_markets_api_polymarket_refresh_post"]; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/api/strategies/{name}": { + "/api/polymarket/analyses": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; - get?: never; - put?: never; - post?: never; /** - * Delete Instance - * @description Delete an instance-backed strategy. Built-ins cannot be deleted. - * - * Refused while the strategy holds an open position — close it first. + * List Analyses + * @description Recent AI bet analyses, newest first — optionally for one market. */ - delete: operations["delete_instance_api_strategies__name__delete"]; + get: operations["list_analyses_api_polymarket_analyses_get"]; + put?: never; + post?: never; + delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/api/strategies/{name}/ai-model": { + "/api/polymarket/markets/{condition_id}/analyze": { parameters: { query?: never; header?: never; @@ -1217,38 +1192,38 @@ export interface paths { }; get?: never; put?: never; - post?: never; + /** + * Analyze Now + * @description Run one AI analysis for a market immediately (a paid LLM call). + */ + post: operations["analyze_now_api_polymarket_markets__condition_id__analyze_post"]; delete?: never; options?: never; head?: never; - /** - * Update Ai Model - * @description Pin an AI strategy to a provider + model (Claude / OpenAI / Gemini / Ollama). - */ - patch: operations["update_ai_model_api_strategies__name__ai_model_patch"]; + patch?: never; trace?: never; }; - "/api/strategies/{name}/allocation": { + "/api/connections/graph": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; - get?: never; + /** + * Get Graph + * @description The full graph. Pending (AI-suggested) edges are opt-in. + */ + get: operations["get_graph_api_connections_graph_get"]; put?: never; post?: never; delete?: never; options?: never; head?: never; - /** - * Update Allocation - * @description Set a strategy's capital budget; the engine caps its exposure to it. - */ - patch: operations["update_allocation_api_strategies__name__allocation_patch"]; + patch?: never; trace?: never; }; - "/api/strategies/{name}/close": { + "/api/connections/nodes": { parameters: { query?: never; header?: never; @@ -1258,17 +1233,17 @@ export interface paths { get?: never; put?: never; /** - * Close Strategy - * @description Close every open position held by the strategy. + * Create Node + * @description Create a node (or return the existing one with the same label). */ - post: operations["close_strategy_api_strategies__name__close_post"]; + post: operations["create_node_api_connections_nodes_post"]; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/api/strategies/{name}/enabled": { + "/api/connections/suggest/{node_id}": { parameters: { query?: never; header?: never; @@ -1277,100 +1252,98 @@ export interface paths { }; get?: never; put?: never; - post?: never; + /** + * Suggest Connections + * @description Ask the LLM to suggest related entities for a node (stored pending). + */ + post: operations["suggest_connections_api_connections_suggest__node_id__post"]; delete?: never; options?: never; head?: never; - /** - * Update Enabled - * @description Enable or disable a strategy. Disabling stops new entries only. - */ - patch: operations["update_enabled_api_strategies__name__enabled_patch"]; + patch?: never; trace?: never; }; - "/api/system/watchdog": { + "/api/connections/edges/{edge_id}/approve": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; + get?: never; + put?: never; /** - * Get Watchdog - * @description Engine liveness — heartbeat age and whether an alert is warranted. + * Approve Edge + * @description Approve a pending edge so it joins the graph. */ - get: operations["get_watchdog_api_system_watchdog_get"]; - put?: never; - post?: never; + post: operations["approve_edge_api_connections_edges__edge_id__approve_post"]; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/api/tokens": { + "/api/connections/edges/{edge_id}": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; - /** - * List Tokens - * @description Every API token, newest-first. The secret is never returned. - */ - get: operations["list_tokens_api_tokens_get"]; + get?: never; put?: never; + post?: never; /** - * Create Token - * @description Create a token. The plaintext is in the response and shown only here. + * Delete Edge + * @description Delete an edge (reject a suggestion or prune a curated link). */ - post: operations["create_token_api_tokens_post"]; - delete?: never; + delete: operations["delete_edge_api_connections_edges__edge_id__delete"]; options?: never; head?: never; patch?: never; trace?: never; }; - "/api/tokens/{token_id}": { + "/api/research": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; - get?: never; - put?: never; - post?: never; /** - * Revoke Token - * @description Revoke a token by id — it can no longer authenticate. + * List Reports + * @description Recent reports, newest first — optionally filtered by `symbol`. */ - delete: operations["revoke_token_api_tokens__token_id__delete"]; + get: operations["list_reports_api_research_get"]; + put?: never; + post?: never; + delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/api/users": { + "/api/research/{report_id}": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; - /** List Users */ - get: operations["list_users_api_users_get"]; + /** + * Get Report + * @description One report with its full sections. + */ + get: operations["get_report_api_research__report_id__get"]; put?: never; - /** Create User */ - post: operations["create_user_api_users_post"]; + post?: never; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/api/users/{user_id}": { + "/api/research/run": { parameters: { query?: never; header?: never; @@ -1379,15 +1352,42 @@ export interface paths { }; get?: never; put?: never; - post?: never; - delete?: never; - options?: never; + /** + * Run Research + * @description Write a report now — for `symbol`, or every watched symbol when blank. + */ + post: operations["run_research_api_research_run_post"]; + delete?: never; + options?: never; head?: never; - /** Update User */ - patch: operations["update_user_api_users__user_id__patch"]; + patch?: never; trace?: never; }; - "/api/users/{user_id}/password": { + "/api/research/{report_id}/review": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Get Review + * @description The newest council review for a report, with every vote. + */ + get: operations["get_review_api_research__report_id__review_get"]; + put?: never; + /** + * Rerun Review + * @description Re-run the council on a report now (admin). + */ + post: operations["rerun_review_api_research__report_id__review_post"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/lab/compare": { parameters: { query?: never; header?: never; @@ -1396,26 +1396,49 @@ export interface paths { }; get?: never; put?: never; - /** Reset Password */ - post: operations["reset_password_api_users__user_id__password_post"]; + /** + * Compare Grid + * @description Backtest every (type × symbol) cell over the same recent range. + */ + post: operations["compare_grid_api_lab_compare_post"]; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/api/venues": { + "/api/lab/recommend": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; + get?: never; + put?: never; /** - * Get Venues - * @description Every supported venue, with the active one flagged. + * Recommend + * @description Ask the configured LLM to pick a strategy for the coin, then backtest it. */ - get: operations["get_venues_api_venues_get"]; + post: operations["recommend_api_lab_recommend_post"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/costs/summary": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Costs Summary + * @description Spend totals and breakdowns over the last 30 days. + */ + get: operations["costs_summary_api_costs_summary_get"]; put?: never; post?: never; delete?: never; @@ -1424,19 +1447,19 @@ export interface paths { patch?: never; trace?: never; }; - "/api/venues/active": { + "/api/costs/ledger": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; - get?: never; /** - * Set Active - * @description Change the active trading venue. + * Costs Ledger + * @description The per-call ledger, newest first — filterable by purpose. */ - put: operations["set_active_api_venues_active_put"]; + get: operations["costs_ledger_api_costs_ledger_get"]; + put?: never; post?: never; delete?: never; options?: never; @@ -1473,30 +1496,29 @@ export interface components { * @description One AI decision surfaced to the operator for confirmation. */ AISignal: { - /** Action */ - action: string; - /** - * Confidence - * @default 0 - */ - confidence: string; - /** - * Created At - * Format: date-time - */ - created_at: string; /** Id */ id?: number | null; + /** Strategy */ + strategy: string; + /** Symbol */ + symbol: string; + /** + * Venue + * @default binance + */ + venue: string; /** * Market * @default spot */ market: string; + /** Action */ + action: string; /** - * Quantity + * Confidence * @default 0 */ - quantity: string; + confidence: string; /** * Reasoning * @default @@ -1507,20 +1529,21 @@ export interface components { * @default 0 */ reference_price: string; + /** + * Quantity + * @default 0 + */ + quantity: string; /** * Status * @default pending */ status: string; - /** Strategy */ - strategy: string; - /** Symbol */ - symbol: string; /** - * Venue - * @default binance + * Created At + * Format: date-time */ - venue: string; + created_at: string; }; /** ActiveVenueUpdate */ ActiveVenueUpdate: { @@ -1537,33 +1560,33 @@ export interface components { }; /** AiModelUpdate */ AiModelUpdate: { + /** Provider */ + provider: string; /** * Model * @default */ model: string; - /** Provider */ - provider: string; }; /** AiSettingsUpdate */ AiSettingsUpdate: { + /** Provider */ + provider: string; /** - * Api Key + * Model * @default */ - api_key: string; + model: string; /** * Base Url * @default */ base_url: string; /** - * Model + * Api Key * @default */ - model: string; - /** Provider */ - provider: string; + api_key: string; }; /** AiSpendCapUpdate */ AiSpendCapUpdate: { @@ -1591,41 +1614,41 @@ export interface components { }; /** ApiTokenCreated */ ApiTokenCreated: { + /** Id */ + id: number; + /** Name */ + name: string; + /** Role */ + role: string; + /** Revoked */ + revoked: boolean; /** * Created At * Format: date-time */ created_at: string; - /** Id */ - id: number; /** Last Used At */ last_used_at: string | null; - /** Name */ - name: string; - /** Revoked */ - revoked: boolean; - /** Role */ - role: string; /** Token */ token: string; }; /** ApiTokenRead */ ApiTokenRead: { + /** Id */ + id: number; + /** Name */ + name: string; + /** Role */ + role: string; + /** Revoked */ + revoked: boolean; /** * Created At * Format: date-time */ created_at: string; - /** Id */ - id: number; /** Last Used At */ last_used_at: string | null; - /** Name */ - name: string; - /** Revoked */ - revoked: boolean; - /** Role */ - role: string; }; /** * AuditLog @@ -1635,53 +1658,50 @@ export interface components { * entries here via `auth.audit.record_audit`. */ AuditLog: { - /** Action */ - action: string; + /** Id */ + id?: number | null; /** Actor */ actor: string; + /** Action */ + action: string; + /** Target */ + target?: string | null; + /** Detail */ + detail?: string | null; /** * Created At * Format: date-time */ created_at?: string; - /** Detail */ - detail?: string | null; - /** Id */ - id?: number | null; - /** Target */ - target?: string | null; }; /** BacktestMetricsRead */ BacktestMetricsRead: { - /** Losses */ - losses: number; + /** Total Return Pct */ + total_return_pct: string; + /** Win Rate Pct */ + win_rate_pct: string; /** Max Drawdown Pct */ max_drawdown_pct: string; /** Sharpe */ sharpe: string; - /** Total Return Pct */ - total_return_pct: string; /** Trades */ trades: number; - /** Win Rate Pct */ - win_rate_pct: string; /** Wins */ wins: number; + /** Losses */ + losses: number; }; /** BacktestRequest */ BacktestRequest: { - /** End */ - end?: string | null; - /** - * Fee Rate - * @default 0.001 - */ - fee_rate: number | string; + /** Strategy */ + strategy: string; /** - * Funding Rate - * @default 0 + * Start + * Format: date-time */ - funding_rate: number | string; + start: string; + /** End */ + end?: string | null; /** * Initial Capital * @default 10000 @@ -1693,54 +1713,57 @@ export interface components { */ slippage_bps: number | string; /** - * Start - * Format: date-time + * Fee Rate + * @default 0.001 */ - start: string; - /** Strategy */ - strategy: string; + fee_rate: number | string; + /** + * Funding Rate + * @default 0 + */ + funding_rate: number | string; }; /** BacktestResponse */ BacktestResponse: { - /** Candles */ - candles: number; - /** Equity Curve */ - equity_curve: components["schemas"]["EquityPoint"][]; - /** Final Equity */ - final_equity: string; - /** Initial Capital */ - initial_capital: string; - /** Interval */ - interval: string; - metrics: components["schemas"]["BacktestMetricsRead"]; - /** Net Pnl */ - net_pnl: string; /** Strategy */ strategy: string; /** Symbol */ symbol: string; + /** Interval */ + interval: string; + /** Initial Capital */ + initial_capital: string; + /** Final Equity */ + final_equity: string; + /** Net Pnl */ + net_pnl: string; /** Total Fees */ total_fees: string; + /** Candles */ + candles: number; + /** Equity Curve */ + equity_curve: components["schemas"]["EquityPoint"][]; /** Trades */ trades: components["schemas"]["BacktestTradeRead"][]; + metrics: components["schemas"]["BacktestMetricsRead"]; }; /** BacktestTradeRead */ BacktestTradeRead: { - /** Fee */ - fee: string; - /** Price */ - price: string; - /** Quantity */ - quantity: string; - /** Realized Pnl */ - realized_pnl: string; - /** Side */ - side: string; /** * Time * Format: date-time */ time: string; + /** Side */ + side: string; + /** Quantity */ + quantity: string; + /** Price */ + price: string; + /** Fee */ + fee: string; + /** Realized Pnl */ + realized_pnl: string; }; /** BinanceKeysUpdate */ BinanceKeysUpdate: { @@ -1751,15 +1774,10 @@ export interface components { }; /** Body_login_api_auth_login_post */ Body_login_api_auth_login_post: { - /** Client Id */ - client_id?: string | null; - /** - * Client Secret - * Format: password - */ - client_secret?: string | null; /** Grant Type */ grant_type?: string | null; + /** Username */ + username: string; /** * Password * Format: password @@ -1770,42 +1788,47 @@ export interface components { * @default */ scope: string; - /** Username */ - username: string; + /** Client Id */ + client_id?: string | null; + /** + * Client Secret + * Format: password + */ + client_secret?: string | null; }; /** * Candle * @description A single OHLCV bar for one symbol/interval/market. */ Candle: { - /** Close */ - close: string; - /** - * Close Time - * Format: date-time - */ - close_time: string; - /** High */ - high: string; /** Id */ id?: number | null; - /** Interval */ - interval: string; - /** Low */ - low: string; /** Market */ market: string; - /** Open */ - open: string; + /** Symbol */ + symbol: string; + /** Interval */ + interval: string; /** * Open Time * Format: date-time */ open_time: string; - /** Symbol */ - symbol: string; + /** Open */ + open: string; + /** High */ + high: string; + /** Low */ + low: string; + /** Close */ + close: string; /** Volume */ volume: string; + /** + * Close Time + * Format: date-time + */ + close_time: string; }; /** CloseResult */ CloseResult: { @@ -1814,150 +1837,150 @@ export interface components { }; /** CompareCellRead */ CompareCellRead: { - /** Equity Sparkline */ - equity_sparkline: number[]; - /** Error */ - error: string; - /** Final Equity */ - final_equity: string; - /** Max Drawdown Pct */ - max_drawdown_pct: string; - /** Net Pnl */ - net_pnl: string; + /** Type */ + type: string; + /** Symbol */ + symbol: string; /** Params */ params: { [key: string]: unknown; }; /** Return Pct */ return_pct: string; + /** Max Drawdown Pct */ + max_drawdown_pct: string; /** Sharpe */ sharpe: string; - /** Symbol */ - symbol: string; - /** Trades */ - trades: number; - /** Type */ - type: string; /** Win Rate Pct */ win_rate_pct: string; + /** Trades */ + trades: number; + /** Net Pnl */ + net_pnl: string; + /** Final Equity */ + final_equity: string; + /** Equity Sparkline */ + equity_sparkline: number[]; + /** Error */ + error: string; }; /** * CompareRequest * @description A grid request — either pivot is just a different shape of the same. */ CompareRequest: { + /** Types */ + types?: string[]; + /** Symbols */ + symbols: string[]; /** - * Capital - * @default 10000 + * Timeframe + * @default 1h */ - capital: number | string; + timeframe: string; /** * Days * @default 90 */ days: number; - /** Symbols */ - symbols: string[]; /** - * Timeframe - * @default 1h + * Capital + * @default 10000 */ - timeframe: string; - /** Types */ - types?: string[]; + capital: number | string; }; /** * CostBucket * @description One aggregation bucket — by model, purpose or day. */ CostBucket: { + /** Key */ + key: string; /** Calls */ calls: number; - /** Cost Usd */ - cost_usd: string; /** Input Tokens */ input_tokens: number; - /** Key */ - key: string; /** Output Tokens */ output_tokens: number; + /** Cost Usd */ + cost_usd: string; }; /** * CostsRead * @description Trading-cost breakdown, the fee-rate reference, and today's LLM spend. */ CostsRead: { - /** Fee Pct Of Volume */ - fee_pct_of_volume: string; + /** Total Fees */ + total_fees: string; /** Fees By Market */ fees_by_market: { [key: string]: string; }; - /** Llm Spend Today */ - llm_spend_today: string; - /** Total Fees */ - total_fees: string; /** Traded Volume */ traded_volume: string; + /** Fee Pct Of Volume */ + fee_pct_of_volume: string; /** Venue Fee Rates */ venue_fee_rates: { [key: string]: string; }; + /** Llm Spend Today */ + llm_spend_today: string; }; /** * CostsSummary * @description Headline spend + the breakdowns the Costs screen renders. */ CostsSummary: { - /** By Day */ - by_day: components["schemas"]["CostBucket"][]; + /** Today Usd */ + today_usd: string; + /** Last 7D Usd */ + last_7d_usd: string; + /** Last 30D Usd */ + last_30d_usd: string; + /** Daily Cap Usd */ + daily_cap_usd: string; /** By Model */ by_model: components["schemas"]["CostBucket"][]; /** By Purpose */ by_purpose: components["schemas"]["CostBucket"][]; - /** Daily Cap Usd */ - daily_cap_usd: string; - /** Last 30D Usd */ - last_30d_usd: string; - /** Last 7D Usd */ - last_7d_usd: string; - /** Today Usd */ - today_usd: string; + /** By Day */ + by_day: components["schemas"]["CostBucket"][]; }; /** CouncilMember */ CouncilMember: { + /** Provider */ + provider: string; /** * Model * @default */ model: string; - /** Provider */ - provider: string; }; /** * CouncilReviewRead * @description A council verdict plus every member's vote. */ CouncilReviewRead: { - /** - * Created At - * Format: date-time - */ - created_at: string; /** Id */ id: number; - /** Quorum Met */ - quorum_met: boolean; /** Report Id */ report_id: number; - /** Strategy Brief */ - strategy_brief: string; /** Verdict */ verdict: string; - /** Votes */ - votes: components["schemas"]["CouncilVoteRead"][]; /** Weighted Score */ weighted_score: string; + /** Quorum Met */ + quorum_met: boolean; + /** Strategy Brief */ + strategy_brief: string; + /** + * Created At + * Format: date-time + */ + created_at: string; + /** Votes */ + votes: components["schemas"]["CouncilVoteRead"][]; }; /** * CouncilSettingsUpdate @@ -1974,14 +1997,14 @@ export interface components { }; /** CouncilVoteRead */ CouncilVoteRead: { + /** Provider */ + provider: string; + /** Model */ + model: string; /** Action */ action: string; /** Confidence */ confidence: string; - /** Model */ - model: string; - /** Provider */ - provider: string; /** Reasoning */ reasoning: string; }; @@ -2011,57 +2034,57 @@ export interface components { }; /** EquityPoint */ EquityPoint: { - /** Equity */ - equity: string; /** * Time * Format: date-time */ time: string; + /** Equity */ + equity: string; }; /** * EquitySnapshot * @description A point-in-time snapshot of portfolio equity — powers the equity curve. */ EquitySnapshot: { - /** Equity */ - equity: string; - /** Fees */ - fees: string; /** Id */ id?: number | null; - /** Net Pnl */ - net_pnl: string; - /** Realized Pnl */ - realized_pnl: string; /** * Ts * Format: date-time */ ts: string; + /** Equity */ + equity: string; + /** Realized Pnl */ + realized_pnl: string; /** Unrealized Pnl */ unrealized_pnl: string; + /** Fees */ + fees: string; + /** Net Pnl */ + net_pnl: string; }; /** * Fill * @description The result of executing an `Order`. */ Fill: { - /** Fee */ - fee: string; + /** Strategy */ + strategy: string; /** Market */ market: string; - /** Price */ - price: string; + /** Symbol */ + symbol: string; + side: components["schemas"]["FillSide"]; /** Quantity */ quantity: string; + /** Price */ + price: string; + /** Fee */ + fee: string; /** Realized Pnl */ realized_pnl: string; - side: components["schemas"]["FillSide"]; - /** Strategy */ - strategy: string; - /** Symbol */ - symbol: string; }; /** * FillSide @@ -2070,6 +2093,8 @@ export interface components { FillSide: "buy" | "sell"; /** FundingRate */ FundingRate: { + /** Symbol */ + symbol: string; /** Funding Rate */ funding_rate: string; /** Mark Price */ @@ -2079,71 +2104,69 @@ export interface components { * Format: date-time */ next_funding_time: string; - /** Symbol */ - symbol: string; }; /** * GraphEdge * @description A directed, labelled relation from one node to another. */ GraphEdge: { - /** - * Approved - * @default true - */ - approved: boolean; - /** Created At */ - created_at?: string | null; /** Id */ id?: number | null; - /** - * Origin - * @default seed - */ - origin: string; - /** - * Relation - * @default related - */ - relation: string; /** Source Id */ source_id: number; /** Target Id */ target_id: number; + /** + * Relation + * @default related + */ + relation: string; /** * Weight * @default 1 */ weight: string; + /** + * Origin + * @default seed + */ + origin: string; + /** + * Approved + * @default true + */ + approved: boolean; + /** Created At */ + created_at?: string | null; }; /** * GraphNode * @description A node: an asset, a product, or a concept. */ GraphNode: { - /** Icon */ - icon?: string | null; /** Id */ id?: number | null; + /** Label */ + label: string; /** * Kind * @default concept */ kind: string; - /** Label */ - label: string; /** Symbol */ symbol?: string | null; + /** Icon */ + icon?: string | null; }; /** * GraphView * @description The connections graph — nodes and the edges between them. */ GraphView: { - /** Edges */ - edges: components["schemas"]["GraphEdge"][]; /** Nodes */ nodes: components["schemas"]["GraphNode"][]; + /** Edges */ + edges: components["schemas"]["GraphEdge"][]; }; /** HTTPValidationError */ HTTPValidationError: { @@ -2155,158 +2178,158 @@ export interface components { * @description Apply a strategy type to a coin as a new named instance. */ InstanceCreate: { + /** Name */ + name: string; + /** Type */ + type: string; + /** Symbol */ + symbol: string; /** - * Allocated - * @default 10000 + * Venue + * @default binance */ - allocated: number | string; + venue: string; /** * Market * @default spot */ market: string; /** - * Max Loss - * @default 0 + * Timeframe + * @default 1h */ - max_loss: number | string; - /** Name */ - name: string; + timeframe: string; /** Params */ params?: { [key: string]: string; }; - /** Symbol */ - symbol: string; /** - * Timeframe - * @default 1h + * Allocated + * @default 10000 */ - timeframe: string; - /** Type */ - type: string; + allocated: number | string; /** - * Venue - * @default binance + * Max Loss + * @default 0 */ - venue: string; + max_loss: number | string; }; /** * LLMUsage * @description One recorded LLM completion — tokens, estimated cost, and the decision. */ LLMUsage: { - /** Action */ - action?: string | null; - /** Confidence */ - confidence?: string | null; - /** - * Created At - * Format: date-time - */ - created_at: string; - /** - * Estimated Cost Usd - * @default 0 - */ - estimated_cost_usd: string; /** Id */ id?: number | null; + /** Provider */ + provider: string; + /** Model */ + model: string; + /** + * Purpose + * @default analyze + */ + purpose: string; + /** Strategy */ + strategy?: string | null; /** * Input Tokens * @default 0 */ input_tokens: number; - /** Model */ - model: string; /** * Output Tokens * @default 0 */ output_tokens: number; - /** Provider */ - provider: string; /** - * Purpose - * @default analyze + * Estimated Cost Usd + * @default 0 */ - purpose: string; - /** Strategy */ - strategy?: string | null; + estimated_cost_usd: string; + /** Action */ + action?: string | null; + /** Confidence */ + confidence?: string | null; + /** + * Created At + * Format: date-time + */ + created_at: string; }; /** * LatencyRead * @description Price-feed latency — per-feed stats plus the overall degraded flag. */ LatencyRead: { + /** Warn Ms */ + warn_ms: number; /** Degraded */ degraded: boolean; /** Feeds */ feeds: components["schemas"]["LatencyStatsRead"][]; - /** Warn Ms */ - warn_ms: number; }; /** * LatencyStatsRead * @description Rolling-window latency for one (market, symbol, kind). */ LatencyStatsRead: { - /** Current Ms */ - current_ms: number; - /** Degraded */ - degraded: boolean; - /** Kind */ - kind: string; /** Market */ market: string; - /** Max Ms */ - max_ms: number; + /** Symbol */ + symbol: string; + /** Kind */ + kind: string; + /** Current Ms */ + current_ms: number; /** P50 Ms */ p50_ms: number; /** P95 Ms */ p95_ms: number; + /** Max Ms */ + max_ms: number; /** Samples */ samples: number; - /** Symbol */ - symbol: string; + /** Degraded */ + degraded: boolean; }; /** * LedgerEntry * @description One paid call, newest first. */ LedgerEntry: { - /** Action */ - action: string | null; - /** Confidence */ - confidence: string | null; - /** Cost Usd */ - cost_usd: string; + /** Id */ + id: number; /** * Created At * Format: date-time */ created_at: string; - /** Id */ - id: number; - /** Input Tokens */ - input_tokens: number; - /** Model */ - model: string; - /** Output Tokens */ - output_tokens: number; /** Provider */ provider: string; + /** Model */ + model: string; /** Purpose */ purpose: string; /** Strategy */ strategy: string | null; + /** Input Tokens */ + input_tokens: number; + /** Output Tokens */ + output_tokens: number; + /** Cost Usd */ + cost_usd: string; + /** Action */ + action: string | null; + /** Confidence */ + confidence: string | null; }; /** LedgerPage */ LedgerPage: { - /** Entries */ - entries: components["schemas"]["LedgerEntry"][]; /** Total */ total: number; + /** Entries */ + entries: components["schemas"]["LedgerEntry"][]; }; /** * LlmCredentialsUpdate @@ -2326,39 +2349,39 @@ export interface components { }; /** LlmfitStatusRead */ LlmfitStatusRead: { - /** Error */ - error: string; - /** Fits */ - fits: components["schemas"]["ModelFitRead"][]; + /** Installed */ + installed: boolean; + /** Install Hint */ + install_hint: string; /** Hardware */ hardware: { [key: string]: unknown; }; - /** Install Hint */ - install_hint: string; - /** Installed */ - installed: boolean; + /** Fits */ + fits: components["schemas"]["ModelFitRead"][]; + /** Error */ + error: string; }; /** * LocalAIRead * @description Local-model readiness — deployed (Ollama) and deployable (llmfit). */ LocalAIRead: { - llmfit: components["schemas"]["LlmfitStatusRead"]; ollama: components["schemas"]["OllamaStatusRead"]; + llmfit: components["schemas"]["LlmfitStatusRead"]; }; /** ManualOrderRequest */ ManualOrderRequest: { + /** Symbol */ + symbol: string; + side: components["schemas"]["FillSide"]; + /** Quantity */ + quantity: number | string; /** * Market * @default spot */ market: string; - /** Quantity */ - quantity: number | string; - side: components["schemas"]["FillSide"]; - /** Symbol */ - symbol: string; }; /** * Market @@ -2370,97 +2393,97 @@ export interface components { * @description One AI pass over a market — estimated probability vs market price. */ MarketAnalysis: { + /** Id */ + id?: number | null; /** Condition Id */ condition_id: string; + /** Question */ + question: string; /** - * Confidence + * Market Price * @default 0 */ - confidence: string; + market_price: string; /** - * Created At - * Format: date-time + * Est Probability + * @default 0 */ - created_at: string; + est_probability: string; /** * Edge * @default 0 */ edge: string; /** - * Est Probability - * @default 0 + * Recommendation + * @default hold */ - est_probability: string; - /** Id */ - id?: number | null; + recommendation: string; /** - * Market Price + * Confidence * @default 0 */ - market_price: string; + confidence: string; /** - * Model + * Reasoning * @default */ - model: string; + reasoning: string; /** * Provider * @default */ provider: string; - /** Question */ - question: string; /** - * Reasoning + * Model * @default */ - reasoning: string; + model: string; /** - * Recommendation - * @default hold + * Created At + * Format: date-time */ - recommendation: string; + created_at: string; }; /** ModeUpdate */ ModeUpdate: { + mode: components["schemas"]["TradingMode"]; /** * Confirm * @default false */ confirm: boolean; - mode: components["schemas"]["TradingMode"]; }; /** ModelFitRead */ ModelFitRead: { - /** Est Speed */ - est_speed: string; - /** Fit */ - fit: string; - /** Memory Gb */ - memory_gb: string; /** Model */ model: string; /** Quantization */ quantization: string; + /** Fit */ + fit: string; + /** Est Speed */ + est_speed: string; + /** Memory Gb */ + memory_gb: string; }; /** * ModelUsage * @description Per-model rollup of LLM activity — 'what each model did'. */ ModelUsage: { - /** Buys */ - buys: number; - /** Decisions */ - decisions: number; - /** Holds */ - holds: number; - /** Model */ - model: string; /** Provider */ provider: string; + /** Model */ + model: string; + /** Decisions */ + decisions: number; + /** Buys */ + buys: number; /** Sells */ sells: number; + /** Holds */ + holds: number; /** Total Cost Usd */ total_cost_usd: string; }; @@ -2469,80 +2492,80 @@ export interface components { * @description A single news headline pulled from an RSS feed. */ NewsItem: { - /** - * Category - * @default world - */ - category: string; - /** - * Fetched At - * Format: date-time - */ - fetched_at: string; /** Id */ id?: number | null; - /** Published At */ - published_at?: string | null; - /** Sentiment */ - sentiment?: string | null; /** Source */ source: string; + /** Title */ + title: string; + /** Url */ + url: string; /** * Summary * @default */ summary: string; + /** + * Category + * @default world + */ + category: string; /** Symbol */ symbol?: string | null; - /** Title */ - title: string; - /** Url */ - url: string; + /** Sentiment */ + sentiment?: string | null; + /** Published At */ + published_at?: string | null; + /** + * Fetched At + * Format: date-time + */ + fetched_at: string; }; /** NodeCreate */ NodeCreate: { - /** Icon */ - icon?: string | null; + /** Label */ + label: string; /** * Kind * @default concept */ kind: string; - /** Label */ - label: string; /** Symbol */ symbol?: string | null; + /** Icon */ + icon?: string | null; }; /** OllamaModelRead */ OllamaModelRead: { /** Name */ name: string; + /** Size Bytes */ + size_bytes: number; /** Parameter Size */ parameter_size: string; /** Quantization */ quantization: string; - /** Size Bytes */ - size_bytes: number; }; /** OllamaStatusRead */ OllamaStatusRead: { - /** Base Url */ - base_url: string; - /** Models */ - models: components["schemas"]["OllamaModelRead"][]; /** Reachable */ reachable: boolean; + /** Base Url */ + base_url: string; /** Version */ version: string; + /** Models */ + models: components["schemas"]["OllamaModelRead"][]; }; /** OrderBook */ OrderBook: { - /** Asks */ - asks: components["schemas"]["OrderBookLevel"][]; - /** Bids */ - bids: components["schemas"]["OrderBookLevel"][]; /** Symbol */ symbol: string; + /** Bids */ + bids: components["schemas"]["OrderBookLevel"][]; + /** Asks */ + asks: components["schemas"]["OrderBookLevel"][]; }; /** OrderBookLevel */ OrderBookLevel: { @@ -2553,18 +2576,18 @@ export interface components { }; /** ParamSpecRead */ ParamSpecRead: { - /** Default */ - default: string; - /** Label */ - label: string; - /** Max */ - max: string; - /** Min */ - min: string; /** Name */ name: string; /** Type */ type: string; + /** Default */ + default: string; + /** Min */ + min: string; + /** Max */ + max: string; + /** Label */ + label: string; }; /** PasswordReset */ PasswordReset: { @@ -2576,16 +2599,6 @@ export interface components { * @description Polymarket configuration — discovery/screening cadence and bars. */ PolymarketSettingsUpdate: { - /** - * Edge Threshold - * @default 0.05 - */ - edge_threshold: number | string; - /** - * Min Confidence - * @default 0.6 - */ - min_confidence: number | string; /** * Refresh Hours * @default 6 @@ -2596,6 +2609,16 @@ export interface components { * @default 6 */ research_hours: number; + /** + * Edge Threshold + * @default 0.05 + */ + edge_threshold: number | string; + /** + * Min Confidence + * @default 0.6 + */ + min_confidence: number | string; /** * Screen Top * @default 10 @@ -2609,26 +2632,26 @@ export interface components { }; /** PortfolioSummary */ PortfolioSummary: { - /** Deployed Capital */ - deployed_capital: string; + /** Total Allocated */ + total_allocated: string; + /** Realized Pnl */ + realized_pnl: string; + /** Unrealized Pnl */ + unrealized_pnl: string; + /** Total Fees */ + total_fees: string; + /** Net Pnl */ + net_pnl: string; /** Equity */ equity: string; + /** Deployed Capital */ + deployed_capital: string; /** Idle Capital */ idle_capital: string; - /** Net Pnl */ - net_pnl: string; /** Open Positions */ open_positions: number; - /** Realized Pnl */ - realized_pnl: string; /** Strategies */ strategies: components["schemas"]["StrategySummary"][]; - /** Total Allocated */ - total_allocated: string; - /** Total Fees */ - total_fees: string; - /** Unrealized Pnl */ - unrealized_pnl: string; }; /** * Position @@ -2639,41 +2662,41 @@ export interface components { * a position goes flat so cumulative PnL is preserved). */ Position: { - /** - * Entry Price - * @default 0 - */ - entry_price: string; - /** - * Fees Paid - * @default 0 - */ - fees_paid: string; /** Id */ id?: number | null; + /** Strategy */ + strategy: string; /** Market */ market: string; - /** Opened At */ - opened_at?: string | null; + /** Symbol */ + symbol: string; + /** + * Side + * @default flat + */ + side: string; /** * Qty * @default 0 */ qty: string; + /** + * Entry Price + * @default 0 + */ + entry_price: string; /** * Realized Pnl * @default 0 */ realized_pnl: string; /** - * Side - * @default flat + * Fees Paid + * @default 0 */ - side: string; - /** Strategy */ - strategy: string; - /** Symbol */ - symbol: string; + fees_paid: string; + /** Opened At */ + opened_at?: string | null; /** Updated At */ updated_at?: string | null; }; @@ -2682,27 +2705,29 @@ export interface components { * @description One Polymarket market, upserted from the Gamma API by `condition_id`. */ PredictionMarket: { + /** Id */ + id?: number | null; + /** Condition Id */ + condition_id: string; + /** Question */ + question: string; + /** + * Slug + * @default + */ + slug: string; /** * Category * @default */ category: string; - /** Condition Id */ - condition_id: string; /** End Date */ end_date?: string | null; /** - * Fetched At - * Format: date-time - */ - fetched_at: string; - /** Id */ - id?: number | null; - /** - * Liquidity - * @default 0 + * Yes Token Id + * @default */ - liquidity: string; + yes_token_id: string; /** * No Token Id * @default @@ -2713,78 +2738,76 @@ export interface components { * @default [] */ outcomes: string; - /** Question */ - question: string; /** - * Resolved Outcome - * @default + * Yes Price + * @default 0 */ - resolved_outcome: string; + yes_price: string; /** - * Slug - * @default + * Volume 24H + * @default 0 */ - slug: string; + volume_24h: string; + /** + * Liquidity + * @default 0 + */ + liquidity: string; /** * Status * @default active */ status: string; /** - * Volume 24H - * @default 0 + * Resolved Outcome + * @default */ - volume_24h: string; + resolved_outcome: string; /** * Watched * @default false */ watched: boolean; /** - * Yes Price - * @default 0 - */ - yes_price: string; - /** - * Yes Token Id - * @default + * Fetched At + * Format: date-time */ - yes_token_id: string; + fetched_at: string; }; /** RecommendRequest */ RecommendRequest: { + /** Symbol */ + symbol: string; /** - * Capital - * @default 10000 + * Timeframe + * @default 1h */ - capital: number | string; + timeframe: string; /** * Days * @default 90 */ days: number; - /** Symbol */ - symbol: string; /** - * Timeframe - * @default 1h + * Capital + * @default 10000 */ - timeframe: string; + capital: number | string; }; /** * RecommendResponse * @description The AI's pick, backtested so it ranks alongside the grid cells. */ RecommendResponse: { - cell: components["schemas"]["CompareCellRead"]; + /** Strategy Type */ + strategy_type: string; /** Params */ params: { [key: string]: unknown; }; /** Reasoning */ reasoning: string; - /** Strategy Type */ - strategy_type: string; + cell: components["schemas"]["CompareCellRead"]; }; /** RefreshRequest */ RefreshRequest: { @@ -2796,29 +2819,29 @@ export interface components { * @description One report — `sections` is the parsed schema-formatted content. */ ResearchReportRead: { + /** Id */ + id: number; + /** Symbol */ + symbol: string; + /** Schema Version */ + schema_version: number; + /** Status */ + status: string; + /** Provider */ + provider: string; + /** Model */ + model: string; + /** Error */ + error: string; /** * Created At * Format: date-time */ created_at: string; - /** Error */ - error: string; - /** Id */ - id: number; - /** Model */ - model: string; - /** Provider */ - provider: string; - /** Schema Version */ - schema_version: number; /** Sections */ sections: { [key: string]: unknown; }; - /** Status */ - status: string; - /** Symbol */ - symbol: string; }; /** * ResearchRun @@ -2836,22 +2859,39 @@ export interface components { * @description Research configuration — watched symbols, interval and writer LLM. */ ResearchSettingsUpdate: { + /** Symbols */ + symbols: string[]; /** * Interval Hours * @default 12 */ interval_hours: number; - /** News Interval Hours */ - news_interval_hours?: number | null; - /** Symbols */ - symbols: string[]; + /** Writer Provider */ + writer_provider: string; /** * Writer Model * @default */ writer_model: string; - /** Writer Provider */ - writer_provider: string; + /** News Interval Hours */ + news_interval_hours?: number | null; + }; + /** + * RiskSettingsUpdate + * @description Global risk limits in percent (SL/TP/drawdown) or quote currency + * (loss limit, notional). 0 disables a limit. + */ + RiskSettingsUpdate: { + /** Stop Loss Pct */ + stop_loss_pct: number | string; + /** Take Profit Pct */ + take_profit_pct: number | string; + /** Max Drawdown Pct */ + max_drawdown_pct: number | string; + /** Daily Loss Limit */ + daily_loss_limit: number | string; + /** Max Position Notional */ + max_position_notional: number | string; }; /** * Role @@ -2862,119 +2902,129 @@ export interface components { Role: "admin" | "user"; /** SettingsRead */ SettingsRead: { - /** Ai Action Mode */ - ai_action_mode: string; + mode: components["schemas"]["TradingMode"]; + /** Binance Keys Configured */ + binance_keys_configured: boolean; + /** Venue Credentials Configured */ + venue_credentials_configured: { + [key: string]: boolean; + }; + /** Ai Provider */ + ai_provider: string; + /** Ai Model */ + ai_model: string; /** Ai Base Url */ ai_base_url: string; /** Ai Key Configured */ ai_key_configured: boolean; - /** Ai Model */ - ai_model: string; - /** Ai Provider */ - ai_provider: string; /** Ai Spend Cap */ ai_spend_cap: string; /** Ai Spend Today */ ai_spend_today: string; - /** Binance Keys Configured */ - binance_keys_configured: boolean; - /** Council Members */ - council_members: { - [key: string]: string; - }[]; - /** Council Quorum */ - council_quorum: string; + /** Ai Action Mode */ + ai_action_mode: string; /** Llm Providers Configured */ llm_providers_configured: { [key: string]: boolean; }; - mode: components["schemas"]["TradingMode"]; + /** Research Symbols */ + research_symbols: string[]; + /** Research Interval Hours */ + research_interval_hours: number; + /** Research Writer Provider */ + research_writer_provider: string; + /** Research Writer Model */ + research_writer_model: string; /** News Interval Hours */ news_interval_hours: number | null; - /** Polymarket Edge Threshold */ - polymarket_edge_threshold: string; - /** Polymarket Min Confidence */ - polymarket_min_confidence: string; + /** Council Members */ + council_members: { + [key: string]: string; + }[]; + /** Council Quorum */ + council_quorum: string; /** Polymarket Refresh Hours */ polymarket_refresh_hours: number; /** Polymarket Research Hours */ polymarket_research_hours: number; + /** Polymarket Edge Threshold */ + polymarket_edge_threshold: string; + /** Polymarket Min Confidence */ + polymarket_min_confidence: string; /** Polymarket Screen Top */ polymarket_screen_top: number; /** Polymarket Stake */ polymarket_stake: string; - /** Research Interval Hours */ - research_interval_hours: number; - /** Research Symbols */ - research_symbols: string[]; - /** Research Writer Model */ - research_writer_model: string; - /** Research Writer Provider */ - research_writer_provider: string; - /** Venue Credentials Configured */ - venue_credentials_configured: { - [key: string]: boolean; - }; + /** Risk Stop Loss Pct */ + risk_stop_loss_pct: string; + /** Risk Take Profit Pct */ + risk_take_profit_pct: string; + /** Risk Max Drawdown Pct */ + risk_max_drawdown_pct: string; + /** Risk Daily Loss Limit */ + risk_daily_loss_limit: string; + /** Risk Max Position Notional */ + risk_max_position_notional: string; }; /** * StrategyRead * @description A strategy's identity, lifecycle state and accounting summary. */ StrategyRead: { - /** Ai Model */ - ai_model?: string | null; - /** Ai Provider */ - ai_provider?: string | null; - /** Allocated */ - allocated: string; - /** Enabled */ - enabled: boolean; - /** Fees */ - fees: string; - /** - * Is Instance - * @default false - */ - is_instance: boolean; + /** Name */ + name: string; /** Kind */ kind: string; + /** Symbol */ + symbol: string; + /** Venue */ + venue: string; /** Market */ market: string; + /** Timeframe */ + timeframe: string; + /** Enabled */ + enabled: boolean; + /** Allocated */ + allocated: string; /** Max Loss */ max_loss: string; - /** Name */ - name: string; - /** Net Pnl */ - net_pnl: string; - /** Open Positions */ - open_positions: number; /** Realized Pnl */ realized_pnl: string; - /** Symbol */ - symbol: string; - /** Timeframe */ - timeframe: string; /** Unrealized Pnl */ unrealized_pnl: string; - /** Venue */ - venue: string; + /** Fees */ + fees: string; + /** Net Pnl */ + net_pnl: string; + /** Open Positions */ + open_positions: number; + /** Ai Provider */ + ai_provider?: string | null; + /** Ai Model */ + ai_model?: string | null; + /** + * Is Instance + * @default false + */ + is_instance: boolean; }; /** StrategySummary */ StrategySummary: { + /** Strategy */ + strategy: string; /** Allocated */ allocated: string; + /** Realized Pnl */ + realized_pnl: string; + /** Unrealized Pnl */ + unrealized_pnl: string; /** Fees */ fees: string; /** Net Pnl */ net_pnl: string; /** Open Positions */ open_positions: number; - /** Realized Pnl */ - realized_pnl: string; - /** Strategy */ - strategy: string; - /** Unrealized Pnl */ - unrealized_pnl: string; }; /** * StrategyTypeRead @@ -2994,14 +3044,14 @@ export interface components { }; /** Ticker */ Ticker: { - /** Change Pct 24H */ - change_pct_24h: string; + /** Symbol */ + symbol: string; /** Price */ price: string; + /** Change Pct 24H */ + change_pct_24h: string; /** Quote Volume 24H */ quote_volume_24h: string; - /** Symbol */ - symbol: string; }; /** TokenPair */ TokenPair: { @@ -3023,42 +3073,42 @@ export interface components { * non-zero when it reduces or closes a position). */ Trade: { - /** Client Order Id */ - client_order_id?: string | null; - /** - * Executed At - * Format: date-time - */ - executed_at: string; + /** Id */ + id?: number | null; + /** Strategy */ + strategy: string; + /** Market */ + market: string; + /** Symbol */ + symbol: string; + /** Side */ + side: string; + /** Quantity */ + quantity: string; + /** Price */ + price: string; /** * Fee * @default 0 */ fee: string; - /** Id */ - id?: number | null; - /** Market */ - market: string; + /** + * Realized Pnl + * @default 0 + */ + realized_pnl: string; /** * Mode * @default sim */ mode: string; - /** Price */ - price: string; - /** Quantity */ - quantity: string; + /** Client Order Id */ + client_order_id?: string | null; /** - * Realized Pnl - * @default 0 + * Executed At + * Format: date-time */ - realized_pnl: string; - /** Side */ - side: string; - /** Strategy */ - strategy: string; - /** Symbol */ - symbol: string; + executed_at: string; }; /** * TradingMode @@ -3068,49 +3118,49 @@ export interface components { TradingMode: "sim" | "testnet" | "live"; /** UserCreate */ UserCreate: { + /** Username */ + username: string; /** Password */ password: string; /** @default user */ role: components["schemas"]["Role"]; - /** Username */ - username: string; }; /** UserInfo */ UserInfo: { - /** Role */ - role: string; /** Username */ username: string; + /** Role */ + role: string; }; /** UserRead */ UserRead: { /** Id */ id: number; - /** Is Active */ - is_active: boolean; - /** Role */ - role: string; /** Username */ username: string; + /** Role */ + role: string; + /** Is Active */ + is_active: boolean; }; /** UserUpdate */ UserUpdate: { + role?: components["schemas"]["Role"] | null; /** Is Active */ is_active?: boolean | null; - role?: components["schemas"]["Role"] | null; }; /** ValidationError */ ValidationError: { - /** Context */ - ctx?: Record; - /** Input */ - input?: unknown; /** Location */ loc: (string | number)[]; /** Message */ msg: string; /** Error Type */ type: string; + /** Input */ + input?: unknown; + /** Context */ + ctx?: Record; }; /** * VenueCredentialsUpdate @@ -3124,16 +3174,16 @@ export interface components { }; /** VenueRead */ VenueRead: { - /** Active */ - active: boolean; - /** Asset Class */ - asset_class: string; - /** Credential Fields */ - credential_fields: string[]; /** Name */ name: string; + /** Asset Class */ + asset_class: string; /** Supports Sandbox */ supports_sandbox: boolean; + /** Active */ + active: boolean; + /** Credential Fields */ + credential_fields: string[]; }; /** WatchUpdate */ WatchUpdate: { @@ -3145,18 +3195,18 @@ export interface components { * @description Engine liveness assessed from the heartbeat and open-position count. */ WatchdogStatus: { - /** Age Seconds */ - age_seconds: number | null; - /** Alert */ - alert: boolean; /** Alive */ alive: boolean; + /** Stale */ + stale: boolean; + /** Alert */ + alert: boolean; /** Last Beat */ last_beat: string | null; + /** Age Seconds */ + age_seconds: number | null; /** Open Positions */ open_positions: number; - /** Stale */ - stale: boolean; }; }; responses: never; @@ -3167,7 +3217,7 @@ export interface components { } export type $defs = Record; export interface operations { - analyze_and_decide_api_ai_analyze_post: { + login_api_auth_login_post: { parameters: { query?: never; header?: never; @@ -3176,7 +3226,7 @@ export interface operations { }; requestBody: { content: { - "application/json": components["schemas"]["AnalyzeRequest"]; + "application/x-www-form-urlencoded": components["schemas"]["Body_login_api_auth_login_post"]; }; }; responses: { @@ -3186,7 +3236,7 @@ export interface operations { [name: string]: unknown; }; content: { - "application/json": components["schemas"]["Decision"]; + "application/json": components["schemas"]["TokenPair"]; }; }; /** @description Validation Error */ @@ -3200,11 +3250,62 @@ export interface operations { }; }; }; - decision_log_api_ai_decisions_get: { + refresh_api_auth_refresh_post: { parameters: { - query?: { - limit?: number; + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["RefreshRequest"]; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["TokenPair"]; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + me_api_auth_me_get: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["UserInfo"]; + }; }; + }; + }; + list_users_api_users_get: { + parameters: { + query?: never; header?: never; path?: never; cookie?: never; @@ -3217,7 +3318,31 @@ export interface operations { [name: string]: unknown; }; content: { - "application/json": components["schemas"]["LLMUsage"][]; + "application/json": components["schemas"]["UserRead"][]; + }; + }; + }; + }; + create_user_api_users_post: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["UserCreate"]; + }; + }; + responses: { + /** @description Successful Response */ + 201: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["UserRead"]; }; }; /** @description Validation Error */ @@ -3231,16 +3356,20 @@ export interface operations { }; }; }; - local_models_api_ai_local_get: { + update_user_api_users__user_id__patch: { parameters: { - query?: { - refresh?: boolean; - }; + query?: never; header?: never; - path?: never; + path: { + user_id: number; + }; cookie?: never; }; - requestBody?: never; + requestBody: { + content: { + "application/json": components["schemas"]["UserUpdate"]; + }; + }; responses: { /** @description Successful Response */ 200: { @@ -3248,7 +3377,7 @@ export interface operations { [name: string]: unknown; }; content: { - "application/json": components["schemas"]["LocalAIRead"]; + "application/json": components["schemas"]["UserRead"]; }; }; /** @description Validation Error */ @@ -3262,31 +3391,43 @@ export interface operations { }; }; }; - model_performance_api_ai_models_get: { + reset_password_api_users__user_id__password_post: { parameters: { query?: never; header?: never; - path?: never; + path: { + user_id: number; + }; cookie?: never; }; - requestBody?: never; + requestBody: { + content: { + "application/json": components["schemas"]["PasswordReset"]; + }; + }; responses: { /** @description Successful Response */ - 200: { + 204: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + /** @description Validation Error */ + 422: { headers: { [name: string]: unknown; }; content: { - "application/json": components["schemas"]["ModelUsage"][]; + "application/json": components["schemas"]["HTTPValidationError"]; }; }; }; }; - list_signals_api_ai_signals_get: { + list_tickers_api_market_tickers_get: { parameters: { query?: { - status_filter?: string | null; - limit?: number; + market?: components["schemas"]["Market"]; }; header?: never; path?: never; @@ -3300,7 +3441,7 @@ export interface operations { [name: string]: unknown; }; content: { - "application/json": components["schemas"]["AISignal"][]; + "application/json": components["schemas"]["Ticker"][]; }; }; /** @description Validation Error */ @@ -3314,13 +3455,16 @@ export interface operations { }; }; }; - confirm_signal_api_ai_signals__signal_id__confirm_post: { + get_klines_api_market_klines_get: { parameters: { - query?: never; - header?: never; - path: { - signal_id: number; + query: { + symbol: string; + interval?: string; + market?: components["schemas"]["Market"]; + limit?: number; }; + header?: never; + path?: never; cookie?: never; }; requestBody?: never; @@ -3331,7 +3475,7 @@ export interface operations { [name: string]: unknown; }; content: { - "application/json": components["schemas"]["AISignal"]; + "application/json": components["schemas"]["Candle"][]; }; }; /** @description Validation Error */ @@ -3345,13 +3489,13 @@ export interface operations { }; }; }; - dismiss_signal_api_ai_signals__signal_id__dismiss_post: { + get_funding_api_market_funding_get: { parameters: { - query?: never; - header?: never; - path: { - signal_id: number; + query: { + symbol: string; }; + header?: never; + path?: never; cookie?: never; }; requestBody?: never; @@ -3362,7 +3506,7 @@ export interface operations { [name: string]: unknown; }; content: { - "application/json": components["schemas"]["AISignal"]; + "application/json": components["schemas"]["FundingRate"]; }; }; /** @description Validation Error */ @@ -3376,18 +3520,18 @@ export interface operations { }; }; }; - login_api_auth_login_post: { + get_orderbook_api_market_orderbook_get: { parameters: { - query?: never; + query: { + symbol: string; + market?: components["schemas"]["Market"]; + limit?: number; + }; header?: never; path?: never; cookie?: never; }; - requestBody: { - content: { - "application/x-www-form-urlencoded": components["schemas"]["Body_login_api_auth_login_post"]; - }; - }; + requestBody?: never; responses: { /** @description Successful Response */ 200: { @@ -3395,7 +3539,7 @@ export interface operations { [name: string]: unknown; }; content: { - "application/json": components["schemas"]["TokenPair"]; + "application/json": components["schemas"]["OrderBook"]; }; }; /** @description Validation Error */ @@ -3409,7 +3553,7 @@ export interface operations { }; }; }; - me_api_auth_me_get: { + feed_latency_api_market_latency_get: { parameters: { query?: never; header?: never; @@ -3424,12 +3568,12 @@ export interface operations { [name: string]: unknown; }; content: { - "application/json": components["schemas"]["UserInfo"]; + "application/json": components["schemas"]["LatencyRead"]; }; }; }; }; - refresh_api_auth_refresh_post: { + place_manual_order_api_orders_manual_post: { parameters: { query?: never; header?: never; @@ -3438,7 +3582,7 @@ export interface operations { }; requestBody: { content: { - "application/json": components["schemas"]["RefreshRequest"]; + "application/json": components["schemas"]["ManualOrderRequest"]; }; }; responses: { @@ -3448,7 +3592,7 @@ export interface operations { [name: string]: unknown; }; content: { - "application/json": components["schemas"]["TokenPair"]; + "application/json": components["schemas"]["Fill"]; }; }; /** @description Validation Error */ @@ -3462,18 +3606,14 @@ export interface operations { }; }; }; - run_api_backtest_run_post: { + summary_api_portfolio_summary_get: { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; - requestBody: { - content: { - "application/json": components["schemas"]["BacktestRequest"]; - }; - }; + requestBody?: never; responses: { /** @description Successful Response */ 200: { @@ -3481,56 +3621,36 @@ export interface operations { [name: string]: unknown; }; content: { - "application/json": components["schemas"]["BacktestResponse"]; - }; - }; - /** @description Validation Error */ - 422: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["HTTPValidationError"]; + "application/json": components["schemas"]["PortfolioSummary"]; }; }; }; }; - delete_edge_api_connections_edges__edge_id__delete: { + equity_api_portfolio_equity_get: { parameters: { query?: never; header?: never; - path: { - edge_id: number; - }; + path?: never; cookie?: never; }; requestBody?: never; responses: { /** @description Successful Response */ - 204: { - headers: { - [name: string]: unknown; - }; - content?: never; - }; - /** @description Validation Error */ - 422: { + 200: { headers: { [name: string]: unknown; }; content: { - "application/json": components["schemas"]["HTTPValidationError"]; + "application/json": components["schemas"]["EquitySnapshot"][]; }; }; }; }; - approve_edge_api_connections_edges__edge_id__approve_post: { + positions_api_portfolio_positions_get: { parameters: { query?: never; header?: never; - path: { - edge_id: number; - }; + path?: never; cookie?: never; }; requestBody?: never; @@ -3541,24 +3661,15 @@ export interface operations { [name: string]: unknown; }; content: { - "application/json": components["schemas"]["GraphEdge"]; - }; - }; - /** @description Validation Error */ - 422: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["HTTPValidationError"]; + "application/json": components["schemas"]["Position"][]; }; }; }; }; - get_graph_api_connections_graph_get: { + trades_api_portfolio_trades_get: { parameters: { query?: { - include_pending?: boolean; + limit?: number; }; header?: never; path?: never; @@ -3572,7 +3683,7 @@ export interface operations { [name: string]: unknown; }; content: { - "application/json": components["schemas"]["GraphView"]; + "application/json": components["schemas"]["Trade"][]; }; }; /** @description Validation Error */ @@ -3586,18 +3697,14 @@ export interface operations { }; }; }; - create_node_api_connections_nodes_post: { + costs_api_portfolio_costs_get: { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; - requestBody: { - content: { - "application/json": components["schemas"]["NodeCreate"]; - }; - }; + requestBody?: never; responses: { /** @description Successful Response */ 200: { @@ -3605,27 +3712,16 @@ export interface operations { [name: string]: unknown; }; content: { - "application/json": components["schemas"]["GraphNode"]; - }; - }; - /** @description Validation Error */ - 422: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["HTTPValidationError"]; + "application/json": components["schemas"]["CostsRead"]; }; }; }; }; - suggest_connections_api_connections_suggest__node_id__post: { + list_strategies_api_strategies_get: { parameters: { query?: never; header?: never; - path: { - node_id: number; - }; + path?: never; cookie?: never; }; requestBody?: never; @@ -3636,40 +3732,31 @@ export interface operations { [name: string]: unknown; }; content: { - "application/json": components["schemas"]["GraphEdge"][]; - }; - }; - /** @description Validation Error */ - 422: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["HTTPValidationError"]; + "application/json": components["schemas"]["StrategyRead"][]; }; }; }; }; - costs_ledger_api_costs_ledger_get: { + create_instance_api_strategies_post: { parameters: { - query?: { - limit?: number; - offset?: number; - purpose?: string | null; - }; + query?: never; header?: never; path?: never; cookie?: never; }; - requestBody?: never; + requestBody: { + content: { + "application/json": components["schemas"]["InstanceCreate"]; + }; + }; responses: { /** @description Successful Response */ - 200: { + 201: { headers: { [name: string]: unknown; }; content: { - "application/json": components["schemas"]["LedgerPage"]; + "application/json": components["schemas"]["StrategyRead"]; }; }; /** @description Validation Error */ @@ -3683,7 +3770,7 @@ export interface operations { }; }; }; - costs_summary_api_costs_summary_get: { + list_strategy_types_api_strategies_types_get: { parameters: { query?: never; header?: never; @@ -3698,63 +3785,28 @@ export interface operations { [name: string]: unknown; }; content: { - "application/json": components["schemas"]["CostsSummary"]; - }; - }; - }; - }; - audit_api_history_audit_get: { - parameters: { - query?: { - limit?: number; - }; - header?: never; - path?: never; - cookie?: never; - }; - requestBody?: never; - responses: { - /** @description Successful Response */ - 200: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["AuditLog"][]; - }; - }; - /** @description Validation Error */ - 422: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["HTTPValidationError"]; + "application/json": components["schemas"]["StrategyTypeRead"][]; }; }; }; }; - trades_api_history_trades_get: { - parameters: { - query?: { - start?: string | null; - end?: string | null; - limit?: number; - }; + delete_instance_api_strategies__name__delete: { + parameters: { + query?: never; header?: never; - path?: never; + path: { + name: string; + }; cookie?: never; }; requestBody?: never; responses: { /** @description Successful Response */ - 200: { + 204: { headers: { [name: string]: unknown; }; - content: { - "application/json": components["schemas"]["Trade"][]; - }; + content?: never; }; /** @description Validation Error */ 422: { @@ -3767,17 +3819,20 @@ export interface operations { }; }; }; - trades_csv_api_history_trades_csv_get: { + update_allocation_api_strategies__name__allocation_patch: { parameters: { - query?: { - start?: string | null; - end?: string | null; - }; + query?: never; header?: never; - path?: never; + path: { + name: string; + }; cookie?: never; }; - requestBody?: never; + requestBody: { + content: { + "application/json": components["schemas"]["AllocationUpdate"]; + }; + }; responses: { /** @description Successful Response */ 200: { @@ -3785,7 +3840,7 @@ export interface operations { [name: string]: unknown; }; content: { - "application/json": unknown; + "application/json": components["schemas"]["StrategyRead"]; }; }; /** @description Validation Error */ @@ -3799,16 +3854,18 @@ export interface operations { }; }; }; - compare_grid_api_lab_compare_post: { + update_enabled_api_strategies__name__enabled_patch: { parameters: { query?: never; header?: never; - path?: never; + path: { + name: string; + }; cookie?: never; }; requestBody: { content: { - "application/json": components["schemas"]["CompareRequest"]; + "application/json": components["schemas"]["EnabledUpdate"]; }; }; responses: { @@ -3818,7 +3875,7 @@ export interface operations { [name: string]: unknown; }; content: { - "application/json": components["schemas"]["CompareCellRead"][]; + "application/json": components["schemas"]["StrategyRead"]; }; }; /** @description Validation Error */ @@ -3832,16 +3889,18 @@ export interface operations { }; }; }; - recommend_api_lab_recommend_post: { + update_ai_model_api_strategies__name__ai_model_patch: { parameters: { query?: never; header?: never; - path?: never; + path: { + name: string; + }; cookie?: never; }; requestBody: { content: { - "application/json": components["schemas"]["RecommendRequest"]; + "application/json": components["schemas"]["AiModelUpdate"]; }; }; responses: { @@ -3851,7 +3910,7 @@ export interface operations { [name: string]: unknown; }; content: { - "application/json": components["schemas"]["RecommendResponse"]; + "application/json": components["schemas"]["StrategyRead"]; }; }; /** @description Validation Error */ @@ -3865,13 +3924,13 @@ export interface operations { }; }; }; - get_funding_api_market_funding_get: { + close_strategy_api_strategies__name__close_post: { parameters: { - query: { - symbol: string; - }; + query?: never; header?: never; - path?: never; + path: { + name: string; + }; cookie?: never; }; requestBody?: never; @@ -3882,7 +3941,7 @@ export interface operations { [name: string]: unknown; }; content: { - "application/json": components["schemas"]["FundingRate"]; + "application/json": components["schemas"]["CloseResult"]; }; }; /** @description Validation Error */ @@ -3896,19 +3955,18 @@ export interface operations { }; }; }; - get_klines_api_market_klines_get: { + run_api_backtest_run_post: { parameters: { - query: { - symbol: string; - interval?: string; - market?: components["schemas"]["Market"]; - limit?: number; - }; + query?: never; header?: never; path?: never; cookie?: never; }; - requestBody?: never; + requestBody: { + content: { + "application/json": components["schemas"]["BacktestRequest"]; + }; + }; responses: { /** @description Successful Response */ 200: { @@ -3916,7 +3974,7 @@ export interface operations { [name: string]: unknown; }; content: { - "application/json": components["schemas"]["Candle"][]; + "application/json": components["schemas"]["BacktestResponse"]; }; }; /** @description Validation Error */ @@ -3930,7 +3988,7 @@ export interface operations { }; }; }; - feed_latency_api_market_latency_get: { + read_settings_api_settings_get: { parameters: { query?: never; header?: never; @@ -3945,23 +4003,23 @@ export interface operations { [name: string]: unknown; }; content: { - "application/json": components["schemas"]["LatencyRead"]; + "application/json": components["schemas"]["SettingsRead"]; }; }; }; }; - get_orderbook_api_market_orderbook_get: { + update_mode_api_settings_mode_put: { parameters: { - query: { - symbol: string; - market?: components["schemas"]["Market"]; - limit?: number; - }; + query?: never; header?: never; path?: never; cookie?: never; }; - requestBody?: never; + requestBody: { + content: { + "application/json": components["schemas"]["ModeUpdate"]; + }; + }; responses: { /** @description Successful Response */ 200: { @@ -3969,7 +4027,7 @@ export interface operations { [name: string]: unknown; }; content: { - "application/json": components["schemas"]["OrderBook"]; + "application/json": components["schemas"]["SettingsRead"]; }; }; /** @description Validation Error */ @@ -3983,25 +4041,25 @@ export interface operations { }; }; }; - list_tickers_api_market_tickers_get: { + update_binance_keys_api_settings_binance_keys_put: { parameters: { - query?: { - market?: components["schemas"]["Market"]; - }; + query?: never; header?: never; path?: never; cookie?: never; }; - requestBody?: never; + requestBody: { + content: { + "application/json": components["schemas"]["BinanceKeysUpdate"]; + }; + }; responses: { /** @description Successful Response */ - 200: { + 204: { headers: { [name: string]: unknown; }; - content: { - "application/json": components["schemas"]["Ticker"][]; - }; + content?: never; }; /** @description Validation Error */ 422: { @@ -4014,27 +4072,27 @@ export interface operations { }; }; }; - list_news_api_news_get: { + update_venue_credentials_api_settings_venue_credentials__venue__put: { parameters: { - query?: { - symbol?: string | null; - category?: string | null; - limit?: number; - }; + query?: never; header?: never; - path?: never; + path: { + venue: string; + }; cookie?: never; }; - requestBody?: never; + requestBody: { + content: { + "application/json": components["schemas"]["VenueCredentialsUpdate"]; + }; + }; responses: { /** @description Successful Response */ - 200: { + 204: { headers: { [name: string]: unknown; }; - content: { - "application/json": components["schemas"]["NewsItem"][]; - }; + content?: never; }; /** @description Validation Error */ 422: { @@ -4047,14 +4105,20 @@ export interface operations { }; }; }; - refresh_news_api_news_refresh_post: { + update_llm_credentials_api_settings_llm_credentials__provider__put: { parameters: { query?: never; header?: never; - path?: never; + path: { + provider: string; + }; cookie?: never; }; - requestBody?: never; + requestBody: { + content: { + "application/json": components["schemas"]["LlmCredentialsUpdate"]; + }; + }; responses: { /** @description Successful Response */ 200: { @@ -4062,14 +4126,21 @@ export interface operations { [name: string]: unknown; }; content: { - "application/json": { - [key: string]: number; - }; + "application/json": components["schemas"]["SettingsRead"]; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; }; }; }; }; - place_manual_order_api_orders_manual_post: { + update_ai_spend_cap_api_settings_ai_spend_cap_put: { parameters: { query?: never; header?: never; @@ -4078,7 +4149,7 @@ export interface operations { }; requestBody: { content: { - "application/json": components["schemas"]["ManualOrderRequest"]; + "application/json": components["schemas"]["AiSpendCapUpdate"]; }; }; responses: { @@ -4088,7 +4159,7 @@ export interface operations { [name: string]: unknown; }; content: { - "application/json": components["schemas"]["Fill"]; + "application/json": components["schemas"]["SettingsRead"]; }; }; /** @description Validation Error */ @@ -4102,17 +4173,18 @@ export interface operations { }; }; }; - list_analyses_api_polymarket_analyses_get: { + update_ai_settings_api_settings_ai_put: { parameters: { - query?: { - condition_id?: string | null; - limit?: number; - }; + query?: never; header?: never; path?: never; cookie?: never; }; - requestBody?: never; + requestBody: { + content: { + "application/json": components["schemas"]["AiSettingsUpdate"]; + }; + }; responses: { /** @description Successful Response */ 200: { @@ -4120,7 +4192,7 @@ export interface operations { [name: string]: unknown; }; content: { - "application/json": components["schemas"]["MarketAnalysis"][]; + "application/json": components["schemas"]["SettingsRead"]; }; }; /** @description Validation Error */ @@ -4134,20 +4206,18 @@ export interface operations { }; }; }; - list_markets_api_polymarket_markets_get: { + update_research_settings_api_settings_research_put: { parameters: { - query?: { - watched?: boolean | null; - status_filter?: string | null; - category?: string | null; - search?: string | null; - limit?: number; - }; + query?: never; header?: never; path?: never; cookie?: never; }; - requestBody?: never; + requestBody: { + content: { + "application/json": components["schemas"]["ResearchSettingsUpdate"]; + }; + }; responses: { /** @description Successful Response */ 200: { @@ -4155,7 +4225,7 @@ export interface operations { [name: string]: unknown; }; content: { - "application/json": components["schemas"]["PredictionMarket"][]; + "application/json": components["schemas"]["SettingsRead"]; }; }; /** @description Validation Error */ @@ -4169,16 +4239,18 @@ export interface operations { }; }; }; - analyze_now_api_polymarket_markets__condition_id__analyze_post: { + update_polymarket_settings_api_settings_polymarket_put: { parameters: { query?: never; header?: never; - path: { - condition_id: string; - }; + path?: never; cookie?: never; }; - requestBody?: never; + requestBody: { + content: { + "application/json": components["schemas"]["PolymarketSettingsUpdate"]; + }; + }; responses: { /** @description Successful Response */ 200: { @@ -4186,7 +4258,7 @@ export interface operations { [name: string]: unknown; }; content: { - "application/json": components["schemas"]["MarketAnalysis"]; + "application/json": components["schemas"]["SettingsRead"]; }; }; /** @description Validation Error */ @@ -4200,18 +4272,16 @@ export interface operations { }; }; }; - update_watched_api_polymarket_markets__condition_id__watch_put: { + update_risk_settings_api_settings_risk_put: { parameters: { query?: never; header?: never; - path: { - condition_id: string; - }; + path?: never; cookie?: never; }; requestBody: { content: { - "application/json": components["schemas"]["WatchUpdate"]; + "application/json": components["schemas"]["RiskSettingsUpdate"]; }; }; responses: { @@ -4221,7 +4291,7 @@ export interface operations { [name: string]: unknown; }; content: { - "application/json": components["schemas"]["PredictionMarket"]; + "application/json": components["schemas"]["SettingsRead"]; }; }; /** @description Validation Error */ @@ -4235,14 +4305,18 @@ export interface operations { }; }; }; - refresh_markets_api_polymarket_refresh_post: { + update_council_settings_api_settings_council_put: { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; - requestBody?: never; + requestBody: { + content: { + "application/json": components["schemas"]["CouncilSettingsUpdate"]; + }; + }; responses: { /** @description Successful Response */ 200: { @@ -4250,41 +4324,32 @@ export interface operations { [name: string]: unknown; }; content: { - "application/json": { - [key: string]: number; - }; - }; - }; - }; - }; - costs_api_portfolio_costs_get: { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - requestBody?: never; - responses: { - /** @description Successful Response */ - 200: { + "application/json": components["schemas"]["SettingsRead"]; + }; + }; + /** @description Validation Error */ + 422: { headers: { [name: string]: unknown; }; content: { - "application/json": components["schemas"]["CostsRead"]; + "application/json": components["schemas"]["HTTPValidationError"]; }; }; }; }; - equity_api_portfolio_equity_get: { + update_ai_action_mode_api_settings_ai_action_mode_put: { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; - requestBody?: never; + requestBody: { + content: { + "application/json": components["schemas"]["AiActionModeUpdate"]; + }; + }; responses: { /** @description Successful Response */ 200: { @@ -4292,12 +4357,21 @@ export interface operations { [name: string]: unknown; }; content: { - "application/json": components["schemas"]["EquitySnapshot"][]; + "application/json": components["schemas"]["SettingsRead"]; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; }; }; }; }; - positions_api_portfolio_positions_get: { + get_watchdog_api_system_watchdog_get: { parameters: { query?: never; header?: never; @@ -4312,14 +4386,18 @@ export interface operations { [name: string]: unknown; }; content: { - "application/json": components["schemas"]["Position"][]; + "application/json": components["schemas"]["WatchdogStatus"]; }; }; }; }; - summary_api_portfolio_summary_get: { + trades_api_history_trades_get: { parameters: { - query?: never; + query?: { + start?: string | null; + end?: string | null; + limit?: number; + }; header?: never; path?: never; cookie?: never; @@ -4332,12 +4410,21 @@ export interface operations { [name: string]: unknown; }; content: { - "application/json": components["schemas"]["PortfolioSummary"]; + "application/json": components["schemas"]["Trade"][]; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; }; }; }; }; - trades_api_portfolio_trades_get: { + audit_api_history_audit_get: { parameters: { query?: { limit?: number; @@ -4354,7 +4441,7 @@ export interface operations { [name: string]: unknown; }; content: { - "application/json": components["schemas"]["Trade"][]; + "application/json": components["schemas"]["AuditLog"][]; }; }; /** @description Validation Error */ @@ -4368,11 +4455,11 @@ export interface operations { }; }; }; - list_reports_api_research_get: { + trades_csv_api_history_trades_csv_get: { parameters: { query?: { - symbol?: string | null; - limit?: number; + start?: string | null; + end?: string | null; }; header?: never; path?: never; @@ -4386,7 +4473,7 @@ export interface operations { [name: string]: unknown; }; content: { - "application/json": components["schemas"]["ResearchReportRead"][]; + "application/json": unknown; }; }; /** @description Validation Error */ @@ -4400,7 +4487,7 @@ export interface operations { }; }; }; - run_research_api_research_run_post: { + analyze_and_decide_api_ai_analyze_post: { parameters: { query?: never; header?: never; @@ -4409,7 +4496,7 @@ export interface operations { }; requestBody: { content: { - "application/json": components["schemas"]["ResearchRun"]; + "application/json": components["schemas"]["AnalyzeRequest"]; }; }; responses: { @@ -4419,7 +4506,7 @@ export interface operations { [name: string]: unknown; }; content: { - "application/json": components["schemas"]["ResearchReportRead"][]; + "application/json": components["schemas"]["Decision"]; }; }; /** @description Validation Error */ @@ -4433,13 +4520,33 @@ export interface operations { }; }; }; - get_report_api_research__report_id__get: { + model_performance_api_ai_models_get: { parameters: { query?: never; header?: never; - path: { - report_id: number; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ModelUsage"][]; + }; + }; + }; + }; + decision_log_api_ai_decisions_get: { + parameters: { + query?: { + limit?: number; }; + header?: never; + path?: never; cookie?: never; }; requestBody?: never; @@ -4450,7 +4557,7 @@ export interface operations { [name: string]: unknown; }; content: { - "application/json": components["schemas"]["ResearchReportRead"]; + "application/json": components["schemas"]["LLMUsage"][]; }; }; /** @description Validation Error */ @@ -4464,13 +4571,14 @@ export interface operations { }; }; }; - get_review_api_research__report_id__review_get: { + list_signals_api_ai_signals_get: { parameters: { - query?: never; - header?: never; - path: { - report_id: number; + query?: { + status_filter?: string | null; + limit?: number; }; + header?: never; + path?: never; cookie?: never; }; requestBody?: never; @@ -4481,7 +4589,7 @@ export interface operations { [name: string]: unknown; }; content: { - "application/json": components["schemas"]["CouncilReviewRead"]; + "application/json": components["schemas"]["AISignal"][]; }; }; /** @description Validation Error */ @@ -4495,12 +4603,12 @@ export interface operations { }; }; }; - rerun_review_api_research__report_id__review_post: { + confirm_signal_api_ai_signals__signal_id__confirm_post: { parameters: { query?: never; header?: never; path: { - report_id: number; + signal_id: number; }; cookie?: never; }; @@ -4512,7 +4620,7 @@ export interface operations { [name: string]: unknown; }; content: { - "application/json": components["schemas"]["CouncilReviewRead"]; + "application/json": components["schemas"]["AISignal"]; }; }; /** @description Validation Error */ @@ -4526,11 +4634,13 @@ export interface operations { }; }; }; - read_settings_api_settings_get: { + dismiss_signal_api_ai_signals__signal_id__dismiss_post: { parameters: { query?: never; header?: never; - path?: never; + path: { + signal_id: number; + }; cookie?: never; }; requestBody?: never; @@ -4541,23 +4651,30 @@ export interface operations { [name: string]: unknown; }; content: { - "application/json": components["schemas"]["SettingsRead"]; + "application/json": components["schemas"]["AISignal"]; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; }; }; }; }; - update_ai_settings_api_settings_ai_put: { + local_models_api_ai_local_get: { parameters: { - query?: never; + query?: { + refresh?: boolean; + }; header?: never; path?: never; cookie?: never; }; - requestBody: { - content: { - "application/json": components["schemas"]["AiSettingsUpdate"]; - }; - }; + requestBody?: never; responses: { /** @description Successful Response */ 200: { @@ -4565,7 +4682,7 @@ export interface operations { [name: string]: unknown; }; content: { - "application/json": components["schemas"]["SettingsRead"]; + "application/json": components["schemas"]["LocalAIRead"]; }; }; /** @description Validation Error */ @@ -4579,18 +4696,14 @@ export interface operations { }; }; }; - update_ai_action_mode_api_settings_ai_action_mode_put: { + list_tokens_api_tokens_get: { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; - requestBody: { - content: { - "application/json": components["schemas"]["AiActionModeUpdate"]; - }; - }; + requestBody?: never; responses: { /** @description Successful Response */ 200: { @@ -4598,21 +4711,12 @@ export interface operations { [name: string]: unknown; }; content: { - "application/json": components["schemas"]["SettingsRead"]; - }; - }; - /** @description Validation Error */ - 422: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["HTTPValidationError"]; + "application/json": components["schemas"]["ApiTokenRead"][]; }; }; }; }; - update_ai_spend_cap_api_settings_ai_spend_cap_put: { + create_token_api_tokens_post: { parameters: { query?: never; header?: never; @@ -4621,17 +4725,17 @@ export interface operations { }; requestBody: { content: { - "application/json": components["schemas"]["AiSpendCapUpdate"]; + "application/json": components["schemas"]["ApiTokenCreate"]; }; }; responses: { /** @description Successful Response */ - 200: { + 201: { headers: { [name: string]: unknown; }; content: { - "application/json": components["schemas"]["SettingsRead"]; + "application/json": components["schemas"]["ApiTokenCreated"]; }; }; /** @description Validation Error */ @@ -4645,18 +4749,16 @@ export interface operations { }; }; }; - update_binance_keys_api_settings_binance_keys_put: { + revoke_token_api_tokens__token_id__delete: { parameters: { query?: never; header?: never; - path?: never; - cookie?: never; - }; - requestBody: { - content: { - "application/json": components["schemas"]["BinanceKeysUpdate"]; + path: { + token_id: number; }; + cookie?: never; }; + requestBody?: never; responses: { /** @description Successful Response */ 204: { @@ -4676,18 +4778,14 @@ export interface operations { }; }; }; - update_council_settings_api_settings_council_put: { + get_venues_api_venues_get: { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; - requestBody: { - content: { - "application/json": components["schemas"]["CouncilSettingsUpdate"]; - }; - }; + requestBody?: never; responses: { /** @description Successful Response */ 200: { @@ -4695,32 +4793,21 @@ export interface operations { [name: string]: unknown; }; content: { - "application/json": components["schemas"]["SettingsRead"]; - }; - }; - /** @description Validation Error */ - 422: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["HTTPValidationError"]; + "application/json": components["schemas"]["VenueRead"][]; }; }; }; }; - update_llm_credentials_api_settings_llm_credentials__provider__put: { + set_active_api_venues_active_put: { parameters: { query?: never; header?: never; - path: { - provider: string; - }; + path?: never; cookie?: never; }; requestBody: { content: { - "application/json": components["schemas"]["LlmCredentialsUpdate"]; + "application/json": components["schemas"]["ActiveVenueUpdate"]; }; }; responses: { @@ -4730,7 +4817,7 @@ export interface operations { [name: string]: unknown; }; content: { - "application/json": components["schemas"]["SettingsRead"]; + "application/json": components["schemas"]["VenueRead"][]; }; }; /** @description Validation Error */ @@ -4744,18 +4831,18 @@ export interface operations { }; }; }; - update_mode_api_settings_mode_put: { + list_news_api_news_get: { parameters: { - query?: never; + query?: { + symbol?: string | null; + category?: string | null; + limit?: number; + }; header?: never; path?: never; cookie?: never; }; - requestBody: { - content: { - "application/json": components["schemas"]["ModeUpdate"]; - }; - }; + requestBody?: never; responses: { /** @description Successful Response */ 200: { @@ -4763,7 +4850,7 @@ export interface operations { [name: string]: unknown; }; content: { - "application/json": components["schemas"]["SettingsRead"]; + "application/json": components["schemas"]["NewsItem"][]; }; }; /** @description Validation Error */ @@ -4777,18 +4864,14 @@ export interface operations { }; }; }; - update_polymarket_settings_api_settings_polymarket_put: { + refresh_news_api_news_refresh_post: { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; - requestBody: { - content: { - "application/json": components["schemas"]["PolymarketSettingsUpdate"]; - }; - }; + requestBody?: never; responses: { /** @description Successful Response */ 200: { @@ -4796,32 +4879,27 @@ export interface operations { [name: string]: unknown; }; content: { - "application/json": components["schemas"]["SettingsRead"]; + "application/json": { + [key: string]: number; + }; }; }; - /** @description Validation Error */ - 422: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["HTTPValidationError"]; - }; + }; + }; + list_markets_api_polymarket_markets_get: { + parameters: { + query?: { + watched?: boolean | null; + status_filter?: string | null; + category?: string | null; + search?: string | null; + limit?: number; }; - }; - }; - update_research_settings_api_settings_research_put: { - parameters: { - query?: never; header?: never; path?: never; cookie?: never; }; - requestBody: { - content: { - "application/json": components["schemas"]["ResearchSettingsUpdate"]; - }; - }; + requestBody?: never; responses: { /** @description Successful Response */ 200: { @@ -4829,7 +4907,7 @@ export interface operations { [name: string]: unknown; }; content: { - "application/json": components["schemas"]["SettingsRead"]; + "application/json": components["schemas"]["PredictionMarket"][]; }; }; /** @description Validation Error */ @@ -4843,27 +4921,29 @@ export interface operations { }; }; }; - update_venue_credentials_api_settings_venue_credentials__venue__put: { + update_watched_api_polymarket_markets__condition_id__watch_put: { parameters: { query?: never; header?: never; path: { - venue: string; + condition_id: string; }; cookie?: never; }; requestBody: { content: { - "application/json": components["schemas"]["VenueCredentialsUpdate"]; + "application/json": components["schemas"]["WatchUpdate"]; }; }; responses: { /** @description Successful Response */ - 204: { + 200: { headers: { [name: string]: unknown; }; - content?: never; + content: { + "application/json": components["schemas"]["PredictionMarket"]; + }; }; /** @description Validation Error */ 422: { @@ -4876,7 +4956,7 @@ export interface operations { }; }; }; - list_strategies_api_strategies_get: { + refresh_markets_api_polymarket_refresh_post: { parameters: { query?: never; header?: never; @@ -4891,31 +4971,32 @@ export interface operations { [name: string]: unknown; }; content: { - "application/json": components["schemas"]["StrategyRead"][]; + "application/json": { + [key: string]: number; + }; }; }; }; }; - create_instance_api_strategies_post: { + list_analyses_api_polymarket_analyses_get: { parameters: { - query?: never; + query?: { + condition_id?: string | null; + limit?: number; + }; header?: never; path?: never; cookie?: never; }; - requestBody: { - content: { - "application/json": components["schemas"]["InstanceCreate"]; - }; - }; + requestBody?: never; responses: { /** @description Successful Response */ - 201: { + 200: { headers: { [name: string]: unknown; }; content: { - "application/json": components["schemas"]["StrategyRead"]; + "application/json": components["schemas"]["MarketAnalysis"][]; }; }; /** @description Validation Error */ @@ -4929,11 +5010,13 @@ export interface operations { }; }; }; - list_strategy_types_api_strategies_types_get: { + analyze_now_api_polymarket_markets__condition_id__analyze_post: { parameters: { query?: never; header?: never; - path?: never; + path: { + condition_id: string; + }; cookie?: never; }; requestBody?: never; @@ -4944,28 +5027,39 @@ export interface operations { [name: string]: unknown; }; content: { - "application/json": components["schemas"]["StrategyTypeRead"][]; + "application/json": components["schemas"]["MarketAnalysis"]; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; }; }; }; }; - delete_instance_api_strategies__name__delete: { + get_graph_api_connections_graph_get: { parameters: { - query?: never; - header?: never; - path: { - name: string; + query?: { + include_pending?: boolean; }; + header?: never; + path?: never; cookie?: never; }; requestBody?: never; responses: { /** @description Successful Response */ - 204: { + 200: { headers: { [name: string]: unknown; }; - content?: never; + content: { + "application/json": components["schemas"]["GraphView"]; + }; }; /** @description Validation Error */ 422: { @@ -4978,18 +5072,16 @@ export interface operations { }; }; }; - update_ai_model_api_strategies__name__ai_model_patch: { + create_node_api_connections_nodes_post: { parameters: { query?: never; header?: never; - path: { - name: string; - }; + path?: never; cookie?: never; }; requestBody: { content: { - "application/json": components["schemas"]["AiModelUpdate"]; + "application/json": components["schemas"]["NodeCreate"]; }; }; responses: { @@ -4999,7 +5091,7 @@ export interface operations { [name: string]: unknown; }; content: { - "application/json": components["schemas"]["StrategyRead"]; + "application/json": components["schemas"]["GraphNode"]; }; }; /** @description Validation Error */ @@ -5013,20 +5105,16 @@ export interface operations { }; }; }; - update_allocation_api_strategies__name__allocation_patch: { + suggest_connections_api_connections_suggest__node_id__post: { parameters: { query?: never; header?: never; path: { - name: string; + node_id: number; }; cookie?: never; }; - requestBody: { - content: { - "application/json": components["schemas"]["AllocationUpdate"]; - }; - }; + requestBody?: never; responses: { /** @description Successful Response */ 200: { @@ -5034,7 +5122,7 @@ export interface operations { [name: string]: unknown; }; content: { - "application/json": components["schemas"]["StrategyRead"]; + "application/json": components["schemas"]["GraphEdge"][]; }; }; /** @description Validation Error */ @@ -5048,12 +5136,12 @@ export interface operations { }; }; }; - close_strategy_api_strategies__name__close_post: { + approve_edge_api_connections_edges__edge_id__approve_post: { parameters: { query?: never; header?: never; path: { - name: string; + edge_id: number; }; cookie?: never; }; @@ -5065,7 +5153,7 @@ export interface operations { [name: string]: unknown; }; content: { - "application/json": components["schemas"]["CloseResult"]; + "application/json": components["schemas"]["GraphEdge"]; }; }; /** @description Validation Error */ @@ -5079,29 +5167,23 @@ export interface operations { }; }; }; - update_enabled_api_strategies__name__enabled_patch: { + delete_edge_api_connections_edges__edge_id__delete: { parameters: { query?: never; header?: never; path: { - name: string; + edge_id: number; }; cookie?: never; }; - requestBody: { - content: { - "application/json": components["schemas"]["EnabledUpdate"]; - }; - }; + requestBody?: never; responses: { /** @description Successful Response */ - 200: { + 204: { headers: { [name: string]: unknown; }; - content: { - "application/json": components["schemas"]["StrategyRead"]; - }; + content?: never; }; /** @description Validation Error */ 422: { @@ -5114,9 +5196,12 @@ export interface operations { }; }; }; - get_watchdog_api_system_watchdog_get: { + list_reports_api_research_get: { parameters: { - query?: never; + query?: { + symbol?: string | null; + limit?: number; + }; header?: never; path?: never; cookie?: never; @@ -5129,16 +5214,27 @@ export interface operations { [name: string]: unknown; }; content: { - "application/json": components["schemas"]["WatchdogStatus"]; + "application/json": components["schemas"]["ResearchReportRead"][]; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; }; }; }; }; - list_tokens_api_tokens_get: { + get_report_api_research__report_id__get: { parameters: { query?: never; header?: never; - path?: never; + path: { + report_id: number; + }; cookie?: never; }; requestBody?: never; @@ -5149,12 +5245,21 @@ export interface operations { [name: string]: unknown; }; content: { - "application/json": components["schemas"]["ApiTokenRead"][]; + "application/json": components["schemas"]["ResearchReportRead"]; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; }; }; }; }; - create_token_api_tokens_post: { + run_research_api_research_run_post: { parameters: { query?: never; header?: never; @@ -5163,17 +5268,17 @@ export interface operations { }; requestBody: { content: { - "application/json": components["schemas"]["ApiTokenCreate"]; + "application/json": components["schemas"]["ResearchRun"]; }; }; responses: { /** @description Successful Response */ - 201: { + 200: { headers: { [name: string]: unknown; }; content: { - "application/json": components["schemas"]["ApiTokenCreated"]; + "application/json": components["schemas"]["ResearchReportRead"][]; }; }; /** @description Validation Error */ @@ -5187,23 +5292,25 @@ export interface operations { }; }; }; - revoke_token_api_tokens__token_id__delete: { + get_review_api_research__report_id__review_get: { parameters: { query?: never; header?: never; path: { - token_id: number; + report_id: number; }; cookie?: never; }; requestBody?: never; responses: { /** @description Successful Response */ - 204: { + 200: { headers: { [name: string]: unknown; }; - content?: never; + content: { + "application/json": components["schemas"]["CouncilReviewRead"]; + }; }; /** @description Validation Error */ 422: { @@ -5216,11 +5323,13 @@ export interface operations { }; }; }; - list_users_api_users_get: { + rerun_review_api_research__report_id__review_post: { parameters: { query?: never; header?: never; - path?: never; + path: { + report_id: number; + }; cookie?: never; }; requestBody?: never; @@ -5231,31 +5340,7 @@ export interface operations { [name: string]: unknown; }; content: { - "application/json": components["schemas"]["UserRead"][]; - }; - }; - }; - }; - create_user_api_users_post: { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - requestBody: { - content: { - "application/json": components["schemas"]["UserCreate"]; - }; - }; - responses: { - /** @description Successful Response */ - 201: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["UserRead"]; + "application/json": components["schemas"]["CouncilReviewRead"]; }; }; /** @description Validation Error */ @@ -5269,18 +5354,16 @@ export interface operations { }; }; }; - update_user_api_users__user_id__patch: { + compare_grid_api_lab_compare_post: { parameters: { query?: never; header?: never; - path: { - user_id: number; - }; + path?: never; cookie?: never; }; requestBody: { content: { - "application/json": components["schemas"]["UserUpdate"]; + "application/json": components["schemas"]["CompareRequest"]; }; }; responses: { @@ -5290,7 +5373,7 @@ export interface operations { [name: string]: unknown; }; content: { - "application/json": components["schemas"]["UserRead"]; + "application/json": components["schemas"]["CompareCellRead"][]; }; }; /** @description Validation Error */ @@ -5304,27 +5387,27 @@ export interface operations { }; }; }; - reset_password_api_users__user_id__password_post: { + recommend_api_lab_recommend_post: { parameters: { query?: never; header?: never; - path: { - user_id: number; - }; + path?: never; cookie?: never; }; requestBody: { content: { - "application/json": components["schemas"]["PasswordReset"]; + "application/json": components["schemas"]["RecommendRequest"]; }; }; responses: { /** @description Successful Response */ - 204: { + 200: { headers: { [name: string]: unknown; }; - content?: never; + content: { + "application/json": components["schemas"]["RecommendResponse"]; + }; }; /** @description Validation Error */ 422: { @@ -5337,7 +5420,7 @@ export interface operations { }; }; }; - get_venues_api_venues_get: { + costs_summary_api_costs_summary_get: { parameters: { query?: never; header?: never; @@ -5352,23 +5435,23 @@ export interface operations { [name: string]: unknown; }; content: { - "application/json": components["schemas"]["VenueRead"][]; + "application/json": components["schemas"]["CostsSummary"]; }; }; }; }; - set_active_api_venues_active_put: { + costs_ledger_api_costs_ledger_get: { parameters: { - query?: never; + query?: { + limit?: number; + offset?: number; + purpose?: string | null; + }; header?: never; path?: never; cookie?: never; }; - requestBody: { - content: { - "application/json": components["schemas"]["ActiveVenueUpdate"]; - }; - }; + requestBody?: never; responses: { /** @description Successful Response */ 200: { @@ -5376,7 +5459,7 @@ export interface operations { [name: string]: unknown; }; content: { - "application/json": components["schemas"]["VenueRead"][]; + "application/json": components["schemas"]["LedgerPage"]; }; }; /** @description Validation Error */ diff --git a/web/src/lib/api/settings.ts b/web/src/lib/api/settings.ts index 21a80a7..fbb8cdf 100644 --- a/web/src/lib/api/settings.ts +++ b/web/src/lib/api/settings.ts @@ -110,6 +110,20 @@ export async function updatePolymarketSettings(body: { return data; } +export async function updateRiskSettings(body: { + stop_loss_pct: string; + take_profit_pct: string; + max_drawdown_pct: string; + daily_loss_limit: string; + max_position_notional: string; +}): Promise { + const { data, error } = await api.PUT("/api/settings/risk", { body }); + if (error || !data) { + throw new Error(errorDetail(error, "Failed to update risk settings")); + } + return data; +} + export async function updateCouncilSettings(body: { members: { provider: string; model: string }[]; quorum: string; diff --git a/web/src/pages/Settings.tsx b/web/src/pages/Settings.tsx index 7df780e..30c82b6 100644 --- a/web/src/pages/Settings.tsx +++ b/web/src/pages/Settings.tsx @@ -17,6 +17,7 @@ import { Modal, SectionHeader, SegmentedControl, + Toggle, } from "../components/ui"; import { GuideButton } from "../components/GuideModal"; import { @@ -33,6 +34,7 @@ import { updateLlmCredentials, updateMode, updateResearchSettings, + updateRiskSettings, updateVenueCredentials, } from "../lib/api/settings"; import { @@ -63,6 +65,9 @@ function fieldLabel(field: string): string { return spaced.charAt(0).toUpperCase() + spaced.slice(1); } +/** A "0"/"0.00" risk limit is disabled — show it as an empty field. */ +const nonZero = (v: string): string => (Number(v) > 0 ? v : ""); + const selectStyle = { height: 34, padding: "0 10px", @@ -117,6 +122,13 @@ export function Settings() { const [pmScreenTop, setPmScreenTop] = useState("10"); const [pmStake, setPmStake] = useState("100"); + // Risk controls — value "" or "0" means the limit is disabled. + const [riskStopLoss, setRiskStopLoss] = useState(""); + const [riskTakeProfit, setRiskTakeProfit] = useState(""); + const [riskMaxDrawdown, setRiskMaxDrawdown] = useState(""); + const [riskDailyLoss, setRiskDailyLoss] = useState(""); + const [riskMaxNotional, setRiskMaxNotional] = useState(""); + const [localAi, setLocalAi] = useState(null); const [localBusy, setLocalBusy] = useState(false); // Per-LLM-provider credential inputs: provider → { api_key, base_url }. @@ -153,6 +165,11 @@ export function Settings() { setPmConfidence(String(s.polymarket_min_confidence)); setPmScreenTop(String(s.polymarket_screen_top)); setPmStake(String(s.polymarket_stake)); + setRiskStopLoss(nonZero(s.risk_stop_loss_pct)); + setRiskTakeProfit(nonZero(s.risk_take_profit_pct)); + setRiskMaxDrawdown(nonZero(s.risk_max_drawdown_pct)); + setRiskDailyLoss(nonZero(s.risk_daily_loss_limit)); + setRiskMaxNotional(nonZero(s.risk_max_position_notional)); }, []); const load = useCallback(async () => { @@ -292,6 +309,20 @@ export function Settings() { setNotice("Polymarket settings saved. Interval changes apply on restart."); }); + const saveRisk = () => + run(async () => { + applySettings( + await updateRiskSettings({ + stop_loss_pct: riskStopLoss || "0", + take_profit_pct: riskTakeProfit || "0", + max_drawdown_pct: riskMaxDrawdown || "0", + daily_loss_limit: riskDailyLoss || "0", + max_position_notional: riskMaxNotional || "0", + }), + ); + setNotice("Risk controls saved — applied on the next engine tick."); + }); + const saveAiActionMode = (mode: string) => run(async () => { applySettings(await updateAiActionMode(mode)); @@ -940,6 +971,31 @@ export function Settings() { + + +
+
+ Changes apply on the next engine tick — a stop-loss you enable may + immediately close an already-losing position. +
+ + + + + + + +
+ +
+
+
+ ); } + +/** + * RiskRow — one risk limit. Percentage rows (those with a `recommended` default) + * get a Toggle that arms/clears the value, pre-filling the recommended default + * on enable. Currency rows have no recommended default, so the numeric Input + * alone controls them (a value > 0 arms the limit, blank/0 disables it) — a + * dead toggle there could never switch on, so it is replaced by a spacer that + * keeps the rows aligned. Inputs are floored at 0, and percentages capped at + * 100, so a stray negative can't leave the field and toggle disagreeing. + */ +function RiskRow({ + label, + hint, + unit, + value, + onChange, + recommended, + placeholder, +}: { + label: string; + hint: string; + unit: string; + value: string; + onChange: (v: string) => void; + recommended?: string; + placeholder?: string; +}) { + const enabled = Number(value) > 0; + const isPercent = unit === "%"; + // Toggle width (md size) — keep the spacer the same so rows stay aligned. + const TOGGLE_WIDTH = 34; + return ( +
+ {recommended !== undefined ? ( + onChange(on ? recommended : "")} /> + ) : ( + + )} +
+
{label}
+
{hint}
+
+
+ onChange(e.target.value)} + suffix={unit} + placeholder={placeholder ?? "off"} + /> +
+
+ ); +}