🚀 AI 一键生成 ptrade 策略代码

如何在 PTrade 中构建一个结合 get_fundamentals 和 get_history 的混合选股策略?

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

问题描述

我想编写一个选股策略,它首先根据市盈率筛选出低估值的股票,然后从这些股票中再选出近期价格表现强势的,该如何实现?

解决方案

这是一个非常经典的“价值+动量”双因子选股策略。

策略逻辑分析

  1. 初选(价值因子):使用市盈率(PE-TTM)作为估值指标。通常认为低市盈率代表股票被低估,具有安全边际。我们将剔除亏损股(PE < 0),并按PE从小到大排序,选取排名靠前的一部分股票。
  2. 复选(动量因子):在低估值股票池中,计算过去一段时间(如20个交易日)的涨跌幅。选取涨幅最高的股票,这代表市场资金近期对该股比较认可,具有上涨惯性。
  3. 交易执行:卖出不在新目标列表中的持仓,买入新选出的股票,通常采用等权重买入。
  4. 调仓周期:为了避免过度交易产生高额手续费,通常设置为按周或按月调仓。

PTrade 策略代码实现

以下是完整的策略代码。该代码兼容 Python 3.5 环境,可以直接在 PTrade 回测或交易环境中运行。

def initialize(context):
    """
    策略初始化函数,只在策略启动时执行一次
    """
    # 设置基准指数:沪深300
    set_benchmark('000300.SS')
    
    # 设置回测期间的佣金和滑点(模拟真实交易成本)
    set_commission(commission_ratio=0.0003, min_commission=5.0, type="STOCK")
    set_slippage(slippage=0.002)
    
    # --- 策略参数设置 ---
    # 股票池范围:沪深300成分股
    g.index_scope = '000300.SS' 
    # 初选:按低PE选出的股票数量
    g.low_pe_count = 50
    # 最终持仓:从初选中再按动量选出的股票数量
    g.hold_count = 10
    # 动量计算周期(过去N天的涨跌幅)
    g.momentum_days = 20
    # 调仓频率(天),这里设置为每5个交易日调仓一次
    g.rebalance_days = 5
    # 计数器
    g.days_counter = 0

def before_trading_start(context, data):
    """
    盘前处理函数,每日开盘前运行
    """
    # 增加计数器
    g.days_counter += 1

def handle_data(context, data):
    """
    盘中处理函数,按分钟或日线频率运行
    """
    # 检查是否达到调仓周期
    if g.days_counter % g.rebalance_days != 0:
        return

    # --- 第一步:获取股票池 ---
    # 获取沪深300成分股
    check_stocks = get_index_stocks(g.index_scope)
    if not check_stocks:
        log.info("未获取到成分股信息")
        return

    # --- 第二步:根据市盈率(PE)筛选低估值股票 ---
    # 获取市盈率数据 (pe_ttm: 滚动市盈率)
    # 注意:get_fundamentals 查询的是财务数据
    q = get_fundamentals(check_stocks, 'valuation', ['pe_ttm'], date=None)
    
    if q is None or len(q) == 0:
        return

    # 过滤掉PE为负(亏损)的股票,且过滤掉PE异常大的数据
    # pandas筛选:pe_ttm > 0
    q = q[q['pe_ttm'] > 0]
    
    # 按照PE从小到大排序
    q = q.sort_values(by='pe_ttm', ascending=True)
    
    # 取出PE最低的前N只股票作为初选池
    if len(q) > g.low_pe_count:
        candidates = list(q.index[:g.low_pe_count])
    else:
        candidates = list(q.index)
    
    if not candidates:
        return

    # --- 第三步:根据近期涨跌幅(动量)筛选强势股 ---
    # 获取初选池股票过去N天的历史价格数据
    # count=g.momentum_days + 1 是为了计算 N 天前的收盘价和昨天的收盘价的涨幅
    price_data = get_history(g.momentum_days + 1, '1d', 'close', security_list=candidates, fq='pre')
    
    momentum_scores = {}
    
    for stock in candidates:
        # 获取该股票的收盘价序列
        # 注意:get_history返回的数据结构在不同版本可能略有不同,这里使用query确保兼容性
        # 或者直接通过列索引获取,如果candidates是列表,price_data通常是DataFrame(列为股票代码)或Panel
        # 在PTrade中,get_history多股查询返回DataFrame时,列名为股票代码
        
        try:
            # 尝试获取该股票的收盘价序列
            closes = price_data[stock]
            # 确保数据长度足够
            if len(closes) == g.momentum_days + 1:
                # 计算涨跌幅:(最新收盘价 - N天前收盘价) / N天前收盘价
                # iloc[-1]是最近一天,iloc[0]是N天前
                ret = (closes.iloc[-1] - closes.iloc[0]) / closes.iloc[0]
                momentum_scores[stock] = ret
        except:
            continue

    # 对动量得分进行排序(从大到小,选涨得最好的)
    # Python 3.5 写法
    sorted_stocks = sorted(momentum_scores.items(), key=lambda x: x[1], reverse=True)
    
    # 取出前K只股票作为最终买入目标
    buy_list = [x[0] for x in sorted_stocks[:g.hold_count]]
    
    log.info("今日选股结果: %s" % buy_list)

    # --- 第四步:执行交易 ---
    # 1. 卖出不在目标列表中的持仓
    current_positions = context.portfolio.positions
    for stock in list(current_positions.keys()):
        if stock not in buy_list:
            # 如果有持仓且不在买入名单中,清仓
            if current_positions[stock].amount > 0:
                order_target_value(stock, 0)
                log.info("卖出: %s" % stock)

    # 2. 买入目标列表中的股票
    if len(buy_list) > 0:
        # 等权重分配资金
        # 获取当前总资产(现金+持仓市值)
        total_value = context.portfolio.portfolio_value
        # 每只股票的目标市值
        target_value = total_value / len(buy_list)
        
        for stock in buy_list:
            order_target_value(stock, target_value)
            log.info("买入/调仓: %s, 目标市值: %f" % (stock, target_value))

代码关键点解析

  1. get_fundamentals:

    • 这是获取财务数据的核心函数。
    • valuation 表包含了估值数据。
    • pe_ttm (滚动市盈率) 比静态市盈率更能反映近期盈利能力。
    • 代码中增加了 q['pe_ttm'] > 0 的过滤条件,剔除了亏损企业,防止选入基本面极差的股票。
  2. get_history:

    • 用于获取历史行情数据来计算动量。
    • 参数 fq='pre' 表示使用前复权数据,计算收益率时必须使用复权价格,否则分红派息会导致价格跳空,影响动量计算准确性。
    • 计算公式为 (当前价格 - N天前价格) / N天前价格
  3. order_target_value:

    • 使用目标市值下单函数。
    • 相比于直接用 order (按股数下单),order_target_value 更适合定期调仓策略。它会自动计算需要买入或卖出的数量,使该股票的持仓市值达到指定金额(这里是总资产的 1/10)。
  4. 调仓控制:

    • 使用 g.days_counter 配合取模运算 % 来控制调仓频率。
    • 过于频繁的调仓(如每天)会导致手续费吞噬利润,对于基本面策略,通常周频或月频较为合适。

常见问题 Q&A

Q: 为什么选择沪深300作为股票池?
A: 沪深300成分股流动性好,财务数据相对规范,不容易出现无法成交或退市暴雷的情况,适合作为策略验证的基准。如果想追求更高收益,可以将 g.index_scope 改为 '000905.SS' (中证500) 或 '000852.SS' (中证1000)。

Q: 为什么要先选低估值再选动量,而不是反过来?
A: 这是一个典型的“安全边际下寻找进攻机会”的思路。先选低估值是为了防守,确保买入的股票不贵;再选动量是为了进攻,避免买入“低估值陷阱”(即长期不涨甚至阴跌的股票)。

Q: 如何查看策略运行结果?
A: 将此代码复制到 PTrade 的策略编辑框中,点击“回测”,设置好回测时间段(例如过去两年),即可看到收益曲线和详细交易记录。