内容
<p align="center">
<img src="assets/banner.png" alt="Valet — an MCP server for Interactive Brokers" width="600">
</p>
<p align="center">
<strong>Valet</strong> — 一个为Interactive Brokers提供MCP服务器的工具,您的代理可以执行交易操作,您来做出决策。
</p>
<p align="center">
<a href="https://github.com/pedrobraiti/mcp-ibkr-agent/actions/workflows/ci.yml"><img src="https://github.com/pedrobraiti/mcp-ibkr-agent/actions/workflows/ci.yml/badge.svg" alt="CI"></a>
<img src="https://img.shields.io/badge/python-3.12%2B-blue" alt="Python 3.12+">
<img src="https://img.shields.io/badge/license-MIT-green" alt="License: MIT">
<img src="https://img.shields.io/badge/status-live--validated-success" alt="Status: live-validated">
</p>
一个 **MCP服务器**,使AI代理(如Claude Code)能够在 **Interactive Brokers** 上进行交易:报价、余额、仓位和订单,以及 **买入/卖出** — 包括按美元金额的 ** fractional shares** (`cashQty`),通过Client Portal API。
投资决策(买入或卖出的时机)由您和您的技能提示决定。该项目仅提供可靠的交易基础设施 — 默认情况下具有安全防护。
> ⚠️ **不是财务建议。** 默认情况下在模拟账户上运行;实时交易需要明确的确认。请自担风险。
> **预期结果。** 首次设置大约需要 **30–60 分钟**。Valet需要一个已充值且开通 **IBKR Pro** 账户的Interactive Brokers账户,以及每天大约 **手动浏览器登录一次** — IBKR没有提供零售OAuth,因此没有完全无人值守的模式(这是IBKR的限制,而不是Valet的)。
## 架构
六边形(端口和适配器)。代理仅与MCP工具通信;安全防护位于每笔订单的路径上;IBKR是一个适配器细节:
```mermaid
flowchart LR
A["AI 代理<br/>(/invest 技能)"] -->|MCP 工具| B["MCP 服务器<br/>(FastMCP)"]
B --> C["GuardedBroker<br/>(安全防护)"]
B --> D["市场数据"]
C --> E["CPAPI 适配器"]
D --> E
E -->|REST localhost:5000| F["IBKR 客户端<br/>门户网关"]
F --> G["Interactive Brokers"]
```
```
domain/ 模型(OrderRequest 包含数量或现金数量)和端口(Broker/MarketData/Auth)
adapters/ cpapi/ — IBKR 客户端门户 API(REST)实现
safety/ GuardedBroker — 防护措施:实时锁定、干预运行、价值限制、交易时间
server/ MCP 服务器(FastMCP)+ 依赖组合
```
稍后更换/扩展券商(例如 `ib_async` 数据适配器)意味着只需修改 `adapters/` + `server/services.py`。相关关键选择的理由见 [DECISIONS.md](DECISIONS.md)。
## 为什么 fractional 很重要
大多数零售交易API强制要求您购买整股股票。该项目依赖于IBKR客户端门户API的 `cashQty` 字段,该字段允许您按 **美元金额** 购买(例如“$50 的 AAPL”)并获得 fractional 仓位 — 解锁了美元成本平均、再平衡和小额账户。有关完整的基本原理,请参见 [DECISIONS.md](DECISIONS.md)。
## 要求
- **Python 3.12+**
- 一个开通、充值且开通 **IBKR Pro** 的Interactive Brokers账户(一个API要求,即使使用关联的模拟账户)。
- **Fractional 权限** 已启用:客户端门户 → 设置 → 交易 → 交易权限 → 股票部分 → 勾选 **"Global (Trade in Fractions)"**。
- **IBKR 客户端门户网关** 在本地运行(Java 8u192+)。
- **机器人专用用户名**:IBKR仅允许 **一个** 券商会话每个用户名 — 使用相同用户登录TWS/移动端会杀死网关会话。
## 安装
```bash
git clone https://github.com/pedrobraiti/mcp-ibkr-agent.git
cd mcp-ibkr-agent
python -m venv .venv
# Windows (PowerShell): & ".venv\Scripts\Activate.ps1" (如果出现策略错误:Set-ExecutionPolicy -Scope Process -ExecutionPolicy Bypass)
# Linux/macOS: source .venv/bin/activate
pip install -e ".[dev]"
cp .env.example .env # 填写 IBKR_ACCOUNT_ID 等
```
## 配置(`.env`)
参见 `.env.example`。主要密钥:
| 密钥 | 默认值 | 描述 |
|---|---|---|
| `IBKR_API_BASE_URL` | `https://localhost:5000/v1/api` | 客户端门户网关端点 |
| `IBKR_ACCOUNT_ID` | — | 账户 ID(例如 `DU1234567` 在模拟账户中) |
| `IBKR_TRADING_MODE` | `paper` | `paper` 或 `live` |
| `TRADING_ALLOW_LIVE` | `false` | 硬锁定:`live` 仅在 `true` 时进行交易 |
| `TRADING_DRY_RUN` | `true` | 验证但 **不发送** 订单 |
| `MAX_ORDER_VALUE` | `100.0` | 每订单限额(美元) |
| `MAX_DAILY_VALUE` | — | 累积每日购买上限(为空 = 无上限) |
| `DUPLICATE_WINDOW_SECONDS` | `5` | 在此时间窗口内拒绝相同订单(`0` = 关闭) |
## 运行
### 网关设置
Valet 与本地 **客户端门户网关** 通信 — 一个由 IBKR 提供的 Java 小应用程序,用于连接到您的账户。这是人们经常遇到问题的地方,因此:
1. 下载 [`clientportal.gw.zip`](https://download2.interactivebrokers.com/portal/clientportal.gw.zip) (来自 IBKR 的 [API 页面](https://www.interactivebrokers.com/en/trading/ib-api.php))。需要 **Java 8u192+**。
2. 将其解压到此存储库之外并启动:
```bash
# Linux/macOS: bin/run.sh root/conf.yaml
# Windows: bin\run.bat root\conf.yaml
```
3. 打开 `https://localhost:5000` 并使用 2FA 登录。接受自签名证书警告 — 它是本地且预期的。
4. **您将知道它已成功**,当页面显示 **"客户端登录成功"** 和 `python -m ibkr_agent.healthcheck` 显示 `authenticated=True connected=True`。
在您使用 Valet 时,请保持网关运行;会话需要每天大约 **手动登录一次** (请参见 [保持会话活跃](#保持会话活跃))。如果浏览器登录行为异常,请参见 [登录故障排除](#登录故障排除)。
### 注册和验证
1. 在网关运行并登录的情况下,使用Claude Code注册MCP服务器:
```bash
claude mcp add ibkr -- /path/to/.venv/Scripts/python.exe -m ibkr_agent.server.app
```
(或直接运行它进行测试:`python -m ibkr_agent.server.app`)
工具出现在 **新** 的Claude Code会话中。
2. **检查连接** 随时(在网关登录的情况下):
```bash
python -m ibkr_agent.healthcheck # 或:ibkr-healthcheck
```
显示服务器版本、身份验证状态、账户标志(`supportsCashQty`/`supportsFractions`)、余额和报价。
### 保持会话活跃
网关会话过期(无 `/tickle` 约 6 分钟;最多约 24 小时;每日维护约 01:00 停止)— 而 IBKR 为零售用户提供 **无** OAuth,因此重新身份验证始终是手动浏览器登录。运行保持活动状态的程序,同时手动使用或安排作业:
```bash
python -m ibkr_agent.keepalive # 或:ibkr-keepalive
```
它每 `TICKLE_INTERVAL_SECONDS` 秒进行 `/tickle` 操作,当会话停止时,发出 **警报** (`[ALERT] 需要重新身份验证:...`) 通知您登录回来。当它仅连接但无券商会话时,它会尝试自行恢复(无需新的 2FA)。
### 登录故障排除
如果您登录并批准 2FA 但 **无反应** — 页面只是停滞,API 仍为 `authenticated:false`/`connected:false` (有时 `ssodh/init` 返回 HTTP 500 / `no bridge`):
- **重新启动网关并进行全新登录** — 这几乎总是能解决问题。杀死 Java 进程,重新启动,重新加载 `https://localhost:5000`,并登录。新开的隐身/私人标签页也有帮助(避免陈旧的 cookie)。
- 登录 **不粘滞**:每次您需要全新登录时,请先重新启动网关 — 不要重试已经运行的网关。
- **如果仍然存在**,请退出其他 IBKR 会话(IBKR 移动版或客户端门户网页)— 每个用户名仅允许 **一个** 券商会话 — 然后重新启动网关并尝试再次登录。
- 旧的 *launcher* 版本(2023 年) **不是** 问题 — 在运行时,网关连接到当前的后端。
## 公开工具
`session_status`, `market_status`, `get_quote`, `account_summary`, `positions`, `portfolio`, `preview_order`, `buy`, `sell`, `close_position`, `cancel_order`, `open_orders`, `trade_history`.
- `preview_order(symbol, side, ...)` 通过 IBKR 的 `whatif` 估算订单的 **保证金影响、佣金和警告** — **不发送** — 以便代理可以评估成本后再提交。
- `buy` 接受 `cash_amount`(美元, fractional 通过 `cashQty`)**或** `quantity`(股数, fractional ok)。
- `sell` 仅接受 `quantity`(股数, fractional ok)。IBKR **不** 允许按美元金额出售 — `cashQty` 仅用于购买。
- `close_position(symbol)` 通过交易确切的 fractional 数量来关闭 100% 的仓位。
- `portfolio()` 返回单个快照:账户摘要 + 开仓 + 总浮动盈亏。
- `trade_history(limit)` 返回最近订单尝试的审计日志(购买、出售、干预运行、阻止)— 回答“我的代理做了什么?”。
## 使用示例
注册了MCP后,您可以用自然语言进行交流,代理将使用工具:
> **您:** *"购买 $50 的 AAPL。*"
> 代理调用 `buy(symbol="AAPL", cash_amount=50)` — IBKR 填写 fractional 订单(≈ 0.16 股),无需支付整股(~$300)。
> **您:** *"关闭我的 AAPL 头寸。*"
> 代理调用 `close_position(symbol="AAPL")`,它读取确切的数量并出售 100%。
每个工具都返回一个 `{"ok": ..., "data": ...}` 信封。一个真实的 fractional 购买执行示例(实时验证对抗 IBKR 账户):
```json
{
"ok": true,
"data": {
"order_id": "8645012XX",
"status": "filled",
"symbol": "AAPL",
"side": "BUY",
"message": "Bought 0.0066 AAPL Market, Day"
}
}
```
> fractional **购买** 使用 `cashQty`(美元金额)。fractional **出售** 按股票 **数量** 进行 — IBKR 拒绝在出售时使用 `cashQty`;这就是为什么 `close_position` 存在的原因,它为您解决确切的数量。
## 安全(默认值)
- **模拟** 默认;**实时** 除非 `TRADING_ALLOW_LIVE=true` 否则被阻止。
- 默认情况下 **干预运行** (不发送真正的订单)。
- 超过 `MAX_ORDER_VALUE` 的订单被拒绝。
- 仅在常规交易时间(RTH)内进行订单,考虑 **NYSE 假日** (通过 `holidays` 库)。
- CPAPI 确认警告仅通过允许列表自动接受;未知警告 **阻止** 订单。
- 可选 **每日支出上限** (`MAX_DAILY_VALUE`) 跨所有购买,跟踪在审计日志中 — 不仅限于每订单。
- **重复订单防护**:在 `DUPLICATE_WINDOW_SECONDS` 内拒绝相同订单(保护免受超时/重试双重购买)。
- 每个订单尝试(发送、干预运行或阻止)都被写入本地 **审计日志** (`logs/trades.jsonl`,gitignored)。
- 可选 **符号允许/拒绝列表** (`SYMBOL_ALLOWLIST` / `SYMBOL_DENYLIST`) 限制代理可以交易的范围。
保持活动状态还可以向可选的 **Webhook** (`REAUTH_WEBHOOK_URL`,例如 ntfy/Discord)发送 POST,当会话需要全新登录时 — 单向通知,无账户数据,无交易。
## 开发
```bash
python -m pytest -q # 测试
python -m ruff check . # 校验
```
参见 [CONTRIBUTING.md](CONTRIBUTING.md) 和 [SECURITY.md](SECURITY.md)。
## 许可证
MIT。
连接信息
猜你喜欢
暂无推荐服务器