问题描述
解决方案
这是一个基于**销售毛利率(Gross Profit Margin)**进行选股的量化策略。
策略逻辑说明
- 核心指标:销售毛利率 (
gross_income_ratio)。该指标反映了企业产品在扣除直接成本后的获利能力。通常毛利率越高,说明企业在产业链中话语权越强,或者产品具有较强的竞争壁垒。 - 股票池:沪深300指数成分股(
000300.SS),保证流动性和基本面质量。 - 调仓频率:月度调仓。每个月的第一天进行检测,如果月份发生变化,则进行换仓。
- 选股逻辑:
- 获取沪深300成分股。
- 剔除ST股、停牌股和退市股。
- 查询最新的销售毛利率数据。
- 按照毛利率从高到低排序。
- 选取排名前
N只(例如前20只)股票作为持仓目标。
- 交易执行:
- 卖出不在目标持仓列表中的股票。
- 对目标列表中的股票进行等权重买入。
策略代码
# 导入必要的库
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_fundamentals 和 get_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变量。