问题描述
解决方案
这是一个基于**销售净利率(Net Profit Margin)**的选股策略设计。
策略逻辑与思路
- 核心指标:销售净利率(
net_profit_ratio)。该指标反映了企业每单位销售收入中包含的净利润,数值越高,说明企业在控制成本、获取利润方面的能力越强。 - 股票池:为了保证流动性和基本面质量,本策略选取沪深300指数成分股作为基础股票池。
- 选股逻辑:
- 每月第一个交易日进行调仓。
- 获取股票池中所有股票的最新财务数据。
- 按“销售净利率”从高到低排序。
- 选取排名前 10 的股票作为持仓目标。
- 交易逻辑:
- 卖出:如果当前持仓的股票不在新的目标列表中,则清仓。
- 买入:对目标列表中的股票进行等权重买入。
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
代码详解
-
initialize(初始化):- 设定了基准指数为沪深300,确保选股范围是基本面较好的蓝筹股。
- 设定持仓数量
g.stock_num = 10,即分散投资于10只股票。 - 设置了手续费和滑点,使回测结果更接近真实交易环境。
-
before_trading_start(盘前选股):- 换月判断:通过比较
context.blotter.current_dt.month和g.last_month来判断是否进入了新的月份。这是一种标准的月度调仓写法。 - 数据获取:使用
get_fundamentals函数查询profit_ability(盈利能力表)中的net_profit_ratio(销售净利率)。 - 排序筛选:使用 Pandas 的
sort_values方法对数据进行降序排列,选取数值最大的前10只股票。
- 换月判断:通过比较
-
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_fundamentals 的 fields 参数中添加相应字段即可。