🚀 AI 一键生成 ptrade 策略代码

PTrade 策略实战:如何结合 get_index_stocks 和 get_fundamentals 实现价值选股?

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

问题描述

如何编写一个策略,先获取所有沪深300的股票,然后筛选出其中市盈率低于20的股票进行买入?

解决方案

这是一个非常经典的基于基本面数据的选股策略。在PTrade中实现该策略主要分为三个步骤:

  1. 获取股票池:在盘前获取沪深300指数的成分股。
  2. 获取财务数据:查询这些股票的市盈率(PE)数据。
  3. 交易执行:筛选出符合条件(PE < 20)的股票,并进行等权重买入,同时卖出不再符合条件的股票。

策略实现思路

  • initialize: 初始化设置,设定基准和手续费。
  • before_trading_start: 每天开盘前运行。获取沪深300成分股,通过 get_fundamentals 获取估值数据,筛选出 0 < PE(TTM) < 20 的股票列表。这里加上大于0的条件是为了排除亏损股(亏损股PE通常为负)。
  • handle_data: 盘中运行。执行调仓逻辑,卖出不在目标列表中的持仓,对目标列表中的股票进行等权重买入。

PTrade 策略代码

def initialize(context):
    """
    初始化函数,设置基准、手续费等
    """
    # 设置本策略的基准为沪深300指数
    set_benchmark('000300.SS')
    
    # 设置股票交易佣金(示例:万分之三)
    set_commission(commission_ratio=0.0003, min_commission=5.0, type="STOCK")
    
    # 定义一个全局变量,用于存储当日计划买入的股票列表
    g.target_stocks = []

def before_trading_start(context, data):
    """
    盘前处理函数,用于获取数据和筛选股票
    """
    # 1. 获取沪深300成分股代码列表
    # 注意:get_index_stocks 建议在盘前或盘后调用,不要在 initialize 中调用
    index_code = '000300.SS'
    stocks = get_index_stocks(index_code)
    
    if not stocks:
        log.info("未获取到沪深300成分股")
        g.target_stocks = []
        return

    # 2. 获取财务数据
    # 查询 valuation 表中的 pe_ttm (滚动市盈率)
    # pe_ttm 比静态市盈率更能反映近期盈利能力
    q = get_fundamentals(stocks, 'valuation', ['pe_ttm'])
    
    # 3. 筛选逻辑
    if q is not None and not q.empty:
        # 筛选条件:0 < PE(TTM) < 20
        # 解释:PE < 20 是用户要求;PE > 0 是为了剔除亏损企业(负PE)
        df_filtered = q[(q['pe_ttm'] > 0) & (q['pe_ttm'] < 20)]
        
        # 获取筛选后的股票代码列表
        g.target_stocks = df_filtered.index.tolist()
        
        log.info("今日符合筛选条件(PE < 20)的股票数量: %s" % len(g.target_stocks))
    else:
        g.target_stocks = []
        log.info("今日未获取到财务数据")

def handle_data(context, data):
    """
    盘中运行函数,执行交易逻辑
    """
    # 如果没有选出股票,则清仓(可选逻辑,视策略需求而定)
    if len(g.target_stocks) == 0:
        for stock in context.portfolio.positions:
            order_target(stock, 0)
        return

    # 1. 卖出逻辑
    # 遍历当前持仓,如果持仓股票不在今日的目标列表中,则卖出
    for stock in context.portfolio.positions:
        if stock not in g.target_stocks:
            order_target(stock, 0)
            log.info("卖出不再符合条件的股票: %s" % stock)

    # 2. 买入逻辑
    # 采用等权重分配资金
    # 计算每只股票的目标持仓价值 = 当前账户总资产 / 目标股票数量
    position_count = len(g.target_stocks)
    target_value = context.portfolio.portfolio_value / position_count
    
    for stock in g.target_stocks:
        # 过滤停牌、退市等无法交易的股票(order函数内部有一定保护,但加上判断更严谨)
        # 这里简单直接下单,PTrade回测引擎会自动处理停牌无法成交的情况
        order_target_value(stock, target_value)

代码关键点解析

  1. get_index_stocks('000300.SS'):
    • 这是获取指数成分股的标准API。注意代码后缀 .SS 代表上交所发布的指数(沪深300由中证指数公司发布,但在PTrade中通常归类在上海市场后缀下或使用 .XBHS)。
  2. get_fundamentals:
    • 这是获取基本面数据的核心函数。
    • valuation 表包含了估值数据。
    • pe_ttm (滚动市盈率) 通常比 pe_static (静态市盈率) 更具参考价值,因为它包含了最近4个季度的净利润。
  3. order_target_value:
    • 这个函数非常适合用于“调仓”策略。它会自动计算需要买入或卖出的数量,使该股票的持仓市值达到指定金额。如果账户资金不足,它会自动调整。
  4. Python 版本兼容性:
    • 代码使用了 %s 格式化字符串,避免了 Python 3.6+ 的 f-string,确保在 PTrade 可能使用的旧版本 Python 环境中也能运行。

Q&A

Q: 为什么在 before_trading_start 中获取成分股而不是 initialize
A: initialize 仅在策略开始时运行一次。指数成分股会随时间调整(通常半年调整一次),在 before_trading_start 中获取可以确保每天使用的是当天最新的成分股列表,避免回测时间较长时成分股数据失真。

Q: pe_ttmpe_dynamic 有什么区别?
A: pe_ttm 是滚动市盈率,基于过去12个月的净利润计算,数据是确定的;pe_dynamic 是动态市盈率,通常基于预测或当前季度年化计算,波动较大。对于价值选股策略,通常推荐使用 pe_ttm

Q: 如何避免每天频繁调仓?
A: 当前策略是每天检查并调仓。如果希望降低换手率,可以在 before_trading_start 中增加日期判断(例如只在月初或每周一运行筛选逻辑),或者使用 run_daily 设置定时任务来控制调仓频率。