🚀 AI 一键生成 qmt 策略代码

QMT 策略风控:如何实现基于 ATR 的钱德勒动态止损?

qmt | 发布时间: 2025/12/17
以下内容由 EasyQuant 生成。

问题描述

我想设置一个能根据市场波动自动调整的止损,应该怎么做?可以基于 ATR 指标吗?

解决方案

这是一个非常好的思路。基于 ATR(平均真实波幅,Average True Range) 的止损策略是量化交易中非常经典的**动态止损(Trailing Stop)**方法,通常被称为“吊灯止损(Chandelier Exit)”或“ATR 移动止损”。

核心逻辑

ATR 指标反映了市场当前的波动率:

  • 波动大时:ATR 变大,止损线会离当前价格更远,防止因正常的市场噪音被震荡出局。
  • 波动小时:ATR 变小,止损线会离当前价格更近,从而在趋势反转时更快锁定利润。

止损公式(以多头持仓为例):
$$ 止损价 = 持仓期间最高价 - (系数 \times 当前ATR值) $$

QMT 策略实现

下面是一个完整的 QMT Python 策略代码。该策略包含以下功能:

  1. 入场:为了演示止损,我设置了一个简单的入场条件(收盘价上穿 20 日均线)。
  2. ATR 计算:使用 talib 库计算 ATR。
  3. 动态止损:记录持仓后的最高价,并根据 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

代码关键点解析

  1. talib.ATR:

    • 我们使用了 Python 的 talib 库来计算 ATR,这是最高效的方法。
    • ContextInfo.atr_period = 14:这是 ATR 的标准周期。
    • ContextInfo.atr_multiplier = 2.0:这是安全垫的宽度。如果你希望止损更宽松(过滤更多震荡),可以将其改为 2.5 或 3.0;如果你希望止损更紧凑,可以改为 1.5。
  2. ContextInfo.highest_price_since_entry:

    • 这是实现**移动止损(Trailing Stop)**的关键。
    • 当持有股票时,我们不断记录价格的最高点。
    • 止损线是基于这个“最高点”向下减去 ATR 距离的。这意味着:如果股价上涨,止损线会跟着上涨(锁定利润);如果股价下跌,止损线保持不变(保护本金)。
  3. 动态性体现:

    • 代码中 current_atr 是每一根 K 线实时计算的。
    • 如果市场突然变得剧烈波动(ATR 变大),atr_multiplier * current_atr 的值会变大,止损线会稍微向下移动(或者上涨得慢一点),给价格更多的波动空间,防止被洗盘。

如何在 QMT 中使用

  1. 新建一个 Python 策略。
  2. 将上述代码复制进去。
  3. 修改 ContextInfo.account_id 为你的实际账号。
  4. 点击“运行”或“回测”。在回测界面的副图中,你可以看到画出的绿色 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。