🚀 AI 一键生成 ptrade 策略代码

PTrade 平台如何利用销售毛利率 (gross_income_ratio) 进行选股?

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

问题描述

请写一个基于销售毛利率选股的策略

解决方案

这是一个基于**销售毛利率(Gross Profit Margin)**进行选股的量化策略。

策略逻辑说明

  1. 核心指标:销售毛利率 (gross_income_ratio)。该指标反映了企业产品在扣除直接成本后的获利能力。通常毛利率越高,说明企业在产业链中话语权越强,或者产品具有较强的竞争壁垒。
  2. 股票池:沪深300指数成分股(000300.SS),保证流动性和基本面质量。
  3. 调仓频率:月度调仓。每个月的第一天进行检测,如果月份发生变化,则进行换仓。
  4. 选股逻辑
    • 获取沪深300成分股。
    • 剔除ST股、停牌股和退市股。
    • 查询最新的销售毛利率数据。
    • 按照毛利率从高到低排序。
    • 选取排名前 N 只(例如前20只)股票作为持仓目标。
  5. 交易执行
    • 卖出不在目标持仓列表中的股票。
    • 对目标列表中的股票进行等权重买入。

策略代码

# 导入必要的库
import pandas as pd

def initialize(context):
    """
    初始化函数,设置策略参数
    """
    # 设定持仓股票数量
    g.stock_num = 20
    
    # 记录上一次调仓的月份,用于判断是否换月
    g.last_month = 0
    
    # 是否需要调仓的标记
    g.rebalance_flag = False
    
    # 目标持仓列表
    g.target_stocks = []
    
    # 设置基准为沪深300
    set_benchmark('000300.SS')
    
    # 开启滑点设置(回测常用配置)
    set_slippage(slippage=0.002)
    
    # 打印策略启动日志
    log.info("策略初始化完成:基于销售毛利率选股策略")

def before_trading_start(context, data):
    """
    盘前处理函数:每日开盘前运行
    用于判断是否换月,并获取当月目标股票池
    """
    # 获取当前日期的月份
    current_month = context.blotter.current_dt.month
    
    # 判断是否换月(当前月份与上一次记录的月份不同)
    if current_month != g.last_month:
        log.info("检测到月份变化,准备进行调仓选股...")
        g.rebalance_flag = True
        g.last_month = current_month
        
        # 1. 获取沪深300成分股
        # 注意:get_index_stocks 建议在 before_trading_start 中调用
        universe = get_index_stocks('000300.SS')
        
        # 2. 过滤掉 ST、停牌、退市的股票
        # filter_stock_by_status 返回的是剔除后的列表
        active_stocks = filter_stock_by_status(universe, filter_type=["ST", "HALT", "DELISTING"])
        
        if len(active_stocks) == 0:
            log.warning("当前无有效股票可选")
            g.target_stocks = []
            return

        # 3. 获取基本面数据:销售毛利率 (gross_income_ratio)
        # 表名: profit_ability (盈利能力表)
        # 字段: gross_income_ratio
        df = get_fundamentals(
            active_stocks, 
            'profit_ability', 
            'gross_income_ratio',
            date=context.blotter.current_dt.strftime("%Y%m%d")
        )
        
        # 4. 数据处理与选股
        if df is not None and not df.empty:
            # 去除空值
            df = df.dropna()
            
            # 按照销售毛利率降序排列 (ascending=False)
            df = df.sort_values(by='gross_income_ratio', ascending=False)
            
            # 取前 N 只股票
            g.target_stocks = df.index[:g.stock_num].tolist()
            
            log.info("本月目标持仓股票: %s" % g.target_stocks)
        else:
            g.target_stocks = []
            log.warning("未获取到基本面数据")
            
    else:
        # 如果不是新的一月,不进行调仓
        g.rebalance_flag = False

def handle_data(context, data):
    """
    盘中运行函数:执行交易逻辑
    """
    # 仅在标记为需要调仓时执行
    if g.rebalance_flag:
        
        # 获取当前持仓
        current_positions = list(context.portfolio.positions.keys())
        
        # 1. 卖出逻辑:卖出不在目标列表中的股票
        for stock in current_positions:
            if stock not in g.target_stocks:
                # 检查股票是否停牌,未停牌才卖出
                if data[stock].is_open:
                    order_target_value(stock, 0)
                    log.info("卖出股票: %s" % stock)
        
        # 如果目标列表为空,则不执行买入
        if not g.target_stocks:
            g.rebalance_flag = False
            return
            
        # 2. 买入逻辑:等权重买入目标股票
        # 计算每只股票的目标持仓金额
        # 使用总资产价值(portfolio_value)来计算,保持满仓运行
        total_value = context.portfolio.portfolio_value
        if len(g.target_stocks) > 0:
            target_value_per_stock = total_value / len(g.target_stocks)
            
            for stock in g.target_stocks:
                # 检查股票是否停牌,未停牌才买入
                if data[stock].is_open:
                    order_target_value(stock, target_value_per_stock)
                    # log.info("调整股票 %s 持仓至金额: %.2f" % (stock, target_value_per_stock))
        
        # 调仓完成后,重置标记,避免当日重复执行(虽然handle_data在日线回测只运行一次,但在分钟回测需防止重复)
        g.rebalance_flag = False

策略Q&A

Q1: 为什么选择在 before_trading_start 中获取数据?
A1: get_fundamentalsget_index_stocks 是数据量较大的操作。在盘前阶段(before_trading_start)处理这些数据可以避免干扰盘中交易逻辑,且符合PTrade的API最佳实践。

Q2: gross_income_ratio 字段在哪里可以查到?
A2: 该字段位于财务数据中的 盈利能力表 (profit_ability)。在PTrade的API文档附录中可以找到对应的字段说明。

Q3: 为什么使用 order_target_value 而不是 order
A3: order_target_value(按目标价值下单)非常适合定期调仓策略。它会自动计算需要买入或卖出的数量,使该股票的持仓市值达到指定金额。如果已经持有该股票且上涨了,它会自动卖出多余部分;如果下跌了,会自动补仓,从而实现“再平衡”。

Q4: 代码中的 filter_stock_by_status 有什么作用?
A4: 这是一个非常重要的风控步骤。它会自动剔除当前处于ST状态、停牌状态或已退市的股票,防止策略买入无法交易或风险极大的标的。

Q5: 如何调整选股范围或数量?
A5:

  • 修改选股范围:更改 get_index_stocks('000300.SS') 中的指数代码(例如改为 000905.SS 即中证500)。
  • 修改持仓数量:更改 initialize 函数中的 g.stock_num 变量。