问题描述
解决方案
在PTrade策略中,获取大量历史数据(如get_history或get_price)变慢通常是因为Pandas DataFrame的构建开销以及频繁的网络IO请求导致的。
以下是针对PTrade平台特性的几种核心提速方案:
1. 使用 is_dict=True 参数(最推荐)
PTrade的API(get_history 和 get_price)默认返回的是 pandas.DataFrame 或 pandas.Panel 对象。构建这些对象在数据量大时非常消耗时间。
通过设置 is_dict=True,API将直接返回原生字典(Dictionary)和 NumPy 数组,这可以显著减少内存开销并提高速度。
优化前(较慢):
# 返回 DataFrame,构建速度慢
df = get_history(100, '1d', 'close', g.security, is_dict=False)
优化后(极快):
# 返回 Dict,速度大幅提升
data_dict = get_history(100, '1d', 'close', g.security, is_dict=True)
# 访问数据示例:data_dict['600570.SS']['close'][-1]
2. 批量获取数据(避免循环调用)
很多策略会写成循环遍历股票池来获取数据,这会产生大量的网络交互请求,导致极大的延迟。务必一次性传入整个股票列表。
优化前(极慢):
# 错误做法:在循环中请求,如果有100只股票,就发生了100次网络IO
for stock in g.security:
h = get_history(100, '1d', 'close', stock)
# 处理逻辑...
优化后(快):
# 正确做法:一次性请求所有股票数据,只发生1次网络IO
# g.security 是一个列表,例如 ['600570.SS', '000001.SZ', ...]
all_history = get_history(100, '1d', 'close', g.security, is_dict=True)
for stock in g.security:
if stock in all_history:
data = all_history[stock]
# 处理逻辑...
3. 将数据获取移至 before_trading_start
如果你的策略逻辑依赖的是日线级别的历史数据,而策略运行在分钟级别或Tick级别,不要在 handle_data 中重复获取历史数据。
handle_data 在分钟回测中每分钟运行一次,重复获取昨日及之前的历史数据是巨大的浪费。
优化方案:
def before_trading_start(context, data):
# 每天开盘前一次性获取历史数据并存储在全局变量 g 中
# 注意:这里获取的是截止到昨天的历史数据
g.history_data = get_history(100, '1d', 'close', g.security, is_dict=True)
def handle_data(context, data):
# 盘中直接使用内存中的数据,无需再次请求API
for stock in g.security:
# 获取历史收盘价
past_closes = g.history_data[stock]['close']
# 获取当前分钟的最新价
current_price = data[stock]['close']
# 结合使用...
4. 减少不必要的字段和数据长度
- 精简字段 (
field):只获取你需要的字段。如果你只需要收盘价,不要请求['open', 'high', 'low', 'close', 'volume'],这会减少数据传输量。 - 精简长度 (
count):只获取计算指标所需的最小长度。例如计算MA5,获取10-20根K线即可,不要获取1000根。
5. 综合优化代码示例
以下是一个结合了上述所有优化技巧的策略代码模板:
def initialize(context):
# 假设股票池有上百只股票
g.security = get_index_stocks('000300.SS')
set_universe(g.security)
def before_trading_start(context, data):
# 优化点1 & 2 & 3:
# 1. 在盘前统一获取
# 2. 传入列表批量获取
# 3. 使用 is_dict=True 避免 DataFrame 开销
# 4. 只获取需要的 'close' 字段
g.all_history = get_history(30, '1d', 'close', g.security, is_dict=True)
def handle_data(context, data):
# 遍历股票池
for stock in g.security:
# 容错处理:确保取到了数据
if stock not in g.all_history:
continue
# 直接从内存读取 Numpy 数组,速度极快
hist_closes = g.all_history[stock]['close']
# 计算均线 (使用 Numpy 计算比 Pandas 更快)
if len(hist_closes) > 20:
ma20 = hist_closes[-20:].mean()
# 获取当前价格
current_price = data[stock]['close']
# 简单的交易逻辑
if current_price > ma20:
# 这里的 order 只是示例
pass
Q&A: 常见数据获取问题
Q: 为什么我使用了 is_dict=True 后,代码报错说没有 iloc 或 loc 属性?
A: is_dict=True 返回的是 Python 字典和 NumPy 数组(numpy.ndarray),而不是 Pandas DataFrame。NumPy 数组不支持 iloc 或 loc。你需要使用数组切片方式访问,例如 data[-1] 获取最后一个元素,data[-5:] 获取最后5个元素。
Q: get_history 和 get_price 可以使用多线程加速吗?
A: 不可以。PTrade 文档明确指出:“该接口与get_price接口不支持多线程同时调用”。在 run_daily 或 run_interval 中与 handle_data 并发调用,或者自己开启 Python 线程调用这些接口,会导致数据获取为空或程序异常。请坚持使用上述的“批量获取”方法代替多线程。
Q: 如果我必须使用 DataFrame 进行复杂的 Pandas 计算怎么办?
A: 建议先使用 is_dict=True 快速获取数据,然后在策略本地将需要的少量数据转换为 DataFrame。这样比让 API 在底层构建巨大的 DataFrame 传输过来要快。或者,尝试使用 NumPy 函数重写你的指标计算逻辑,NumPy 通常比 Pandas 更快。