问题描述
解决方案
编写基于市销率(Price-to-Sales Ratio, PS)的选股策略,核心逻辑是寻找营收能力强但市场估值相对较低的股票。PS = 总市值 / 主营业务收入。这种策略常用于评估尚未盈利但有营收增长的成长型公司,或者周期性行业。
以下是一个完整的聚宽(JoinQuant)策略代码。
策略逻辑说明
- 股票池:沪深300指数成分股(确保流动性)。
- 选股因子:市销率(PS_TTM)。
- 过滤条件:
- 剔除停牌股票。
- 剔除ST、*ST股票。
- 剔除涨跌停股票(可选,本策略暂不剔除以保持简洁)。
- 剔除 PS 为负或无效值的股票。
- 排序逻辑:按 PS 值从小到大排序(选取低估值)。
- 持仓数量:持有 PS 最低的 10 只股票。
- 调仓频率:按月调仓(每月第一个交易日)。
- 资金分配:等权重买入。
策略代码
# -*- coding: utf-8 -*-
from jqdata import *
def initialize(context):
"""
初始化函数,设定基准、手续费、滑点等
"""
# 设定沪深300作为基准
set_benchmark('000300.XSHG')
# 开开启真实价格模式(动态复权)
set_option('use_real_price', True)
# 过滤掉order系列API产生的比error级别低的log
log.set_level('order', 'error')
# 设定股票交易手续费:买入万分之三,卖出万分之三加千分之一印花税,最低5元
set_order_cost(OrderCost(close_tax=0.001, open_commission=0.0003, close_commission=0.0003, min_commission=5), type='stock')
# 设定全局变量:持仓数量
g.stock_num = 10
# 设定按月运行,每月第一个交易日开盘后运行
run_monthly(weekly_selection, monthday=1, time='09:30')
def weekly_selection(context):
"""
选股与调仓主逻辑
"""
# 1. 获取股票池:沪深300
target_index = '000300.XSHG'
initial_list = get_index_stocks(target_index)
# 2. 过滤掉 ST 和 停牌 的股票
current_data = get_current_data()
valid_list = []
for code in initial_list:
# 剔除停牌 和 ST
if not current_data[code].paused and not current_data[code].is_st:
valid_list.append(code)
# 3. 查询财务数据:市销率 (PS)
# valuation.ps_ratio: 市销率(TTM)
q = query(
valuation.code,
valuation.ps_ratio
).filter(
valuation.code.in_(valid_list),
valuation.ps_ratio > 0 # 剔除无效或负值
).order_by(
valuation.ps_ratio.asc() # 按 PS 从小到大排序
).limit(g.stock_num) # 取前 N 只
df = get_fundamentals(q)
# 获取最终的目标股票列表
target_list = list(df['code'])
# 4. 执行调仓
rebalance(context, target_list)
def rebalance(context, target_list):
"""
调仓执行函数
"""
# 获取当前持仓
current_holdings = list(context.portfolio.positions.keys())
# 1. 卖出不在目标列表中的股票
for stock in current_holdings:
if stock not in target_list:
order_target_value(stock, 0)
# 2. 买入目标列表中的股票
if len(target_list) > 0:
# 等权重分配资金
# 注意:这里简单处理,用总资产除以目标数量。实际交易中可能需要预留少量现金防止手续费不够
value_per_stock = context.portfolio.total_value / len(target_list)
for stock in target_list:
order_target_value(stock, value_per_stock)
log.info("本月调仓完成,持有股票数量: %d" % len(target_list))
log.info("持有列表: %s" % target_list)
代码关键点解析
-
get_fundamentals与query:- 这是聚宽获取财务数据的核心函数。
valuation.ps_ratio:代表市销率(TTM,滚动12个月)。.filter(valuation.ps_ratio > 0):排除掉数据异常或营收为负的情况。.order_by(valuation.ps_ratio.asc()):核心策略逻辑,即选择 PS 值最小的股票。
-
get_current_data:- 用于获取当天的实时状态,主要用于过滤停牌(
paused)和 ST 股票(is_st),防止买入无法交易或风险过大的标的。
- 用于获取当天的实时状态,主要用于过滤停牌(
-
order_target_value:- 这是最常用的下单函数之一,它会自动计算需要买入或卖出的股数,使该股票的持仓市值达到指定金额。如果目标金额为 0,则表示清仓。
-
run_monthly:- 定时运行函数,这里设置为每月第一个交易日进行选股和调仓。低频调仓有助于减少手续费对收益的侵蚀。
常见问题 (Q&A)
Q1: 为什么要用市销率(PS)而不是市盈率(PE)?
A: 市盈率依赖于净利润,而很多高成长公司(如科技股)或者周期性低谷的公司可能暂时亏损(PE为负或极高),但营收依然在增长。市销率相对于净利润更稳定,不易被会计手段操纵,适合评估这类公司。
Q2: 代码中的 valuation.ps_ratio 是静态的还是动态的?
A: 在聚宽的 valuation 表中,ps_ratio 默认通常是 TTM(Trailing Twelve Months,滚动12个月),即使用最近4个季度的财务数据计算,这样能更及时地反映公司当前的估值状态。
Q3: 如何修改持仓数量?
A: 修改 initialize 函数中的 g.stock_num = 10,将 10 改为您想要的数量即可。
Q4: 如何更换股票池?
A: 修改 weekly_selection 函数中的 target_index。例如,改为 '000905.XSHG' 即可基于中证500指数进行选股。