🚀 AI 一键生成 qmt 策略代码

如何在策略盈利达到一定幅度后,自动将止损位调整至成本价?

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

问题描述

我希望在我买入的股票盈利超过5%之后,自动把止损设置在我的买入成本价上,该如何编写这个逻辑?

解决方案

这是一个非常实用的风控逻辑,通常被称为保本止损(Breakeven Stop)或移动止损

策略逻辑分析

要实现“盈利超过5%后,将止损设置在成本价”,我们需要在代码中维护一个状态记忆。因为单纯判断“当前是否盈利5%”是不够的,我们需要知道“历史上(持仓期间)是否曾经盈利超过5%”。

逻辑步骤如下:

  1. 获取持仓:遍历当前账户的所有持仓股票。
  2. 获取价格:获取持仓股票的持仓成本价和当前最新价。
  3. 状态标记:如果 (最新价 - 成本价) / 成本价 > 0.05,则在全局变量中记录该股票已经“达标”。
  4. 触发止损:如果该股票已经被记录为“达标”,且 最新价 <= 成本价,则触发卖出操作(保本平仓)。
  5. 清理状态:如果股票已卖出或不再持仓,清理掉对应的记录。

QMT 策略代码实现

以下是完整的 Python 策略代码。请注意,为了演示方便,我使用了 order_target_percent 函数将仓位调整为 0 来实现全仓卖出。

# -*- coding: gbk -*-
import pandas as pd

def init(ContextInfo):
    # 设置资金账号,请修改为您自己的账号
    ContextInfo.accid = '600000248' 
    ContextInfo.accountType = 'STOCK'
    ContextInfo.set_account(ContextInfo.accid)
    
    # 定义一个全局字典,用于记录哪些股票曾经盈利超过 5%
    # 格式: {'股票代码': True}
    ContextInfo.triggered_stocks = {}
    
    print("策略初始化完成,监控保本止损逻辑...")

def handlebar(ContextInfo):
    # 获取当前账号的持仓信息
    positions = get_trade_detail_data(ContextInfo.accid, ContextInfo.accountType, 'POSITION')
    
    # 获取当前持仓的股票代码列表,用于后续清理不再持有的股票状态
    current_holding_codes = []
    
    for pos in positions:
        stock_code = pos.m_strInstrumentID
        volume = pos.m_nVolume
        open_cost = pos.m_dOpenPrice # 持仓成本价
        
        # 如果持仓量为0,跳过
        if volume == 0:
            continue
            
        current_holding_codes.append(stock_code)
        
        # 获取该股票的最新行情数据
        # 使用 get_market_data_ex 获取最新 tick 或 分钟线数据
        last_data = ContextInfo.get_market_data_ex(
            ['close'], 
            [stock_code], 
            period='1d', 
            count=1, 
            dividend_type='follow', 
            subscribe=True
        )
        
        if stock_code not in last_data or last_data[stock_code].empty:
            continue
            
        # 获取最新价
        current_price = last_data[stock_code].iloc[-1]['close']
        
        # 计算当前收益率
        if open_cost > 0:
            profit_rate = (current_price - open_cost) / open_cost
        else:
            profit_rate = 0
            
        # --- 核心逻辑开始 ---
        
        # 1. 检查是否达到激活保本止损的条件(盈利 > 5%)
        if profit_rate > 0.05:
            if stock_code not in ContextInfo.triggered_stocks:
                print(f"【激活保本】{stock_code} 当前盈利 {profit_rate*100:.2f}%,已超过5%,止损线自动上移至成本价 {open_cost}")
                ContextInfo.triggered_stocks[stock_code] = True
        
        # 2. 检查是否触发保本止损(已激活 且 价格回落至成本价及以下)
        # 这里使用 <= open_cost,实际交易中可能需要考虑手续费,例如 open_cost * 1.002
        if ContextInfo.triggered_stocks.get(stock_code, False):
            if current_price <= open_cost:
                print(f"【触发止损】{stock_code} 价格回落至成本价 {open_cost} (当前价: {current_price}),执行保本卖出。")
                
                # 执行卖出,将仓位调整为 0
                order_target_percent(stock_code, 0, ContextInfo, ContextInfo.accid)
                
                # 卖出后,从记录中移除(防止重复触发,虽然仓位为0也会被上面过滤,但清理是个好习惯)
                del ContextInfo.triggered_stocks[stock_code]

    # --- 状态清理逻辑 ---
    # 如果 ContextInfo.triggered_stocks 里的股票已经不在当前持仓列表中(比如手动卖出了),则清除记录
    # 避免下次买入同一只股票时,错误的继承了之前的“达标”状态
    recorded_codes = list(ContextInfo.triggered_stocks.keys())
    for code in recorded_codes:
        if code not in current_holding_codes:
            del ContextInfo.triggered_stocks[code]

代码关键点说明

  1. ContextInfo.triggered_stocks:

    • 这是一个全局字典,用于充当“内存”。
    • 如果不使用这个变量,程序无法知道股票是从 +6% 跌回来的,还是刚买入涨到 +1% 的。只有记录了“曾经超过5%”,才能实现回撤止损。
  2. get_trade_detail_data:

    • 用于获取真实的持仓成本 (m_dOpenPrice)。这是保本止损的核心依据。
  3. 状态清理:

    • 代码末尾的 状态清理逻辑 非常重要。如果你手动卖出了这只股票,或者止损触发了,必须把这个标记清除。否则,当你下次再买入这只股票时(此时盈利为0%),程序会误以为它已经达标并立即卖出。
  4. 价格比较细节:

    • 代码中使用 current_price <= open_cost 作为触发条件。
    • 优化建议:在实盘中,考虑到交易手续费(印花税、佣金),“成本价”卖出实际上是微亏的。如果你希望完全覆盖手续费,可以将触发条件改为 current_price <= open_cost * (1 + 手续费率)

如何运行

  1. 将代码复制到 QMT 的 Python 策略编辑器中。
  2. 修改 init 函数中的 ContextInfo.accid 为你的实际资金账号。
  3. 点击“运行”或在实盘模式下挂载。策略会随着行情刷新(handlebar)自动监控持仓。

Q&A

Q: 这个策略在回测模式下有效吗?
A: 有效。get_trade_detail_data 在回测模式下会返回回测账户的虚拟持仓数据,逻辑是通用的。

Q: 如果我想把止损线设置在成本价上方一点点(比如保住1%的利润)怎么改?
A: 修改触发止损的判断条件即可。将 if current_price <= open_cost: 改为 if current_price <= open_cost * 1.01:

Q: 为什么使用 order_target_percent(..., 0, ...) 而不是 passorder
A: order_target_percent 是封装好的高级函数,设置为 0 意味着“清仓”。它会自动计算需要卖出的股数,比手动计算 passorder 的 volume 参数更方便且不易出错。