问题描述
解决方案
这是一个非常经典的**均值回归(Mean Reversion)**策略思路。在量化交易中,最常用的衡量“跌多了”或“涨多了”的技术指标是 RSI(相对强弱指标)。
策略逻辑
- 核心思想:价格总是围绕价值上下波动,涨多了会回调,跌多了会反弹。
- 选股范围:沪深300成分股(流动性好,避免小盘股的流动性陷阱)。
- 买入条件:当股票的 RSI 指标低于 30 时,认为处于“超卖”状态(跌多了),执行买入。
- 卖出条件:当股票的 RSI 指标高于 70 时,认为处于“超买”状态(涨多了),执行卖出。
- 仓位管理:设定最大持仓股票数量(例如5只),每只股票等权重分配资金。
以下是基于聚宽(JoinQuant)API 实现的完整策略代码:
# -*- coding: utf-8 -*-
import jqdata
import talib
import numpy as np
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')
# --- 策略参数设置 ---
# RSI计算的时间周期,通常为14天
g.rsi_period = 14
# RSI买入阈值(跌多了)
g.rsi_low = 30
# RSI卖出阈值(涨多了)
g.rsi_high = 70
# 最大持仓股票数量
g.max_holdings = 5
# 每天开盘时运行
run_daily(trade, 'every_bar')
def trade(context):
"""
交易逻辑函数
"""
# 1. 获取股票池:沪深300成分股
# 也可以换成 get_all_securities(['stock']).index 获取全A股,但回测速度会变慢
stock_pool = get_index_stocks('000300.XSHG')
# 2. 卖出逻辑:遍历当前持仓
for security in list(context.portfolio.positions.keys()):
# 获取持仓对象
position = context.portfolio.positions[security]
# 如果可用仓位为0,跳过
if position.closeable_amount == 0:
continue
# 获取计算RSI所需的历史收盘价数据
# 取 rsi_period + 20 天的数据以确保 talib 计算结果稳定
close_data = attribute_history(security, g.rsi_period + 20, '1d', ['close'])
# 如果数据不足(比如停牌或新股),跳过
if len(close_data) < g.rsi_period + 1:
continue
# 计算RSI
close_prices = close_data['close'].values
rsi_values = talib.RSI(close_prices, timeperiod=g.rsi_period)
current_rsi = rsi_values[-1]
# 如果 RSI > 70 (涨多了),卖出
if current_rsi > g.rsi_high:
order_target(security, 0)
log.info("卖出 %s, RSI: %.2f (涨多了)" % (security, current_rsi))
# 3. 买入逻辑
# 如果持仓数量已达上限,则不再买入
if len(context.portfolio.positions) >= g.max_holdings:
return
# 遍历股票池寻找买入机会
for security in stock_pool:
# 如果已经持仓,跳过
if security in context.portfolio.positions:
continue
# 过滤停牌和ST股票(可选,这里简单处理,attribute_history会自动处理停牌数据填充)
# 获取历史数据
close_data = attribute_history(security, g.rsi_period + 20, '1d', ['close'])
if len(close_data) < g.rsi_period + 1:
continue
# 计算RSI
close_prices = close_data['close'].values
# 处理可能存在的NaN值
if np.isnan(close_prices).any():
continue
rsi_values = talib.RSI(close_prices, timeperiod=g.rsi_period)
current_rsi = rsi_values[-1]
# 如果 RSI < 30 (跌多了),且持仓未满,买入
if current_rsi < g.rsi_low:
# 计算每只股票应该分配的资金:总资产 / 最大持仓数
# 注意:这里简单均分,实际资金可能因为卖出操作而变化
target_value = context.portfolio.total_value / g.max_holdings
# 检查可用资金是否足够
if context.portfolio.available_cash > target_value * 0.9: # 0.9是为了留点余地给手续费
order_target_value(security, target_value)
log.info("买入 %s, RSI: %.2f (跌多了)" % (security, current_rsi))
# 如果买入后达到持仓上限,停止循环
if len(context.portfolio.positions) >= g.max_holdings:
break
代码关键点解析
talib.RSI: 使用了talib库来计算 RSI 指标。这是金融领域标准的计算库,比自己写公式更准确、效率更高。attribute_history: 获取过去一段时间的收盘价。为了计算 14 日 RSI,我们需要获取比 14 天更多的数据(代码中取了g.rsi_period + 20),因为指标计算通常需要一定的预热数据来消除初始值的偏差。- 仓位控制:
g.max_holdings = 5: 限制最多持有5只股票,防止资金过于分散或满仓单一股票风险过大。order_target_value: 按照目标价值下单,系统会自动计算需要买入多少股(自动向下取整到100股的倍数)。
- 买卖顺序: 先执行卖出逻辑释放资金,再执行买入逻辑,这样可以提高资金利用率。
常见问题 (Q&A)
Q: 为什么选择 RSI 指标?
A: RSI(相对强弱指标)是衡量价格变动速度和幅度的经典动量指标。它在 0 到 100 之间波动,直观地反映了市场的超买(>70)和超卖(<30)状态,非常适合定义“涨多了”和“跌多了”。
Q: 这个策略适合什么市场环境?
A: 反转策略最适合震荡市。在单边的大牛市中,RSI可能长期高于70(导致过早卖出);在单边大熊市中,RSI可能长期低于30(导致过早抄底被套,即“接飞刀”)。
Q: 如何改进这个策略?
A:
- 增加止损:反转策略容易“抄在半山腰”,可以加入固定比例止损(如亏损10%强平)。
- 结合基本面:只在基本面良好(如PE较低、盈利增长)的股票池中做反转,避免买入即将退市的垃圾股。
- 动态阈值:在牛市中将买入阈值提高(如40),在熊市中将卖出阈值降低(如60)。
Q: 为什么回测时没有交易?
A: 可能是因为选定的时间段内,沪深300成分股波动较小,没有触发 RSI < 30 或 RSI > 70 的极端情况。你可以尝试调整阈值(例如改为 40/60)或者扩大股票池(例如使用中证500)。