Aroon指标趋势交易策略

一、交易策略解释

核心思想

Aroon指标趋势交易策略的核心思想是基于市场趋势形成与持续的时间维度进行分析。与传统的基于价格水平或动量的指标不同,Aroon指标专注于测量自特定周期内最高价/最低价形成以来所经过的时间,从而评估趋势的强度和潜在的趋势变化。

Aroon指标由两个主要组成部分构成:

Aroon Up:测量自特定周期内最高价形成以来所经过的时间

Aroon Down:测量自特定周期内最低价形成以来所经过的时间

该策略基于以下核心假设:强势上涨趋势会频繁创造新高点(Aroon Up接近100),而强势下跌趋势会频繁创造新低点(Aroon Down接近100)。通过观察这两条线的相对位置、交叉点和极端读数,交易者可以识别趋势的初始形成、持续强度以及可能的转变,从而做出相应的交易决策。

理论基础

Aroon指标由技术分析师Tushar Chande于1995年开发,其理论基础建立在趋势识别和趋势持续性的研究上。"Aroon"在梵语中意为"黎明的早期光芒",象征着新趋势的开始。Chande的研究表明,趋势不仅体现在价格的方向上,更体现在新高/新低形成的时间频率上。

Aroon指标的理论贡献在于:

  • 引入时间维度:大多数技术指标关注价格变动的幅度和速度,而Aroon指标独特地引入了时间维度,认为强劲趋势的一个关键特征是快速创造新高或新低的能力。

  • 趋势强度量化:通过将时间因素转化为0-100的标准化范围,提供了对趋势强度的客观量化方法。

  • 趋势周期性研究:提供了识别趋势周期变化的理论框架,是对市场周期理论的重要补充。

策略适用场景

  • 明确趋势市场

  • 中长期时间框架

  • 波动率适中的市场

  • 趋势转换期

二、天勤介绍

天勤平台概述

天勤(TqSdk)是一个由信易科技开发的开源量化交易系统,为期货、期权等衍生品交易提供专业的量化交易解决方案。平台具有以下特 点:

  • 丰富的行情数据 提供所有可交易合约的全部Tick和K线数据,基于内存数据库实现零延迟访问。
  • 一站式的解决方案 从历史数据分析到实盘交易的完整工具链,打通开发、回测、模拟到实盘的全流程。
  • 专业的技术支持 近百个技术指标源码,深度集成pandas和numpy,采用单线程异步模型保证性能。

策略开发流程

  • 环境准备
    • 安装Python环境(推荐Python 3.6或以上版本)
    • 安装tqsdk包:pip install tqsdk
    • 注册天勤账户获取访问密钥
  • 数据准备
    • 订阅近月与远月合约行情
    • 获取历史K线或者Tick数据(用于分析与行情推进)
  • 策略编写
    • 设计信号生成逻辑(基于价差、均值和标准差)
    • 编写交易执行模块(开仓、平仓逻辑)
    • 实现风险控制措施(止损、资金管理)
  • 回测验证
    • 设置回测时间区间和初始资金
    • 运行策略获取回测结果
    • 分析绩效指标(胜率、收益率、夏普率等)
  • 策略优化
    • 调整参数(标准差倍数、窗口大小等)
    • 添加过滤条件(成交量、波动率等)
    • 完善风险控制机制

三、天勤实现策略

策略原理

Aroon指标由两个主要分量组成:Aroon Up和Aroon Down,计算公式如下:

Aroon Up = [(周期长度 - 自最高价形成以来的天数) / 周期长度] × 100

Aroon Down = [(周期长度 - 自最低价形成以来的天数) / 周期长度] × 100

其中:

  • 周期长度为选定的时间期间

  • 自最高/最低价形成以来的天数 是指在当前周期内,最高/最低价出现距今的天数。

  • Aroon指标的值范围为0至100:

    • 值接近100表示最高/最低价是在最近形成的(趋势强劲)

    • 值接近0表示最高/最低价是在较远时间形成的(趋势减弱)

交易逻辑

开仓信号:

该策略使用两种主要的开仓信号:

  • 交叉信号
    • 多头信号:Aroon Up从下方穿越Aroon Down
    • 空头信号:Aroon Down从下方穿越Aroon Up

代码实现:

aroon_cross_up = prev.aroon_up < prev.aroon_down and current.aroon_up > current.aroon_down
aroon_cross_down = prev.aroon_up > prev.aroon_down and current.aroon_up < current.aroon_down
  • 强势信号
    • 多头强势:Aroon Up > 75 且 Aroon Down < 25
    • 空头强势:Aroon Down > 75 且 Aroon Up < 25

代码实现:

strong_up = current.aroon_up > AROON_UPPER_THRESHOLD and current.aroon_down < AROON_LOWER_THRESHOLD
strong_down = current.aroon_down > AROON_UPPER_THRESHOLD and current.aroon_up < AROON_LOWER_THRESHOLD

平仓信号:

平仓信号基于Aroon指标的反向交叉:

  • 平多信号:Aroon Down从下方穿越Aroon Up
  • 平空信号:Aroon Up从下方穿越Aroon Down

这些反向信号往往代表趋势可能发生转变,是获利了结的好时机。

止损机制:

该策略使用基于百分比的固定止损:

  • 止损百分比设定为1.2%(STOP_LOSS_PCT = 1.2)
  • 多头止损:当价格下跌超过入场价格的1.2%时触发
  • 空头止损:当价格上涨超过入场价格的1.2%时触发

代码实现:

# 多头止损
profit_pct = (current_price / entry_price - 1) * 100
if profit_pct < -STOP_LOSS_PCT:
    # 执行止损

# 空头止损
profit_pct = (entry_price / current_price - 1) * 100
if profit_pct < -STOP_LOSS_PCT:
    # 执行止损

回测

回测初始设置

  • 测试周期: 2023 年 2 月 20 日至 2023 年 5 月 5 日
  • 交易品种: SHFE.au2306
  • 交易频率: 基于日线信号,较低频
  • 参数配置:
    • Aroon周期选择(10天)
      • 短于经典的14天设置,使指标对价格变化更为敏感
      • 适合中期趋势交易,可以更快捕捉趋势转变
      • 在黄金期货交易中,10天周期显示出较好的信号质量
    • 阈值设置(75/25)
      • 设置较高的阈值(>70)可减少虚假信号
      • 75/25的区间在历史回测中显示出较好的信噪比
      • 适合波动性较大的期货市场
    • 止损比例(1.2%)
      • 基于黄金期货的日均波动率(约0.5-0.8%)设定
      • 提供足够的价格呼吸空间,同时控制单笔风险

回测结果

上述回测累计收益走势图

完整代码示例

#!/usr/bin/env python
# -*- coding: utf-8 -*-
__author__ = "Chaos"

from tqsdk import TqApi, TqAuth, TqBacktest, TargetPosTask, BacktestFinished
import pandas as pd
from datetime import date

# ===== 全局参数设置 =====
SYMBOL = "SHFE.au2306"  # 黄金期货合约
POSITION_SIZE = 30  # 持仓手数
START_DATE = date(2023, 2, 20)  # 回测开始日期
END_DATE = date(2023, 5, 5)  # 回测结束日期

# Aroon指标参数
AROON_PERIOD = 10  # Aroon计算周期
AROON_UPPER_THRESHOLD = 75  # Aroon上线阈值
AROON_LOWER_THRESHOLD = 25  # Aroon下线阈值

# 风控参数
STOP_LOSS_PCT = 1.2  # 止损百分比

# ===== 全局变量 =====
position = 0  # 当前持仓
entry_price = 0  # 入场价格
trades = []  # 交易记录

# ===== 主程序 =====
print(f"开始回测 {SYMBOL} 的Aroon指标策略...")
print(f"参数: Aroon周期={AROON_PERIOD}, 上阈值={AROON_UPPER_THRESHOLD}, 下阈值={AROON_LOWER_THRESHOLD}")
print(f"回测期间: {START_DATE}{END_DATE}")

try:
    # 创建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)  # 日K线
    target_pos = TargetPosTask(api, SYMBOL)

    # 主循环
    while True:
        api.wait_update()

        if api.is_changing(klines.iloc[-1], "datetime"):
            # 确保有足够的数据
            if len(klines) < AROON_PERIOD + 10:
                continue
            
            # ===== 计算Aroon指标 =====
            # 找出最近N周期内最高价和最低价的位置
            klines['rolling_high'] = klines['high'].rolling(window=AROON_PERIOD).max()
            klines['rolling_low'] = klines['low'].rolling(window=AROON_PERIOD).min()

            # 初始化Aroon Up和Aroon Down数组
            aroon_up = []
            aroon_down = []

            # 遍历计算每个时间点的Aroon值
            for i in range(len(klines)):
                if i < AROON_PERIOD - 1:
                    aroon_up.append(0)
                    aroon_down.append(0)
                    continue
                
                period_data = klines.iloc[i-AROON_PERIOD+1:i+1]
                # 明确指定skipna=True来处理NaN值
                high_idx = period_data['high'].fillna(float('-inf')).argmax()
                low_idx = period_data['low'].fillna(float('inf')).argmin()
                
                days_since_high = i - (i-AROON_PERIOD+1+high_idx)
                days_since_low = i - (i-AROON_PERIOD+1+low_idx)
                
                aroon_up.append(((AROON_PERIOD - days_since_high) / AROON_PERIOD) * 100)
                aroon_down.append(((AROON_PERIOD - days_since_low) / AROON_PERIOD) * 100)

            # 将Aroon值添加到klines
            klines['aroon_up'] = aroon_up
            klines['aroon_down'] = aroon_down

            # 计算Aroon Oscillator (可选)
            klines['aroon_osc'] = klines['aroon_up'] - klines['aroon_down']

            # 获取当前和前一个周期的数据
            current_price = float(klines.close.iloc[-1])
            current_time = pd.to_datetime(klines.datetime.iloc[-1], unit='ns')
            current_aroon_up = float(klines.aroon_up.iloc[-1])
            current_aroon_down = float(klines.aroon_down.iloc[-1])
            
            prev_aroon_up = float(klines.aroon_up.iloc[-2])
            prev_aroon_down = float(klines.aroon_down.iloc[-2])

            # 输出当前指标值,帮助调试
            print(f"当前K线: {current_time.strftime('%Y-%m-%d')}, 价格: {current_price:.2f}")
            print(f"Aroon Up: {current_aroon_up:.2f}, Aroon Down: {current_aroon_down:.2f}")

            # ===== 止损检查 =====
            if position != 0 and entry_price != 0:
                if position > 0:  # 多头止损
                    profit_pct = (current_price / entry_price - 1) * 100
                    if profit_pct < -STOP_LOSS_PCT:
                        print(f"触发止损: 当前价格={current_price}, 入场价={entry_price}, 亏损={profit_pct:.2f}%")
                        target_pos.set_target_volume(0)
                        trades.append({
                            'type': '止损平多',
                            'time': current_time,
                            'price': current_price,
                            'profit_pct': profit_pct
                        })
                        print(f"止损平多: {current_time}, 价格: {current_price:.2f}, 亏损: {profit_pct:.2f}%")
                        position = 0
                        entry_price = 0
                        continue

                elif position < 0:  # 空头止损
                    profit_pct = (entry_price / current_price - 1) * 100
                    if profit_pct < -STOP_LOSS_PCT:
                        print(f"触发止损: 当前价格={current_price}, 入场价={entry_price}, 亏损={profit_pct:.2f}%")
                        target_pos.set_target_volume(0)
                        trades.append({
                            'type': '止损平空',
                            'time': current_time,
                            'price': current_price,
                            'profit_pct': profit_pct
                        })
                        print(f"止损平空: {current_time}, 价格: {current_price:.2f}, 亏损: {profit_pct:.2f}%")
                        position = 0
                        entry_price = 0
                        continue

            # ===== 交易信号判断 =====
            # 1. Aroon交叉信号
            aroon_cross_up = prev_aroon_up < prev_aroon_down and current_aroon_up > current_aroon_down
            aroon_cross_down = prev_aroon_up > prev_aroon_down and current_aroon_up < current_aroon_down

            # 2. 强势信号
            strong_up = current_aroon_up > AROON_UPPER_THRESHOLD and current_aroon_down < AROON_LOWER_THRESHOLD
            strong_down = current_aroon_down > AROON_UPPER_THRESHOLD and current_aroon_up < AROON_LOWER_THRESHOLD

            # ===== 交易决策 =====
            if position == 0:  # 空仓状态
                # 多头信号
                if aroon_cross_up or strong_up:
                    position = POSITION_SIZE
                    entry_price = current_price
                    target_pos.set_target_volume(position)
                    signal_type = "交叉" if aroon_cross_up else "强势"
                    trades.append({
                        'type': '开多',
                        'time': current_time,
                        'price': current_price,
                        'signal': signal_type
                    })
                    print(f"开多仓: {current_time}, 价格: {current_price:.2f}, 信号: Aroon {signal_type}")

                # 空头信号
                elif aroon_cross_down or strong_down:
                    position = -POSITION_SIZE
                    entry_price = current_price
                    target_pos.set_target_volume(position)
                    signal_type = "交叉" if aroon_cross_down else "强势"
                    trades.append({
                        'type': '开空',
                        'time': current_time,
                        'price': current_price,
                        'signal': signal_type
                    })
                    print(f"开空仓: {current_time}, 价格: {current_price:.2f}, 信号: Aroon {signal_type}")

            elif position > 0:  # 持有多头
                # 平多信号
                if aroon_cross_down:
                    profit_pct = (current_price / entry_price - 1) * 100
                    target_pos.set_target_volume(0)
                    trades.append({
                        'type': '平多',
                        'time': current_time,
                        'price': current_price,
                        'profit_pct': profit_pct
                    })
                    print(f"平多仓: {current_time}, 价格: {current_price:.2f}, 盈亏: {profit_pct:.2f}%")
                    position = 0
                    entry_price = 0

            elif position < 0:  # 持有空头
                # 平空信号
                if aroon_cross_up:
                    profit_pct = (entry_price / current_price - 1) * 100
                    target_pos.set_target_volume(0)
                    trades.append({
                        'type': '平空',
                        'time': current_time,
                        'price': current_price,
                        'profit_pct': profit_pct
                    })
                    print(f"平空仓: {current_time}, 价格: {current_price:.2f}, 盈亏: {profit_pct:.2f}%")
                    position = 0
                    entry_price = 0

except BacktestFinished as e:
    print("回测结束")
    api.close()