🚀 AI 一键生成 ptrade 策略代码

PTrade 量化选股:如何基于销售净利率 (net_profit_ratio) 构建投资组合?

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

问题描述

如何编写一个基于销售净利率(Net Profit Margin)的选股策略?

解决方案

这是一个基于**销售净利率(Net Profit Margin)**的选股策略设计。

策略逻辑与思路

  1. 核心指标:销售净利率(net_profit_ratio)。该指标反映了企业每单位销售收入中包含的净利润,数值越高,说明企业在控制成本、获取利润方面的能力越强。
  2. 股票池:为了保证流动性和基本面质量,本策略选取沪深300指数成分股作为基础股票池。
  3. 选股逻辑
    • 每月第一个交易日进行调仓。
    • 获取股票池中所有股票的最新财务数据。
    • 按“销售净利率”从高到低排序。
    • 选取排名前 10 的股票作为持仓目标。
  4. 交易逻辑
    • 卖出:如果当前持仓的股票不在新的目标列表中,则清仓。
    • 买入:对目标列表中的股票进行等权重买入。

PTrade 策略代码实现

import pandas as pd
import numpy as np

def initialize(context):
    """
    初始化函数,设置策略参数
    """
    # 设置基准为沪深300
    set_benchmark('000300.SS')
    # 设置股票池基准指数(沪深300)
    g.index_security = '000300.SS'
    # 设置最大持仓数量
    g.stock_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")
    # 设置滑点(千二)
    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:
        g.rebalance_flag = True
        g.last_month = current_month
        log.info("检测到月份更替,今日将执行调仓")
    else:
        g.rebalance_flag = False
        return

    if g.rebalance_flag:
        # 1. 获取沪深300成分股
        stocks = get_index_stocks(g.index_security)
        if not stocks:
            log.warning("未获取到指数成分股")
            g.target_stocks = []
            return

        # 2. 获取财务数据:销售净利率 (net_profit_ratio)
        # 表名: profit_ability (盈利能力)
        # 字段: net_profit_ratio (销售净利率)
        df = get_fundamentals(
            stocks, 
            table='profit_ability', 
            fields='net_profit_ratio', 
            date=context.blotter.current_dt.strftime("%Y%m%d")
        )
        
        if df is None or df.empty:
            log.warning("未获取到财务数据")
            g.target_stocks = []
            return

        # 3. 数据处理与选股
        # 剔除空值
        df = df.dropna()
        # 按销售净利率降序排列(从大到小)
        df = df.sort_values(by='net_profit_ratio', ascending=False)
        
        # 取前N只股票
        g.target_stocks = df.index[:g.stock_num].tolist()
        
        log.info("今日目标持仓股票: %s" % g.target_stocks)

def handle_data(context, data):
    """
    盘中处理函数:执行交易逻辑
    """
    # 如果不需要调仓,直接返回
    if not g.rebalance_flag:
        return

    # 获取当前持仓
    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 == 0:
                log.info("%s 停牌,无法卖出" % stock)
                continue
            
            order_target(stock, 0)
            log.info("卖出非目标股票: %s" % stock)

    # 2. 买入逻辑:等权重买入目标股票
    target_len = len(g.target_stocks)
    if target_len > 0:
        # 计算每只股票应分配的资金(总资产 / 目标数量)
        # 注意:这里使用总资产 portfolio_value 来计算,实现复利投资
        value_per_stock = context.portfolio.portfolio_value / target_len
        
        for stock in g.target_stocks:
            # 检查股票是否停牌
            if data[stock].is_open == 0:
                log.info("%s 停牌,无法买入" % stock)
                continue
            
            # 下单调整至目标金额
            order_target_value(stock, value_per_stock)
            log.info("调整 %s 持仓至金额: %.2f" % (stock, value_per_stock))
    
    # 交易完成后,重置调仓标志,避免重复交易
    g.rebalance_flag = False

代码详解

  1. initialize (初始化):

    • 设定了基准指数为沪深300,确保选股范围是基本面较好的蓝筹股。
    • 设定持仓数量 g.stock_num = 10,即分散投资于10只股票。
    • 设置了手续费和滑点,使回测结果更接近真实交易环境。
  2. before_trading_start (盘前选股):

    • 换月判断:通过比较 context.blotter.current_dt.monthg.last_month 来判断是否进入了新的月份。这是一种标准的月度调仓写法。
    • 数据获取:使用 get_fundamentals 函数查询 profit_ability(盈利能力表)中的 net_profit_ratio(销售净利率)。
    • 排序筛选:使用 Pandas 的 sort_values 方法对数据进行降序排列,选取数值最大的前10只股票。
  3. handle_data (盘中交易):

    • 卖出:遍历当前持仓,如果持仓股票不在新的 g.target_stocks 列表中,则调用 order_target(stock, 0) 进行清仓。
    • 买入:计算每只股票的理论持仓市值(总资产 / 10),使用 order_target_value 自动计算股数并下单。
    • 停牌处理:增加了 data[stock].is_open 的判断,防止对停牌股票下单导致报错或逻辑错误。

常见问题 (Q&A)

Q: 为什么选择月度调仓而不是每天调仓?
A: 销售净利率属于财务基本面数据,通常随季度报表(季报、半年报、年报)更新,数据变化频率较低。每天调仓不仅没有必要,还会产生大量不必要的手续费。月度调仓足以捕捉基本面变化。

Q: get_fundamentals 函数中的 date 参数有什么作用?
A: 在回测模式下,如果不传 date,默认获取回测当日已发布的最新数据(避免未来函数)。在代码中显式传入 context.blotter.current_dt 是为了确保逻辑清晰,获取的是截止到调仓日可见的最新财报数据。

Q: 如何查看策略的表现?
A: 将上述代码复制到 PTrade 的回测环境中,设置回测时间段(例如过去3年),点击“开始回测”。系统会生成收益率曲线、最大回撤、夏普比率等指标供您分析。

Q: 可以结合其他指标吗?
A: 可以。例如,您可以同时获取 net_profit_ratio (销售净利率) 和 roe (净资产收益率),将两者加权打分,或者先按一个指标筛选,再按另一个指标排序。只需在 get_fundamentalsfields 参数中添加相应字段即可。