options-orb-mcp
by tobisal
README.md
# Options ORB MCP System
A **demo-first** system of [MCP](https://modelcontextprotocol.io) servers that expose
market-analysis, research, optimisation, and execution tools to an LLM client
(Cursor / Claude Desktop). It trades **defined-risk options vertical spreads**
driven by an **Opening Range Breakout (ORB)** signal, optimised per session
window (Asia / London / New York), with strict risk controls sized for ~£1000
of capital.
> **Risk notice.** This is educational software, not financial advice. Trading
> options risks loss of capital. The system defaults to a **paper account** and
> refuses to place live orders unless you deliberately flip two independent
> safety switches. "Steady returns" is a design *goal*, never a guarantee.
## Why the design looks like this
- Your brief described ORB, session windows, SL/TP and MetaTrader. Real options
(strikes/expiries/Greeks) don't live on MT5, so this uses **Interactive
Brokers** (paper API + UK access + US options).
- £1000 means **defined-risk spreads only** (verticals). The ORB breakout on the
*underlying* is the directional signal; the executor places the spread.
- Session windows are reframed for US options: **Asia** = overnight globex range,
**London** = EU/pre-market, **New York** = the classic US-open ORB.
## Architecture
```
LLM client (Cursor / Claude)
| MCP (stdio)
+-- market-data-mcp (ORB signal, regime, option chain, IV)
+-- research-mcp (trade journal, performance, learning)
+-- optimiser-mcp (backtest, walk-forward, compare strategies)
+-- execution-mcp (preview/place spreads with bracket SL/TP)
|
core/ library <---- dashboard/ (read-only web GUI, port 8787)
|
IB Gateway / TWS (paper first)
```
## Prerequisites
1. **Python 3.11+** (3.13 tested).
2. A virtual environment with `pip` (steps below). [uv](https://docs.astral.sh/uv/)
is supported as an optional alternative if you have it.
3. **Interactive Brokers** account with **paper trading** enabled, plus
**TWS** or **IB Gateway** running with the API enabled
(Configure -> API -> Settings -> "Enable ActiveX and Socket Clients").
- Paper defaults: TWS `7497`, IB Gateway `4002`.
- New to this? Follow the step-by-step [IBKR setup guide](docs/IBKR_SETUP.md)
(install Gateway, log into paper, enable the API, free delayed data).
Paper trading uses **live IBKR data** by default; synthetic/"Demo data" is only
an explicit offline toggle. The backtester and pricing tools work **without**
IBKR - only live market data and order placement need Gateway/TWS running.
Paper accounts get **free 15-minute delayed data**, and the system falls back to
it automatically when you lack a real-time subscription.
## Setup
Run these from the repo root. The commands assume your virtual environment is
**activated** (so `python`/`pytest` resolve to the `.venv`).
```bash
# 1. Create and activate a virtual environment
python -m venv .venv
.venv\Scripts\activate # PowerShell / CMD
# source .venv/Scripts/activate # Git Bash on Windows
# source .venv/bin/activate # macOS / Linux
# 2. Install the project + dev tools (pytest, ruff)
pip install -e ".[dev]"
# 3. Configure
copy .env.example .env # Windows; Unix: cp .env.example .env
# then edit IBKR_PORT (IB Gateway paper = 4002, TWS paper = 7497)
# 4. Register the MCP servers with your client (writes .cursor/mcp.json)
python -m scripts.setup
# 5. Sanity check the tests (no IBKR needed)
pytest -q
# 6. Watch the whole loop run offline on synthetic data (no IBKR needed)
python -m scripts.demo
# 7. Open the GUI dashboard (historic trades, strategy used, live positions)
python -m dashboard.app # then open http://127.0.0.1:8787
# (once IB Gateway is running) confirm the live connection + a sample quote
python -m scripts.check_ibkr
```
The demo exercises every agent end to end: it finds an ORB breakout, risk-sizes a
defined-risk spread, simulates a paper fill, logs it, then backtests, optimises
and walk-forward-validates the strategy - all offline.
<details>
<summary>Using <code>uv</code> instead of pip (optional)</summary>
```bash
uv sync --extra dev # install
uv run orb-setup # = python -m scripts.setup
uv run pytest -q # = pytest -q
uv run python -m scripts.demo # = python -m scripts.demo
uv run orb-dashboard # = python -m dashboard.app
uv run python -m scripts.check_ibkr
```
`orb-setup` and `orb-dashboard` are the console-script entry points defined in
`pyproject.toml`; they're available on your `PATH` after `pip install -e .` too.
</details>
## Dashboard (GUI)
A read-only web dashboard gives a clean view of everything at a glance:
```bash
python -m dashboard.app # (uv: uv run orb-dashboard)
# open http://127.0.0.1:8787
```
It shows:
- **Account cards** - environment (PAPER/LIVE badge), IBKR connection, daily P&L,
per-trade risk budget, open positions, and the daily kill-switch status.
- **Equity curve** - cumulative P&L of closed trades from your starting capital.
- **Live signals** - the current ORB read per session window (with a "Demo data"
toggle so it works without IBKR).
- **Performance by window and by strategy** - win rate, expectancy, profit factor
and total P&L, so you can see which ORB windows and which spread types work.
- **Auto-trading (play button)** - a start/stop control that runs the ORB entry
loop automatically: each interval it evaluates the active session window and,
if a qualifying breakout passes every risk gate, places a risk-sized spread
(paper or simulated) and logs it. Entries use the parameter set you choose
(optimiser ranking, optimisation history, or **Use these for trading** on the
advanced parameters). Until you choose one, ``windows.json`` defaults apply.
It enters up to 3 trades per session window
(max 9 per day across Asia / London / New York), respects the per-trade cap
and the daily kill-switch, and shows a live activity log. **Disabled for LIVE
accounts** as a safety measure - paper/simulated only.
- **Backtesting & simulation** - pick a symbol, session window and lookback, then
**Run backtest** to pull historical data (live IBKR history, or demo data
offline) and simulate the ORB spread strategy. You get a simulated equity
curve, full metrics (win rate, expectancy, profit factor, drawdown, Sharpe,
Monte-Carlo) and every simulated trade. **Optimise** grid-searches the
parameter space, ranks the top sets by the balanced score, and saves the best.
- **Optimisations made** - a history of every optimiser run with its best
parameters and metrics, so you can see what has been tried and what won.
- **Open positions** - journal trades plus live IBKR positions when connected.
- **Trade history** - every trade with the strategy used (e.g. `bull call debit`,
`bull put credit`), direction, size, max loss, status and realised P&L.
It refreshes every 8 seconds. Auto-trade (when you Start it) places paper
spreads through the dashboard process; other execution stays with the
execution agent. The view is populated from `data/trades.db`; delete that
file to reset to an empty journal.
## Discord remote control
The bot runs **on this PC** next to the dashboard and talks to
`http://127.0.0.1:8787`. Slash commands from your phone (or any Discord
client) then drive paper auto-trade and stream live events back.
1. Create an application at [discord.com/developers/applications](https://discord.com/developers/applications)
→ **Bot** → copy the token into `.env` as `DISCORD_BOT_TOKEN`.
2. OAuth2 → URL Generator: scopes **`bot`** and **`applications.commands`**,
permission **Send Messages**. Open the URL and invite the bot to a server
you own.
3. Discord **User Settings → Advanced → Developer Mode**. Right-click your
avatar → **Copy User ID** → `DISCORD_ALLOWED_USER_IDS`. Right-click the
server name → **Copy Server ID** → `DISCORD_GUILD_ID` (slash commands
appear immediately). Optional: right-click a channel → **Copy Channel ID**
→ `DISCORD_LOG_CHANNEL_ID` for live fill / error / start / stop posts.
4. Keep the dashboard running, then in a second terminal:
```powershell
pip install -e ".[dev]" # once, so discord.py is in .venv
python -m dashboard.app # already running is fine; restart it once
python -m scripts.discord_bot # or: orb-discord
```
Commands: `/help`, `/status`, `/signals`, `/preview`, `/positions`, `/trades`,
`/auto start|stop|status`, `/optimise` (ranks, does not apply), `/nightly`
(default dry-run). `/auto start` is refused if `ACCOUNT_MODE=LIVE`. There is
no Discord command that places a live IBKR order.
## Verifying the IBKR connection
With IB Gateway/TWS running and logged into the **paper** account:
```bash
python -m scripts.check_ibkr
```
It prints the resolved host/port/mode, connects, and fetches a sample quote plus
your account summary. If it fails, it tells you exactly what to check. Full
walkthrough in [docs/IBKR_SETUP.md](docs/IBKR_SETUP.md).
## Historical data (backtests)
IBKR only returns about a month of 5-minute bars per request. To cache a year of
SPY history locally (used by dashboard backtest / optimiser lookback **1 year**):
```bash
python -m scripts.fetch_history --symbol SPY --days 365
```
Bars are written to `data/history/SPY_5mins.csv`. Re-running the command reuses
the cache when it already covers the requested lookback.
## Running a server manually
Each server speaks MCP over stdio and is normally launched by the client, but
you can smoke-test one directly:
```bash
python -m servers.market_data_mcp.server # (uv: uv run python -m ...)
```
## The four agents (MCP tool groups)
| Server | Purpose | Key tools |
| --- | --- | --- |
| `market-data-mcp` | Market analysis | `get_session_orb`, `classify_regime`, `get_option_chain`, `get_iv` |
| `research-mcp` | Learn from history | `log_trade`, `query_trades`, `performance_report`, `learn_from_history` |
| `optimiser-mcp` | Test & compare strategies | `backtest`, `walk_forward`, `compare` |
| `execution-mcp` | Place trades | `preview_spread`, `place_spread`, `close_position`, `positions`, `account` |
## The trading loop (how the client uses the tools)
1. `market-data-mcp.get_session_orb` -> breakout direction + strength for the active window.
2. `market-data-mcp.classify_regime` -> trend vs range (chooses debit vs credit spread).
3. `research-mcp.learn_from_history` -> does this window/regime have positive expectancy?
4. `execution-mcp.preview_spread` -> defined-risk vertical sized to the risk cap.
5. `execution-mcp.place_spread` -> submits combo order + bracket SL/TP (paper by default).
6. Outcome is logged via `research-mcp.log_trade`; `optimiser-mcp` refines params.
## Going live (deliberately hard)
Live trading requires **both**:
- `ACCOUNT_MODE=live`, and
- `LIVE_TRADING_CONFIRM=I_UNDERSTAND_THE_RISK`
and pointing `IBKR_PORT` at your live TWS/Gateway port. If only one is set, the
executor refuses to trade. Start on paper for weeks first.
## Docker
The Python stack (dashboard, Discord bot, MCP servers, configs) is one image.
IB Gateway stays a separate community container because it is a Java desktop
app.
```bash
copy .env.example .env # then set IB_GATEWAY_USER / IB_GATEWAY_PASSWORD
docker compose up -d --build # dashboard: http://127.0.0.1:8787
# Optional Discord bot (needs DISCORD_* in .env)
docker compose --profile discord up -d
```
Pushing `main` also publishes the image to GitHub Container Registry:
```bash
docker pull ghcr.io/<owner>/options-orb-mcp:latest
```
One-off commands in the image:
```bash
docker compose run --rm dashboard demo # offline demo (no IBKR)
docker compose run --rm dashboard check # IBKR connectivity
```
MCP servers still typically run on the host (Cursor/Claude launch them over
stdio). They are installed in the image if you want to exec them:
```bash
docker compose exec dashboard python -m servers.market_data_mcp.server
```
## Another PC (clone this machine)
Do **not** copy `.venv`, `.env`, or `.cursor/mcp.json` — those are tied to this
computer's Python path and secrets. Clone the repo, then on the **new** PC:
```powershell
git clone <your-repo-url> "Options Trading"
cd "Options Trading"
python -m venv .venv
.\.venv\Scripts\activate
pip install -e ".[dev]"
copy .env.example .env
# edit .env: IBKR_PORT=4002, ACCOUNT_MODE=paper
python -m scripts.setup
pytest -q
```
Then on that PC only:
1. Install **IB Gateway**, log into the **same paper** account, enable the API on
port **4002** ([docs/IBKR_SETUP.md](docs/IBKR_SETUP.md)). The API is
`127.0.0.1` — Gateway must run on that machine.
2. Optional: copy `data/history/SPY_5mins.csv` from this PC to skip a long
history download. Otherwise:
`python -m scripts.fetch_history --symbol SPY --days 365`
3. Leave `data/trades.db` behind unless you want this PC's journal. A missing
file is a fresh paper ledger.
4. `python -m dashboard.app` → http://127.0.0.1:8787 then **Start** auto-trade.
5. Nightly optimiser (23:30 GMT): `python -m scripts.nightly_optimise --install-task`
6. Restart Cursor so MCP servers pick up `.cursor/mcp.json`.
Confirm with `python -m scripts.check_ibkr`. After a Windows DST change, re-run
`--install-task`.
## Repository layout
```
core/ shared library (config, models, db, pricing, risk, strategy, ibkr)
servers/ one MCP server per agent
dashboard/ read-only web GUI (Starlette API + single-page UI)
configs/ per-window ORB parameters
scripts/ setup / demo / check_ibkr / discord_bot / nightly_optimise
tests/ unit tests (pricing, ORB, risk, metrics)
data/ SQLite journal + backtest artifacts (gitignored)
docker/ container entrypoint
Dockerfile Python stack image (dashboard, Discord, MCP servers)
docker-compose.yml IB Gateway + dashboard (+ optional Discord)
```
This server cannot be deployed
Maintenance
ActivityMaintained
ResponsivenessNo issues