🚀 AI 一键生成 joinquant 策略代码
立即体验
👉 AI 生成代码 👈

在JoinQuant策略中如何优雅地加入止损条件并避免重复买入?

joinquant | 发布时间: 2026/6/24
以下内容由 EasyQuant 生成。

问题描述

各位大佬,止损条件一般如何加入自己的系统当中呢?

我看不少大佬的系统除了买卖点都有额外增加止损条件用来应付意外风险。
比如说一种止损条件是资金回撤10%,就清仓,全部买入债券/货币基金。而我的交易策略在确定股票池之后,利用60均线来确定买卖点。但是60均线卖出的时候反应不够快,所以回撤很大。我想加入这个止损条件来降低回撤。
问题来了,我通过资金回撤10%卖出所有股票之后,此时我的股票依旧在60均线之上,满足买点,而会继续买入。诸如此类,还有像用估值来确定仓位的,低估买入高估卖出,通过止损卖出所有股票之后,策略依旧判断是低估,还会继续买入。这就矛盾了,止损条件也没有发挥它的作用。
这种问题有解决方法吗?还是说这就是策略的局限性?大佬们一般是怎么加入这个条件的呢?

解决方案

这是一个在量化交易系统设计中非常经典且常见的问题:止损逻辑与建仓逻辑的冲突(Whipsaw/反复打脸)

当你的止损条件(如总资金回撤10%)触发时,如果你的建仓条件(如价格大于60日均线、估值低于某阈值)是一个状态条件而非触发条件,系统就会在下一秒立刻重新买入。要解决这个问题,通常有以下几种成熟的方案,你可以直接在 JoinQuant 平台中实现:

方案一:引入“冷却期”(Cooldown Period)

这是最简单直接的方法。当触发止损清仓后,强制系统进入一段时间的“休眠期”(比如5天、10天或一个月),在冷却期内,即使满足买入条件也不允许开仓。

JoinQuant 实现思路:
在全局变量 g 中设置一个冷却倒计时变量。每天递减,只有当冷却期为0时才允许买入。

def initialize(context):
    g.security = '000001.XSHE'
    g.cooldown_days = 0  # 冷却期天数
    g.max_value = context.portfolio.total_value  # 记录历史最高净值
    run_daily(market_open, time='every_bar')

def market_open(context):
    # 1. 计算总资金最大回撤
    current_value = context.portfolio.total_value
    if current_value > g.max_value:
        g.max_value = current_value
    drawdown = (g.max_value - current_value) / g.max_value

    # 2. 冷却期递减
    if g.cooldown_days > 0:
        g.cooldown_days -= 1

    # 3. 止损逻辑:回撤超过10%
    if drawdown >= 0.10 and len(context.portfolio.positions) > 0:
        log.info("触发总资金10%止损,清仓并进入冷却期")
        for stock in context.portfolio.positions:
            order_target(stock, 0)
        g.cooldown_days = 10  # 设置10天冷却期
        g.max_value = current_value  # 重置最高净值,避免连续触发
        return  # 止损当天不再执行后续买入逻辑

    # 4. 买入逻辑(必须满足冷却期结束)
    if g.cooldown_days == 0:
        # 获取均线等数据进行买入判断
        close_data = attribute_history(g.security, 60, '1d', ['close'])
        ma60 = close_data['close'].mean()
        current_price = close_data['close'][-1]
        
        if current_price > ma60 and context.portfolio.positions[g.security].closeable_amount == 0:
            order_value(g.security, context.portfolio.available_cash)
            log.info("买入 %s" % g.security)

方案二:将“状态信号”改为“交叉信号”(金叉买入)

你提到“股票依旧在60均线之上,满足买点”。这里的买点是一个状态(Price > MA60)。如果将其改为动作/交叉(Price 刚刚向上突破 MA60),矛盾就迎刃而解了。

止损卖出后,虽然价格还在MA60之上,但它并没有发生“向上突破”的动作,因此不会触发买入。只有等价格跌破MA60,然后再次向上突破MA60时,才会形成新的买点。

JoinQuant 实现思路:

# 获取过去两天的收盘价
close_data = attribute_history(g.security, 61, '1d', ['close'])
ma60_today = close_data['close'][-60:].mean()
ma60_yesterday = close_data['close'][-61:-1].mean()

price_today = close_data['close'][-1]
price_yesterday = close_data['close'][-2]

# 判断金叉:昨天在均线之下,今天在均线之上
cross_above = (price_yesterday <= ma60_yesterday) and (price_today > ma60_today)

if cross_above and g.cooldown_days == 0:
    # 执行买入
    pass

方案三:引入“止损重置条件”(状态机)

对于估值策略(低估买入),无法使用“交叉”逻辑。此时可以为每只股票或整个策略引入一个“止损黑名单”状态。当某只股票被止损后,将其加入黑名单。只有当该股票满足某个重置条件(例如:估值恢复到正常水平,或者价格创出新高,或者MACD底背离等)时,才将其移出黑名单,允许再次买入。

总结

大佬们的系统通常是这三者的结合:

  1. 宏观风控:总资金回撤达到阈值,强制清仓并开启冷却期(方案一),防止在极端恶劣行情中反复摩擦。
  2. 微观入场:入场条件极其严格,通常要求是触发型信号(如金叉、突破,方案二)而非持续型状态。
  3. 逻辑闭环:止损后,必须等待市场结构发生实质性改变(方案三),才会重新评估该标的。