🚀 AI 一键生成 ptrade 策略代码

PTrade `before_trading_start` 函数正确使用指南(附实战代码)

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

问题描述

在每天开盘前,我应该在 `before_trading_start` 这个函数里做哪些准备工作才是最合适的?

解决方案

在 PTrade 量化交易平台中,before_trading_start 是一个非常关键的事件函数。它在每个交易日的开盘前运行(回测模式下通常为 08:30,实盘模式下通常为 09:10 或策略启动时)。

利用这个时间窗口进行准备工作,可以显著提高策略的运行效率和逻辑的严谨性。以下是在 before_trading_start 中最合适做的几类准备工作:

1. 更新和过滤股票池 (最核心用途)

这是该函数最常见的用途。由于指数成分股会调整,或者股票状态(如停牌、ST)会发生变化,因此需要在每日开盘前刷新目标股票池。

  • 获取成分股:调用 get_index_stocks 获取最新的指数成分股(如沪深300)。
  • 剔除异常状态:使用 filter_stock_by_statusget_stock_status 剔除 ST、停牌、退市整理期的股票,防止买入无法交易或高风险的标的。
  • 设置 Universe:调用 set_universe 将清洗后的列表设置为当天的股票池。

2. 预计算技术指标 (提升效率)

如果你的策略依赖于日线级别的技术指标(如日均线、ATR、MACD 等),不要在盘中每分钟(handle_data)都去重复计算。

  • 获取历史数据:调用 get_history 获取过去 N 天的收盘价。
  • 计算指标:计算出昨天的 MA5、MA10 等指标,并存储在全局变量 g 中。盘中只需直接调用这些计算好的值,能大幅减少计算量,避免回测变慢或实盘延迟。

3. 重置每日计数器 (逻辑控制)

很多策略有“每日限制”逻辑,例如“每天最多买入 5 只股票”或“单只股票每天只交易一次”。

  • 清空列表/字典:在 before_trading_start 中将这些计数变量(如 g.bought_today = [])重置为空,确保新的一天逻辑重新开始。

4. 获取基本面/财务数据

财务数据(如市盈率、净利润增长率)通常不会在盘中变化。

  • 查询财务数据:调用 get_fundamentals 获取最新的财务因子。由于该函数涉及跨表查询,开销较大,强烈建议仅在盘前调用一次,而不是在盘中频繁调用。

5. 盘前风控检查

  • 资金检查:检查账户可用资金是否充足。
  • 持仓核对:在实盘中,可以核对本地记录的持仓与柜台返回的实际持仓是否一致。

综合代码示例

以下是一个标准的 before_trading_start 实现模板,涵盖了上述主要功能:

def initialize(context):
    # 初始化全局变量
    g.stock_pool = []
    g.ma_data = {}
    g.bought_today = [] # 记录当天已买入的股票
    # 设定基准
    set_benchmark('000300.SS')

def before_trading_start(context, data):
    # 1. 【重置每日变量】
    g.bought_today = [] 
    log.info("每日盘前初始化:清空当日买入记录")

    # 2. 【更新股票池】
    # 获取沪深300成分股
    index_stocks = get_index_stocks('000300.SS')
    
    # 过滤掉 ST、停牌、退市的股票
    # filter_stock_by_status 默认过滤 ST, HALT, DELISTING
    valid_stocks = filter_stock_by_status(index_stocks)
    
    # 进一步过滤:比如只保留上市超过90天的股票(需自定义逻辑,此处略)
    g.stock_pool = valid_stocks
    
    # 设置为当天的 Universe,供 handle_data 使用
    set_universe(g.stock_pool)
    log.info("今日股票池数量: %d" % len(g.stock_pool))

    # 3. 【预计算技术指标】
    # 批量获取股票池中所有股票过去 20 天的收盘价
    # 注意:count=21 是为了计算 20日均线,需要包含今天之前的20个数据点
    history_data = get_history(21, '1d', 'close', g.stock_pool, fq='pre', include=False)
    
    # 计算每只股票的 MA20 并存入全局字典
    # 注意:get_history 返回的数据结构处理
    g.ma_data = {}
    
    # 遍历股票池计算
    for stock in g.stock_pool:
        # 提取该股票的收盘价序列
        # 注意:根据 PTrade 版本和 get_history 返回类型(DataFrame 或 Panel),取值方式可能略有不同
        # 这里假设返回的是 DataFrame 且包含 code 列,或者直接通过 query 查询
        try:
            # 仅 Python 3.11+ 支持多股查询返回 DataFrame 包含 code 列
            # 如果是旧版本或单股循环,逻辑如下:
            closes = history_data[stock] # 假设返回的是 dict 或 panel 结构
            
            if len(closes) >= 20:
                ma20 = closes[-20:].mean()
                g.ma_data[stock] = ma20
        except Exception as e:
            # 容错处理,防止某只股票数据缺失导致策略崩溃
            continue
            
    log.info("完成 %d 只股票的 MA20 预计算" % len(g.ma_data))

    # 4. 【获取基本面数据】(可选)
    # 获取市盈率小于 50 的股票列表,用于盘中进一步筛选
    df_fund = get_fundamentals(g.stock_pool, 'valuation', 'pe_ttm', date=context.blotter.current_dt)
    if df_fund is not None and not df_fund.empty:
        # 筛选 PE < 50 的股票
        g.low_pe_stocks = df_fund[df_fund['pe_ttm'] < 50].index.tolist()
    else:
        g.low_pe_stocks = []

def handle_data(context, data):
    # 盘中只需直接使用 g.ma_data 和 g.low_pe_stocks
    pass

总结

before_trading_start 中做准备工作的核心原则是:将所有不需要实时更新的计算任务、数据查询任务和状态重置任务都放在这里完成。 这样可以让盘中高频运行的 handle_data 函数保持轻量和快速。


Q&A

Q1: before_trading_start 里可以下单交易吗?
A: 通常不可以。在回测中,该函数运行时间(08:30)早于交易所开盘时间;在实盘中,虽然部分券商支持隔夜单或早盘委托,但 PTrade 的撮合机制主要在 handle_datatick_data 中进行。建议将交易逻辑放在盘中函数,盘前仅做数据准备。

Q2: 为什么不把 get_index_stocks 放在 initialize 里?
A: initialize 只在策略启动的第一天运行一次。如果你在 initialize 里获取指数成分股,那么随着回测时间的推移,你的股票池永远是几年前的旧名单,无法捕捉到新纳入指数的优质股票或剔除被调出的股票。因此必须在 before_trading_start 中每日更新。

Q3: 在实盘模式下,before_trading_start 获取的 get_history 数据包含今天吗?
A: 不包含。before_trading_start 运行时,当天的行情尚未开始(或刚开始),get_history(..., include=False) 获取的是截止到昨天收盘的数据。这是计算日线级别指标(如昨日收盘价、昨日均线)的最佳时机。