问题描述
解决方案
这是一个基于 DeMarker (DeM) 指标的均值回归策略实现。
策略原理
DeMarker 指标 是一种震荡指标,用于评估价格变动的需求。它的取值范围在 0 到 1 之间。
- 超卖区(Oversold):通常低于 0.3。意味着价格可能被低估,存在反弹(均值回归)的需求。
- 超买区(Overbought):通常高于 0.7。意味着价格可能被高估,存在回调的需求。
交易逻辑:
- 买入信号:当 DeMarker 指标值 小于 0.3(超卖)时,认为价格偏低,全仓买入。
- 卖出信号:当 DeMarker 指标值 大于 0.7(超买)时,认为价格偏高,清仓卖出。
- 标的:本示例使用沪深300 ETF(510300.XSHG)作为标的,ETF通常比个股更适合均值回归策略。
策略代码
# -*- coding: utf-8 -*-
import numpy as np
import pandas as pd
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')
# --- 策略参数设置 ---
# 操作标的:这里选择沪深300ETF,适合均值回归
g.security = '510300.XSHG'
# DeMarker 计算周期
g.n_period = 13
# 超买阈值 (卖出)
g.upper_bound = 0.7
# 超卖阈值 (买入)
g.lower_bound = 0.3
# 每天开盘时运行
run_daily(market_open, time='09:30')
def calculate_demarker(security, n_days):
"""
计算 DeMarker 指标
"""
# 获取历史数据:我们需要 N+1 天的数据来计算 N 天的差值
# 为了保证 rolling 计算有足够数据,多取一些缓冲数据
fetch_count = n_days + 5
# 获取最高价和最低价
h_data = attribute_history(security, fetch_count, '1d', ['high', 'low'])
if len(h_data) < 2:
return None
highs = h_data['high']
lows = h_data['low']
# 1. 计算 DeMax
# DeMax = High(t) - High(t-1), 如果 > 0, 否则 0
de_max = highs - highs.shift(1)
de_max[de_max < 0] = 0
# 2. 计算 DeMin
# DeMin = Low(t-1) - Low(t), 如果 > 0, 否则 0
de_min = lows.shift(1) - lows
de_min[de_min < 0] = 0
# 3. 计算移动平均 (SMA)
# 这里的 mean() 对应 SMA
sma_de_max = de_max.rolling(window=n_days).mean()
sma_de_min = de_min.rolling(window=n_days).mean()
# 4. 计算 DeMarker
# DeM = SMA(DeMax) / (SMA(DeMax) + SMA(DeMin))
dem = sma_de_max / (sma_de_max + sma_de_min)
# 返回最新的 DeM 值
return dem.iloc[-1]
def market_open(context):
"""
每日交易逻辑
"""
security = g.security
# 计算当前的 DeMarker 值
current_dem = calculate_demarker(security, g.n_period)
if current_dem is None or np.isnan(current_dem):
log.info("数据不足,跳过计算")
return
# 获取当前持仓
position = context.portfolio.positions[security]
# 获取可用现金
cash = context.portfolio.available_cash
# --- 交易信号判断 ---
# 信号1:超卖 (DeM < 0.3) -> 买入
if current_dem < g.lower_bound:
if cash > 0:
# 全仓买入
order_value(security, cash)
log.info("DeMarker值: %.3f (超卖), 买入 %s" % (current_dem, security))
# 信号2:超买 (DeM > 0.7) -> 卖出
elif current_dem > g.upper_bound:
if position.closeable_amount > 0:
# 清仓卖出
order_target(security, 0)
log.info("DeMarker值: %.3f (超买), 卖出 %s" % (current_dem, security))
# 记录指标值以便在回测图中查看
record(DeMarker=current_dem)
常见问题 (Q&A)
Q1: 为什么选择 ETF 而不是个股进行均值回归?
A: 均值回归策略假设价格会围绕价值中枢波动。个股容易受到黑天鹅事件(如财务造假、重大利空)影响出现单边下跌不回头的现象。而指数 ETF 代表一篮子股票,波动相对平稳,更符合均值回归的统计特性。
Q2: 代码中的 attribute_history 获取的是什么时间的数据?
A: 在 run_daily(market_open, time='09:30') 中调用 attribute_history,默认获取的是截止到前一个交易日收盘的数据。这意味着我们是利用昨天的收盘指标来决定今天开盘的操作,避免了未来函数。
Q3: 如何调整策略的灵敏度?
A:
- 调整周期
g.n_period:减小周期(如改为 9)会使指标更敏感,交易更频繁;增大周期(如改为 20)会使指标更平滑,信号更少但可能更可靠。 - 调整阈值:将买入阈值调低(如 0.2)或卖出阈值调高(如 0.8)会减少交易次数,试图捕捉更极端的反转机会。
Q4: 策略可以做空吗?
A: 上述代码是针对 A 股股票/ETF 市场的,默认只能做多(Long Only)。如果需要做空,需要开通融资融券账户或交易股指期货,并修改 order 函数为 order_target(security, 0) (平仓) 或开空单逻辑。