🚀 AI 一键生成 ptrade 策略代码

提升 PTrade 策略运行效率的几个关键技巧

ptrade | 发布时间: 2025/12/18
以下内容由 EasyQuant 生成。

问题描述

如何优化我的量化策略代码,减少不必要的API调用,提高运行速度?

解决方案

在PTrade量化交易平台中,优化策略代码的核心在于减少I/O操作(网络请求/API调用)利用Pandas/Numpy的向量化计算代替Python原生循环。

以下是针对PTrade平台的具体优化建议和代码示例。

PTrade 策略代码优化指南

1. 批量获取行情数据 (最关键的优化)

问题:handle_data 中使用 for 循环遍历股票池,对每一只股票单独调用 get_historyget_price。这是导致回测和实盘慢的主要原因。

优化方案: get_historyget_pricesecurity_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_positionget_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_snapshotget_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_startrun_daily 中调用,并将结果存入全局变量 g