问题描述
解决方案
这是一个基于基本面数据的选股策略。根据您的需求,我们需要使用 JoinQuant 的 get_fundamentals 函数来查询财务数据。
策略逻辑分析
- 选股条件:
- ROE (净资产收益率) > 15%:这通常代表公司运用自有资本的效率较高,盈利能力强。在聚宽的
indicator表中,字段为roe。 - 资产负债率 < 50%:代表公司财务结构相对稳健,杠杆风险较低。计算公式为
总负债 / 总资产,对应balance表中的total_liability和total_assets。
- ROE (净资产收益率) > 15%:这通常代表公司运用自有资本的效率较高,盈利能力强。在聚宽的
- 交易频率:
- 由于财务数据通常按季度更新,按日调仓没有必要且手续费高。我们设定为 按月调仓(例如每个月第一个交易日)。
- 资金管理:
- 为了避免买入过多股票导致资金过于分散,我们在满足条件的股票中,按 市值从大到小 排序,选取前 20 只进行等权重买入。
- 风控:
- 剔除 ST 股、停牌股和次新股(上市不满 60 天)。
策略代码实现
# -*- 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')
# 设定股票类每笔交易时的手续费
set_order_cost(OrderCost(close_tax=0.001, open_commission=0.0003, close_commission=0.0003, min_commission=5), type='stock')
# 设定最大持仓数量
g.stock_num = 20
# 设定每月第一个交易日进行调仓
run_monthly(rebalance, 1, time='09:30')
def rebalance(context):
"""
调仓主函数
"""
# 1. 选股
target_list = get_stock_list(context)
# 2. 交易
trade(context, target_list)
def get_stock_list(context):
"""
根据基本面数据筛选股票
"""
# 获取当前时间
date = context.previous_date
# 查询财务数据
# 筛选条件:ROE > 15 且 资产负债率 < 50% (0.5)
# 排序:按总市值降序排列,优先买大市值的龙头
q = query(
valuation.code,
valuation.market_cap
).filter(
indicator.roe > 15, # ROE大于15%
balance.total_liability / balance.total_assets < 0.5 # 资产负债率小于50%
).order_by(
valuation.market_cap.desc() # 按市值降序
)
# 获取查询结果
df = get_fundamentals(q, date=date)
if len(df) == 0:
return []
# 初步筛选出的股票列表
initial_list = list(df['code'])
# 进一步过滤:剔除ST、停牌、次新股
final_list = filter_paused_and_st_stocks(initial_list, date)
# 截取前 g.stock_num 只股票
return final_list[:g.stock_num]
def filter_paused_and_st_stocks(stock_list, date):
"""
过滤ST股、停牌股、上市时间不足60天的股票
"""
current_data = get_current_data()
filtered_list = []
for stock in stock_list:
# 过滤停牌和ST
if current_data[stock].paused or current_data[stock].is_st:
continue
# 过滤上市时间不足60天的股票
# 获取股票信息
security_info = get_security_info(stock)
if not security_info:
continue
# 计算上市天数
days_listed = (date - security_info.start_date).days
if days_listed < 60:
continue
filtered_list.append(stock)
return filtered_list
def trade(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)
log.info("卖出 %s" % stock)
# 如果没有目标股票,直接返回
if len(target_list) == 0:
return
# 2. 买入目标列表中的股票
# 将资金等分
position_count = len(target_list)
total_value = context.portfolio.total_value
# 预留一点现金防止手续费不够,乘以0.99
value_per_stock = total_value * 0.99 / position_count
for stock in target_list:
order_target_value(stock, value_per_stock)
log.info("买入/调整 %s 到目标市值" % stock)
常见问题 (Q&A)
Q: 为什么使用 run_monthly 而不是 run_daily?
A: 财务数据(如 ROE 和 资产负债表)是按季度发布的。每天运行筛选逻辑不仅不会获得新数据,反而会因为股价波动导致频繁调仓,增加不必要的手续费和滑点成本。按月调仓足以捕捉基本面的变化。
Q: 代码中的 indicator.roe > 15 是指 15% 吗?
A: 是的。在聚宽的数据库中,indicator.roe 的单位是百分比。所以 > 15 代表 ROE 大于 15%。
Q: 为什么要按市值排序?
A: 满足 ROE > 15% 且负债率低的公司可能很多。如果不进行排序限制数量,策略可能会买入几百只股票,导致单只股票资金过少(甚至买不够一手),或者包含很多流动性差的小盘股。按市值排序可以优先选择流动性较好的大盘蓝筹股。
Q: 如何查看回测结果?
A: 将上述代码复制到聚宽官网的“我的策略” -> “新建策略”中,点击“编译运行”即可进行回测。建议回测时间选择较长跨度(如 3-5 年)以验证策略在不同市场环境下的表现。