🚀 AI 一键生成 joinquant 策略代码

JoinQuant 平台如何编写 EMV 简易波动指标策略?

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

问题描述

如何编写 EMV 简易波动指标策略?

解决方案

EMV 简易波动指标策略

EMV(Ease of Movement Value,简易波动指标)是由 Richard W. Arms Jr. 开发的一种技术分析指标。它结合了价格变动和成交量,用以衡量价格变动的“难易程度”。

策略逻辑:

  1. 计算 EMV:结合最高价、最低价和成交量计算每日的 EMV 值。
  2. 计算 MAEMV:对 EMV 进行移动平均(通常为 14 日),以平滑数据。
  3. 买入信号:当 MAEMV 由下向上穿过 0 轴时,表示股价上涨阻力较小,买入。
  4. 卖出信号:当 MAEMV 由上向下穿过 0 轴时,表示股价下跌较容易或上涨乏力,卖出。

下面是基于聚宽(JoinQuant)API 实现的 EMV 策略代码。

策略代码

# -*- 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')
    
    # 设定手续费(股票类:买入万三,卖出万三加千一印花税)
    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'
    # EMV的计算周期(通常为14)
    g.N = 14
    # 成交量缩放比例(用于调整EMV数值的数量级,不影响正负方向,通常取1000000或更大)
    g.scale_factor = 100000000 
    
    # 按天运行策略
    run_daily(trade_logic, time='every_bar')

def trade_logic(context):
    """
    每日交易逻辑
    """
    security = g.security
    
    # 1. 获取历史数据
    # 我们需要计算 N 日的移动平均,且计算EMV需要用到前一日数据,
    # 所以获取 N + 2 天的数据以确保数据足够计算当天的 MAEMV
    # fields: 最高价, 最低价, 成交量
    data = attribute_history(security, g.N + 2, '1d', ['high', 'low', 'volume'])
    
    # 如果数据不足,直接返回
    if len(data) < g.N + 2:
        return

    # 2. 计算 EMV 指标
    # 提取数据列
    high = data['high']
    low = data['low']
    volume = data['volume']
    
    # 计算 Midpoint Move (MM): (今日最高+今日最低)/2 - (昨日最高+昨日最低)/2
    # high_low_avg = (high + low) / 2
    # mm = high_low_avg - high_low_avg.shift(1)
    # 使用 diff() 计算差值
    mm = ((high + low) / 2).diff()
    
    # 计算 Box Ratio (BR): (成交量 / Scale) / (最高价 - 最低价)
    # 注意:为了防止除以0(High == Low的情况),给分母加一个极小值
    high_low_diff = high - low
    high_low_diff[high_low_diff == 0] = 0.0001 # 避免除零错误
    
    br = (volume / g.scale_factor) / high_low_diff
    
    # 计算 EMV: MM / BR
    emv = mm / br
    
    # 计算 MAEMV (EMV的N日简单移动平均)
    ma_emv = emv.rolling(window=g.N).mean()
    
    # 获取当前和前一日的 MAEMV 值用于判断交叉
    # ma_emv 的最后一行是当前值,倒数第二行是前一日值
    current_maemv = ma_emv.iloc[-1]
    prev_maemv = ma_emv.iloc[-2]
    
    # 记录指标值以便在回测图中查看
    record(MAEMV=current_maemv)
    
    # 3. 交易信号判断与执行
    curr_position = context.portfolio.positions[security].closeable_amount
    available_cash = context.portfolio.available_cash
    
    # 信号:MAEMV 上穿 0 轴 -> 买入
    if prev_maemv < 0 and current_maemv > 0:
        if available_cash > 0:
            log.info("MAEMV上穿0轴,买入 %s" % security)
            order_value(security, available_cash)
            
    # 信号:MAEMV 下穿 0 轴 -> 卖出
    elif prev_maemv > 0 and current_maemv < 0:
        if curr_position > 0:
            log.info("MAEMV下穿0轴,卖出 %s" % security)
            order_target(security, 0)

代码解析

  1. 初始化 (initialize):

    • 设置了基准指数(沪深300)和动态复权(use_real_price),这是回测的标准配置。
    • 定义了全局变量 g.N 为 14,这是 EMV 指标的标准周期。
    • 定义了 g.scale_factor。EMV 公式中成交量通常很大,而价格波动很小,直接计算会导致 Box Ratio 极大,EMV 极小。除以一个缩放因子可以让数值更易读。虽然这不影响 0 轴交叉的逻辑,但对于 record 画图和数据观察很有必要。
  2. 数据获取:

    • 使用 attribute_history 获取 high, low, volume 数据。
    • 获取长度为 g.N + 2,因为计算差分(diff)会损失一天数据,计算移动平均(rolling)需要 N 天窗口。
  3. 指标计算:

    • Midpoint Move (MM): 衡量当日中间价相对于昨日中间价的变动。
    • Box Ratio (BR): 衡量成交量相对于价格范围的比率。如果价格波动大但成交量小,BR 就小,意味着价格变动“容易”。
    • EMV: MM 除以 BR。
    • MAEMV: 对 EMV 进行 N 日平滑处理,减少假信号。
  4. 交易逻辑:

    • 使用 prev_maemv < 0current_maemv > 0 判断金叉(上穿 0 轴)。
    • 使用 prev_maemv > 0current_maemv < 0 判断死叉(下穿 0 轴)。
    • 使用 order_value 全仓买入,order_target 清仓卖出。

Q&A

Q1: 为什么计算 Box Ratio 时要给分母加一个极小值?
A: 在某些交易日(如一字涨停或跌停,或者极度窄幅震荡),最高价可能等于最低价(High - Low = 0)。在计算机中除以 0 会导致无穷大(inf)或错误(NaN),从而导致策略崩溃。加上 0.0001 是为了保证程序的健壮性。

Q2: 这个策略适合什么类型的市场?
A: EMV 是一个趋势指标。它在趋势明显的市场中表现较好,因为它能识别出伴随成交量支持的价格移动。在横盘震荡市场中,EMV 可能会频繁在 0 轴附近波动,导致频繁交易和亏损。

Q3: 如何优化这个策略?
A:

  1. 多因子结合:EMV 仅考虑价量关系,可以结合趋势指标(如 MA、MACD)来过滤信号。
  2. 止损止盈:当前代码仅根据反向信号平仓,建议加入固定的止损百分比或跟踪止损。
  3. 股票池:当前代码仅针对单只股票(平安银行),可以结合 get_index_stocks 扩展到多只股票轮动。