使用ib_insync获取盈透证券逐笔数据时陷入无限循环求助
解决ib_insync获取逐笔数据时的无限循环问题
问题场景
使用ib_insync编写Python脚本从盈透证券(Interactive Brokers/TWS)获取单日逐笔数据,预期逻辑为:每次请求1000条逐笔数据,将最新tick的时间作为下一次请求的起始时间,循环直至达到结束时间。但实际脚本陷入无限循环,每次请求返回的最新tick时间始终固定,导致起始时间无法推进,重复请求相同数据。
原函数代码
def ticker_data(ticker, start, end='',numberOfTicks=1000, Exchange='SMART', Currency='USD', ip='127.0.0.1', port=7496, clientId=1): ib = IB() ib.connect(ip, port, clientId=clientId) contract = Stock(ticker, Exchange, Currency) real_end = start df = pd.DataFrame() while end != real_end: ticks = ib.reqHistoricalTicks(contract, startDateTime=real_end, endDateTime=end, numberOfTicks=numberOfTicks, whatToShow='TRADES', useRth=True) df = pd.concat([df, util.df(ticks)], ignore_index=True) print("Latest time in DataFrame:", df.iloc[-1]['time']) print("Updated real_end_1:", real_end) print("End time:", end) print("length of recived_data:", len(ticks)) print() real_end = datetime.strptime(str(df.iloc[-1]['time']), "%Y-%m-%d %H:%M:%S%z").strftime("%Y%m%d %H:%M:%S")#+' US/Eastern' ib.disconnect() return df
原调用代码
from functions.simple_ib_data import ticker_data from datetime import datetime, timedelta import pandas as pd start = (datetime.now() - timedelta(days=1)).replace(hour=9, minute=30, second=0, microsecond=0).strftime("%Y%m%d %H:%M:%S") end = (datetime.now() - timedelta(days=1)).replace(hour=23, minute=59, second=59, microsecond=0).strftime("%Y%m%d %H:%M:%S") data = ticker_data(ticker="NCLH", start=start, end=end) print(data)
问题原因
- 时区信息丢失:处理
real_end时,将带时区的时间格式化为不带时区的字符串,IB的API无法正确解析时间范围,导致每次请求都从错误的起始点获取数据。 - 循环终止条件失效:直接比较字符串格式的时间,忽略了时区、毫秒级精度的差异,即使时间已经到达结束点,条件依然成立。
- 未处理空数据情况:当没有更多数据返回时,未终止循环,会继续重复请求同一范围的数据。
修复后的代码
from datetime import datetime, timezone, timedelta import pandas as pd from ib_insync import IB, Stock, util def ticker_data(ticker, start, end='', numberOfTicks=1000, Exchange='SMART', Currency='USD', ip='127.0.0.1', port=7496, clientId=1): ib = IB() ib.connect(ip, port, clientId=clientId) contract = Stock(ticker, Exchange, Currency) # 将起始/结束时间字符串转为UTC时区的datetime对象 start_dt = datetime.strptime(start, "%Y%m%d %H:%M:%S").replace(tzinfo=timezone.utc) end_dt = datetime.strptime(end, "%Y%m%d %H:%M:%S").replace(tzinfo=timezone.utc) current_start = start_dt df = pd.DataFrame() while current_start < end_dt: # 使用带时区的时间字符串作为请求参数 ticks = ib.reqHistoricalTicks( contract, startDateTime=current_start.strftime("%Y%m%d %H:%M:%S %Z"), endDateTime=end_dt.strftime("%Y%m%d %H:%M:%S %Z"), numberOfTicks=numberOfTicks, whatToShow='TRADES', useRth=True ) if not ticks: # 无新数据时终止循环 print("No more ticks to fetch.") break tick_df = util.df(ticks) df = pd.concat([df, tick_df], ignore_index=True) print(f"Latest time in DataFrame: {tick_df.iloc[-1]['time']}") print(f"Current start time: {current_start}") print(f"End time: {end_dt}") print(f"Number of ticks received: {len(ticks)}") print() # 更新起始时间为最后一条tick的时间+1微秒,避免重复获取同一条数据 current_start = tick_df.iloc[-1]['time'] + timedelta(microseconds=1) ib.disconnect() return df
关键修改点
- 时区统一:将起始、结束时间转为UTC时区的datetime对象,请求时使用带时区的字符串格式,确保IB API正确解析时间范围。
- 循环条件优化:用带时区的datetime对象比较
current_start < end_dt,避免字符串比较的精度问题。 - 空数据处理:判断
ticks为空时直接终止循环,避免无效请求。 - 避免重复数据:更新
current_start时给最后一条tick的时间加1微秒,确保下一次请求从下一个tick开始。
修改后的调用代码
无需调整原调用代码,确保生成的start和end字符串格式正确即可。
内容的提问来源于stack exchange,提问作者Tomas barta
相关产品推荐
相关产品推荐

