3 分钟快速生成代码
输入想法,AI 即刻生成可运行代码
EMV(Ease of Movement Value,简易波动指标)是由 Richard W. Arms Jr. 开发的一种技术分析指标。它通过结合价格变动和成交量来衡量价格运动的“难易程度”。
EMV 的核心理念是:
交易信号:
以下是一个完整的 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))
数据获取 (get_history):
high (最高价), low (最低价), 和 volume (成交量) 来计算 EMV。g.N + 10,是为了确保在使用 rolling(window=g.N) 计算移动平均时,前面有足够的数据填充,避免结果为 NaN。指标计算逻辑:
g.scale = 10000。这是为了让 Box Ratio 的数值不至于过大,从而使最终的 EMV 数值更易读。这个参数不改变指标的正负方向,只改变数值的绝对大小。price_range 时,如果当日最高价等于最低价(如一字涨跌停),分母为 0 会导致报错或无穷大。代码使用了 .replace(0, 0.0001) 进行保护。交易信号:
prev_emv < 0 and curr_emv > 0 判断上穿 0 轴。prev_emv > 0 and curr_emv < 0 判断下穿 0 轴。EMV 是一个趋势跟随指标,在震荡市中可能会频繁发出虚假信号。在实际应用中,可以考虑以下优化方向:
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' 表示使用前复权数据。在量化回测中,为了消除分红、配股、拆股等对价格造成的跳空影响,必须使用复权数据。前复权可以保证当前价格与实际盘面一致,是回测中最常用的复权方式。