问题描述
解决方案
这是一个基于 Fisher Transform (费舍尔变换) 指标的反转交易策略。
策略原理
Fisher Transform 是一种将价格数据(通常不服从正态分布)转换为近似高斯正态分布的方法。通过这种转换,价格的极端转折点(超买/超卖)会变得更加清晰。
核心逻辑:
- 计算 Fisher 值:利用过去 N 天的最高价和最低价计算价格在区间内的相对位置,并进行对数变换和平滑处理。
- 买入信号:当 Fisher 指标低于超卖阈值(例如 -2.0)并开始拐头向上时,视为反转买入信号。
- 卖出信号:当 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)
策略说明
- 数据获取与平滑:
calculate_fisher函数中,我们获取了过去 60 天的数据。虽然参数N设为 10,但 Fisher Transform 包含递归平滑公式(即当前值依赖于前一天的值),因此需要较长的历史数据来让计算结果收敛稳定,消除初始值设为 0 带来的误差。
- 数值限制:
- 在计算对数
math.log之前,必须将输入值限制在(-0.999, 0.999)之间。如果价格剧烈波动导致归一化值达到 1 或 -1,对数函数会趋向无穷大或报错。
- 在计算对数
- 交易逻辑:
- 买入:寻找“超卖反弹”的时机。要求前一天的 Fisher 值小于 -2(处于极度超卖区域),且今天的 Fisher 值大于昨天(开始反弹)。
- 卖出:寻找“超买回落”的时机。要求前一天的 Fisher 值大于 2(处于极度超买区域),且今天的 Fisher 值小于昨天(开始下跌)。
- 可视化:
- 使用
record函数将 Fisher 值绘制在回测图表中,方便观察指标与股价的对应关系。
- 使用
Q&A
Q: 为什么 Fisher Transform 需要递归计算?
A: 原始的 Ehlers Fisher Transform 算法中包含平滑步骤(0.33 * raw + 0.67 * prev),这种设计是为了过滤掉高频噪声,使指标曲线更平滑,从而减少假信号。
Q: 如何调整策略的灵敏度?
A: 可以调整两个参数:
g.N(周期):减小 N(如 5)会使指标更灵敏,但噪音更多;增大 N(如 20)会使指标更滞后,但信号更可靠。g.threshold(阈值):降低阈值(如 1.5)会增加交易频率;提高阈值(如 2.5)会只捕捉极端的反转机会。
Q: 这个策略适合什么市场环境?
A: Fisher Transform 是典型的均值回归(震荡)策略。它在震荡市中表现最好,能够捕捉高抛低吸的机会。在单边强趋势行情中,指标可能会长时间停留在超买或超卖区域(钝化),导致过早离场或逆势抄底,因此建议结合趋势过滤器(如均线)一起使用。