自我刻度·记录 文章详情

← 返回主页

涡旋指标期货量化策略

一、交易策略解释

核心思想

涡旋指标(Vortex Indicator,VI)量化交易策略的核心思想是利用价格运动中的"涡旋"模式来识别和跟踪市场趋势。 该策略基于价格运动在形成趋势时展现的特定方向性运动模式,通过比较正向涡旋指标(+VI)和负向涡旋指标(-VI)的相对强度来确定趋势的方向和力度。

当+VI线上穿-VI线时,表明上升动量增强,可能形成上升趋势,产生做多信号;当-VI线上穿+VI线时,表明下降动量增强,可能形成下降趋势,产生做空信号。 该策略的本质是抓住市场趋势的起始阶段并伴随趋势发展获利,同时通过涡旋指标值的大小判断趋势强度,协助进行动态仓位管理。

理论基础

涡旋指标由Etienne Botes和Douglas Siepman于2010年首次引入交易社区,其理论基础源于自然界中涡旋流动的现象(如水流绕过障碍物时形成的涡流模式)。 该指标理论受到J. Welles Wilder方向运动概念的影响,认为连续价格柱之间的关系可以提供关于市场方向的有价值见解。学术研究表明,价格运动在形成趋势时确实表现出类似涡旋的特性:

  • 价格波动的方向性集聚:当市场形成趋势时,价格波动会在某一方向上集聚,表现为连续的高点比前一个高点更高,低点比前一个低点更高(上升趋势);或连续的低点比前一个低点更低,高点比前一个高点更低(下降趋势)。

  • 多周期相关性:趋势形成通常跨越多个时间周期,形成自相似的价格模式,这与自然界中的涡旋结构类似。

  • 动量累积效应:市场趋势往往伴随着动量的积累过程,这可以通过涡旋指标中+VI和-VI的相对变化捕捉到。

策略适用场景

  • 明显趋势市场:策略在明显趋势形成时表现最佳,特别是在大宗商品、外汇和期货等波动性较大的市场中。

  • 中长期交易:虽然涡旋指标可用于不同时间框架,但通常在日线、4小时线等中长期时间框架上表现更佳,可以过滤掉短期噪音。

  • 高波动性品种:指标在波动较大的期货品种中能更好地捕捉趋势起点,如能源期货、金属期货和农产品期货等。

  • 宏观经济转折期:在宏观经济出现明显转折,导致商品价格趋势发生改变时,涡旋指标能较早捕捉到这种变化。

  • 季节性波动市场:对于具有季节性波动特征的商品期货,涡旋指标能够有效识别季节性价格趋势的开始。

    策略原理

    涡旋指标(VI)的计算涉及以下几个步骤:

    1. 计算真实范围(TR): TR = max(当前高点 - 当前低点, |当前高点 - 前一收盘价|, |当前低点 - 前一收盘价|)

    2. 计算正向涡旋运动(+VM): +VM = |当前高点 - 前一低点|

    3. 计算负向涡旋运动(-VM): -VM = |当前低点 - 前一高点|

    4. 计算N周期内TR、+VM和-VM的总和: 通常N=14,但可根据不同市场条件优化

    5. 计算正向涡旋指标(+VI): +VI = (N周期+VM总和) / (N周期TR总和)

    6. 计算负向涡旋指标(-VI): -VI = (N周期-VM总和) / (N周期TR总和)

    交易逻辑

    • 入场信号:

      • 当+VI上穿-VI时,产生做多信号

      • 当-VI上穿+VI时,产生做空信号

      • 为减少假信号,加入条件:只有当穿越线的VI值大于1时,信号才有效

    • 出场信号:

      • 当持有多头仓位时,如果-VI上穿+VI,平仓离场

      • 当持有空头仓位时,如果+VI上穿-VI,平仓离场

    • 止损设置:

      • 基于ATR(平均真实范围)设置动态止损

      • 多头止损位 = 入场价 - 2 * ATR

      • 空头止损位 = 入场价 + 2 * ATR

    回测

    回测初始设置

    • 测试周期: 2022 年 11 月 1 日至 2023 年 4 月 19 日
    • 交易品种: CFFEX.IC2306
    • 初始资金: 1000万元

    回测结果

    涡旋指标期货量化策略 - 图片 1

    上述回测累计收益走势图

    涡旋指标期货量化策略 - 图片 2

    代码

    #!/usr/bin/env python
    # -*- coding: utf-8 -*-
    __author__ = "Chaos"
    
    from datetime import date
    import numpy as np
    import pandas as pd
    from tqsdk import TqApi, TqAuth, TqBacktest, TargetPosTask, BacktestFinished
    from tqsdk.ta import ATR
    
    # ===== 全局参数设置 =====
    SYMBOL = "CFFEX.IC2306"  # 螺纹钢期货合约
    POSITION_SIZE = 30  # 固定交易手数
    START_DATE = date(2022, 11, 1)  # 回测开始日期
    END_DATE = date(2023, 4, 19)  # 回测结束日期
    
    # 涡旋指标参数
    VI_PERIOD = 14  # 涡旋指标周期
    ATR_PERIOD = 14  # ATR指标周期
    ATR_MULTIPLIER = 2.0  # 止损倍数
    VI_THRESHOLD = 1.0  # VI值阈值,筛选强度较大的信号
    
    # ===== 全局变量 =====
    current_direction = 0   # 当前持仓方向:1=多头,-1=空头,0=空仓
    entry_price = 0         # 开仓价格
    stop_loss_price = 0     # 止损价格
    
    # ===== 涡旋指标计算函数 =====
    def calculate_vortex(df, period=14):
        """计算涡旋指标"""
        # 计算真实范围(TR)
        df['tr'] = np.maximum(
            np.maximum(
                df['high'] - df['low'],
                np.abs(df['high'] - df['close'].shift(1))
            ),
            np.abs(df['low'] - df['close'].shift(1))
        )
        
        # 计算正向涡旋运动(+VM)
        df['plus_vm'] = np.abs(df['high'] - df['low'].shift(1))
        
        # 计算负向涡旋运动(-VM)
        df['minus_vm'] = np.abs(df['low'] - df['high'].shift(1))
        
        # 计算N周期内的总和
        df['tr_sum'] = df['tr'].rolling(window=period).sum()
        df['plus_vm_sum'] = df['plus_vm'].rolling(window=period).sum()
        df['minus_vm_sum'] = df['minus_vm'].rolling(window=period).sum()
        
        # 计算涡旋指标
        df['plus_vi'] = df['plus_vm_sum'] / df['tr_sum']
        df['minus_vi'] = df['minus_vm_sum'] / df['tr_sum']
        
        return df
    
    # ===== 策略开始 =====
    print("开始运行涡旋指标(VI)期货策略...")
    
    # 创建API实例
    api = TqApi(backtest=TqBacktest(start_dt=START_DATE, end_dt=END_DATE),
                auth=TqAuth("快期账号", "快期密码"))
    
    # 订阅合约的日K线数据
    klines = api.get_kline_serial(SYMBOL, 60 * 60 * 24)  # 日线数据
    
    # 创建目标持仓任务
    target_pos = TargetPosTask(api, SYMBOL)
    
    try:
        while True:
            # 等待更新
            api.wait_update()
            
            # 如果K线有更新
            if api.is_changing(klines.iloc[-1], "datetime"):
                # 确保有足够的数据计算指标
                if len(klines) < max(VI_PERIOD, ATR_PERIOD) + 5:
                    continue
                
                # 计算涡旋指标
                df = pd.DataFrame(klines)
                df = calculate_vortex(df, VI_PERIOD)
                
                # 计算ATR
                atr_data = ATR(klines, ATR_PERIOD)
                current_atr = float(atr_data.atr.iloc[-1])
                
                # 获取最新和前一个周期的数据
                current_price = float(klines.close.iloc[-1])
                current_plus_vi = float(df.plus_vi.iloc[-1])
                current_minus_vi = float(df.minus_vi.iloc[-1])
                
                prev_plus_vi = float(df.plus_vi.iloc[-2])
                prev_minus_vi = float(df.minus_vi.iloc[-2])
                
                # 获取当前日期
                current_datetime = pd.to_datetime(klines.datetime.iloc[-1], unit='ns')
                date_str = current_datetime.strftime('%Y-%m-%d')
                
                # 输出调试信息
                print(f"日期: {date_str}, 价格: {current_price}, +VI: {current_plus_vi:.4f}, -VI: {current_minus_vi:.4f}, ATR: {current_atr:.2f}")
                
                # ===== 交易逻辑 =====
                
                # 空仓状态 - 寻找开仓机会
                if current_direction == 0:
                    # 多头信号: +VI上穿-VI且+VI > 阈值
                    if prev_plus_vi <= prev_minus_vi and current_plus_vi > current_minus_vi and current_plus_vi > VI_THRESHOLD:
                        # 设置入场价格
                        entry_price = current_price
                        
                        # 设置止损价格
                        stop_loss_price = entry_price - ATR_MULTIPLIER * current_atr
                        
                        # 设置持仓方向和目标持仓
                        current_direction = 1
                        target_pos.set_target_volume(POSITION_SIZE)
                        
                        print(f"多头开仓: 价格={entry_price}, 手数={POSITION_SIZE}, 止损价={stop_loss_price:.2f}")
                    
                    # 空头信号: -VI上穿+VI且-VI > 阈值
                    elif prev_minus_vi <= prev_plus_vi and current_minus_vi > current_plus_vi and current_minus_vi > VI_THRESHOLD:
                        # 设置入场价格
                        entry_price = current_price
                        
                        # 设置止损价格
                        stop_loss_price = entry_price + ATR_MULTIPLIER * current_atr
                        
                        # 设置持仓方向和目标持仓
                        current_direction = -1
                        target_pos.set_target_volume(-POSITION_SIZE)
                        
                        print(f"空头开仓: 价格={entry_price}, 手数={POSITION_SIZE}, 止损价={stop_loss_price:.2f}")
                
                # 多头持仓 - 检查平仓条件
                elif current_direction == 1:
                    # 条件1: 止损触发
                    if current_price <= stop_loss_price:
                        profit_pct = (current_price - entry_price) / entry_price * 100
                        target_pos.set_target_volume(0)
                        current_direction = 0
                        print(f"多头止损平仓: 价格={current_price}, 盈亏={profit_pct:.2f}%")
                    
                    # 条件2: 信号反转 (-VI上穿+VI)
                    elif prev_minus_vi <= prev_plus_vi and current_minus_vi > current_plus_vi:
                        profit_pct = (current_price - entry_price) / entry_price * 100
                        target_pos.set_target_volume(0)
                        current_direction = 0
                        print(f"多头信号平仓: 价格={current_price}, 盈亏={profit_pct:.2f}%")
                
                # 空头持仓 - 检查平仓条件
                elif current_direction == -1:
                    # 条件1: 止损触发
                    if current_price >= stop_loss_price:
                        profit_pct = (entry_price - current_price) / entry_price * 100
                        target_pos.set_target_volume(0)
                        current_direction = 0
                        print(f"空头止损平仓: 价格={current_price}, 盈亏={profit_pct:.2f}%")
                    
                    # 条件2: 信号反转 (+VI上穿-VI)
                    elif prev_plus_vi <= prev_minus_vi and current_plus_vi > current_minus_vi:
                        profit_pct = (entry_price - current_price) / entry_price * 100
                        target_pos.set_target_volume(0)
                        current_direction = 0
                        print(f"空头信号平仓: 价格={current_price}, 盈亏={profit_pct:.2f}%")
    
    except BacktestFinished as e:
        print(f"策略运行异常: {e}")
        api.close()

投资思想 · 其他文章 (273)