🚀 AI 一键生成 joinquant 策略代码

JoinQuant 策略教程:利用 VHF 指标区分震荡与趋势行情

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

问题描述

请提供一个基于 VHF 指标的趋势过滤策略代码

解决方案

这是一个基于 VHF (Vertical Horizontal Filter,垂直水平过滤指标) 的趋势过滤策略。

策略原理

VHF 指标主要用于判断市场是处于趋势状态还是震荡状态,它本身不指示方向,只指示趋势的强度。

  1. VHF 计算公式
    • 分子 = N周期内最高收盘价 - N周期内最低收盘价
    • 分母 = N周期内每日收盘价变动绝对值的总和
    • VHF = 分子 / 分母
  2. 策略逻辑
    • 趋势判断:当 VHF 值较高时,表明处于趋势行情;当 VHF 值较低时,表明处于震荡行情。
    • 方向判断:由于 VHF 不具备方向性,我们结合 双均线 (MA) 来判断买卖方向。
    • 买入条件:短期均线 > 长期均线(多头排列) VHF > 设定阈值(确认趋势强度足够)。
    • 卖出条件:短期均线 < 长期均线(空头排列) VHF < 设定阈值(趋势减弱进入震荡)。

策略代码

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

def initialize(context):
    """
    初始化函数,设定基准、手续费、滑点及策略参数
    """
    # 设定沪深300作为基准
    set_benchmark('000300.XSHG')
    # 开启动态复权模式(真实价格)
    set_option('use_real_price', True)
    
    # 过滤掉order系列API产生的比error级别低的log
    log.set_level('order', 'error')
    
    # 设定手续费:股票类每笔交易时的手续费是:买入时佣金万分之三,卖出时佣金万分之三加千分之一印花税, 每笔交易佣金最低扣5块钱
    set_order_cost(OrderCost(close_tax=0.001, open_commission=0.0003, close_commission=0.0003, min_commission=5), type='stock')
    
    # --- 策略参数设置 ---
    # 操作标的:这里以沪深300ETF为例,也可以换成具体股票
    g.security = '510300.XSHG' 
    
    # VHF指标周期 N
    g.vhf_n = 28
    
    # VHF过滤阈值 (通常在0.3到0.4之间,需根据标的波动特性调整)
    # 大于此值认为有趋势,小于此值认为震荡
    g.vhf_threshold = 0.30
    
    # 均线周期,用于判断方向
    g.ma_short_window = 10
    g.ma_long_window = 30
    
    # 每天开盘时运行
    run_daily(market_open, time='09:30')

def calculate_vhf(security, n):
    """
    计算VHF指标
    VHF = (HCP - LCP) / Sum of absolute close price changes
    """
    # 获取过去 N+1 天的数据(因为计算变动需要多一天前值)
    # 注意:attribute_history 获取的是不包含当天的历史数据
    data = attribute_history(security, n + 1, '1d', ['close'])
    closes = data['close'].values
    
    if len(closes) < n + 1:
        return 0
    
    # 取最近 N 天的收盘价用于计算最高最低
    recent_n_closes = closes[-n:]
    
    # 分子:N周期内最高价 - 最低价
    hcp = np.max(recent_n_closes)
    lcp = np.min(recent_n_closes)
    numerator = hcp - lcp
    
    # 分母:N周期内收盘价变动绝对值之和
    # np.diff 计算相邻元素差值
    diffs = np.abs(np.diff(closes))
    denominator = np.sum(diffs[-n:])
    
    if denominator == 0:
        return 0
        
    vhf = numerator / denominator
    return vhf

def market_open(context):
    """
    每日交易逻辑
    """
    security = g.security
    
    # 1. 获取均线数据
    # 获取足够长度的历史数据以计算长期均线
    hist_data = attribute_history(security, g.ma_long_window, '1d', ['close'])
    if len(hist_data) < g.ma_long_window:
        return
        
    closes = hist_data['close'].values
    
    # 计算短期和长期均线
    ma_short = np.mean(closes[-g.ma_short_window:])
    ma_long = np.mean(closes)
    
    # 2. 计算 VHF 指标
    vhf_value = calculate_vhf(security, g.vhf_n)
    
    # 3. 获取当前持仓和资金
    position = context.portfolio.positions[security]
    cash = context.portfolio.available_cash
    
    # 记录指标值方便回测查看
    record(VHF=vhf_value, MA_Short=ma_short, MA_Long=ma_long)
    
    # --- 交易信号判断 ---
    
    # 信号1:趋势强度足够 (VHF > 阈值)
    is_trending = vhf_value > g.vhf_threshold
    
    # 信号2:方向判断 (短均线 > 长均线)
    is_bullish = ma_short > ma_long
    
    # 买入逻辑:
    # 1. 处于上升趋势 (均线金叉)
    # 2. 且 VHF 指示当前处于趋势行情中 (过滤掉均线纠缠的震荡期)
    if is_bullish and is_trending:
        if position.closeable_amount == 0:
            log.info("趋势确立且向上,VHF: %.3f, 买入 %s" % (vhf_value, security))
            order_value(security, cash)
            
    # 卖出逻辑:
    # 1. 趋势反转 (均线死叉)
    # 2. 或者 VHF 指示进入震荡期 (趋势消失,获利了结或止损)
    elif (not is_bullish) or (not is_trending):
        if position.closeable_amount > 0:
            reason = "均线死叉" if not is_bullish else "进入震荡(VHF过低)"
            log.info("卖出信号触发 [%s],VHF: %.3f, 卖出 %s" % (reason, vhf_value, security))
            order_target(security, 0)

策略要点说明

  1. VHF 的计算细节
    • 分母计算需要 N 个变动值,因此在 attribute_history 中我们需要获取 N+1 个收盘价数据,利用 np.diff 计算每日的差值。
  2. 阈值选择 (g.vhf_threshold)
    • 代码中默认设置为 0.30
    • 如果标的波动剧烈(如创业板),VHF 普遍较高,可能需要调高阈值。
    • 如果标的走势平稳(如银行股),可能需要调低阈值。
  3. 结合均线
    • VHF 最大的缺点是无法区分上涨趋势还是下跌趋势。因此,代码中引入了 MA10MA30。只有当均线呈多头排列且 VHF 确认趋势存在时才买入。
  4. 止盈止损
    • 本策略通过 VHF 下穿阈值(趋势结束)或均线死叉(趋势反转)作为自然的离场信号。

Q&A

Q: 如何优化 VHF 阈值?
A: 固定阈值(如0.3)可能无法适应所有市场阶段。一种优化方法是使用动态阈值,例如计算过去 100 天 VHF 的移动平均值或分位数,当当前 VHF 大于其历史平均值时视为趋势强烈。

Q: 这个策略适合什么标的?
A: 该策略适合具有明显趋势性的标的,如宽基指数 ETF(沪深300、中证500)或强周期性行业股票。对于长期横盘织布的股票,VHF 会长期低于阈值,导致策略一直空仓,这本身也是一种保护机制。

Q: 为什么使用 attribute_history 而不是 get_price
A: 在 JoinQuant 的回测引擎(run_dailyhandle_data)中,attribute_history 是获取回测当前时间点之前历史数据的标准且高效的方法,它会自动处理复权和停牌填充,且不会引入未来数据。