🚀 AI 一键生成 joinquant 策略代码

JoinQuant 高级指标:费舍尔变换(Fisher Transform)捕捉价格极值

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

问题描述

请实现一个基于 Fisher Transform 的反转交易策略

解决方案

这是一个基于 Fisher Transform (费舍尔变换) 指标的反转交易策略。

策略原理

Fisher Transform 是一种将价格数据(通常不服从正态分布)转换为近似高斯正态分布的方法。通过这种转换,价格的极端转折点(超买/超卖)会变得更加清晰。

核心逻辑:

  1. 计算 Fisher 值:利用过去 N 天的最高价和最低价计算价格在区间内的相对位置,并进行对数变换和平滑处理。
  2. 买入信号:当 Fisher 指标低于超卖阈值(例如 -2.0)并开始拐头向上时,视为反转买入信号。
  3. 卖出信号:当 Fisher 指标高于超买阈值(例如 2.0)并开始拐头向下时,视为反转卖出信号(或平仓)。

策略代码

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

def initialize(context):
    """
    初始化函数
    """
    # 设定基准
    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')
    
    # --- 策略参数设置 ---
    g.security = '000001.XSHE'  # 操作标的:平安银行
    g.N = 10                    # 计算周期
    g.threshold = 2.0           # 阈值(超买/超卖界限)
    
    # 运行函数
    run_daily(trade, 'every_bar')

def calculate_fisher(security, n_days, data_len=50):
    """
    计算 Fisher Transform 指标
    输入:标的,周期N,获取历史数据长度(需要足够长度以平滑数据)
    输出:当前的 Fisher 值,上一期的 Fisher 值
    """
    # 获取历史数据 (High, Low)
    # 获取比 n_days 长的数据是为了让递归平滑算法稳定
    h_data = attribute_history(security, data_len, '1d', ['high', 'low'])
    
    highs = h_data['high'].values
    lows = h_data['low'].values
    
    # 初始化数组
    length = len(highs)
    value1 = np.zeros(length)
    fisher = np.zeros(length)
    
    # 遍历计算 (Ehlers 算法通常包含递归平滑,无法完全向量化,循环计算更准确)
    for i in range(length):
        if i < n_days:
            continue
            
        # 获取过去 N 天的最高价和最低价
        # 注意:切片是 [i-n_days+1 : i+1] 包含当前点 i
        window_high = np.max(highs[i-n_days+1 : i+1])
        window_low = np.min(lows[i-n_days+1 : i+1])
        
        # 计算中间价位置,并归一化到 [-1, 1]
        # 防止除以0
        divisor = window_high - window_low
        if divisor == 0:
            divisor = 0.00001
            
        mid_price = (highs[i] + lows[i]) / 2
        
        # 原始归一化值
        raw_val = 2 * ((mid_price - window_low) / divisor) - 1
        
        # 平滑处理 (Ehlers 推荐的平滑系数)
        # Value1[i] = 0.33 * raw_val + 0.67 * Value1[i-1]
        if i == 0:
            value1[i] = 0
        else:
            value1[i] = 0.33 * raw_val + 0.67 * value1[i-1]
            
        # 限制数值范围在 (-0.999, 0.999) 之间,防止对数计算出错
        if value1[i] > 0.999:
            value1[i] = 0.999
        if value1[i] < -0.999:
            value1[i] = -0.999
            
        # 计算 Fisher Transform
        # Fisher[i] = 0.5 * ln((1 + Value1[i]) / (1 - Value1[i])) + 0.5 * Fisher[i-1]
        if i == 0:
            fisher[i] = 0
        else:
            fisher[i] = 0.5 * math.log((1 + value1[i]) / (1 - value1[i])) + 0.5 * fisher[i-1]
            
    # 返回当前值和前一天的值
    return fisher[-1], fisher[-2]

def trade(context):
    """
    每日交易逻辑
    """
    security = g.security
    
    # 1. 获取 Fisher 指标
    # 获取足够长的数据以消除初始值的偏差
    current_fisher, prev_fisher = calculate_fisher(security, g.N, data_len=60)
    
    # 获取当前持仓
    position = context.portfolio.positions[security]
    
    # 2. 生成信号
    # 信号逻辑:
    # 买入:Fisher < -2 (超卖) 且 当前值 > 昨日值 (拐头向上)
    # 卖出:Fisher > 2 (超买) 且 当前值 < 昨日值 (拐头向下)
    
    signal_buy = (prev_fisher < -g.threshold) and (current_fisher > prev_fisher)
    signal_sell = (prev_fisher > g.threshold) and (current_fisher < prev_fisher)
    
    # 3. 执行交易
    if signal_buy:
        # 如果没有持仓,则全仓买入
        if position.total_amount == 0:
            log.info("买入信号触发: Fisher由 %.2f 回升至 %.2f" % (prev_fisher, current_fisher))
            order_value(security, context.portfolio.available_cash)
            
    elif signal_sell:
        # 如果有持仓,则清仓
        if position.closeable_amount > 0:
            log.info("卖出信号触发: Fisher由 %.2f 回落至 %.2f" % (prev_fisher, current_fisher))
            order_target(security, 0)
            
    # 记录指标值用于回测图表分析
    record(Fisher=current_fisher, Threshold_Up=g.threshold, Threshold_Down=-g.threshold)

策略说明

  1. 数据获取与平滑
    • calculate_fisher 函数中,我们获取了过去 60 天的数据。虽然参数 N 设为 10,但 Fisher Transform 包含递归平滑公式(即当前值依赖于前一天的值),因此需要较长的历史数据来让计算结果收敛稳定,消除初始值设为 0 带来的误差。
  2. 数值限制
    • 在计算对数 math.log 之前,必须将输入值限制在 (-0.999, 0.999) 之间。如果价格剧烈波动导致归一化值达到 1 或 -1,对数函数会趋向无穷大或报错。
  3. 交易逻辑
    • 买入:寻找“超卖反弹”的时机。要求前一天的 Fisher 值小于 -2(处于极度超卖区域),且今天的 Fisher 值大于昨天(开始反弹)。
    • 卖出:寻找“超买回落”的时机。要求前一天的 Fisher 值大于 2(处于极度超买区域),且今天的 Fisher 值小于昨天(开始下跌)。
  4. 可视化
    • 使用 record 函数将 Fisher 值绘制在回测图表中,方便观察指标与股价的对应关系。

Q&A

Q: 为什么 Fisher Transform 需要递归计算?
A: 原始的 Ehlers Fisher Transform 算法中包含平滑步骤(0.33 * raw + 0.67 * prev),这种设计是为了过滤掉高频噪声,使指标曲线更平滑,从而减少假信号。

Q: 如何调整策略的灵敏度?
A: 可以调整两个参数:

  1. g.N(周期):减小 N(如 5)会使指标更灵敏,但噪音更多;增大 N(如 20)会使指标更滞后,但信号更可靠。
  2. g.threshold(阈值):降低阈值(如 1.5)会增加交易频率;提高阈值(如 2.5)会只捕捉极端的反转机会。

Q: 这个策略适合什么市场环境?
A: Fisher Transform 是典型的均值回归(震荡)策略。它在震荡市中表现最好,能够捕捉高抛低吸的机会。在单边强趋势行情中,指标可能会长时间停留在超买或超卖区域(钝化),导致过早离场或逆势抄底,因此建议结合趋势过滤器(如均线)一起使用。