🚀 AI 一键生成 qmt 策略代码

如何实现持仓 N 天或收盘前强制平仓的时间止损?

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

问题描述

如果持仓超过3天不涨就卖出,代码怎么写?

解决方案

这是一个非常经典的时间止损策略。在 QMT 中实现这个逻辑,我们需要利用 get_trade_detail_data 获取持仓信息(包括建仓日期),然后结合 get_trading_dates 计算持仓天数,最后判断盈亏状态进行卖出。

以下是完整的策略代码及详细说明。

策略逻辑说明

  1. 获取持仓:读取当前账户的所有持仓。
  2. 计算天数:获取每只股票的建仓日期(m_strOpenDate),计算从建仓日到当前日期的交易日数量
  3. 判断条件
    • 条件 A:持仓交易日数量 > 3天。
    • 条件 B:浮动盈亏 $\le$ 0(即没有上涨,或者亏损)。
  4. 执行卖出:如果同时满足 A 和 B,则以最新价市价卖出全部持仓。

QMT 策略代码

# -*- coding: gbk -*-
import time

def init(ContextInfo):
    # -------------------------------------------------------
    # 1. 设置资金账号 (请务必修改为您自己的资金账号)
    # -------------------------------------------------------
    ContextInfo.accID = '600000248' 
    ContextInfo.accountType = 'STOCK' # 账号类型:'STOCK'股票, 'FUTURE'期货
    
    # 绑定账号,用于接收回报和获取持仓
    ContextInfo.set_account(ContextInfo.accID)
    
    print("策略初始化完成,监控持仓中...")

def handlebar(ContextInfo):
    # -------------------------------------------------------
    # 2. 获取当前时间和持仓信息
    # -------------------------------------------------------
    # 获取当前K线的时间戳,并转为 YYYYMMDD 格式字符串
    index = ContextInfo.barpos
    timetag = ContextInfo.get_bar_timetag(index)
    current_date_str = timetag_to_datetime(timetag, '%Y%m%d')
    
    # 获取当前账户的持仓列表
    # 参数:账号ID, 账号类型, 数据类型('POSITION'为持仓)
    positions = get_trade_detail_data(ContextInfo.accID, ContextInfo.accountType, 'POSITION')
    
    # 如果没有持仓,直接返回
    if not positions:
        return

    # -------------------------------------------------------
    # 3. 遍历持仓进行判断
    # -------------------------------------------------------
    for pos in positions:
        stock_code = pos.m_strInstrumentID # 证券代码
        open_date = pos.m_strOpenDate      # 建仓日期 (字符串格式 YYYYMMDD)
        volume = pos.m_nVolume             # 持仓数量
        can_use_volume = pos.m_nCanUseVolume # 可用数量 (防止卖出当日买入的股票)
        
        # 如果可用持仓为0,说明是今日新买的,跳过
        if can_use_volume == 0:
            continue

        # --- 计算持仓天数 ---
        # 获取从建仓日(open_date)到当前日期(current_date_str)之间的交易日列表
        # 注意:需要确保本地有下载对应的日线数据,否则 get_trading_dates 可能返回空
        trading_days_list = ContextInfo.get_trading_dates(stock_code, open_date, current_date_str, -1, '1d')
        
        if not trading_days_list:
            # 如果获取不到交易日数据,无法计算天数,跳过
            continue
            
        days_held = len(trading_days_list)
        
        # --- 判断是否上涨 ---
        # 使用 m_dFloatProfit (浮动盈亏) 来判断
        # 如果浮动盈亏 <= 0,视为"不涨" (包含保本和亏损)
        is_not_rising = (pos.m_dFloatProfit <= 0)
        
        # 也可以使用价格判断:现价 <= 成本价
        # is_not_rising = (pos.m_dLastPrice <= pos.m_dOpenPrice)

        # -------------------------------------------------------
        # 4. 执行卖出逻辑
        # -------------------------------------------------------
        # 逻辑:持仓超过3个交易日 且 不涨
        if days_held > 3 and is_not_rising:
            print(f"触发止损: {stock_code}, 建仓日: {open_date}, 持仓天数: {days_held}, 浮动盈亏: {pos.m_dFloatProfit}")
            
            # 执行下单:卖出
            # opType=24 (卖出), orderType=1101 (单股单账号普通下单)
            # priceType=5 (最新价), price=-1 (最新价模式下价格填-1即可)
            passorder(24, 1101, ContextInfo.accID, stock_code, 5, -1, can_use_volume, ContextInfo)

代码关键点解析

  1. get_trade_detail_data:

    • 这是获取账户真实持仓的核心函数。
    • 返回的对象 pos 中,m_strOpenDate 属性记录了该笔持仓的建仓日期,这是计算持仓时间的关键。
  2. ContextInfo.get_trading_dates:

    • 我们不能简单地用 当前日期 - 建仓日期,因为中间可能包含周末和节假日。
    • 使用此函数获取两个日期之间的实际交易日列表,列表的长度 (len) 就是真实的持仓交易天数。
  3. pos.m_dFloatProfit:

    • 这是持仓对象的“浮动盈亏”属性。
    • 代码中 pos.m_dFloatProfit <= 0 意味着当前是亏损或者持平状态,符合你“不涨”的定义。
  4. passorder 参数:

    • 24: 代表股票卖出。
    • 1101: 代表普通交易模式。
    • 5: 代表报价类型为“最新价”。
    • can_use_volume: 使用可用持仓量,避免T+1规则下卖出当日买入的股票导致报错。

使用前注意事项

  1. 修改账号:请务必将 init 函数中的 ContextInfo.accID 修改为你实际的资金账号。
  2. 数据下载get_trading_dates 依赖本地的基础日线数据。请在 QMT 客户端顶部菜单点击 “数据管理” -> “下载历史数据”,勾选日线数据进行补充,否则计算天数可能会出错。
  3. 运行模式:此代码适用于实盘模拟盘模式(因为用到了 get_trade_detail_data 读取账户信息)。如果是纯历史回测,逻辑需要稍作调整(使用虚拟持仓变量)。

Q&A

Q: 这里的“3天”是指自然日还是交易日?
A: 代码中使用的是 get_trading_dates,所以计算的是交易日。如果你周五买入,周六周日不算时间,周一算第2天。如果你想用自然日,可以直接解析日期字符串相减。

Q: 如果我想改成“盈利少于 1% 也卖出”怎么办?
A: 可以修改判断条件。
is_not_rising = (pos.m_dFloatProfit <= 0)
改为 is_not_rising = (pos.m_dFloatProfit / pos.m_dMarketValue < 0.01) (即盈亏比例小于1%)。

Q: 为什么使用 can_use_volume 而不是 volume
A: A股是 T+1 制度,当天买入的股票当天不能卖出。m_nVolume 是总持仓,m_nCanUseVolume 是可用持仓(昨仓)。使用可用持仓可以防止程序试图卖出今仓而报错。