🚀 AI 一键生成 qmt 策略代码

QMT 技术指标策略:如何编写一目均衡表 (Ichimoku) 交易系统?

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

问题描述

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

解决方案

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

策略原理

一目均衡表由五个指标组成,本策略主要利用以下逻辑进行趋势跟踪:

  1. 指标计算

    • 转换线 (Tenkan-sen): (过去9期最高价 + 过去9期最低价) / 2
    • 基准线 (Kijun-sen): (过去26期最高价 + 过去26期最低价) / 2
    • 先行带 A (Senkou Span A): (转换线 + 基准线) / 2,向未来平移26期(即当前K线对比的是26期前计算出的云带值)。
    • 先行带 B (Senkou Span B): (过去52期最高价 + 过去52期最低价) / 2,向未来平移26期。
  2. 交易信号

    • 买入开仓 (Long Entry)
      • 收盘价 高于 云带(即 Close > Span A 且 Close > Span B)。
      • 转换线 上穿 基准线(金叉),表示短期动能强于长期。
    • 卖出平仓 (Long Exit)
      • 收盘价 跌破 云带(Close < Span A 或 Close < Span B)。
      • 或者 转换线 下穿 基准线(死叉)。

QMT 策略代码

# -*- coding: gbk -*-
import pandas as pd
import numpy as np
import time

def init(ContextInfo):
    # ================= 策略参数设置 =================
    # 转换线周期 (默认9)
    ContextInfo.tenkan_window = 9
    # 基准线周期 (默认26)
    ContextInfo.kijun_window = 26
    # 先行带B计算周期 (默认52)
    ContextInfo.senkou_b_window = 52
    # 云带位移周期 (默认26)
    ContextInfo.displacement = 26
    
    # 交易标的 (示例:平安银行)
    ContextInfo.stock_code = '000001.SZ'
    # 设置股票池
    ContextInfo.set_universe([ContextInfo.stock_code])
    
    # 资金账号 (请修改为您的实际账号)
    ContextInfo.account_id = '6000000000'
    # 账号类型:'STOCK'股票, 'FUTURE'期货
    ContextInfo.account_type = 'STOCK'
    
    # 设置交易账号
    ContextInfo.set_account(ContextInfo.account_id)
    
    # 每次交易数量 (股)
    ContextInfo.trade_vol = 1000

def get_ichimoku_data(ContextInfo, stock_code):
    """
    计算一目均衡表数据
    """
    # 获取足够的历史数据,长度需要覆盖最长周期 + 位移 + 缓冲
    # 52 (Senkou B) + 26 (Displacement) + buffer
    count = 150 
    
    # 获取行情数据
    # 注意:get_market_data_ex 返回的是 {code: dataframe}
    data_map = ContextInfo.get_market_data_ex(
        ['high', 'low', 'close'], 
        [stock_code], 
        period=ContextInfo.period, 
        count=count, 
        dividend_type='front' # 前复权
    )
    
    if stock_code not in data_map:
        return None
        
    df = data_map[stock_code]
    
    if len(df) < ContextInfo.senkou_b_window + ContextInfo.displacement:
        return None

    # 1. 计算转换线 (Tenkan-sen): (9日最高 + 9日最低) / 2
    high_9 = df['high'].rolling(window=ContextInfo.tenkan_window).max()
    low_9 = df['low'].rolling(window=ContextInfo.tenkan_window).min()
    df['tenkan_sen'] = (high_9 + low_9) / 2

    # 2. 计算基准线 (Kijun-sen): (26日最高 + 26日最低) / 2
    high_26 = df['high'].rolling(window=ContextInfo.kijun_window).max()
    low_26 = df['low'].rolling(window=ContextInfo.kijun_window).min()
    df['kijun_sen'] = (high_26 + low_26) / 2

    # 3. 计算先行带 A (Senkou Span A): (转换线 + 基准线) / 2
    # 注意:在图表中是向未来平移26期。
    # 在代码逻辑中,我们要比较的是"当前价格"与"当前时刻对应的云带"。
    # 当前时刻的云带A,实际上是26期之前计算出来的 (Tenkan + Kijun)/2
    df['senkou_span_a'] = ((df['tenkan_sen'] + df['kijun_sen']) / 2).shift(ContextInfo.displacement)

    # 4. 计算先行带 B (Senkou Span B): (52日最高 + 52日最低) / 2
    # 同样向未来平移26期
    high_52 = df['high'].rolling(window=ContextInfo.senkou_b_window).max()
    low_52 = df['low'].rolling(window=ContextInfo.senkou_b_window).min()
    df['senkou_span_b'] = ((high_52 + low_52) / 2).shift(ContextInfo.displacement)

    # 删除包含NaN的行 (主要是由于shift和rolling造成的)
    df.dropna(inplace=True)
    
    return df

def handlebar(ContextInfo):
    # 获取当前K线索引
    index = ContextInfo.barpos
    # 获取当前时间
    realtime = ContextInfo.get_bar_timetag(index)
    
    stock_code = ContextInfo.stock_code
    
    # 计算指标
    df = get_ichimoku_data(ContextInfo, stock_code)
    
    if df is None or len(df) < 2:
        return

    # 获取最新一根K线的数据 (当前时刻)
    curr_bar = df.iloc[-1]
    # 获取前一根K线的数据 (用于判断交叉)
    prev_bar = df.iloc[-2]
    
    # 提取变量
    close = curr_bar['close']
    tenkan = curr_bar['tenkan_sen']
    kijun = curr_bar['kijun_sen']
    span_a = curr_bar['senkou_span_a']
    span_b = curr_bar['senkou_span_b']
    
    prev_tenkan = prev_bar['tenkan_sen']
    prev_kijun = prev_bar['kijun_sen']
    
    # 获取当前持仓
    position = 0
    positions = get_trade_detail_data(ContextInfo.account_id, ContextInfo.account_type, 'POSITION')
    for pos in positions:
        if pos.m_strInstrumentID == stock_code:
            position = pos.m_nVolume
            break

    # ================= 交易逻辑 =================
    
    # 1. 买入条件
    # 条件A: 收盘价在云带上方 (Close > Span A AND Close > Span B)
    price_above_cloud = (close > span_a) and (close > span_b)
    
    # 条件B: 转换线金叉基准线 (Tenkan 上穿 Kijun)
    golden_cross = (prev_tenkan <= prev_kijun) and (tenkan > kijun)
    
    # 也可以放宽条件:只要转换线大于基准线,且价格在云上
    trend_is_up = tenkan > kijun
    
    # 最终买入信号:价格在云上 且 (发生金叉 或 已经是多头排列)
    # 这里演示严格策略:价格在云上 且 发生金叉
    buy_signal = price_above_cloud and golden_cross
    
    # 2. 卖出条件
    # 条件A: 收盘价跌入云带或跌破云带
    price_enter_cloud = (close < span_a) or (close < span_b)
    
    # 条件B: 转换线死叉基准线
    death_cross = (prev_tenkan >= prev_kijun) and (tenkan < kijun)
    
    sell_signal = price_enter_cloud or death_cross

    # ================= 执行交易 =================
    
    # 只有在最后一根K线(实时行情)或回测模式下才下单
    if not ContextInfo.is_last_bar():
        # 如果是回测,每一根K线都要跑,不需要return
        # 如果是实盘,通常只在最新K线跑,但QMT机制是历史K线也会跑一遍handlebar
        # 这里为了兼容回测,不做return拦截,但请注意实盘时的信号闪烁问题
        pass

    # 买入操作
    if buy_signal and position == 0:
        print(f"[{ContextInfo.stock_code}] 触发买入信号: 价格={close}, 云带A={span_a}, 云带B={span_b}")
        # 23: 买入, 1101: 单股单账号普通下单
        passorder(23, 1101, ContextInfo.account_id, stock_code, 5, -1, ContextInfo.trade_vol, ContextInfo)
        
    # 卖出操作
    elif sell_signal and position > 0:
        print(f"[{ContextInfo.stock_code}] 触发卖出信号: 价格={close}, 转换线={tenkan}, 基准线={kijun}")
        # 24: 卖出
        passorder(24, 1101, ContextInfo.account_id, stock_code, 5, -1, position, ContextInfo)

    # 绘图 (可选,用于回测界面观察)
    ContextInfo.paint('Tenkan', tenkan, -1, 0, 'yellow')
    ContextInfo.paint('Kijun', kijun, -1, 0, 'blue')
    ContextInfo.paint('SpanA', span_a, -1, 0, 'green')
    ContextInfo.paint('SpanB', span_b, -1, 0, 'red')

代码要点解析

  1. 数据获取 (get_market_data_ex):

    • 一目均衡表需要较长的历史数据(至少52期用于计算 Senkou Span B,再加上26期的位移 shift,以及计算 Rolling 时的损耗),因此 count 设置为 150 以确保数据充足。
    • 使用 dividend_type='front' 进行前复权,保证技术指标计算不受分红配股影响。
  2. 云带的位移 (shift):

    • 在经典理论中,先行带 A 和 B 是画在“未来”的。
    • 在代码逻辑中,我们需要判断当前价格当前时刻对应的云带的关系。
    • 因此,代码中使用 .shift(26) 将计算出的云带值向后移动,使得 DataFrame 中当前索引 (iloc[-1]) 对应的 senkou_span_a 正是 26 天前基于当时数据预测出来的、用于当前时刻支撑/阻力的数值。
  3. 交易逻辑:

    • 买入: 采用较严格的趋势确认,要求价格完全处于云带上方(强趋势),并且短期均线(转换线)上穿中期均线(基准线)。
    • 卖出: 采用保护性止损逻辑,一旦价格跌入云层内部(趋势减弱)或发生死叉,即平仓。
  4. 下单函数 (passorder):

    • 使用 23 (买入) 和 24 (卖出) 操作码。
    • 1101 代表普通股票交易。
    • 价格类型 5 代表最新价(市价)。

Q&A

Q: 为什么代码中要用 shift(26)
A: 一目均衡表的先行带(Senkou Span)定义是将计算结果向未来平移26天。在 Pandas DataFrame 中,如果不平移,当前行的数据是基于当前收盘价计算的。为了让“今天”的行显示“26天前预测的云带值”,我们需要将数据向下(向未来)移动26行。

Q: 如何在实盘中避免信号闪烁?
A: handlebar 函数在实盘中会随每一个 Tick 更新。如果只希望在 K 线走完时确认信号,可以在逻辑中加入 if ContextInfo.is_new_bar(): 的判断,或者使用 do_order 函数配合,或者仅在收盘前几分钟运行策略。本代码使用的是实时触发逻辑。

Q: 这个策略适用于什么周期?
A: 一目均衡表传统上是为日线 (1d) 设计的。如果用于分钟线(如 15m, 60m),建议适当调整参数(9, 26, 52)以适应更高频的波动,或者保持参数不变但接受更多的噪音。