🚀 AI 一键生成 joinquant 策略代码

如何在 JoinQuant 中设置止损单和止盈单?(附 Python 代码)

joinquant | 发布时间: 2025/11/28
以下内容由 EasyQuant 生成。

问题描述

请帮我编写一个带有止损和止盈功能的交易策略

解决方案

这是一个基于聚宽(JoinQuant)API 编写的完整交易策略示例。

为了演示止损和止盈的功能,我构建了一个基础的双均线策略(金叉买入,死叉卖出)作为入场和出场的基础,并在此之上增加了固定比例的止损和止盈逻辑。

策略逻辑说明

  1. 入场信号:当短期均线(5日)上穿长期均线(10日)时,全仓买入。
  2. 出场信号(基础):当短期均线(5日)下穿长期均线(10日)时,清仓卖出。
  3. 止损逻辑:当当前价格低于持仓成本的 5% 时,触发止损,强制卖出。
  4. 止盈逻辑:当当前价格高于持仓成本的 10% 时,触发止盈,强制卖出。
  5. 优先级:止损/止盈的判断优先级高于均线信号。

策略代码

# -*- coding: utf-8 -*-
import jqdata

def initialize(context):
    """
    初始化函数,设定基准、股票池、参数等
    """
    # 设定沪深300作为基准
    set_benchmark('000300.XSHG')
    # 开启动态复权模式(真实价格)
    set_option('use_real_price', True)
    # 过滤掉order系列API产生的比error级别低的log
    log.set_level('order', 'error')
    
    # 设定手续费:买入万三,卖出万三加千一印花税
    set_order_cost(OrderCost(close_tax=0.001, open_commission=0.0003, close_commission=0.0003, min_commission=5), type='stock')
    
    # --- 策略参数设置 ---
    # 要操作的股票:平安银行
    g.security = '000001.XSHE'
    # 短期均线窗口
    g.short_window = 5
    # 长期均线窗口
    g.long_window = 10
    
    # 止损比例 (例如 -0.05 代表亏损5%止损)
    g.stop_loss_pct = -0.05
    # 止盈比例 (例如 0.10 代表盈利10%止盈)
    g.take_profit_pct = 0.10
    
    # 每天开盘时运行
    run_daily(market_open, time='09:30')

def market_open(context):
    """
    每日交易逻辑
    """
    security = g.security
    
    # 获取历史收盘价数据,长度取长均线+1天,为了计算均线
    # include_now=True 表示包含当前分钟/日的数据,但在回测daily频率下,开盘时取不到当日收盘价,
    # 这里我们取过去的数据来计算昨日的均线情况,避免未来函数
    hist_data = attribute_history(security, g.long_window + 1, '1d', ['close'], skip_paused=True)
    
    # 如果数据不足(例如新股),则跳过
    if len(hist_data) < g.long_window + 1:
        return

    # 获取当前持仓信息
    position = context.portfolio.positions[security]
    # 获取当前最新价格 (回测中daily频率在09:30运行时,current_price通常为开盘价)
    current_data = get_current_data()
    current_price = current_data[security].last_price
    
    # --- 1. 优先执行止损止盈检查 ---
    if position.total_amount > 0:
        # 计算收益率:(当前价格 - 持仓成本) / 持仓成本
        # avg_cost 是建仓成本,包含手续费
        cost = position.avg_cost
        if cost > 0:
            returns = (current_price - cost) / cost
            
            # 检查止损
            if returns <= g.stop_loss_pct:
                log.info("触发止损: 标的=%s, 成本=%.2f, 现价=%.2f, 收益率=%.2f%%" % (security, cost, current_price, returns * 100))
                order_target(security, 0)
                return # 止损后直接结束本日策略逻辑,不再判断均线

            # 检查止盈
            if returns >= g.take_profit_pct:
                log.info("触发止盈: 标的=%s, 成本=%.2f, 现价=%.2f, 收益率=%.2f%%" % (security, cost, current_price, returns * 100))
                order_target(security, 0)
                return # 止盈后直接结束本日策略逻辑

    # --- 2. 均线策略逻辑 ---
    
    # 计算短期均线 (取最近 short_window 天的均值)
    # values[:-1] 是为了取昨日及之前的数据,模拟昨日收盘后的信号
    ma_short = hist_data['close'][-g.short_window:].mean()
    
    # 计算长期均线
    ma_long = hist_data['close'][-g.long_window:].mean()
    
    # 获取上一日的短期和长期均线值,用于判断交叉
    prev_ma_short = hist_data['close'][-(g.short_window+1):-1].mean()
    prev_ma_long = hist_data['close'][-(g.long_window+1):-1].mean()
    
    # 获取可用资金
    cash = context.portfolio.available_cash
    
    # 信号判断
    # 金叉:短期均线 上穿 长期均线
    # 逻辑:昨天短均线 < 昨天长均线  且  今天短均线 > 今天长均线
    # 注意:这里的“今天”指的是基于历史数据计算出的最新均线值
    
    # 买入逻辑:金叉 且 当前无持仓
    if ma_short > ma_long and prev_ma_short <= prev_ma_long and position.total_amount == 0:
        log.info("均线金叉买入: 标的=%s, MA5=%.2f, MA10=%.2f" % (security, ma_short, ma_long))
        order_value(security, cash)

    # 卖出逻辑:死叉 且 有持仓
    elif ma_short < ma_long and prev_ma_short >= prev_ma_long and position.total_amount > 0:
        log.info("均线死叉卖出: 标的=%s, MA5=%.2f, MA10=%.2f" % (security, ma_short, ma_long))
        order_target(security, 0)

代码关键点解析

  1. position.avg_cost:

    • 这是聚宽提供的持仓对象属性,代表持仓平均成本
    • 止盈止损的核心就是比较 current_price(当前价)和 avg_cost(成本价)的偏离程度。
  2. 优先级控制:

    • 代码中先进行了止损止盈的判断。如果触发了止损或止盈,执行 order_target(security, 0) 卖出后,直接 return
    • 这样做是为了防止同一天内既触发止损,又因为均线信号产生其他操作(虽然在日线级别通常不会冲突,但在逻辑上应保持清晰的优先级)。
  3. run_daily:

    • 设置在 09:30 运行。在回测模式下,这使用的是当天的开盘价进行撮合。
    • 如果您希望盘中实时监控止损(例如分钟级止损),需要将回测频率调整为“分钟”,并将逻辑放入 handle_data 或设置 run_daily(func, 'every_bar')
  4. order_target(security, 0):

    • 这是一个非常方便的函数,意为“将目标股票的仓位调整为0”,即清仓卖出。

常见问题 (Q&A)

Q: 如何实现移动止损(Trailing Stop)?
A: 移动止损不是基于成本价,而是基于持仓期间的最高价回撤。你需要使用全局变量(如 g.highest_price)记录持仓以来的最高价。

  • 买入时初始化 g.highest_price = cost
  • 每天更新:if current_price > g.highest_price: g.highest_price = current_price
  • 止损判断:if (current_price - g.highest_price) / g.highest_price < -0.10: (回撤10%止损)。

Q: 这个策略可以用于多只股票吗?
A: 可以。你需要将 g.security 改为一个列表(例如 g.stocks = ['000001.XSHE', '600519.XSHG']),然后在 market_open 函数中使用 for security in g.stocks: 循环遍历每一只股票进行相同的逻辑判断。

Q: 为什么回测时止损价格和设定的不一样?
A: 这里的代码是按日回测(Daily)。如果某一天开盘直接跌停,或者开盘价已经远低于止损线,系统只能以开盘价卖出,这会导致实际亏损超过设定的5%。这是市场风险(Gap Risk),在实盘中同样存在。如果需要更精确的止损,建议使用分钟级回测。