问题描述
解决方案
在PTrade量化交易平台中,优化策略代码的核心在于减少I/O操作(网络请求/API调用)和利用Pandas/Numpy的向量化计算代替Python原生循环。
以下是针对PTrade平台的具体优化建议和代码示例。
PTrade 策略代码优化指南
1. 批量获取行情数据 (最关键的优化)
问题: 在 handle_data 中使用 for 循环遍历股票池,对每一只股票单独调用 get_history 或 get_price。这是导致回测和实盘慢的主要原因。
优化方案: get_history 和 get_price 的 security_list 参数支持传入列表。一次性获取所有股票的数据,返回一个 DataFrame 或 Panel,然后在本地进行计算。
❌ 低效写法
def handle_data(context, data):
for stock in g.security:
# 每次循环都发起一次API调用,效率极低
h = get_history(10, '1d', 'close', stock)
ma5 = h['close'][-5:].mean()
# ... 交易逻辑
✅ 高效写法
def handle_data(context, data):
# 一次性获取所有股票的收盘价,返回 DataFrame (列是股票代码,行是时间)
# 注意:Python 3.5环境下多股多字段可能返回Panel,单字段多股返回DataFrame
# 建议明确指定 is_dict=False (默认)
hist = get_history(10, '1d', 'close', g.security, is_dict=False)
# 利用 Pandas 直接计算所有股票的均值
ma5_series = hist.iloc[-5:].mean()
for stock in g.security:
ma5 = ma5_series[stock]
# ... 交易逻辑
2. 合理利用 before_trading_start
问题: 在 handle_data(每分钟或每天运行)中查询那些全天不会改变的数据,例如基本面数据、指数成分股、ST状态等。
优化方案: 将这些查询移动到 before_trading_start(每天开盘前运行一次),并将结果存储在全局对象 g 中。
❌ 低效写法
def handle_data(context, data):
# 每天/每分钟都查询一次沪深300成分股,浪费资源
stocks = get_index_stocks('000300.SS')
# 每次都查询财务数据
df = get_fundamentals(stocks, 'valuation', 'pe_ttm')
✅ 高效写法
def before_trading_start(context, data):
# 每天开盘前只获取一次
g.stocks = get_index_stocks('000300.SS')
# 提前获取财务数据并存储
g.fundamentals = get_fundamentals(g.stocks, 'valuation', 'pe_ttm')
def handle_data(context, data):
# 直接使用 g.stocks 和 g.fundamentals
pass
3. 缓存持仓和订单信息
问题: 在策略逻辑判断中多次调用 get_position 或 get_open_orders。
优化方案: 在 handle_data 开始时一次性获取所有持仓或订单信息,存储在变量中供后续逻辑使用。
✅ 高效写法
def handle_data(context, data):
# 获取所有持仓,返回字典 {code: Position对象}
all_positions = get_positions()
# 获取所有未完成订单
open_orders = get_open_orders()
for stock in g.security:
# 直接从字典查,不需要API调用
pos = all_positions.get(stock)
current_amount = pos.amount if pos else 0
# 检查是否有未完成订单
has_open_order = False
for order in open_orders:
if order.symbol == stock:
has_open_order = True
break
4. 避免在循环中进行重复计算
问题: 在循环内部计算与当前循环变量无关的指标。
优化方案: 将公共计算提取到循环外部。
综合优化代码示例
以下是一个完整的双均线策略示例,展示了如何应用上述优化技巧。
def initialize(context):
# 初始化股票池,这里只做设定,不获取数据
# 假设我们关注沪深300
g.index = '000300.SS'
set_universe(get_index_stocks(g.index))
# 设置手续费等(仅回测有效)
set_commission(commission_ratio=0.0003, min_commission=5.0)
def before_trading_start(context, data):
# 1. 【优化】在盘前更新股票池,避免盘中调用 get_index_stocks
g.security = get_index_stocks(g.index)
set_universe(g.security)
# 2. 【优化】过滤掉停牌和ST股票,减少后续无效计算
# 注意:filter_stock_by_status 仅在 before_trading_start 可用
g.trade_stocks = filter_stock_by_status(g.security, ["ST", "HALT", "DELISTING"])
def handle_data(context, data):
# 如果没有可交易股票,直接返回
if not g.trade_stocks:
return
# 3. 【优化】批量获取历史数据,一次API调用代替循环调用
# 获取过去20天的数据用于计算MA10和MA5
# 返回 DataFrame,索引为时间,列为股票代码
hist_data = get_history(20, '1d', 'close', g.trade_stocks, is_dict=False)
# 4. 【优化】使用 Pandas 向量化计算均线,速度远快于循环
ma5_series = hist_data.iloc[-5:].mean()
ma10_series = hist_data.iloc[-10:].mean()
# 5. 【优化】一次性获取所有持仓,避免循环中反复调用 get_position
positions_dict = get_positions()
# 6. 【优化】一次性获取未完成订单,避免重复下单
open_orders = get_open_orders()
open_order_stocks = [o.symbol for o in open_orders]
# 遍历筛选后的股票池
for stock in g.trade_stocks:
# 如果有未完成订单,跳过该股票,防止重复下单
if stock in open_order_stocks:
continue
# 获取当前价格
if stock not in data:
continue
current_price = data[stock]['close']
# 获取均线值
ma5 = ma5_series.get(stock)
ma10 = ma10_series.get(stock)
# 获取当前持仓
pos = positions_dict.get(stock)
current_amount = pos.amount if pos else 0
# 交易逻辑
# 金叉且无持仓 -> 买入
if ma5 > ma10 and current_amount == 0:
# 简单的资金管理:每只股票买入10000元
order_value(stock, 10000)
log.info("买入 %s, 价格: %s" % (stock, current_price))
# 死叉且有持仓 -> 卖出
elif ma5 < ma10 and current_amount > 0:
order_target(stock, 0)
log.info("卖出 %s, 价格: %s" % (stock, current_price))
Q&A: PTrade 策略优化常见问题
Q: get_snapshot 和 get_history 哪个更快?
A: 在交易模式下,get_snapshot 获取的是实时快照(Tick级数据),速度很快,但它不包含历史K线。如果你需要计算均线等指标,必须用 get_history。在回测模式下,不支持 get_snapshot。优化 get_history 的关键是使用批量获取(传入列表)。
Q: 为什么我的策略在回测时很快,实盘时很慢?
A: 回测是本地数据模拟,速度主要受CPU限制。实盘涉及网络通信。如果在 handle_data 中使用了 for 循环逐个请求数据,网络延迟会被放大。务必使用批量接口。另外,实盘中 handle_data 的运行频率如果是 tick 级别,逻辑必须非常精简。
Q: 如何判断我的代码哪里慢?
A: 可以使用 Python 的 time 模块进行简单的打点计时。
import time
def handle_data(context, data):
t1 = time.time()
# ... 你的代码 ...
t2 = time.time()
log.info("耗时: %s 秒" % (t2 - t1))
如果耗时超过了你的运行频率(例如tick级策略耗时超过3秒),就会导致信号延迟或丢失。
Q: get_fundamentals 可以放在 handle_data 里吗?
A: 强烈不建议。财务数据更新频率很低(季度更新),且该接口通常有流控限制(如每秒调用次数限制)。应放在 before_trading_start 或 run_daily 中调用,并将结果存入全局变量 g。