问题描述
解决方案
这是一个非常经典的量化交易思路。要实现一个“自适应”策略,核心在于市场状态的识别(Market Regime Classification)。
我们需要一个指标来判断当前是“趋势市”还是“震荡市”,然后根据判断结果动态切换交易逻辑。
策略设计思路
-
市场状态识别(核心):
- 我们将使用 考夫曼效率系数 (Kaufman Efficiency Ratio, ER) 来衡量趋势强度。
- ER计算公式:
ER = 价格总位移 / 价格总路程。 - 逻辑:ER值在0到1之间。ER越接近1,表示趋势越强(价格走直线);ER越接近0,表示震荡越剧烈(价格上下波动但位移很小)。
- 阈值:设定一个阈值(例如 0.3),大于该值视为趋势,小于该值视为震荡。
-
趋势策略(当 ER > 阈值):
- 采用 双均线策略。
- 逻辑:短期均线 > 长期均线 -> 买入;短期均线 < 长期均线 -> 卖出。
-
震荡/反转策略(当 ER <= 阈值):
- 采用 RSI均值回归策略。
- 逻辑:RSI < 30 (超卖) -> 买入;RSI > 70 (超买) -> 卖出。
PTrade 策略代码实现
以下是完整的策略代码,您可以直接复制到 PTrade 的回测环境中运行。
import numpy as np
import pandas as pd
def initialize(context):
"""
初始化函数,设置参数和股票池
"""
# 1. 设置要操作的标的(这里以沪深300ETF为例,也可以换成具体股票)
g.security = '510300.SS'
set_universe(g.security)
# 2. 市场状态识别参数 (效率系数 ER)
g.er_period = 20 # 计算ER的时间窗口
g.er_threshold = 0.25 # ER阈值,大于此值判断为趋势,小于为震荡
# 3. 趋势策略参数 (双均线)
g.ma_short_len = 10 # 短期均线
g.ma_long_len = 30 # 长期均线
# 4. 震荡策略参数 (RSI)
g.rsi_period = 14 # RSI周期
g.rsi_low = 30 # 超卖阈值(买入)
g.rsi_high = 70 # 超买阈值(卖出)
# 设置手续费(可选,模拟真实交易成本)
set_commission(commission_ratio=0.0003, min_commission=5.0, type="ETF")
def get_efficiency_ratio(close_prices):
"""
计算考夫曼效率系数 (Efficiency Ratio)
ER = |总位移| / 总路程
"""
if len(close_prices) < 2:
return 0
# 计算总位移:终点 - 起点 的绝对值
net_change = abs(close_prices[-1] - close_prices[0])
# 计算总路程:每天价格变化幅度的绝对值之和
diffs = np.diff(close_prices)
sum_of_changes = np.sum(np.abs(diffs))
if sum_of_changes == 0:
return 0
er = net_change / sum_of_changes
return er
def handle_data(context, data):
"""
盘中运行函数,每日或每分钟调用
"""
security = g.security
# -------------------------------------------------------
# 1. 数据获取
# -------------------------------------------------------
# 获取足够长的历史数据以计算指标
# 需要的最大长度是长期均线长度 + 一些缓冲
lookback = max(g.er_period, g.ma_long_len, g.rsi_period) + 5
# 获取历史收盘价
history_df = get_history(lookback, '1d', 'close', security, fq='pre', include=True)
if len(history_df) < lookback:
return # 数据不足,暂不交易
close_prices = history_df['close'].values
# -------------------------------------------------------
# 2. 市场状态识别 (计算ER)
# -------------------------------------------------------
# 取最近 g.er_period 天的数据计算ER
er_data = close_prices[-g.er_period:]
current_er = get_efficiency_ratio(er_data)
# 获取当前持仓
position = get_position(security).amount
cash = context.portfolio.cash
current_price = data[security]['close']
# 记录日志以便调试
# log.info("日期: %s, ER值: %.4f" % (context.blotter.current_dt, current_er))
# -------------------------------------------------------
# 3. 策略分流
# -------------------------------------------------------
if current_er > g.er_threshold:
# === 场景 A: 趋势市场 (动量策略) ===
# 使用双均线逻辑
ma_short = close_prices[-g.ma_short_len:].mean()
ma_long = close_prices[-g.ma_long_len:].mean()
# 趋势策略逻辑:金叉买入,死叉卖出
if ma_short > ma_long:
# 趋势向上,满仓买入
if cash > 0:
order_value(security, cash)
log.info("[趋势模式] 金叉买入: ER=%.2f, MA_S=%.2f, MA_L=%.2f" % (current_er, ma_short, ma_long))
elif ma_short < ma_long:
# 趋势向下,清仓
if position > 0:
order_target(security, 0)
log.info("[趋势模式] 死叉卖出: ER=%.2f" % (current_er))
else:
# === 场景 B: 震荡市场 (反转策略) ===
# 使用 RSI 逻辑
# PTrade 内置 get_RSI 接口,直接调用
# 注意:get_RSI 返回的是 numpy array,取最后一个值
rsi_values = get_RSI(close_prices, g.rsi_period)
current_rsi = rsi_values[-1]
# 震荡策略逻辑:超卖买入,超买卖出
if current_rsi < g.rsi_low:
# 超卖,预期反弹,买入
if cash > 0:
order_value(security, cash)
log.info("[震荡模式] RSI超卖买入: ER=%.2f, RSI=%.2f" % (current_er, current_rsi))
elif current_rsi > g.rsi_high:
# 超买,预期回调,卖出
if position > 0:
order_target(security, 0)
log.info("[震荡模式] RSI超买卖出: ER=%.2f, RSI=%.2f" % (current_er, current_rsi))
代码关键点解析
-
市场状态过滤器 (
get_efficiency_ratio):- 这是策略的大脑。如果价格在20天内从10元涨到20元,且中间几乎没有回调,ER值会接近1(强趋势)。
- 如果价格在20天内反复上下跳动,最后收盘还是10元,ER值会接近0(强震荡)。
- 通过
g.er_threshold(0.25) 来划分界限。您可以根据回测结果调整这个值。
-
数据获取 (
get_history):- 我们使用了
get_history获取收盘价序列。为了保证计算准确,count(lookback) 设置得比所需周期稍大一些。 include=True确保包含当前周期的最新数据。
- 我们使用了
-
趋势模块 (Moving Average):
- 当识别为趋势时,我们忽略 RSI 指标,只看均线。这是为了防止在单边大涨行情中,RSI 长期钝化(一直大于70)导致策略过早卖出踏空。
-
震荡模块 (RSI):
- 当识别为震荡时,我们忽略均线交叉(因为震荡市中均线会频繁发出错误信号,即“左右打脸”),转而使用 RSI 捕捉高抛低吸的机会。
优化建议
- 参数调优:
g.er_threshold是最敏感的参数。对于波动率大的标的(如科技股),可能需要调高阈值;对于大盘股,可能需要调低。 - 止损机制:上述代码是纯信号驱动的。在实盘中,建议加入固定比例止损(例如亏损5%强制平仓),以防止市场状态判断错误(例如误判震荡为趋势,或者在暴跌时RSI持续超卖)。
- 替代指标:
- 除了ER,您还可以使用 ADX (平均趋向指标) 来判断趋势强度(ADX > 25 为趋势)。
- 也可以使用 布林带带宽 (Bandwidth),带宽收窄通常意味着震荡,开口扩大意味着趋势开始。
Q&A
Q: 为什么不直接同时使用均线和RSI,而要分状态?
A: 因为指标之间存在互斥性。在强趋势中,RSI会失效(长期超买/超卖);在震荡中,均线会失效(频繁金叉死叉造成磨损)。分状态处理可以扬长避短。
Q: 这个策略适合什么频率?
A: 代码中默认使用的是日线级别 ('1d')。对于自适应策略,日线级别通常比分钟级别更稳定,因为分钟级别的噪音(Noise)更大,ER系数容易失真。
Q: 如何在PTrade中查看ER值的变化情况?
A: 代码中有一行注释掉的 log.info。您可以取消注释,回测时在日志面板中查看每天的ER值,以此来辅助您调整阈值。