问题描述
解决方案
这是一个非常好的思路。基于 ATR(平均真实波幅,Average True Range) 的止损策略是量化交易中非常经典的**动态止损(Trailing Stop)**方法,通常被称为“吊灯止损(Chandelier Exit)”或“ATR 移动止损”。
核心逻辑
ATR 指标反映了市场当前的波动率:
- 波动大时:ATR 变大,止损线会离当前价格更远,防止因正常的市场噪音被震荡出局。
- 波动小时:ATR 变小,止损线会离当前价格更近,从而在趋势反转时更快锁定利润。
止损公式(以多头持仓为例):
$$ 止损价 = 持仓期间最高价 - (系数 \times 当前ATR值) $$
QMT 策略实现
下面是一个完整的 QMT Python 策略代码。该策略包含以下功能:
- 入场:为了演示止损,我设置了一个简单的入场条件(收盘价上穿 20 日均线)。
- ATR 计算:使用
talib库计算 ATR。 - 动态止损:记录持仓后的最高价,并根据 ATR 动态计算止损线。如果价格跌破该线,则平仓。
# -*- coding: gbk -*-
import pandas as pd
import numpy as np
import talib
def init(ContextInfo):
# ================= 策略参数设置 =================
ContextInfo.stock_code = '600000.SH' # 示例股票:浦发银行
ContextInfo.account_id = '600000248' # 请替换为您的资金账号
ContextInfo.period = '1d' # 运行周期
# ATR 止损参数
ContextInfo.atr_period = 14 # ATR 计算周期
ContextInfo.atr_multiplier = 2.0 # ATR 倍数(通常设置为 2.0 到 3.0)
# 均线参数(用于简单的入场信号)
ContextInfo.ma_period = 20
# ================= 全局变量初始化 =================
ContextInfo.set_universe([ContextInfo.stock_code])
ContextInfo.set_account(ContextInfo.account_id)
# 用于记录持仓期间的最高价,用于计算移动止损
ContextInfo.highest_price_since_entry = 0.0
def handlebar(ContextInfo):
# 获取当前 K 线位置
index = ContextInfo.barpos
stock = ContextInfo.stock_code
# 获取历史行情数据 (多取一些数据以保证指标计算准确)
# 注意:get_market_data_ex 返回的是 DataFrame
data_len = ContextInfo.ma_period + ContextInfo.atr_period + 10
market_data = ContextInfo.get_market_data_ex(
['high', 'low', 'close', 'open'],
[stock],
period=ContextInfo.period,
count=data_len,
dividend_type='front' # 前复权
)
if stock not in market_data or len(market_data[stock]) < data_len:
return
df = market_data[stock]
# 提取 numpy 数组用于 talib 计算
high_arr = df['high'].values
low_arr = df['low'].values
close_arr = df['close'].values
# ================= 计算指标 =================
# 1. 计算 ATR
atr_values = talib.ATR(high_arr, low_arr, close_arr, timeperiod=ContextInfo.atr_period)
current_atr = atr_values[-1]
# 2. 计算 MA (用于入场)
ma_values = talib.SMA(close_arr, timeperiod=ContextInfo.ma_period)
current_ma = ma_values[-1]
prev_ma = ma_values[-2]
current_close = close_arr[-1]
prev_close = close_arr[-2]
# ================= 获取持仓信息 =================
positions = get_trade_detail_data(ContextInfo.account_id, 'stock', 'position')
current_holding = 0
for pos in positions:
if pos.m_strInstrumentID + '.' + pos.m_strExchangeID == stock:
current_holding = pos.m_nVolume
break
# ================= 交易逻辑 =================
# --- 场景 A: 当前无持仓,检查买入条件 ---
if current_holding == 0:
# 重置最高价记录
ContextInfo.highest_price_since_entry = 0.0
# 简单的金叉买入逻辑:收盘价上穿 MA20
if prev_close < prev_ma and current_close > current_ma:
print(f"[{ContextInfo.get_bar_timetag(index)}] 触发买入信号,当前价: {current_close}")
# 全仓买入 (示例:买入 1000 股)
passorder(23, 1101, ContextInfo.account_id, stock, 5, -1, 1000, ContextInfo)
# 买入后立即初始化“持仓最高价”为当前价格
ContextInfo.highest_price_since_entry = current_close
# --- 场景 B: 当前有持仓,检查 ATR 动态止损 ---
else:
# 1. 更新持仓期间的最高价
# 如果当前最高价创出新高,则更新记录,止损线也会随之上移
if df['high'].values[-1] > ContextInfo.highest_price_since_entry:
ContextInfo.highest_price_since_entry = df['high'].values[-1]
# 2. 计算动态止损价
# 止损价 = 持仓期间最高价 - (系数 * 当前ATR)
stop_loss_price = ContextInfo.highest_price_since_entry - (ContextInfo.atr_multiplier * current_atr)
# 在图表上画出止损线方便观察 (仅回测或界面运行时有效)
ContextInfo.paint('ATR_Stop_Line', stop_loss_price, -1, 0, 'green')
# 3. 检查是否触发止损
if current_close < stop_loss_price:
print(f"[{ContextInfo.get_bar_timetag(index)}] 触发 ATR 动态止损!")
print(f"持仓最高价: {ContextInfo.highest_price_since_entry}, 当前 ATR: {current_atr:.3f}")
print(f"止损线: {stop_loss_price:.3f}, 当前价: {current_close}")
# 卖出平仓
passorder(24, 1101, ContextInfo.account_id, stock, 5, -1, current_holding, ContextInfo)
# 重置最高价
ContextInfo.highest_price_since_entry = 0.0
代码关键点解析
-
talib.ATR:- 我们使用了 Python 的
talib库来计算 ATR,这是最高效的方法。 ContextInfo.atr_period = 14:这是 ATR 的标准周期。ContextInfo.atr_multiplier = 2.0:这是安全垫的宽度。如果你希望止损更宽松(过滤更多震荡),可以将其改为 2.5 或 3.0;如果你希望止损更紧凑,可以改为 1.5。
- 我们使用了 Python 的
-
ContextInfo.highest_price_since_entry:- 这是实现**移动止损(Trailing Stop)**的关键。
- 当持有股票时,我们不断记录价格的最高点。
- 止损线是基于这个“最高点”向下减去 ATR 距离的。这意味着:如果股价上涨,止损线会跟着上涨(锁定利润);如果股价下跌,止损线保持不变(保护本金)。
-
动态性体现:
- 代码中
current_atr是每一根 K 线实时计算的。 - 如果市场突然变得剧烈波动(ATR 变大),
atr_multiplier * current_atr的值会变大,止损线会稍微向下移动(或者上涨得慢一点),给价格更多的波动空间,防止被洗盘。
- 代码中
如何在 QMT 中使用
- 新建一个 Python 策略。
- 将上述代码复制进去。
- 修改
ContextInfo.account_id为你的实际账号。 - 点击“运行”或“回测”。在回测界面的副图中,你可以看到画出的绿色
ATR_Stop_Line,观察它是否随着股价上涨而阶梯式上升。
常见问题 Q&A
Q: 为什么使用 get_market_data_ex 而不是 get_market_data?
A: get_market_data_ex 是 QMT 较新的接口,返回的数据结构更清晰(字典包含 DataFrame),且处理复权和多股数据更方便。
Q: 这个策略是盘中实时触发吗?
A: 上述代码写在 handlebar 中。
- 回测模式:每根 K 线结束时判断一次。
- 实盘模式:如果运行周期是日线,它会在每天收盘前(或收盘后,取决于设置)运行一次。如果你需要盘中实时监控(例如价格一破位马上卖),需要将运行周期改为 1分钟 或 5分钟,或者使用
ContextInfo.subscribe_quote订阅 Tick 数据进行更精细的控制。
Q: 如何调整止损的灵敏度?
A: 调整 ContextInfo.atr_multiplier。
- 短线交易:建议 1.5 - 2.0 倍 ATR。
- 中长线趋势:建议 2.5 - 3.0 倍 ATR。