🚀 AI 一键生成 joinquant 策略代码

如何用 JoinQuant 实现经典的海龟交易法则?

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

问题描述

请帮我编写一个海龟交易策略。

解决方案

这是一个基于聚宽(JoinQuant)API 实现的海龟交易策略(Turtle Trading Strategy)

策略核心逻辑

海龟交易法则是一个著名的趋势跟随策略,其核心要素包含:

  1. 入场(唐奇安通道突破):当价格突破过去 N 天(通常为20天)的最高价时买入。
  2. 头寸规模(ATR倒数):根据市场的绝对波动幅度(ATR)来调整仓位大小。波动大买得少,波动小买得多。
  3. 加仓(金字塔式):当价格在上次买入价的基础上上涨 0.5 * ATR 时,进行加仓。
  4. 止损:当价格比最后一次买入价下跌 2 * ATR 时,清仓止损。
  5. 离场:当价格跌破过去 M 天(通常为10天)的最低价时,止盈离场。

策略代码

# -*- coding: utf-8 -*-
import numpy as np
import pandas as pd
from jqdata import *

def initialize(context):
    """
    初始化函数
    """
    # 1. 设定基准
    set_benchmark('000300.XSHG')
    # 2. 开启动态复权模式(真实价格)
    set_option('use_real_price', True)
    # 3. 过滤掉order系列API产生的比error级别低的log
    log.set_level('order', 'error')
    
    # --- 海龟策略参数设置 ---
    # 入场周期(突破过去多少天的最高价)
    g.enter_window = 20
    # 离场周期(跌破过去多少天的最低价)
    g.exit_window = 10
    # ATR计算周期
    g.atr_window = 20
    
    # 单笔交易风险占总资金的比例 (1%)
    g.risk_ratio = 0.01
    # 最大持仓单位限制 (海龟法则通常限制为4个单位)
    g.max_units = 4
    
    # 设定要操作的股票池 (这里示例使用沪深300成分股的前5只,实际使用可替换)
    g.security_list = get_index_stocks('000300.XSHG')[:5]
    
    # 记录持仓详情的字典
    # 格式: {security: {'last_price': float, 'units': int, 'atr': float}}
    g.position_details = {}

    # 每天运行一次
    run_daily(trade_logic, time='open')

def get_atr(security, days):
    """
    计算ATR (平均真实波幅)
    """
    # 获取过去 days + 1 天的数据,因为计算TR需要前一日收盘价
    data = attribute_history(security, days + 1, '1d', ['high', 'low', 'close'])
    
    if len(data) < 2:
        return None
        
    high = data['high'].values
    low = data['low'].values
    close = data['close'].values
    
    tr_list = []
    for i in range(1, len(close)):
        # TR = max(H-L, abs(H-PDC), abs(L-PDC))
        hl = high[i] - low[i]
        h_pdc = abs(high[i] - close[i-1])
        l_pdc = abs(low[i] - close[i-1])
        tr = max(hl, h_pdc, l_pdc)
        tr_list.append(tr)
    
    # 计算ATR
    atr = np.mean(tr_list)
    return atr

def trade_logic(context):
    """
    每日交易主逻辑
    """
    # 获取当前账户总资产
    total_value = context.portfolio.total_value
    
    for security in g.security_list:
        # 获取历史数据用于计算突破
        # 取 max(enter_window, exit_window) + 1 数据
        hist_len = max(g.enter_window, g.exit_window) + 1
        data = attribute_history(security, hist_len, '1d', ['high', 'low', 'close'])
        
        if len(data) < hist_len:
            continue
            
        # 计算关键价格指标
        current_price = data['close'][-1]
        # 过去N天的最高价(不包含今天,用于入场)
        high_n = data['high'][-(g.enter_window+1):-1].max()
        # 过去M天的最低价(不包含今天,用于离场)
        low_m = data['low'][-(g.exit_window+1):-1].min()
        
        # 计算ATR
        atr = get_atr(security, g.atr_window)
        if atr is None or atr == 0:
            continue
            
        # --- 检查持仓状态 ---
        if security in context.portfolio.positions and context.portfolio.positions[security].closeable_amount > 0:
            # 已有持仓,检查 止损、离场 或 加仓
            
            # 获取记录的上次买入价格和持仓单位数
            record = g.position_details.get(security)
            if not record:
                # 如果因为某些原因丢失记录,重置为当前成本
                record = {'last_price': context.portfolio.positions[security].avg_cost, 'units': 1, 'atr': atr}
                g.position_details[security] = record
            
            last_entry_price = record['last_price']
            units = record['units']
            # 使用建仓时的ATR还是最新的ATR?海龟法则通常使用建仓时的ATR来计算止损,这里简化使用动态ATR
            
            # 1. 止损检查: 价格下跌 2 * ATR
            stop_loss_price = last_entry_price - 2 * atr
            
            # 2. 离场检查: 跌破过去M天最低价 (唐奇安通道下轨)
            exit_signal = current_price < low_m
            
            if current_price <= stop_loss_price:
                log.info(f"{security} 触发止损 (现价: {current_price}, 止损价: {stop_loss_price}),清仓。")
                order_target(security, 0)
                if security in g.position_details:
                    del g.position_details[security]
            
            elif exit_signal:
                log.info(f"{security} 跌破{g.exit_window}日低点 (现价: {current_price}, 低点: {low_m}),止盈离场。")
                order_target(security, 0)
                if security in g.position_details:
                    del g.position_details[security]
            
            else:
                # 3. 加仓检查: 价格上涨 0.5 * ATR 且 未达到最大单位限制
                if units < g.max_units and current_price > last_entry_price + 0.5 * atr:
                    # 计算加仓数量 (1个Unit)
                    # Unit = (1% 总资金) / ATR
                    shares_per_unit = int((total_value * g.risk_ratio) / atr / 100) * 100
                    
                    if shares_per_unit >= 100:
                        log.info(f"{security} 触发加仓 (现价: {current_price}, 上次: {last_entry_price})。")
                        order(security, shares_per_unit)
                        
                        # 更新记录
                        g.position_details[security]['last_price'] = current_price
                        g.position_details[security]['units'] += 1
                        g.position_details[security]['atr'] = atr # 更新ATR
        
        else:
            # 无持仓,检查入场信号
            # 价格突破过去N天最高价
            if current_price > high_n:
                # 计算初始建仓数量 (1个Unit)
                shares_per_unit = int((total_value * g.risk_ratio) / atr / 100) * 100
                
                if shares_per_unit >= 100:
                    log.info(f"{security} 突破{g.enter_window}日高点 (现价: {current_price}, 高点: {high_n}),开仓。")
                    order(security, shares_per_unit)
                    
                    # 记录开仓信息
                    g.position_details[security] = {
                        'last_price': current_price,
                        'units': 1,
                        'atr': atr
                    }

def after_code_changed(context):
    """
    模拟交易更新代码后执行
    """
    g.security_list = get_index_stocks('000300.XSHG')[:5]

代码详解

  1. 初始化 (initialize):

    • 设置了海龟法则的经典参数:20日突破入场,10日跌破离场。
    • 定义了风险参数:每次交易只承担总资金 1% 的波动风险。
    • g.position_details:这是一个非常关键的全局字典,用于记录每只股票上一次的买入价格和当前的加仓次数(Unit),这是判断是否加仓和止损的依据。
  2. ATR 计算 (get_atr):

    • ATR(平均真实波幅)是海龟策略的核心。它衡量了市场的波动性。
    • 代码中手动计算了 TR(真实波幅),然后取平均值。
  3. 交易逻辑 (trade_logic):

    • 入场:如果当前没有持仓,且价格高于过去20天的最高价(high_n),则买入 1 个 Unit。
      • Unit 计算公式:Unit数量 = (总资金 * 1%) / ATR。这意味着如果股票波动大(ATR大),买的数量就少;波动小,买的数量就多。
    • 止损:如果持有仓位,且价格低于 上次买入价 - 2 * ATR,则强制平仓。这是海龟法则保护本金的核心规则。
    • 离场(止盈):如果价格跌破过去10天的最低价(low_m),说明趋势可能结束,平仓离场。
    • 加仓:如果价格上涨了 0.5 * ATR,并且当前持仓单位少于 4 个,则继续加仓 1 个 Unit,并更新“上次买入价”。

如何在聚宽使用

  1. 登录聚宽官网,进入“我的策略”。
  2. 新建一个“股票策略”。
  3. 将上述代码复制粘贴到代码编辑区,覆盖原有代码。
  4. 点击“编译运行”进行回测。

注意事项

  • 股票池:代码中默认使用了沪深300的前5只股票作为示例。在实盘或正式回测中,建议根据您的需求修改 g.security_list,或者使用 get_index_stocks 获取整个指数成分股(注意资金容量)。
  • 资金量:海龟策略由于基于 ATR 计算仓位,如果资金量太小(例如小于10万),可能计算出的 shares_per_unit 不足 100 股,导致无法开仓。
  • 适用性:海龟策略在明显的趋势行情中表现最好,但在震荡市中可能会频繁止损(由于突破即买入,假突破会导致亏损)。

Q&A

Q: 为什么海龟策略要用 ATR 来计算仓位?
A: ATR 代表了股票的波动性。通过 (总资金 * 1%) / ATR 计算仓位,可以保证无论买入波动大的股票还是波动小的股票,其承担的单日波动风险金额是基本一致的(都是总资金的1%)。这实现了风险的均等化。

Q: 代码中的 g.position_details 是做什么用的?
A: 这是一个全局字典,用于“记忆”状态。海龟策略的加仓和止损都依赖于上一次买入的价格。普通的持仓信息(context.portfolio)只记录平均成本,无法知道最后一次加仓是在什么价格,所以需要用这个变量单独记录。

Q: 这个策略可以做空吗?
A: 原版海龟策略是双向交易的(做多和做空)。但由于 A 股市场对普通投资者主要是单向做多机制(融券门槛高且券源不稳定),上述代码实现的是仅做多版本。如果需要做期货回测,需要修改 order 函数并增加开空单的逻辑。