🚀 AI 一键生成 ptrade 策略代码

PTrade 价值投资策略:如何利用 get_fundamentals API 实现自动化选股与交易?

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

问题描述

请帮我编写一个基于低市盈率和低市净率的价值投资选股策略

解决方案

这是一个基于PTrade平台的低市盈率(Low PE)与低市净率(Low PB)双低价值投资策略

策略逻辑说明

  1. 选股范围:沪深300指数成分股(000300.SS),确保流动性和基本面相对稳定。
  2. 选股指标
    • PE (TTM):滚动市盈率,衡量盈利能力与股价的关系。
    • PB:市净率,衡量净资产与股价的关系。
  3. 筛选规则
    • 剔除停牌、ST、退市股票。
    • 剔除PE为负(亏损)的股票。
    • 双重排序:首先选取PE最低的前50只股票,然后在这些股票中选取PB最低的10只。这是一种经典的“双低”策略,旨在寻找盈利能力尚可且资产价格被低估的标的。
  4. 调仓频率月度调仓。每个月第一个交易日进行选股和换仓。
  5. 资金分配:等权重买入。

策略代码

import pandas as pd
import numpy as np

def initialize(context):
    """
    初始化函数,设置策略参数和全局变量
    """
    # 设置股票池基准:沪深300
    g.index_security = '000300.SS'
    # 设置最大持仓数量
    g.target_num = 10
    # 记录上一个交易日的月份,用于判断是否换月
    g.last_month = 0
    # 换仓标志位
    g.rebalance_flag = False
    # 目标持仓列表
    g.target_stocks = []
    
    # 设置手续费:股票买卖万分之三,最低5元
    set_commission(commission_ratio=0.0003, min_commission=5.0, type="STOCK")
    # 设置滑点:0.2%
    set_slippage(slippage=0.002)

def before_trading_start(context, data):
    """
    盘前处理函数,每日开盘前运行
    主要用于判断调仓日期和生成目标股票池
    """
    # 获取当前回测/交易日期的月份
    current_month = context.blotter.current_dt.month
    
    # 判断是否换月(即每个月第一个交易日进行调仓)
    if g.last_month != current_month:
        g.rebalance_flag = True
        g.last_month = current_month
    else:
        g.rebalance_flag = False
        
    # 如果是调仓日,执行选股逻辑
    if g.rebalance_flag:
        # 1. 获取沪深300成分股
        stocks = get_index_stocks(g.index_security)
        
        # 2. 过滤掉ST、停牌、退市的股票
        # filter_stock_by_status 默认过滤 ST, HALT, DELISTING
        stocks = filter_stock_by_status(stocks)
        
        if len(stocks) == 0:
            log.warning("股票池为空,跳过选股")
            g.target_stocks = []
            return

        # 3. 获取基本面数据:PE(TTM) 和 PB
        # pe_ttm: 市盈率TTM, pb: 市净率
        df = get_fundamentals(stocks, 'valuation', ['pe_ttm', 'pb'], date=context.blotter.current_dt)
        
        if df is None or df.empty:
            log.warning("未获取到财务数据")
            g.target_stocks = []
            return
            
        # 4. 数据清洗与筛选
        # 剔除PE为负(亏损)或为0的股票
        df = df[df['pe_ttm'] > 0]
        
        # 5. 双重排序逻辑
        # 第一步:按PE从小到大排序,取前50名(初选低估值)
        df = df.sort_values(by='pe_ttm', ascending=True)
        df_temp = df.head(50)
        
        # 第二步:在初选结果中,按PB从小到大排序,取前 g.target_num 名(优中选优)
        df_final = df_temp.sort_values(by='pb', ascending=True).head(g.target_num)
        
        # 更新目标持仓列表
        g.target_stocks = df_final.index.tolist()
        
        log.info("本月目标持仓股票: %s" % g.target_stocks)

def handle_data(context, data):
    """
    盘中处理函数,主要用于执行交易
    """
    # 仅在触发换仓标志时执行交易
    if g.rebalance_flag:
        # 1. 卖出不在目标池中的股票
        # 获取当前所有持仓
        current_positions = list(context.portfolio.positions.keys())
        
        for stock in current_positions:
            # 如果当前持仓股票不在新的目标列表中,且当前可卖(停牌股无法卖出,需注意)
            if stock not in g.target_stocks:
                # 检查是否停牌,非停牌才卖出
                if data[stock].is_open:
                    order_target(stock, 0)
                    log.info("卖出非目标股票: %s" % stock)
        
        # 2. 买入目标池中的股票
        target_len = len(g.target_stocks)
        if target_len > 0:
            # 等权重分配资金
            # 注意:这里简单使用总资产/目标数量,实际交易中可能需要预留少量现金防止手续费不足
            value_per_stock = context.portfolio.portfolio_value / target_len
            
            for stock in g.target_stocks:
                # 检查是否停牌,非停牌才买入
                if data[stock].is_open:
                    order_target_value(stock, value_per_stock)
                    # log.info("买入/调整股票: %s, 目标金额: %f" % (stock, value_per_stock))
        
        # 交易完成后重置标志位,避免当日重复交易
        g.rebalance_flag = False

代码关键点解析

  1. get_index_stocks 的位置

    • PTrade建议在 before_trading_start 中调用此函数获取成分股,而不是在 initialize 中,因为指数成分股会随时间变化。
  2. filter_stock_by_status

    • 这是PTrade特有的便捷函数,用于一键过滤掉ST(风险警示)、HALT(停牌)、DELISTING(退市)的股票,防止买入无法交易或高风险的标的。
  3. 财务数据获取 (get_fundamentals)

    • 使用了 valuation 表中的 pe_ttm (滚动市盈率) 和 pb (市净率)。
    • pe_ttm > 0 的过滤条件非常重要,因为负的市盈率代表公司亏损,不符合价值投资逻辑。
  4. 双重排序逻辑

    • 策略没有简单地将PE和PB相加,而是采用了漏斗筛选法:先选PE最低的一批,再从中选PB最低的。这能有效避免选出“PE极低但资产质量极差”或者“PB极低但盈利能力极差”的股票,平衡了盈利与净资产的估值。
  5. Python 3.5 兼容性

    • 代码中使用了 %s 格式化字符串,避免了 Python 3.6+ 才有的 f-string 语法,确保在旧版本 Python 环境中也能运行。

常见问题 (Q&A)

Q: 为什么选择沪深300而不是全市场选股?
A: 全市场选股容易选中流动性差的小盘股或基本面有雷的股票。对于价值策略,沪深300成分股通常是行业龙头,财务数据相对规范,流动性好,更适合作为价值投资的基础池。

Q: 如何调整调仓频率?
A: 当前代码通过 if g.last_month != current_month: 实现月度调仓。如果想改为季度调仓,可以判断 current_month % 3 == 0。如果想改为每周调仓,可以使用 context.blotter.current_dt.weekday() 判断是否为周一。

Q: 为什么使用 order_target_value
A: order_target_value 会自动计算需要买卖的股数,使该股票的持仓市值达到指定金额。这比计算股数后使用 order 更方便,能自动处理加仓和减仓操作。