Accounts & Brokers
Status: active development. The UTA stack and trading workflow are evolving fast and known to be unstable across releases. Treat live trading as opt-in only after validating with paper / demo accounts. If you hit a bug or unexpected behaviour, please open an issue at github.com/TraderAlice/OpenAlice/issues so it can be reproduced and fixed.
Every trading account in OpenAlice is a Unified Trading Account (UTA) — a self-contained entity that bundles a broker connection, a git-like operation history, a guard pipeline, and a snapshot scheduler.
AI and the frontend interact with UTAs exclusively. Brokers are internal implementation details.
Preset-based configuration
UTA config is preset-driven: you pick one of the bundled presets (OKX, Bybit, Alpaca, IBKR, ...) and fill in the credentials it asks for. The preset itself decides which engine to use, which fields to render, which fields to mask as passwords, and how to translate the form values into the engine's internal config.
This replaced the older {type, brokerConfig} schema. Existing accounts.json files in the old shape are auto-migrated on first startup (see Migration below).
[
{
"id": "alpaca-paper",
"label": "Alpaca Paper",
"presetId": "alpaca",
"enabled": true,
"guards": [
{ "type": "max-position-size", "options": { "maxPercentOfEquity": 20 } }
],
"presetConfig": {
"mode": "paper",
"apiKey": "your-key",
"apiSecret": "your-secret"
}
}
]
| Field | Description |
|---|---|
id | Unique identifier, used in source parameters and as the aliceId prefix |
label | Display name (optional, defaults to id) |
presetId | One of the catalog ids (okx, bybit, hyperliquid, bitget, ccxt-custom, alpaca, ibkr-tws) |
enabled | Set to false to skip loading this account |
guards | Array of guard configs (type + options) |
presetConfig | User-filled form values, validated against the preset's own Zod schema |
Built-in presets
The preset catalog is the single source of truth. Each preset ships with a Zod schema, an optional Mode dropdown (Live / Demo / Testnet / Paper / etc.), per-field password masking, and a toEngineConfig() translator.
| Preset id | Engine | Modes | Notes |
|---|---|---|---|
okx | CCXT | Live, Demo Trading | Demo mode requires demo-specific API keys generated from OKX's demo console — live keys are rejected |
bybit | CCXT | Live, Testnet, Demo Trading | Three environments: Testnet (separate domain, fake market data), Demo Trading (real market data, simulated fills), Live |
hyperliquid | CCXT | Mainnet, Testnet | Wallet-based auth — generate a dedicated API wallet at app.hyperliquid.xyz/API, never paste your main wallet's key |
bitget | CCXT | Live, Demo Trading | Requires API key + secret + passphrase; demo routes to simulated matching on prod domain |
ccxt-custom | CCXT | — | Power-user escape hatch for any of CCXT's 100+ exchanges that doesn't have a dedicated preset; sandbox/demoTrading semantics vary per exchange |
alpaca | Alpaca | Paper, Live | Paper and Live use separate API keys generated from the matching dashboard |
ibkr-tws | IBKR | (port-driven) | Auth via local TWS / IB Gateway socket — no API keys here. Default ports: TWS 7496/7497, Gateway 4001/4002 |
CCXT-engine presets all map to the same CcxtBroker class — the preset just picks the right exchange id and translates its mode flag (sandbox / demoTrading) for that exchange's quirks. Adding a new exchange-specific preset is a one-file edit; see Custom Brokers.
The new-account wizard
The Web UI's Trading page has a multi-step wizard that walks you through creating a UTA without hand-editing JSON:
- Pick — choose a preset from the catalog (grouped by category: crypto / securities / custom).
- Config — fill in the fields rendered from that preset's Zod schema. Password fields are masked as you type. Mode selectors render as a dropdown when the preset declares them.
- Test — the wizard POSTs to
/api/trading/config/test-connection, which actually tries to connect to the broker with the supplied credentials. Only after a successful test does the Save button enable; the validated config is then written toaccounts.jsonand the new UTA reconnects.
This flow catches three common mistakes at creation time: bad keys, wrong region/environment (e.g., live keys against demo endpoint), and missing credentials a particular exchange requires.
You can also create or edit a UTA programmatically by writing to accounts.json directly — the wizard is a convenience layer, not a requirement.
Brokers
The presets cover three engine implementations. The engine is invisible to the AI and the UI — both only talk to UTAs.
CCXT (Crypto)
Connects to 100+ crypto exchanges via the CCXT library. The OKX, Bybit, Hyperliquid, and Bitget presets all use this engine; ccxt-custom exposes the raw credential field set for any other exchange.
Spot synthesis. CCXT brokers now synthesize spot holdings into Position records: balances are pulled via fetchBalance, paired with the preferred quote market (USDT > USDC > USD) for live pricing, and merged with derivatives positions in a single getPositions() response. Stablecoins (USDT/USDC) are normalized to USD. Before this, spot accounts looked empty in the AI's view of positions; now they're first-class.
Exchange-specific overrides. Most exchanges work out of the box. Two presets carry quirks worth flagging:
- Bybit — three modes (Live / Testnet / Demo Trading). Testnet and Demo Trading each need their own API keys from the matching environment.
- Hyperliquid — uses wallet signatures, not API keys. The "wallet address" can be either the main vault wallet or the API wallet; the "private key" must be the API wallet's. Generate one at
app.hyperliquid.xyz/API.
Alpaca (US Equities)
Commission-free US equities and ETFs with fractional share support via Alpaca. The alpaca preset offers a Paper / Live mode toggle. Paper accounts are free and provide realistic simulation with delayed market data; Paper and Live use separate API keys.
IBKR (Interactive Brokers)
Professional-grade trading via TWS (Trader Workstation) or IB Gateway. Supports stocks, options, futures, and bonds. The ibkr-tws preset connects to a local TWS/Gateway socket — there are no API keys, just host/port/clientId and an optional accountId.
Default ports:
| Mode | TWS | IB Gateway |
|---|---|---|
| Paper | 7497 | 4002 |
| Live | 7496 | 4001 |
IBKR requires TWS or IB Gateway running locally with "Enable ActiveX and Socket Clients" turned on (File → Global Configuration → API → Settings).
Contract Identity — aliceId
Every contract in OpenAlice has an aliceId in the format accountId|nativeKey:
alpaca-paper|AAPL → AAPL on Alpaca paper account
bybit-demo|BTC/USDT:USDT → BTC perpetual on Bybit demo
ibkr-live|265598 → AAPL by conId on IBKR
The nativeKey part is broker-specific:
- Alpaca: ticker symbol (
AAPL) - CCXT: unified symbol (
BTC/USDT:USDT) - IBKR: conId (numeric contract identifier)
All trading tools accept aliceId to unambiguously identify which contract on which account. Use searchContracts (or the cross-UTA search described below) to discover available contracts.
Contract Search
Contract discovery is a first-class capability that runs across every configured UTA in one query. The broker side has two strategies:
| Strategy | Brokers | How it works |
|---|---|---|
| EnumeratingCatalog | Alpaca, CCXT, Mock | Loads the full market list at startup into memory; refreshed every 6h |
| SearchingCatalog | IBKR | Delegates to reqMatchingSymbols server-side — no local cache |
A central fuzzy ranker scores hits (exact 100 → prefix 80 → name boundary 70 → full word 50 → substring 30 → quote-currency 20). Same score keeps the broker's original order, so CCXT's liquidity-sorted results stay liquidity-sorted.
A normalization rule set translates data-vendor symbols (e.g., yfinance BTCUSDT) into broker-side patterns (BTC) before searching, so a single market panel symbol can resolve to tradeable contracts on every exchange that lists it.
HTTP route (used by the frontend's market workbench):
GET /api/trading/contracts/search?pattern=AAPL&assetClass=equity
assetClass is a hint: equity, crypto, currency, commodity, or unknown (default). For crypto, the rule set strips the quote suffix so a search for BTCUSDT matches BTC/USDT on every exchange.
The Web UI's market analysis panels show a Tradeable Contracts card with the top results — clicking one jumps to the corresponding UTA's order entry surface.
Catalog refresh cron. Every 6 hours the engine calls refreshCatalog() on every UTA. For EnumeratingCatalog brokers this re-pulls the market list; for IBKR (SearchingCatalog) it's a no-op. Newly listed assets surface, delisted ones drop. This is an internal timer, separate from user-defined cron jobs.
Multiple Accounts
You can run multiple accounts simultaneously — even across different brokers and presets:
[
{ "id": "alpaca-paper", "presetId": "alpaca", "presetConfig": { "mode": "paper", ... } },
{ "id": "bybit-demo", "presetId": "bybit", "presetConfig": { "mode": "demo", ... } },
{ "id": "okx-live", "presetId": "okx", "presetConfig": { "mode": "live", ... } }
]
Each account gets its own UTA with independent git history, guards, and snapshots. Tools that accept a source parameter let you target specific accounts or query all at once.
Account Lifecycle
The AccountManager manages UTA lifecycle:
- Init — Creates a UTA from config, starts async broker connection
- Connect — Broker initializes and verifies credentials
- Healthy — Broker responding normally
- Degraded — 3+ consecutive failures, still accepting requests
- Offline — 6+ failures, auto-recovery starts (exponential backoff)
- Disabled — Permanent error (bad credentials), requires manual fix
Health transitions emit events and are visible in the Web UI. The Web UI also exposes a per-UTA detail page at /uta/:id with positions, orders, market clock, and a manual order entry form that bypasses the AI for direct trade entry.
Migration
If your accounts.json is in the old {type, brokerConfig} shape, the engine auto-migrates it on the first startup that sees the new code:
- The original file is backed up to
accounts.json.backup-pre-preset(so a bad migration is never destructive). - Each legacy record is mapped to the closest preset:
type: "ccxt"withexchange: "okx"→presetId: "okx",presetConfig.modederived fromsandbox/demoTradingtype: "alpaca"→presetId: "alpaca",presetConfig.modefrompapertype: "ibkr"→presetId: "ibkr-tws", host/port/clientId carried over- Unknown CCXT exchanges fall through to
ccxt-custom
- The translated records are validated against the new schema and written back to
accounts.json. - Records that can't be mapped (unknown engine, missing exchange) are skipped and logged — recreate them via the wizard.
This migration block is scheduled to remove before v1.0.
Runtime Management
Accounts can be added, enabled, or disabled at runtime:
- Use the Web UI's account management panel (add via wizard, edit, enable/disable, reconnect — all hot)
- Or edit
accounts.jsondirectly and call/api/trading/uta/:id/reconnectto apply
The Web UI is the recommended path because the wizard's connection test catches misconfigurations before they hit disk.