This repo is a paper-first starter environment for algorithmic trading on Interactive Brokers with a clean local dashboard.
It gives you:
- FastAPI backend for broker sync, local performance storage, and risk metrics.
- SQLite performance database for account snapshots, positions, orders, fills, strategy states, and alerts.
- React/Vite dashboard for equity, P&L, drawdown, exposure, positions, orders, fills, and strategy status.
- Market-data routing that uses Futu OpenAPI for US quotes, Sinotrade/Shioaji for Taiwan quotes, and yfinance as the global/fallback source.
- Mock mode that runs immediately without IB Gateway.
- IBKR mode that connects to TWS or IB Gateway after you enable socket API access.
flowchart LR
Strategy["Strategy runner"] --> Risk["Risk checks"]
Risk --> Broker["Broker adapter"]
Broker --> IBKR["IB Gateway / TWS"]
API --> MarketData["Market-data router"]
MarketData --> Futu["Futu OpenD"]
MarketData --> Sinotrade["Sinotrade / Shioaji"]
MarketData --> YFinance["yfinance"]
Broker --> Store["SQLite performance store"]
Store --> API["FastAPI API"]
API --> Dashboard["React dashboard"]
cp .env.example .env
make install
make devThis starts both the FastAPI backend and Vite frontend in one process with prefixed logs. When
MARKET_DATA_US_PROVIDER=futu, it starts Futu OpenD before the app. When BROKER_MODE=ibkr, it starts
IB Gateway, autofills the login if local credentials are configured, and starts the IBKR ingestion worker
so account data stays fresh. You can also run the underlying npm script directly:
npm run devFor an IBKR-connected development session, set BROKER_MODE=ibkr and use make dev. The worker
defaults to client ID 19 so it does not conflict with the API server's IBKR_CLIENT_ID; override it
with IBKR_WORKER_CLIENT_ID=20 make dev if another client already uses 19. Set
IBKR_WORKER_AUTOSTART=false only when you intentionally want to run the worker yourself.
Open the dashboard at http://127.0.0.1:5173.
The backend is at http://127.0.0.1:8000/api/dashboard.
- Install and log into IB Gateway or Trader Workstation.
- In TWS/Gateway, enable API socket access:
ConfigureorGlobal Configuration->API->Settings-> enableEnable ActiveX and Socket Clients. - Start with paper trading:
- IB Gateway paper:
IBKR_PORT=4002 - TWS paper:
IBKR_PORT=7497
- IB Gateway paper:
- Keep API read-only until the dashboard shows stable account, position, and order data.
- Set
BROKER_MODE=ibkrin.env, then restart the backend. - Keep
ENABLE_ORDER_SUBMISSION=falseuntil paper-trade tests and risk limits are proven.
make dev can launch the local broker apps for you on macOS. The launcher auto-discovers common app
locations such as ~/Applications/IB Gateway */IB Gateway *.app and ~/Applications/Futu_OpenD.app.
If IB Gateway or Futu OpenD is already open, it waits for the local API socket instead of opening the
app again. Override paths if needed:
IBKR_GATEWAY_APP=/Users/you/Applications/IB Gateway 10.45/IB Gateway 10.45.app
FUTU_OPEND_APP=/Users/you/Applications/Futu_OpenD.appTo autofill the IB Gateway login prompt, keep credentials only in your local .env:
IBKR_USERNAME=your_ibkr_username
IBKR_PASSWORD=your_ibkr_password
IBKR_GATEWAY_TRADING_MODE=liveIf macOS blocks autofill, grant your terminal app accessibility permission in
System Settings -> Privacy & Security -> Accessibility. Two-factor approval still has to be
completed manually. Set IBKR_GATEWAY_AUTOSTART=false, IBKR_GATEWAY_LOGIN_AUTOFILL=false, or
FUTU_OPEND_AUTOSTART=false when you want to manage those apps yourself.
make dev prefers Homebrew Node at /opt/homebrew/bin/node or /usr/local/bin/node when present, so
native frontend packages load with the same Node toolchain used by local npm installs. Set
DEV_NODE_BIN=/path/to/node if you need to force another Node binary.
For an advisor account with multiple linked client/subaccounts, the worker auto-detects every account
returned by IB Gateway managedAccounts(). Leave both account fields empty to reconcile all accessible
subaccounts:
IBKR_ACCOUNT=
IBKR_ACCOUNTS=Set IBKR_ACCOUNTS=DU1111111,DU2222222 only when you want an explicit allowlist. Set
IBKR_ACCOUNT=DU1111111 if you want one account selected by default.
To keep specific accounts out of the frontend account dropdown without changing backend reconciliation, set a comma-separated UI-only hide list:
VITE_ACCOUNT_MENU_HIDDEN_ACCOUNTS=F1234567,U1234567Run one read-only reconciliation:
make ibkr-onceRun the continuous ingestion worker:
make ibkr-workerWith BROKER_MODE=ibkr in .env, make dev runs the full local app with the worker included.
ENABLE_ORDER_SUBMISSION=false is the application kill switch for order placement. IBKR_API_READONLY
controls the API session mode. Full order/fill reconciliation may require IBKR_API_READONLY=false,
because Gateway/TWS read-only API mode can reject order and execution requests. The dashboard order
ticket stays blocked unless ENABLE_ORDER_SUBMISSION=true, IBKR_API_READONLY=false, and the order
preview passes account checks. Set ENFORCE_GROSS_EXPOSURE_LIMIT=false to keep exposure visible in
the dashboard without blocking manual orders.
IBKR is not used for market-data quote requests. The dashboard uses a separate market-data router:
MARKET_DATA_US_PROVIDER=futu
MARKET_DATA_TW_PROVIDER=sinotrade
MARKET_DATA_GLOBAL_PROVIDER=yfinance
FUTU_OPEND_HOST=127.0.0.1
FUTU_OPEND_PORT=11111
FUTU_CONNECT_TIMEOUT_SECONDS=1
SINOTRADE_STREAM_INTERVAL_SECONDS=2
QUOTE_CACHE_TTL_SECONDS=10For US stocks and ETFs, run Futu OpenD locally so order previews and watchlist refreshes can use Futu snapshots. For Taiwan stocks, Sinotrade/Shioaji resolves contract metadata and streams snapshot quotes. For other global symbols, use the Yahoo Finance ticker format; yfinance prices are labeled indicative and market orders are blocked from using them as execution-grade quotes. Limit orders can still use the explicit limit price you enter.
If you place a trade from IBKR Mobile, TWS, or another client, the worker should ingest it as an external trade through IBKR executions/positions and surface it in the dashboard after reconciliation.
IBKR is treated as the source of truth. Each reconciliation:
- Pulls current account values, positions, open orders, and executions from the selected IBKR account.
- In advisor mode, repeats reconciliation for each detected or allowlisted subaccount in the same Gateway session.
- Extracts IBKR
ExchangeRateaccount-value rows and stores an FX snapshot for USD reporting. - Rewrites only the scoped current-position projection for that account, so stale local positions are removed.
- Marks local open orders as
NotOpenwhen IBKR no longer reports them as open. - Upserts fills by IBKR execution ID, making execution ingestion idempotent.
- Builds normalized broker and database snapshots, hashes both, and stores a row in
reconciliation_audits. - Updates
sync_state.integrity_statusplus position/order/fill mismatch counts for dashboard visibility.
The dashboard should show integrity_status=ok and zero mismatch counts after a healthy sync. If any
post-reconciliation mismatch remains, treat the dashboard as stale and investigate before trusting P&L
or position exposure.
Native prices and P&L are preserved in the database, but dashboard-facing prices, exposures, fills,
commissions, and risk calculations are normalized into REPORTING_CURRENCY (USD by default).
For history before the local worker was running, use IBKR Activity Flex Queries in XML format. The equity curve needs Net Asset Value (NAV) Summary in Base. Group performance needs daily symbol-level position data, so create a second Activity Flex Query with:
- Open Positions: Account ID, Currency, FX Rate to Base, Symbol, Conid, Report Date, Quantity, Mark Price, Position Value, Open Price or Cost Basis Price, Cost Basis Money, FIFO Unrealized PNL, Side, and Level of Detail. Summary level is preferred; lot-level rows are aggregated by symbol/date.
- Mark-to-Market Performance Summary in Base: Account ID, Symbol, Conid, Report Date, Previous Close Quantity, Close Quantity, Close Price, Transaction MTM PNL, Prior Open MTM PNL, Commissions, Other, and Total.
Configure Flex Web Service in IBKR Portal, then set:
IBKR_FLEX_TOKEN=...
IBKR_FLEX_QUERY_ID=...
IBKR_FLEX_POSITION_QUERY_ID=...Keep Sinotrade/Shioaji credentials only in your local .env, which is already ignored by git:
SINOTRADE_API_KEY=your_sinotrade_api_key
SINOTRADE_API_SECRET=your_sinotrade_api_secret
SINOTRADE_CERT_PATH=/absolute/path/to/Sinopac.pfx
SINOTRADE_CERT_PASSWORD=your_certificate_password
SINOTRADE_ENABLED=true
SINOTRADE_HISTORY_DAYS=90
SINOTRADE_TWD_TO_USD=0.031
SINOTRADE_ORDER_LOT=IntradayOdd
MARKET_DATA_TW_PROVIDER=sinotrade
SINOTRADE_STREAM_INTERVAL_SECONDS=2These values are loaded by the backend as settings.sinotrade_api_key and
settings.sinotrade_api_secret, with the certificate available as settings.sinotrade_cert_path.
The optional certificate password is loaded as settings.sinotrade_cert_password. Do not add them as
VITE_* variables, because those are exposed to the browser bundle.
When BROKER_MODE=ibkr, broker sync also reconciles the Sinotrade stock account if Sinotrade
credentials are configured. The Settings screen can link raw IBKR and Sinotrade accounts into a
single display account; dashboard, reports, positions, orders, and fills then aggregate through that
linked account while order submission still routes to the broker account that owns the instrument.
Taiwan stock orders route through Shioaji for Sinotrade accounts and stay behind
ENABLE_ORDER_SUBMISSION; live orders also require certificate activation with the certificate
password. The default order lot is IntradayOdd, so TW stock quantities are interpreted as shares
(股). Set SINOTRADE_ORDER_LOT=Common only when you intentionally want quantities interpreted as
board lots (張).
Run a date-range backfill:
FLEX_FROM=2026-01-01 FLEX_TO=2026-05-28 make flex-backfillOr import a downloaded XML file:
cd backend
BROKER_MODE=ibkr uv run python -m app.workers.flex_backfill --file /path/to/flex.xmlUseful current references:
- IBKR API Software lists API 10.47, released May 20, 2026, and recommends TWS or IB Gateway 1045 or higher for comprehensive support.
- IBKR TWS API initial setup explains that API clients connect through a running TWS or IB Gateway session.
- IBKR socket connectivity explains host, port, and client ID connection behavior.
- IBKR Flex Web Service Version 3 documents the token/query request flow used for historical backfill.
- IBKR Activity Flex Query Reference lists the report sections, including Net Asset Value (NAV) Summary in Base, Open Positions, and Mark-to-Market Performance Summary in Base.
- ib_async on PyPI is the maintained async-friendly Python wrapper used by this starter.
- Futu OpenAPI is the US quote source used by the market-data router.
- yfinance on PyPI is used for indicative global quotes.
- Use paper mode first. Do not connect this to live trading until the full path has been tested with small orders and monitored exits.
- Keep one unique
IBKR_CLIENT_IDper running bot or dashboard process. - Record every account snapshot, order, fill, and strategy state before trusting P&L.
- Add hard limits before enabling order submission: max order notional, max gross exposure, and a manual pause switch.
- Do not run headless IB Gateway/TWS without planning for authentication prompts, reconnects, and daily restarts.
This is infrastructure, not trading advice.