🚀 AI 一键生成 ptrade 策略代码

PTrade 量化开发:EMV 简易波动指标的算法实现与应用

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

问题描述

怎么编写一个 EMV 简易波动指标的交易策略?

解决方案

EMV (简易波动指标) 交易策略

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

策略原理

EMV 的核心理念是:

  1. 价格上涨且成交量低:表示上涨阻力小,价格能够“轻松”移动,EMV 为正值。
  2. 价格下跌且成交量低:表示下跌支撑弱,价格能够“轻松”下跌,EMV 为负值。
  3. 成交量大但价格变动小:表示多空争夺激烈,移动困难,EMV 趋向于 0。

交易信号:

  • 买入信号:EMV 指标(通常是 EMV 的移动平均线,如 EMV14)由下向上穿越 0 轴。
  • 卖出信号:EMV 指标由上向下穿越 0 轴。

PTrade 策略代码实现

以下是一个完整的 PTrade 策略代码。该策略计算 14 日 EMV 均线,并在金叉 0 轴时买入,死叉 0 轴时卖出。

import numpy as np
import pandas as pd

def initialize(context):
    """
    初始化函数,设置股票池和全局变量
    """
    # 设置要操作的股票,这里以恒生电子为例
    g.security = '600570.SS'
    set_universe(g.security)
    
    # EMV指标参数
    g.N = 14        # EMV的平滑周期,通常取14天
    g.scale = 10000 # 成交量缩放比例,用于调整数值大小,不影响正负方向
    
    # 设置手续费(可选,回测时建议设置)
    set_commission(commission_ratio=0.0003, min_commission=5.0, type="STOCK")

def handle_data(context, data):
    """
    盘中运行函数,每日运行一次(日线级别)
    """
    security = g.security
    
    # 1. 获取历史数据
    # 我们需要计算N日的MAEMV,且计算EMV需要用到前一日数据(diff),
    # 所以获取 N + 10 天的数据以确保有足够的窗口进行计算
    hist_len = g.N + 10
    df = get_history(hist_len, frequency='1d', field=['high', 'low', 'volume'], security_list=security, fq='pre')
    
    # 如果数据不足,直接返回
    if len(df) < hist_len:
        return

    # 2. 计算 EMV 指标
    # 提取数据序列
    high = df['high']
    low = df['low']
    volume = df['volume']
    
    # 2.1 计算 MidPoint Move (中间价变动)
    # 公式: ((今日最高+今日最低)/2 - (昨日最高+昨日最低)/2)
    mid_point = (high + low) / 2
    mid_move = mid_point.diff() # 计算与前一日的差值
    
    # 2.2 计算 Box Ratio (箱体比率)
    # 公式: (成交量 / Scale) / (今日最高 - 今日最低)
    # 注意:为了防止除以0(当最高价等于最低价时),将0替换为一个极小值
    price_range = high - low
    price_range = price_range.replace(0, 0.0001) 
    
    box_ratio = (volume / g.scale) / price_range
    
    # 2.3 计算 1日 EMV
    # 公式: MidPoint Move / Box Ratio
    emv_1d = mid_move / box_ratio
    
    # 2.4 计算 MAEMV (EMV的移动平均线)
    # 通常使用 N 周期的简单移动平均
    ma_emv = emv_1d.rolling(window=g.N).mean()
    
    # 3. 获取当前和前一日的指标值用于判断交叉
    # iloc[-1] 为今日值(回测中为当前回测日),iloc[-2] 为昨日值
    curr_emv = ma_emv.iloc[-1]
    prev_emv = ma_emv.iloc[-2]
    
    # 检查是否计算出有效值(剔除NaN)
    if np.isnan(curr_emv) or np.isnan(prev_emv):
        return

    # 4. 交易逻辑
    # 获取当前持仓
    position = get_position(security).amount
    cash = context.portfolio.cash
    
    # 买入信号:EMV 均线向上突破 0 轴
    if prev_emv < 0 and curr_emv > 0:
        if cash > 0:
            # 全仓买入
            order_value(security, cash)
            log.info("EMV上穿0轴,买入 %s,当前EMV: %.4f" % (security, curr_emv))
            
    # 卖出信号:EMV 均线向下跌破 0 轴
    elif prev_emv > 0 and curr_emv < 0:
        if position > 0:
            # 清仓卖出
            order_target(security, 0)
            log.info("EMV下穿0轴,卖出 %s,当前EMV: %.4f" % (security, curr_emv))

代码解析

  1. 数据获取 (get_history)

    • 我们需要 high (最高价), low (最低价), 和 volume (成交量) 来计算 EMV。
    • 获取长度设置为 g.N + 10,是为了确保在使用 rolling(window=g.N) 计算移动平均时,前面有足够的数据填充,避免结果为 NaN。
  2. 指标计算逻辑

    • MidPoint Move (中间价移动):衡量价格重心的移动方向。
    • Box Ratio (箱体比率):衡量成交量相对于价格波动范围的比率。如果成交量很大但价格波动很小(Box Ratio 大),说明移动困难;反之则移动容易。
    • Scale (缩放因子):代码中 g.scale = 10000。这是为了让 Box Ratio 的数值不至于过大,从而使最终的 EMV 数值更易读。这个参数不改变指标的正负方向,只改变数值的绝对大小。
    • 防除零处理:在计算 price_range 时,如果当日最高价等于最低价(如一字涨跌停),分母为 0 会导致报错或无穷大。代码使用了 .replace(0, 0.0001) 进行保护。
  3. 交易信号

    • 使用 prev_emv < 0 and curr_emv > 0 判断上穿 0 轴。
    • 使用 prev_emv > 0 and curr_emv < 0 判断下穿 0 轴。

策略优化建议

EMV 是一个趋势跟随指标,在震荡市中可能会频繁发出虚假信号。在实际应用中,可以考虑以下优化方向:

  1. 结合趋势指标:例如,只有当股价在 60 日均线之上时,才执行 EMV 的买入信号(过滤掉下跌趋势中的反弹)。
  2. 调整周期:默认 14 天是中期参数。如果是短线交易,可以尝试缩短周期(如 9 天);如果是长线,可以延长周期。
  3. 止损机制:EMV 反应的是“轻松程度”,并不直接指示价格支撑位。建议加入固定比例止损(如亏损 5% 止损)或跟踪止损。

Q&A

Q1: 为什么计算 Box Ratio 时要除以 Scale (10000)?
A1: 在股票市场中,成交量(Volume)通常是数百万甚至上亿股,而价格波动(High - Low)通常只有几块钱。直接相除会导致 Box Ratio 数值极大,进而导致 EMV 数值极小(接近 0)。除以 Scale 是为了调整量纲,让 EMV 的读数更符合人类阅读习惯,它不会改变指标的形态和交叉信号。

Q2: EMV 指标适合什么类型的股票?
A2: EMV 比较适合股性活跃、成交量变化明显的股票。对于长期横盘、成交量极度低迷或者被高度控盘(成交量失真)的股票,EMV 的参考意义较小。

Q3: 代码中的 fq='pre' 是什么意思?
A3: fq='pre' 表示使用前复权数据。在量化回测中,为了消除分红、配股、拆股等对价格造成的跳空影响,必须使用复权数据。前复权可以保证当前价格与实际盘面一致,是回测中最常用的复权方式。