Python Interactive经纪人IB API非常慢

时间:2017-04-26 23:19:42

标签: python interactive-brokers

我正在尝试新的Python Interactive Broker API,但我在第一步遇到了一些严重的速度问题......

以下代码(见下文)时间

0:00:08.832813直到收到数据

0:00:36.000785直到应用完全断开连接...

为什么这么慢? 什么是加快它的最好方法?

from ibapi import wrapper
from ibapi.client import EClient
from ibapi.utils import iswrapper #just for decorator
from ibapi.common import *
from ibapi.contract import *
import datetime
from datetime import timedelta


class DataApp(wrapper.EWrapper, EClient):
    def __init__(self):
        wrapper.EWrapper.__init__(self)
        EClient.__init__(self, wrapper=self)

    @iswrapper
    def historicalData(self, reqId: TickerId, date: str, open: float, high: float,
                            low: float, close: float, volume: int, barCount: int,
                            WAP: float, hasGaps: int):
        super().historicalData(reqId, date, open, high, low, close, volume,
                                barCount, WAP, hasGaps)
        print("HistoricalData. ", reqId, " Date:", date, "Open:", open,
               "High:", high, "Low:", low, "Close:", close, "Volume:", volume)

    @iswrapper
    def historicalDataEnd(self, reqId: int, start: str, end: str):
        super().historicalDataEnd(reqId, start, end)
        print("HistoricalDataEnd ", reqId, "from", start, "to", end)
        print(datetime.datetime.now()-startime)
        self.done = True # This ends the messages loop - this was not in the example code...

    def get_data(self):        
        self.connect("127.0.0.1", 4002, clientId=10)
        print("serverVersion:%s connectionTime:%s" % (self.serverVersion(),
                                                self.twsConnectionTime()))

        cont = Contract()
        cont.symbol = "ES"
        cont.secType = "FUT"
        cont.currency = "USD"
        cont.exchange = "GLOBEX"
        cont.lastTradeDateOrContractMonth = "201706"
        self.reqHistoricalData(1, cont, datetime.datetime.now().strftime("%Y%m%d %H:%M:%S"),
                               "1800 S", "30 mins", "TRADES", 0, 1, [])
        self.run()        
        self.disconnect()
        print(datetime.datetime.now()-startime)

global starttime
startime = datetime.datetime.now()
DA = DataApp()
DA.get_data()

我还尝试不断运行它是后台,以便只使用

动态提交请求
def runMe():
    app.run() # where run() has be removed from the class definition

import threading
thread = threading.Thread(target = runMe)
thread.start()

但它也非常缓慢。 任何建议赞赏

3 个答案:

答案 0 :(得分:2)

我建议你修改ibapi模块中连接类中的连接套接字锁。该建议来自heshiming on github;如果您有权访问私人互动经纪人回购,您可以访问https://github.com/InteractiveBrokers/tws-api/issues/464

中的讨论

我这样做了,它显着提高了性能。

Heshiming建议您减少套接字锁定对象的超时,每次发送或接收消息时都会调用该超时。 要修改套接字锁定,请转到ibapi的site-packages文件夹并修改connection.py中的connect函数,将“self.socket.settimeout(1)”更改为“self.socket.settimeout(0.01)”。对于我的版本,这是connection.py中的第48行。

如果你看不到heshiming的帖子,我把它包含在这篇文章的底部。

备选方案:另一个有趣的解决方案是利用异步事件循环的asyncio。我没有这样做,但看起来很有希望。请参阅示例Ewald汇总https://github.com/erdewit/tws_async

Heshiming评论:

  

Connection /ibapi/connection.py的实现   在sendMsg和recvMsg中共享一个Lock对象。自连接以来   调用self.socket.settimeout(1),因此是底层的   self.socket.recv(4096)每秒只运行一次。

     

此类实现会产生性能问题。因为锁是   共享,套接字在接收时无法发送数据。在场景中   收到的消息长度小于4k字节,recvMsg   功能将在释放锁定之前等待1秒钟   后续sendMsg等待。在我的实验中,大多数消息似乎都是   短于4k字节。换句话说,这就是一个上限   每秒recvMsg。

     

有几种策略可以缓解这个问题。一个可以减少   接收缓冲区到一个小于4k的数字,或减少套接字   超时至0.001秒以使其阻塞更少。

     

或根据   http://stackoverflow.com/questions/1981372/are-parallel-calls-to-send-recv-on-the-same-socket-valid   ,套接字本身实际上是线程安全的。因此没有锁   必要的。

     

我尝试了所有三种策略。卸下锁是最好的。和   将超时减少到0.001会以类似的方式工作。

     

我只能担保linux / unix平台,我还没试过   视窗。您是否考虑更改实施以改进   此?

答案 1 :(得分:0)

如@BenM在他的回答中所建议的,当前的API(2019年5月)已通过删除recvMsg中的锁来提高速度。但是,根据请求的类型,它仍然可能很慢。

删除大多数日志记录很有帮助,大概是因为某些msg类型很大。最初,我尝试使用logging库对其进行过滤,但从整体上来说,删除代码的速度更快,并且将其归结为生成更大字符串之前甚至需要传递给{{ 1}}。

用双端队列替换队列:用collections.deque替换logging中的Queue也可能是值得的,因为client.py快10倍以上。我已经在其他地方进行了此操作以提高速度,但目前为止还没有完成。 deque应该是线程安全的,并且不使用在此处使用的锁定方式:“双端队列支持双线程,从双端队列的任一侧进行线程安全的内存高效添加和弹出操作”(来自文档)。

Queue vs deque speed comparison

答案 2 :(得分:-1)

app.run()app.done == False时的无限循环,但在app.done设置为True时不会立即停止。 (我不知道为什么)。

我所做的是编写一种新方法,而不是使用app.run()

这是我的解决方案:

import time
from ibapi import (decoder, reader, comm)

并将此功能放入您的客户端类。

def getMessage(self, wait=3):
    # wait 3 secs for response to come in
    time.sleep(wait)
    # get everything in app.msg_queue
    while not self.msg_queue.empty():
        text = self.msg_queue.get(block=True, timeout=0.2)
        fields = comm.read_fields(text)
        self.decoder.interpret(fields)

用法很简单。只需使用app.getMessage()代替app.run()