问题描述
解决方案
编写一个结合**财务状况(基本面)与近期股价表现(技术面)**的选股策略,通常被称为“基本面+技术面”双驱动策略。
策略设计思路
为了实现这一目标,我们可以设计如下逻辑:
- 股票池范围:以沪深300指数成分股为基础,确保流动性和公司规模。
- 财务筛选(基本面):
- 盈利能力:净资产收益率(ROE) > 8%,筛选赚钱能力强的公司。
- 估值安全:市盈率(PE)在 0 到 50 之间,剔除亏损股和极度高估的股票。
- 排序:按 ROE 从高到低排序,初选前 50 只。
- 股价表现筛选(技术面):
- 趋势判断:当前股价 > 60日均线(MA60),确保处于上升或多头趋势中。
- 交易逻辑:
- 调仓频率:按月调仓。
- 买入:买入同时满足上述条件的股票,最多持有 10 只,等权重分配。
- 卖出:不在新一期选股列表中的股票全部卖出。
策略代码实现
以下是基于聚宽(JoinQuant)API 的完整策略代码:
# -*- 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 = 10 # 最大持仓数量
# 设定按月运行,每月第一个交易日进行调仓
run_monthly(trade, monthday=1, time='09:30')
def trade(context):
"""
交易函数,包含选股和调仓逻辑
"""
# 1. 获取初始股票池(沪深300)
initial_pool = get_index_stocks('000300.XSHG')
# 2. 基本面筛选(财务状况)
# 查询条件:PE在0-50之间,ROE大于8%
# 排序:按ROE从高到低排序
q = query(
valuation.code,
valuation.pe_ratio,
indicator.roe
).filter(
valuation.code.in_(initial_pool),
valuation.pe_ratio > 0,
valuation.pe_ratio < 50,
indicator.roe > 8
).order_by(
indicator.roe.desc()
).limit(50) # 初选前50只
df_fund = get_fundamentals(q, date=context.previous_date)
if len(df_fund) == 0:
return
fundamental_list = list(df_fund['code'])
# 3. 技术面筛选(近期股价表现)
# 逻辑:当前价格站上60日均线
final_buy_list = []
for stock in fundamental_list:
# 获取过去60天的收盘价数据
# 注意:为了计算MA60,我们需要取60天的数据
h_data = attribute_history(stock, 60, '1d', ['close'], skip_paused=True)
# 如果数据不足60天(如新股),跳过
if len(h_data) < 60:
continue
close_price = h_data['close'][-1] # 当前收盘价(前一分钟/前一天)
ma60 = h_data['close'].mean() # 60日均线
# 如果股价在60日均线之上,加入最终买入列表
if close_price > ma60:
final_buy_list.append(stock)
# 如果选够了目标数量,停止筛选
if len(final_buy_list) >= g.stock_num:
break
# 4. 执行调仓操作
# 获取当前持仓
current_holdings = list(context.portfolio.positions.keys())
# 卖出不在目标列表中的股票
for stock in current_holdings:
if stock not in final_buy_list:
order_target_value(stock, 0)
log.info("卖出 %s" % stock)
# 买入目标列表中的股票
if len(final_buy_list) > 0:
# 等权重分配资金
# 注意:这里简单处理,用总资产除以目标数量。实际交易中可能需要考虑保留现金等。
value_per_stock = context.portfolio.total_value / g.stock_num
for stock in final_buy_list:
order_target_value(stock, value_per_stock)
log.info("买入/调整 %s" % stock)
def after_code_changed(context):
"""
模拟交易更换代码后运行函数
"""
# 可以在这里重置参数或更新设置
pass
代码逻辑详解
-
initialize(初始化):- 设置了基准为沪深300指数,以便对比策略表现。
- 设置了真实价格模式(动态复权),这是回测准确性的关键。
- 使用
run_monthly设定策略为月度调仓,避免频繁交易产生过高手续费。
-
trade(核心逻辑):- 基本面筛选:使用
get_fundamentals函数查询财务数据。valuation.pe_ratio:市盈率,用于过滤高估值泡沫股。indicator.roe:净资产收益率,衡量公司利用股东资本赚钱的效率,是巴菲特最看重的指标之一。
- 技术面筛选:使用
attribute_history获取历史行情。- 计算
ma60(60日均线),这是股市的“生命线”。股价在均线之上通常意味着中期趋势向好。
- 计算
- 资金管理:采用简单的等权重模型,将资金平均分配给选出的股票。
- 基本面筛选:使用
-
风险控制:
- 通过限制
g.stock_num = 10,实现分散投资,避免单只股票暴雷导致账户重创。 - 通过 PE 和 ROE 的双重过滤,避开了垃圾股和过热股。
- 通过限制
常见问题 Q&A
Q: 为什么要结合基本面和技术面?
A: 仅看基本面(财务)容易买入“价值陷阱”,即公司很好但股价一直不涨甚至阴跌;仅看技术面(股价)容易买入“垃圾概念股”,一旦炒作结束会面临暴跌。结合两者可以在公司质地优良的基础上,选择市场认可(趋势向上)的时机介入。
Q: 如何调整策略的激进程度?
A:
- 更稳健:可以降低 PE 的上限(如改为 0-30),提高 ROE 的下限(如 >15%),或者选择更大的均线周期(如 MA120)。
- 更激进:可以放宽 PE 限制,加入成长因子(如净利润增长率 > 30%),或者使用更短期的均线(如 MA20)来捕捉短期爆发力。
Q: 策略中的 order_target_value 是什么意思?
A: 这是一个非常实用的下单函数。它的意思是“将某只股票的持仓调整到指定金额”。如果当前没持仓,它会买入;如果持仓不足,它会补仓;如果持仓过多,它会卖出部分。这自动帮我们完成了调仓和再平衡的操作。