alexgcherk/InteractiveBrokers_ClientPortal_WebAPI
GitHub: alexgcherk/InteractiveBrokers_ClientPortal_WebAPI
为 Interactive Brokers Client Portal Gateway 提供强类型、异步优先的 .NET 8 客户端,封装交易、行情、账户、警报等全流程操作。
Stars: 0 | Forks: 0
# IB ClientPortal WebAPI 客户端
[](https://www.nuget.org/packages/IB.ClientPortal.Client)
[](https://www.nuget.org/packages/IB.ClientPortal.Client)
[](https://github.com/alexgcherk/InteractiveBrokers_ClientPortal_WebAPI/actions/workflows/ci.yml)
[](LICENSE)
一个用于 [Interactive Brokers Client Portal Web API](https://www.interactivebrokers.com/en/trading/ib-api.php)(也称为 **IB Gateway**)的强类型 .NET 8 客户端库。它将每个端点封装在强类型、异步优先的接口中,使您无需手动构建 HTTP 调用即可进行交易、查询持仓、流式传输市场数据和管理警报。
```
dotnet add package IB.ClientPortal.Client
```
## 目录
- [架构](#architecture)
- [前置条件](#prerequisites)
- [项目结构](#project-structure)
- [配置](#configuration)
- [快速开始](#quick-start)
- [认证流程](#authentication-flow)
- [客户端参考](#client-reference)
- [认证](#auth)
- [账户](#account)
- [订单](#orders)
- [投资组合](#portfolio)
- [市场数据](#market-data)
- [合约](#contracts)
- [警报](#alerts)
- [绩效分析](#performance-analytics)
- [监视列表](#watchlists)
- [下单流程](#order-placement-flow)
- [示例](#examples)
- [安全性](#security)
- [测试](#testing)
## 架构
该解决方案围绕单个 `IBPortalClient` 构建,每个域公开一个类型化客户端。所有客户端共享一个带有持久 `CookieContainer` 的 `HttpClient` 实例,因此会话 cookie(滚动的 `x-sess-uuid`)会被自动处理。
```
graph TD
App["Your Application"]
Client["IBPortalClient"]
Http["IBPortalHttpClient\n(shared HttpClient + CookieContainer)"]
Gateway["IB Client Portal Gateway\nhttps://localhost:5000/v1/api/"]
App --> Client
Client --> Auth["AuthClient"]
Client --> Account["AccountClient"]
Client --> Orders["OrderClient"]
Client --> Portfolio["PortfolioClient"]
Client --> MarketData["MarketDataClient"]
Client --> Contracts["ContractClient"]
Client --> Alerts["AlertClient"]
Client --> Performance["PerformanceClient"]
Client --> Watchlists["WatchlistClient"]
Client --> Fyi["FyiClient (generated)"]
Client --> Calendar["CalendarClient (generated)"]
Auth --> Http
Account --> Http
Orders --> Http
Portfolio --> Http
MarketData --> Http
Contracts --> Http
Alerts --> Http
Performance --> Http
Watchlists --> Http
Http -->|"HTTPS + cookies"| Gateway
```
## 前置条件
| 需求 | 版本 |
|---|---|
| [.NET SDK](https://dotnet.microsoft.com/download) | 8.0+ |
| [IB Client Portal Gateway](https://www.interactivebrokers.com/en/trading/ib-api.php) | 最新版 |
| Interactive Brokers 账户 | 模拟或实盘 |
| [Newtonsoft.Json](https://www.nuget.org/packages/Newtonsoft.Json) | 13.0.3 *(唯一的运行时依赖项)* |
在进行任何调用之前,Gateway 必须在本地运行。它使用自签名 TLS 证书 —— 库会自动处理 `localhost` 的此问题(请参阅 [安全性](#security))。
## 项目结构
```
InteractiveBrokers_ClientPortal_WebAPI/
├── IB.ClientPortal.Client/ # Library — the client you reference
│ ├── IBPortalClient.cs # Main entry point
│ ├── IBPortalClientOptions.cs # Configuration
│ ├── IBPortalHttpClient.cs # Low-level HTTP wrapper
│ ├── Clients/ # One file per domain
│ │ ├── AuthClient.cs
│ │ ├── AccountClient.cs
│ │ ├── OrderClient.cs
│ │ ├── PortfolioClient.cs
│ │ ├── MarketDataClient.cs
│ │ ├── ContractClient.cs
│ │ ├── AlertClient.cs
│ │ ├── PerformanceClient.cs
│ │ └── WatchlistClient.cs
│ ├── Models/ # Strongly-typed request/response models
│ └── Generated/ # NSwag-generated FYI and Calendar clients
│
├── IB.ClientPortal.Client.UnitTests/ # Fast, offline unit tests (Moq + NUnit)
│ ├── Clients/ # Per-domain test files
│ │ └── SecurityTests.cs # SSL bypass, URL encoding, exception handling
│ └── MockHttpHandler.cs # Shared mock infrastructure
│
└── IB.ClientPortal.IntegrationTests/ # Live integration tests (require running gateway)
├── GlobalSetup.cs # Assembly-level setup; creates shared client
├── GatewaySettings.cs # Config model
├── appsettings.integration.json # Non-secret defaults (committed)
└── appsettings.integration.local.json # ⚠ Private values (gitignored)
```
## 配置
### IBPortalClientOptions
| 属性 | 类型 | 默认值 | 描述 |
|---|---|---|---|
| `BaseUrl` | `string` | `"https://localhost:5000"` | Gateway 基础 URL |
| `AccountId` | `string?` | `null` | 用于账户范围调用的默认账户 ID |
| `IgnoreSslErrors` | `bool` | `false` | 跳过 TLS 验证。**对于 localhost 不需要** —— 库会自动为环回地址跳过验证 |
| `RequestTimeout` | `TimeSpan` | 15 秒 | 每次请求的 HTTP 超时时间 |
| `SessionCookie` | `string?` | `null` | 预置 `x-sess-uuid` cookie(高级 —— 请参阅 [认证流程](#authentication-flow)) |
### 集成测试机密
将您的真实凭据复制到 gitignore 的本地覆盖文件中:
```
// IB.ClientPortal.IntegrationTests/appsettings.integration.local.json (gitignored)
{
"Gateway": {
"BaseUrl": "https://localhost:5000",
"AccountId": "DU1234567",
"IgnoreSslErrors": false,
"RequestTimeoutSeconds": 30,
"SessionCookie": "" // optional — leave empty for automatic acquisition
}
}
```
## 快速开始
```
using IB.ClientPortal.Client;
// 1. Create the client
var options = new IBPortalClientOptions
{
BaseUrl = "https://localhost:5000",
AccountId = "DU1234567"
// IgnoreSslErrors is false by default; localhost is bypassed automatically
};
using var client = new IBPortalClient(options);
// 2. Verify gateway is alive
var alive = await client.Auth.PingAsync();
Console.WriteLine($"Gateway alive: {alive}");
// 3. Authenticate
var status = await client.Auth.GetStatusAsync();
if (status?.Authenticated != true)
{
await client.Auth.InitSsoDhAsync();
await Task.Delay(2000);
}
// 4. REQUIRED before any order operations
var accounts = await client.Account.GetAccountsAsync();
Console.WriteLine($"Accounts: {string.Join(", ", accounts!.Accounts!)}");
// 5. Keep the session alive every ~5 minutes
await client.Auth.TickleAsync();
```
## 认证流程
IB Gateway 使用基于 Web 的 SSO 会话。客户端必须在任何交易或市场数据调用之前进行认证,并且必须至少每 5 分钟发送一次保活(`/tickle`),否则会话将过期。
```
sequenceDiagram
participant App
participant Client as IBPortalClient
participant GW as IB Gateway
App->>Client: PingAsync()
Client->>GW: GET /sso/ping
GW-->>Client: "true" / error
Client-->>App: bool (is alive)
App->>Client: GetStatusAsync()
Client->>GW: GET /iserver/auth/status
GW-->>Client: AuthStatus
Client-->>App: AuthStatus
alt Not authenticated
App->>Client: InitSsoDhAsync()
Client->>GW: POST /iserver/auth/ssodh/init
GW-->>Client: AuthStatus (authenticated=true)
Client-->>App: AuthStatus
end
loop Every ≤ 5 minutes
App->>Client: TickleAsync()
Client->>GW: GET /tickle
GW-->>Client: TickleResponse (ssoExpires countdown)
Client-->>App: TickleResponse
end
App->>Client: LogoutAsync()
Client->>GW: GET /logout
GW-->>Client: ok
```
## 客户端参考
### 认证 (Auth)
| 方法 | 描述 |
|---|---|
| `PingAsync()` | 检查 Gateway 进程是否可达 |
| `GetStatusAsync()` | 返回当前的经纪会话状态(`Authenticated`、`Established`) |
| `InitSsoDhAsync()` | 打开(或重新打开)经纪会话 |
| `ReauthenticateAsync()` | 强制重新认证 |
| `TickleAsync()` | 保活 —— 至少每 5 分钟调用一次 |
| `LogoutAsync()` | 结束当前会话 |
### 账户 (Account)
| 方法 | 描述 |
|---|---|
| `GetAccountsAsync()` | 检索账户列表并设置交易上下文。**必须在订单操作之前调用。** |
| `GetSummaryAsync(accountId)` | 完整的账户摘要:购买力、保证金、余额 |
| `GetPnlAsync()` | 按账户/细分的每日盈亏 (PnL) |
| `GetUserAsync()` | 当前用户信息和功能标志 |
| `GetMtaAsync()` | Mobile Trading Assistant 警报数据 |
| `SwitchAccountAsync(accountId)` | 切换活动账户 |
| `GetCurrencyPairsAsync(currency)` | 基础货币(默认为 `"USD"`)的所有货币对 |
| `GetExchangeRateAsync(source, target)` | 两种货币之间的即期汇率 |
| `GetSignaturesAndOwnersAsync(accountId)` | 账户上的申请人姓名 |
### 订单 (Orders)
| 方法 | 描述 |
|---|---|
| `GetOrdersAsync(filters?)` | 活动订单;可选按状态筛选(`"filled,cancelled"`) |
| `GetTradesAsync(days?)` | 今日已成交交易;可选回溯 N 天 |
| `PlaceOrdersAsync(accountId, orders[])` | 下单一个或多个订单 |
| `WhatIfAsync(accountId, orders[])` | 模拟订单影响(保证金/佣金)而不实际下单 |
| `ModifyOrderAsync(accountId, orderId, order)` | 修改现有订单 |
| `CancelOrderAsync(accountId, orderId)` | 取消订单;`orderId = -1` 取消所有订单 |
| `GetOrderStatusAsync(orderId)` | 单个订单的详细状态 |
| `ReplyAsync(replyId, confirmed)` | 确认待处理的订单警告(必须在警告后立即调用) |
| `SuppressMessagesAsync(messageIds[])` | 预先抑制已知的警告代码(例如 `"o163"`) |
| `ResetSuppressedMessagesAsync()` | 清除所有已抑制的消息 |
### 投资组合 (Portfolio)
| 方法 | 描述 |
|---|---|
| `GetAccountsAsync()` | 所有账户及其完整元数据 |
| `GetSubAccountsAsync()` | 子账户列表 |
| `GetSubAccountsAsync(page)` | 分页子账户(适用于拥有 >100 个账户的顾问) |
| `GetPositionsAsync(accountId, page)` | 持仓页面(基于 0) |
| `GetFirstPositionsAsync(accountId)` | 获取第一页持仓的快捷方式 |
| `GetPositionByConidAsync(accountId, conid)` | 特定合约的持仓 |
| `InvalidatePositionCacheAsync(accountId)` | 使服务器端持仓缓存失效 |
| `GetAllocationAsync(accountId)` | 资产类别、板块和组分配明细 |
| `GetLedgerAsync(accountId)` | 每种货币的现金余额 |
| `GetMetaAsync(accountId)` | 完整的账户元数据 |
### 市场数据 (Market Data)
| 方法 | 描述 |
|---|---|
| `GetSnapshotAsync(conids, fields)` | 实时快照(首次调用订阅;第二次返回数据) |
| `GetAltSnapshotAsync(conids, fields)` | 通过 `/md/snapshot` 的替代快照端点 |
| `GetRegSnapshotAsync(conid)` | 监管快照 —— ⚠ 每次调用费用 $0.01 |
| `GetHistoryAsync(conid, period, bar, exchange?, outsideRth)` | 历史 OHLCV 柱状图 |
| `UnsubscribeAllAsync()` | 取消所有活动的市场数据订阅 |
| `UnsubscribeAsync(conid)` | 取消单个合约的订阅 |
| `GetScannerParamsAsync()` | 完整扫描器配置(缓存 15 分钟) |
| `RunScannerAsync(request)` | 执行市场扫描器 |
**常用字段代码**(通过 `MarketDataFields`):
```
MarketDataFields.Last // "31" — last traded price
MarketDataFields.Bid // "84"
MarketDataFields.Ask // "86"
MarketDataFields.Volume // "87"
MarketDataFields.EquityDefault // pre-built set: Last, Bid, Ask, Size, Volume, OHLC, Change
```
### 合约 (Contracts)
| 方法 | 描述 |
|---|---|
| `SearchAsync(symbol, secType?, nameSearch)` | 按代码搜索合约 |
| `GetInfoAsync(conid)` | 合约详情 |
| `GetInfoAndRulesAsync(conid)` | 合约信息及订单规则 |
| `GetRulesAsync(request)` | 特定合约和买卖方向的订单规则 |
| `GetStrikesAsync(conid, month, secType, exchange?)` | 期权行权价列表 |
| `GetSecDefInfoAsync(conid, secType, month?, strike?, right?)` | 衍生品 secdef |
| `GetSecDefAsync(conids)` | 包含交易规则的完整 secdef |
| `GetStocksAsync(symbols)` | 按代码获取股票合约 |
| `GetFuturesAsync(symbols)` | 未过期的期货合约 |
| `GetAllConidsAsync(exchange, assetClass)` | 交易所上的所有合约 ID |
| `GetTradingScheduleAsync(assetClass, symbol, exchange?)` | 每个场所的交易时间表 |
| `GetContractTradingScheduleAsync(conid, exchange?)` | 包含 epoch 时间的 6 天时间表 |
| `GetAlgosAsync(conid, algos?, addDescription, addParams)` | IB Algo 策略 |
| `GetBondFiltersAsync(symbol, issuerId)` | 债券搜索筛选选项 |
### 警报 (Alerts)
| 方法 | 描述 |
|---|---|
| `GetAlertsAsync(accountId)` | 列出账户的所有警报 |
| `GetAlertAsync(orderId)` | 特定警报的完整详情 |
| `CreateAlertAsync(accountId, request)` | 创建或更新警报 |
| `SetAlertActiveAsync(accountId, alertId, active)` | 激活或停用警报 |
| `DeleteAlertAsync(accountId, alertId)` | 删除警报;`alertId = 0` 删除所有警报 |
| `GetMtaAlertAsync()` | Mobile Trading Assistant 警报 |
### 绩效分析 (Performance Analytics)
| 方法 | 描述 |
|---|---|
| `GetPerformanceAsync(accountIds[], period)` | 投资组合绩效时间序列。周期:`"1M"`、`"3M"`、`"6M"`、`"1Y"`、`"2Y"`、`"3Y"`、`"5Y"`、`"MTD"`、`"YTD"` |
| `GetSummaryAsync(accountIds[])` | 按资产类别的投资组合摘要 |
| `GetTransactionsAsync(accountIds[], conids[], currency)` | 特定合约的交易历史 |
### 监视列表 (Watchlists)
| 方法 | 描述 |
|---|---|
| `GetWatchlistsAsync()` | 所有监视列表(系统和用户定义) |
| `CreateWatchlistAsync(id, name, conids[])` | 创建监视列表 |
| `GetWatchlistAsync(id)` | 获取特定监视列表 |
| `DeleteWatchlistAsync(id)` | 删除监视列表 |
## 下单流程
下单可能会返回 **直接确认** 或需要立即回复的 **警告**。客户端处理这两种响应形式。
```
sequenceDiagram
participant App
participant Client as IBPortalClient
participant GW as IB Gateway
Note over App,GW: REQUIRED: call GetAccountsAsync() before any order operations
App->>Client: GetAccountsAsync()
Client->>GW: GET /iserver/accounts
GW-->>Client: AccountsResponse
Client-->>App: AccountsResponse
App->>Client: PlaceOrdersAsync(accountId, orders[])
Client->>GW: POST /iserver/account/{accountId}/orders
alt Direct confirmation
GW-->>Client: [{ order_id, order_status }]
Client-->>App: PlaceOrderResponse[] (OrderId set)
else Warning requires confirmation
GW-->>Client: { id: "replyId", message: ["warning text"] }
Client-->>App: PlaceOrderResponse[] (ReplyId set, check Message)
App->>Client: ReplyAsync(replyId, confirmed: true)
Client->>GW: POST /iserver/reply/{replyId}
GW-->>Client: [{ order_id, order_status }]
Client-->>App: PlaceOrderResponse[] (OrderId set)
end
```
## 示例
### 下限价单
```
// Pre-flight
await client.Account.GetAccountsAsync();
// Suppress common warnings so PlaceOrdersAsync confirms directly
await client.Orders.SuppressMessagesAsync(["o163", "o354"]);
var order = new PlaceOrderBody
{
AccountId = "DU1234567",
Conid = 265598, // AAPL contract ID
SecType = "265598:STK",
Side = "BUY",
OrderType = "LMT",
Price = 195.00,
Quantity = 10,
Tif = "DAY"
};
var responses = await client.Orders.PlaceOrdersAsync("DU1234567", [order]);
foreach (var r in responses!)
{
if (r.ReplyId is not null)
{
Console.WriteLine($"Warning: {string.Join("; ", r.Message!)}");
// Confirm the warning immediately — before any other request
await client.Orders.ReplyAsync(r.ReplyId, confirmed: true);
}
else
{
Console.WriteLine($"Order placed: {r.OrderId} — {r.OrderStatus}");
}
}
```
### 模拟订单 (What-If)
```
var whatIf = await client.Orders.WhatIfAsync("DU1234567", [order]);
Console.WriteLine($"Estimated commission: {whatIf!.Amount?.Change}");
Console.WriteLine($"Initial margin impact: {whatIf.Initial?.Change}");
```
### 实时市场数据快照
```
// First call subscribes; poll until data is returned
string conids = "265598,8314"; // AAPL, IBM
string fields = MarketDataFields.EquityDefault;
MarketDataSnapshot[]? snapshot = null;
for (int i = 0; i < 5 && snapshot is null or { Length: 0 }; i++)
{
snapshot = await client.MarketData.GetSnapshotAsync(conids, fields);
if (snapshot?.Length == 0) await Task.Delay(500);
}
foreach (var s in snapshot!)
Console.WriteLine($"Conid {s.Conid}: last={s[MarketDataFields.Last]}");
// Clean up
await client.MarketData.UnsubscribeAllAsync();
```
### 历史柱状图
```
var history = await client.MarketData.GetHistoryAsync(
conid: 265598,
period: "5d",
bar: "1h",
outsideRth: false);
foreach (var bar in history!.Data!)
Console.WriteLine($"{bar.TimeMs}: O={bar.Open} H={bar.High} L={bar.Low} C={bar.Close} V={bar.Volume}");
```
### 搜索合约并获取行权价
```
// 1. Find AAPL
var results = await client.Contracts.SearchAsync("AAPL", secType: "STK");
long conid = results![0].Conid;
// 2. Get option strikes for a specific expiry month
var strikes = await client.Contracts.GetStrikesAsync(conid, month: "SEP25", secType: "OPT");
Console.WriteLine($"Call strikes: {string.Join(", ", strikes!.Call!)}");
```
### 投资组合快照
```
string accountId = "DU1234567";
var positions = await client.Portfolio.GetFirstPositionsAsync(accountId);
var allocation = await client.Portfolio.GetAllocationAsync(accountId);
var ledger = await client.Portfolio.GetLedgerAsync(accountId);
var pnl = await client.Account.GetPnlAsync();
Console.WriteLine($"Positions: {positions?.Length}");
Console.WriteLine($"USD cash: {ledger!["USD"].CashBalance}");
Console.WriteLine($"Day PnL: {pnl?.Upnl?.Values.Sum(x => x.Dpl):F2}");
```
### 创建价格警报
```
var alert = new CreateAlertRequest
{
OrderType = "P",
AlertName = "AAPL above 200",
AlertRepeatable = 1,
Conditions = [
new AlertCondition
{
Type = 1,
Conid = 265598,
Operator = ">=",
TriggerValue = "200.00",
TimeZone = "America/New_York"
}
]
};
await client.Alerts.CreateAlertAsync("DU1234567", alert);
```
### 保活循环
```
using var cts = new CancellationTokenSource();
_ = Task.Run(async () =>
{
while (!cts.Token.IsCancellationRequested)
{
await Task.Delay(TimeSpan.FromMinutes(4), cts.Token);
await client.Auth.TickleAsync(cts.Token);
}
}, cts.Token);
```
## 安全性
```
flowchart TD
A[IBPortalClientOptions] --> B{IgnoreSslErrors = true?}
B -- Yes --> E[Bypass TLS validation\nDangerousAcceptAnyServerCertificateValidator]
B -- No --> C{Host is loopback?\nlocalhost / 127.x.x.x / ⟨::1⟩}
C -- Yes --> E
C -- No --> F[Full TLS validation\nstandard certificate chain check]
style E fill:#f66,color:#fff
style F fill:#6a6,color:#fff
```
该库的关键安全特性:
- **`IgnoreSslErrors` 默认为 `false`** —— 对于非本地主机必须显式启用
- **Localhost 自动绕过** —— `localhost`、`127.x.x.x` 和 `::1` 始终绕过 TLS(IB Gateway 附带自签名证书;这是预期行为)
- **在远程主机上设置 `IgnoreSslErrors = true`** 存在中间人 (MITM) 攻击风险 —— 仅在受控的内部网络中使用
- **所有字符串参数在插入查询字符串之前都经过 URL 编码**(`Uri.EscapeDataString`)
- **`PingAsync` 仅捕获 `HttpRequestException` / `TaskCanceledException`** —— 意外异常(包括安全错误)会传播
- **机密信息不包含在源代码控制中** —— `appsettings.integration.local.json` 已被 gitignore;仅提交占位符值
## 测试
单元测试
完全离线 —— 不需要 Gateway。使用 `Moq` 在处理程序级别拦截 HTTP。
```
dotnet test IB.ClientPortal.Client.UnitTests
```
71 个测试涵盖:
- 每个域客户端的响应反序列化
- 安全场景(`SecurityTests.cs`):SSL 绕过逻辑、特殊字符的 URL 编码、`PingAsync` 异常处理
### 集成测试
需要运行中的 IB Gateway 和有效会话。首先填充 `appsettings.integration.local.json`(请参阅 [配置](#configuration))。
```
dotnet test IB.ClientPortal.IntegrationTests
```
程序集级别的 `GlobalSetup` 会创建单个共享的 `IBPortalClient`,验证身份验证,并在任何测试运行前预热 `/iserver/accounts`。
标签:API 封装, HttpClient, IB Gateway, Interactive Brokers, .NET 8, NuGet 包, REST API, WebAPI 客户端, 外汇交易, 市场数据, 异步编程, 盈透证券, 算法交易, 组合管理, 股票交易, 自动交易系统, 量化交易, 金融科技