🚀 AI 一键生成 joinquant 策略代码

如何用 Python 在 JoinQuant 实现一目均衡表(Ichimoku Cloud)交易策略?

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

问题描述

请帮我编写一个基于一目均衡表(Ichimoku Cloud)的交易策略

解决方案

这是一个基于 一目均衡表 (Ichimoku Cloud) 的交易策略。

策略原理

一目均衡表(Ichimoku Kinko Hyo)是一个综合性的技术分析指标,主要包含以下五个部分:

  1. 转折线 (Tenkan-sen / Conversion Line): (过去9日最高价 + 过去9日最低价) / 2
  2. 基准线 (Kijun-sen / Base Line): (过去26日最高价 + 过去26日最低价) / 2
  3. 先行带 A (Senkou Span A): (转折线 + 基准线) / 2,向未来平移26个周期
  4. 先行带 B (Senkou Span B): (过去52日最高价 + 过去52日最低价) / 2,向未来平移26个周期
  5. 迟行带 (Chikou Span): 当前收盘价,向过去平移26个周期(本策略主要使用前四者构建云带)。

云带 (Kumo) 由先行带 A 和 先行带 B 组成。

交易逻辑

本策略采用经典的 “云带突破 + TK交叉” 逻辑:

  • 买入信号
    1. 收盘价 高于 云带(即 Price > Span A 且 Price > Span B),表示趋势向上。
    2. 转折线 大于 基准线(多头排列)。
  • 卖出信号
    1. 收盘价 跌破 云带(Price < Span A 或 Price < Span B)。
    2. 或者 转折线 小于 基准线(空头排列)。

策略代码

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

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')
    
    # 设定要操作的股票池,这里以 上证50 成分股为例
    g.stock_pool = get_index_stocks('000016.XSHG')
    
    # 一目均衡表参数
    g.n1 = 9   # 转折线周期
    g.n2 = 26  # 基准线周期
    g.n3 = 52  # 先行带B周期
    g.n_shift = 26 # 平移周期
    
    # 每天开盘时运行
    run_daily(market_open, time='09:30')

def get_ichimoku_data(security, n1, n2, n3, n_shift):
    """
    计算一目均衡表数据
    """
    # 获取足够长的历史数据
    # 需要的数据长度 = max(n3, n_shift + n3) + 安全余量
    count = n3 + n_shift + 10
    h_data = attribute_history(security, count, '1d', ['high', 'low', 'close'])
    
    if len(h_data) < count:
        return None
    
    # 1. 计算当前的 转折线 (Tenkan-sen) 和 基准线 (Kijun-sen)
    # 用于判断 TK 交叉
    high_n1 = h_data['high'][-n1:].max()
    low_n1 = h_data['low'][-n1:].min()
    tenkan_sen = (high_n1 + low_n1) / 2
    
    high_n2 = h_data['high'][-n2:].max()
    low_n2 = h_data['low'][-n2:].min()
    kijun_sen = (high_n2 + low_n2) / 2
    
    # 2. 计算当前的 云带 (Kumo)
    # 注意:今天的云带是由 26 天前的数据计算出来的
    # 所以我们需要切片获取 26 天前的数据窗口
    
    # 数据截止到 n_shift (26) 天前
    past_data = h_data.iloc[:-n_shift]
    
    # 基于过去数据计算当时的转折线
    p_high_n1 = past_data['high'][-n1:].max()
    p_low_n1 = past_data['low'][-n1:].min()
    p_tenkan = (p_high_n1 + p_low_n1) / 2
    
    # 基于过去数据计算当时的基准线
    p_high_n2 = past_data['high'][-n2:].max()
    p_low_n2 = past_data['low'][-n2:].min()
    p_kijun = (p_high_n2 + p_low_n2) / 2
    
    # 计算 先行带 A (Senkou Span A) = (过去转折 + 过去基准) / 2
    span_a = (p_tenkan + p_kijun) / 2
    
    # 基于过去数据计算 先行带 B (Senkou Span B)
    p_high_n3 = past_data['high'][-n3:].max()
    p_low_n3 = past_data['low'][-n3:].min()
    span_b = (p_high_n3 + p_low_n3) / 2
    
    return {
        'tenkan': tenkan_sen,
        'kijun': kijun_sen,
        'span_a': span_a,
        'span_b': span_b,
        'close': h_data['close'][-1]
    }

def market_open(context):
    """
    每日交易逻辑
    """
    # 更新股票池(剔除停牌、ST等,这里简单处理直接用指数成分)
    # 实际实盘建议增加过滤逻辑
    current_date = context.current_dt.date()
    
    # 获取持仓
    positions = context.portfolio.positions
    
    # 待买入列表
    buy_list = []
    # 待卖出列表
    sell_list = []
    
    for stock in g.stock_pool:
        # 获取一目均衡表数据
        ichi = get_ichimoku_data(stock, g.n1, g.n2, g.n3, g.n_shift)
        if not ichi:
            continue
            
        close = ichi['close']
        span_a = ichi['span_a']
        span_b = ichi['span_b']
        tenkan = ichi['tenkan']
        kijun = ichi['kijun']
        
        # 云带顶部和底部
        kumo_top = max(span_a, span_b)
        kumo_bottom = min(span_a, span_b)
        
        # --- 卖出逻辑 ---
        # 如果持有该股票
        if stock in positions and positions[stock].closeable_amount > 0:
            # 1. 价格跌破云带 (收盘价 < 云带底部)
            # 2. 或者 转折线 下穿 基准线 (死叉)
            if close < kumo_bottom or tenkan < kijun:
                sell_list.append(stock)
        
        # --- 买入逻辑 ---
        # 如果没有持仓
        elif stock not in positions:
            # 1. 价格在云带上方 (收盘价 > 云带顶部)
            # 2. 且 转折线 > 基准线 (多头排列)
            if close > kumo_top and tenkan > kijun:
                buy_list.append(stock)

    # 执行卖出
    for stock in sell_list:
        order_target_value(stock, 0)
        log.info("卖出 %s: 跌破云带或TK死叉" % stock)
        
    # 执行买入
    # 简单的资金分配:将可用资金平均分配给待买入股票
    if len(buy_list) > 0:
        cash = context.portfolio.available_cash
        # 预留一点资金防止手续费不够
        cash_per_stock = cash / len(buy_list) * 0.98 
        
        for stock in buy_list:
            order_value(stock, cash_per_stock)
            log.info("买入 %s: 突破云带且TK金叉" % stock)

代码关键点解析

  1. 数据获取与平移 (get_ichimoku_data):

    • 一目均衡表最难处理的是“时间平移”。
    • 先行带 A 和 B 在图表上是画在“未来26天”的。这意味着,今天看到的云带,实际上是利用 26天前 的历史数据计算出来的数值。
    • 代码中 past_data = h_data.iloc[:-n_shift] 这一行非常关键,它模拟了回溯到26天前,利用当时的数据计算出 Span A 和 Span B,这两个值构成了今天的云带支撑/阻力位。
  2. 信号判断:

    • Kumo Breakout (云带突破): close > kumo_top。这是最强的趋势信号,表明价格已经突破了历史阻力区。
    • TK Cross (转折/基准线交叉): tenkan > kijun。类似于移动平均线的金叉,用于确认短期动能。
  3. 资金管理:

    • 示例代码使用了简单的等权分配。如果有多个买入信号,将当前现金平均分配。

常见问题 Q&A

Q: 为什么 Span A 和 Span B 的计算要用过去的数据?
A: 在一目均衡表的定义中,Span A 和 B 是“前移”绘制的。这意味着,对于交易者来说,今天的云带位置是由过去(26天前)的市场状态决定的。在编写代码时,我们需要“回到过去”计算出那个值,然后拿来和“今天”的价格做对比。

Q: 这个策略适合什么频率?
A: 一目均衡表最初是为日线设计的(Daily),在日线级别上效果最稳定。如果用于分钟级别(如60分钟),需要调整参数或注意噪音干扰。

Q: 如何优化这个策略?
A:

  1. 增加迟行带确认:要求当前价格高于26天前的价格。
  2. 止损优化:可以使用基准线(Kijun-sen)作为动态止损线,而不是死叉才卖出。
  3. 股票池筛选:一目均衡表是趋势指标,在震荡市中会频繁止损。建议结合基本面因子或波动率因子,只在趋势明显的标的上运行。