3 分钟快速生成代码
输入想法,AI 即刻生成可运行代码
EMV(Ease of Movement Value,简易波动指标)是由 Richard W. Arms Jr. 开发的一种技术分析指标。它结合了价格变动和成交量,用以衡量价格变动的“难易程度”。
策略逻辑:
下面是基于聚宽(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)
初始化 (initialize):
use_real_price),这是回测的标准配置。g.N 为 14,这是 EMV 指标的标准周期。g.scale_factor。EMV 公式中成交量通常很大,而价格波动很小,直接计算会导致 Box Ratio 极大,EMV 极小。除以一个缩放因子可以让数值更易读。虽然这不影响 0 轴交叉的逻辑,但对于 record 画图和数据观察很有必要。数据获取:
attribute_history 获取 high, low, volume 数据。g.N + 2,因为计算差分(diff)会损失一天数据,计算移动平均(rolling)需要 N 天窗口。指标计算:
交易逻辑:
prev_maemv < 0 且 current_maemv > 0 判断金叉(上穿 0 轴)。prev_maemv > 0 且 current_maemv < 0 判断死叉(下穿 0 轴)。order_value 全仓买入,order_target 清仓卖出。Q1: 为什么计算 Box Ratio 时要给分母加一个极小值?
A: 在某些交易日(如一字涨停或跌停,或者极度窄幅震荡),最高价可能等于最低价(High - Low = 0)。在计算机中除以 0 会导致无穷大(inf)或错误(NaN),从而导致策略崩溃。加上 0.0001 是为了保证程序的健壮性。
Q2: 这个策略适合什么类型的市场?
A: EMV 是一个趋势指标。它在趋势明显的市场中表现较好,因为它能识别出伴随成交量支持的价格移动。在横盘震荡市场中,EMV 可能会频繁在 0 轴附近波动,导致频繁交易和亏损。
Q3: 如何优化这个策略?
A:
get_index_stocks 扩展到多只股票轮动。