IB 接入(Interactive Brokers)

连接 Interactive Brokers。

译文说明

  • 原文链接:docs/IB.md
  • 原作者:Rob Carver
  • 对应版本:master
  • 译者:fanrong
  • 许可:GPL-3.0(见 GPL-3.0.txt

注:本文为非官方翻译,可能存在疏漏;请以原文为准。

本文档专门讨论如何使用 pysystemtrade 连接 Interactive Brokers(IB)

从 0.28.0 版本开始,这需要依赖库 ib-insync

尽管本文围绕 Interactive Brokers 展开,但如果你计划接入其他券商,也应该认真阅读本文,因为这里解释了需要修改哪些类来实现这一点;或者,如果你想用其它 Python 封装来对接 IB API,这篇文档也会很有帮助。

相关文档:

重要提示:务必确认你清楚自己在做什么。所有金融交易都可能带来亏损。杠杆交易(例如期货交易)可能会导致你亏光全部资金,甚至出现欠款。回测结果并不能保证未来表现。本软件不提供任何形式的担保或保证。因使用 pysystemtrade 进行实盘交易而造成的任何损失,本人概不负责。使用本软件风险自负。

目录

gh-md-toc 生成

Preliminaries

Getting started with interactive brokers

如果这是你第一次接触 IB 的 Python API,建议先阅读我的博客文章,了解整体架构和工作方式。
遇到 IB 相关问题,可以到这个讨论组提问:twsapi 组
IB 官方也提供了一个 API 相关的 webinar
IB API 的官方手册在这里,ib-insync 的官方文档在这里

Gateway / TWS

你需要从 IB 网站下载 Gateway 或 TWS 软件。
建议使用 Gateway,因为它更稳定、更轻量,而且不会像 TWS 那样定期自重启。

ib-insync library

我使用 ib-insync 作为对 IB API 的封装层。这个库是 pysystemtrade 的一个必需依赖。

非常建议先跑一遍 ib-insync cookbook 里的示例,确认:

  • IB 连接是正常的;
  • Gateway 设置正确;
  • 权限、行情订阅等都配置妥当。

如果 ib-insync 都跑不通,pysystemtrade 自然也无法正常工作。

提示:ib-insync 自 2024 年 3 月起已被标记为归档项目(archived)。未来我们可能需要迁移到它的某个 fork 上。

IBC

很多人会使用 ibcAlpha 这个项目:

  • 它可以自动维持一个长期在线的 IB Gateway 会话;
  • 避免你每天手动重启 Gateway 的痛苦;
  • 尤其适合在“无头”交易服务器上跑全自动系统时使用。

Launching and configuring the Gateway

在运行任何 Python 代码之前,你必须先启动 Gateway 软件。当前版本的 Gateway 通常通过桌面图标启动。

你需要使用以下账户之一登录:

  • 纸面交易账户(paper trading account);
  • 实盘交易账户(如果使用实盘账户,请务必确认自己知道在做什么。因使用 pysystemtrade 进行实盘交易导致的任何损失,本人概不负责,风险自担!)。

你还需要对 Gateway 做一些配置:

  • Socket port:应设置为 4001。如果你使用其他端口,需要在稍后建立连接时同步修改端口设置(见这里这里)。
  • 白名单(trusted IP addresses):应包含 127.0.0.1
    如果你计划在一台机器上运行 Gateway,而在另一台机器上通过网络连接它,则应在白名单中添加那台“客户端机器”的 IP 地址。
  • 如果你要进行真实交易,必须关闭“Read only API”选项(也就是允许通过 API 下单)。
  • 你可能还需要检查并调整“precautions”和“preset options”等选项(这些与 IB 的风控提示和下单模板相关)。

Making a connection

1
2
3
4
5
from sysbrokers.IB.ib_connection import connectionIB
conn = connectionIB( 999, ib_ipaddress = "127.0.0.1", ib_port=4001, account="U999999") # 第一个必填参数是 client_id;后面这些关键字参数是默认值,可以省略
conn
# 在生产环境中,client id 通常从数据库中分配,以避免冲突
Out[13]: IB broker connection{'ipaddress': '127.0.0.1', 'port': 4001, 'client': 999} 

更多细节见下文的 Creating and closing connection objects

Reference

Classes and object references

在 pysystemtrade 的 sysbrokers/IB 目录中,IB 相关代码可以大致分为三类对象:

  • Data source objects(数据源对象)

    • 向系统其它部分提供“标准数据对象 API”。
    • 例如,无论期货合约价格来自数据库还是来自 IB,调用方式应该保持一致。
    • 它们通常通过 /sysproduction/data/broker/ 中的接口函数被生产环境代码调用。
    • 创建这类对象时,需要传入一个 connection object;内部再通过它调用各类 client object
  • Client objects(客户端对象)

    • 这些对象通过 ib-insync 在具体领域内与 IB 通信(获取数据、下单等)。
    • 它们同样在初始化时接收一个 connection object
  • Connection objects(连接对象)

    • 封装一个具体的 IB Gateway 连接;
    • 内部持有 ib-insync 的 IB 实例,用于实际的网络交互。

Data source objects

在设计上,我们把 IB 视作“另一种数据源”,因此它必须遵守通用的数据对象 API(参见存储期货与即期外汇数据)。
区别在于:对于 IB 这类“外部券商”,我们不会执行“删除”或“写入”操作(至少不会像对数据库那样随意操作)。

在生产环境中,数据源对象通常通过 /sysproduction/data/broker/ 中的接口函数被调用;
不建议在业务逻辑中直接操作 IB 数据源对象,因为接口层会屏蔽掉“具体对接的是哪家券商”的细节。

所有 IB 数据源对象都继承自 sysbrokers/ 目录下的相关基类(例如 broker*data.py 中的类)。
这样做有两点好处:

  • 可以在这些基类中添加“券商特有”的通用方法(这些方法对数据库就没意义);
  • 也清楚地展示了:如果要对接其它券商,你需要实现哪些接口。

数据源对象在初始化时会接收并持有:

  • 一个 connection object(连接对象);
  • 可选的 logger(日志对象)。

它们内部包含并调用若干 client objects,具体实现位于该模块目录

你可以从某个数据源对象上访问到它所使用的 client 和 connection,例如:

1
2
3
4
from sysbrokers.IB.ib_orders_data import ibOrdersData
ib_orders_data = ibOrdersData(conn)
ib_orders_data.ib_client
ib_orders_data.ibconnection

FX Data

1
2
3
4
5
6
from sysbrokers.IB.ib_Fx_prices_data import ibFxPricesData
from sysdata.data_blob import dataBlob
ibfxpricedata = ibFxPricesData(conn, dataBlob())

ibfxpricedata.get_list_of_fxcodes()  # FX 代码必须在 .csv 文件 /sysbrokers/IB/ibConfigSpotFX.csv 中配置
ibfxpricedata.get_fx_prices("GBPUSD") # 返回 fxPrices 对象

Futures price data

1
2
3
4
5
6
7
8
from sysobjects.contracts import futuresContract
from sysbrokers.IB.ib_futures_contract_price_data import ibFuturesContractPriceData
from sysdata.data_blob import dataBlob
ibfuturesdata = ibFuturesContractPriceData(conn, dataBlob())

ibfuturesdata.get_list_of_instrument_codes_with_merged_price_data() # 返回在 [futures config 文件](https://github.com/robcarver17/pysystemtrade/blob/master/sysbrokers/IB/ibConfigFutures.csv) 中定义的品种列表
ibfuturesdata.contract_dates_with_price_data_for_instrument_code("DAX") # 返回该品种有价格数据的合约日期列表
ibfuturesdata.get_prices_for_contract_object(futuresContract("DAX", "201203")) # 返回该合约的 OHLC 价格和成交量数据

Capital data

1
2
3
4
from sysbrokers.IB.ib_capital_data import ibCapitalData
ib_capital_data = ibCapitalData(conn)

ib_capital_data.get_account_value_across_currency()

Contracts data

1
2
3
4
5
6
7
8
9
from sysobjects.contracts import futuresContract
contract = futuresContract("DAX", "202306")

from sysbrokers.IB.ib_futures_contracts_data import ibFuturesContractData
ib_futures_contract_data = ibFuturesContractData(conn)
ib_futures_contract_data.get_contract_object_with_IB_data(contract) # 很多函数的第一步都是拿到这个对象,再去下单或取价
ib_futures_contract_data.get_actual_expiry_date_for_single_contract(contract)
ib_futures_contract_data.get_min_tick_size_for_contract(contract)
ib_futures_contract_data.get_trading_hours_for_contract(contract)

Instruments data

1
2
3
4
5
6
from sysbrokers.IB.ib_instruments_data import ibFuturesInstrumentData
ib_futures_instrument_data = ibFuturesInstrumentData(conn)
ib_futures_instrument_data.get_list_of_instruments()
ib_futures_instrument_data.get_futures_instrument_object_with_IB_data("DAX") # 其它函数会用它的“元数据”来映射 IB 合约
ib_futures_instrument_data.get_brokers_instrument_code("DAX") # 下一函数的反向操作
ib_futures_instrument_data.get_instrument_code_from_broker_contract_object("DAX") # 上一函数的反向操作

Orders data

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
from sysbrokers.IB.ib_orders import ibExecutionStackData
ib_orders_data = ibExecutionStackData(conn)

ib_orders_data.get_list_of_broker_orders_with_account_id() # 获取过去 24 小时内,券商端执行过的订单列表
ib_orders_data.get_list_of_orders_from_storage() # 获取本实例记录的订单列表
ib_orders_data.put_order_on_stack(broker_order) # 这条会真正下单!返回 orderWithControls:包含动态 IB 订单对象的 broker order
ib_orders_data.match_db_broker_order_to_order_from_brokers(broker_order) # 用于检查某个订单是否已在券商端成交
ib_orders_data.match_db_broker_order_to_control_order_from_brokers(broker_order) # 有时直接拿控制对象(control object)更方便
ib_orders_data.cancel_order_on_stack(broker_order)  # 发送取消订单的指令...
ib_orders_data.check_order_is_cancelled(broker_order)  # ... 检查取消是否生效
ib_orders_data.check_order_is_cancelled_given_control_object(broker_order_with_controls)   # 针对有 control object 的订单做相同检查(省去匹配步骤)
ib_orders_data.check_order_can_be_modified_given_control_object(broker_order_with_controls) 
ib_orders_data.modify_limit_price_given_control_object(broker_order_with_controls) 

Position data

1
2
3
from sysbrokers.IB.ib_contract_position_data import ibContractPositionData
ib_contract_position_data = ibContractPositionData(conn)
ib_contract_position_data.get_all_current_positions_as_list_with_contract_objects()

Client objects

Client 对象负责通过 ib-insync 向券商发出请求(取数、下单、查询持仓等)。
它们通常由“券商数据源对象”在初始化时创建,并从数据源对象那里接收:

  • 一个连接对象(connection);
  • 可选的日志记录器。

这些类位于此模块目录,并通过一个略显“奇怪”的继承树连接在一起:

  • 基类 ibClient
    • ibAccountingClient(ibClient)
    • ibPositionsClient(ibClient)
    • ibContractsClient(ibClient)
      • ibOrdersClient(ibContractsClient)
      • ibPriceClient(ibContractsClient)
        • ibFxClient(ibPriceClient)

Client 对象内部还持有:

  • 一个连接对象;
  • 一个实时的 ib_inysnc.IB 实例(实际执行请求的 ib-insync 对象)。

示例:

1
2
3
4
from sysbrokers.IB.client.ib_price_client import ibPriceClient
ib_price_client = ibPriceClient()
ib_price_client.ib_connection # 连接对象
ib_price_client.ib # 实时 ib_inysnc.IB 实例

Connection objects

在 pysystemtrade 中,你通常不会自己手动新建 IB 连接对象——在生产环境中,这些连接由 dataBlob(见 data blobs)统一管理并复用。
不过了解它们的工作方式有助于你在调试或扩展系统时更灵活。

Creating and closing connection objects

1
2
from sysbrokers.IB.ib_connection import connectionIB
conn = connectionIB(1, ib_ipaddress = "127.0.0.1", ib_port=4001, account="U123456")
  • ib_port 应与 Gateway 配置中的端口号一致;
  • client_id(例如这里的 1)不能与任何已连接的 Python 进程重复(哪怕是“卡死挂着”的进程也不行)。
    在生产环境中,client id 通常通过数据库统一分配,以避免冲突;
  • ib_ipaddress127.0.0.1 时表示“本机”;如果 Gateway 跑在局域网中另一台机器上,你需要在这里填那台机器的 IP。

如果没有显式传入 accountib_ipaddressib_port 参数,则会按以下优先级使用默认值:

  1. private_config.yaml 文件中的配置(见下文);
  2. defaults.yaml 配置文件中的默认值。

你应该先在 pysystemtrade 的 private 目录 中创建 private_config.yaml 文件,然后加入一行或多行:

1
2
3
ib_ipaddress: 192.168.0.10
ib_port: 4001
broker_account: U123456

之后也可以用“从配置对象创建连接”的方式:

1
conn = connectionIB(config)

连接对象在创建时会立即尝试连接 IB,因此只有在你准备好连接 IB 时才应创建它们。

同样,在生产环境中,连接通常由包含它们的 dataBlob 对象在适当时机关闭;
如有需要,你也可以手动调用 conn.close_connection() 来关闭连接。

Using connections

我们把 IB 当作“另一种数据源”,因此它也要遵守统一的数据对象 API(参见存储期货与即期外汇数据)。
由于连接对象抽象了“与券商交互”的细节,只要接口兼容,理论上也可以很容易把它们替换为其他券商的连接实现。

连接对象提供的最重要服务是:封装了一个实时的 ib_inysnc.IB 实例:

1
conn.ib

如果你熟悉 ib-insync,可以直接使用它,例如:conn.ib.positions()
但在典型用法中,这个实例主要由上文的各类 IB client 对象内部使用。

Make multiple connections

可以从多个进程同时连接到同一个 IB Gateway,每个进程维护自己的连接对象;
但每个连接必须使用唯一的 client id

已经使用过的 client id 会存储在“活动数据库”(通常是 MongoDB)中,以确保不会重复使用正在活跃的 client id。