问题描述
解决方案
成交量震荡指标 (VOSC) 策略编写指南
成交量震荡指标(Volume Oscillator,简称 VOSC)是通过计算短期成交量移动平均线与长期成交量移动平均线之间的差值(或百分比差值),来判断成交量趋势的指标。
VOSC 的核心理念是:成交量的变化往往先于价格的变化。
- VOSC > 0:短期成交量大于长期成交量,市场交易活跃,趋势可能延续。
- VOSC < 0:短期成交量小于长期成交量,市场交易清淡,趋势可能衰竭。
1. 策略逻辑设计
为了构建一个可执行的量化策略,单纯依靠成交量是不够的,通常需要结合价格趋势。本示例策略逻辑如下:
- 计算 VOSC 指标:
- 计算成交量的短期均线(Short MA,如 12 日)。
- 计算成交量的长期均线(Long MA,如 26 日)。
- $VOSC = \frac{MA_{short} - MA_{long}}{MA_{long}} \times 100$
- 计算价格趋势:
- 计算收盘价的均线(如 MA 20)作为趋势过滤。
- 交易信号:
- 买入条件:VOSC 上穿 0 轴(成交量放大) 且 收盘价 > 20日均线(处于上升趋势)。
- 卖出条件:VOSC 下穿 0 轴(成交量萎缩) 或 收盘价 < 20日均线(趋势破坏)。
2. QMT 策略代码实现
以下是完整的 Python 策略代码。代码包含了数据获取、指标计算、绘图以及交易下单功能。
# -*- coding: gbk -*-
import pandas as pd
import numpy as np
def init(ContextInfo):
"""
初始化函数,设定策略参数和账户
"""
# 1. 设置策略参数
ContextInfo.short_period = 12 # VOSC 短期周期
ContextInfo.long_period = 26 # VOSC 长期周期
ContextInfo.trend_period = 20 # 价格趋势过滤周期 (MA20)
# 2. 设定股票池 (示例使用 平安银行)
ContextInfo.stock_code = '000001.SZ'
ContextInfo.set_universe([ContextInfo.stock_code])
# 3. 设定资金账号 (请替换为您真实的资金账号)
ContextInfo.account_id = 'YOUR_ACCOUNT_ID'
ContextInfo.account_type = 'STOCK' # 账号类型:STOCK 股票, FUTURE 期货
ContextInfo.set_account(ContextInfo.account_id)
print("VOSC 策略初始化完成")
def get_vosc(volume_series, short_p, long_p):
"""
计算 VOSC 指标
公式: (MA_short - MA_long) / MA_long * 100
"""
ma_short = volume_series.rolling(window=short_p).mean()
ma_long = volume_series.rolling(window=long_p).mean()
# 避免除以0的情况
vosc = (ma_short - ma_long) / ma_long * 100
return vosc
def handlebar(ContextInfo):
"""
K线周期回调函数
"""
# 获取当前 K 线位置
index = ContextInfo.barpos
realtime = ContextInfo.get_bar_timetag(index)
# 1. 获取历史行情数据
# 获取足够长度的数据以计算长期均线
data_len = ContextInfo.long_period + 10
# 使用 get_market_data_ex 获取数据 (推荐使用 ex 接口)
# 注意:subscribe=True 在实盘中会自动订阅行情
market_data = ContextInfo.get_market_data_ex(
['close', 'volume'],
[ContextInfo.stock_code],
period=ContextInfo.period,
count=data_len,
subscribe=True
)
if ContextInfo.stock_code not in market_data:
return
df = market_data[ContextInfo.stock_code]
# 数据长度不足时不计算
if len(df) < ContextInfo.long_period:
return
# 2. 计算指标
# 计算 VOSC
df['vosc'] = get_vosc(df['volume'], ContextInfo.short_period, ContextInfo.long_period)
# 计算价格均线 (MA)
df['ma_price'] = df['close'].rolling(window=ContextInfo.trend_period).mean()
# 获取最新和前一根 K 线的数值
current_vosc = df['vosc'].iloc[-1]
prev_vosc = df['vosc'].iloc[-2]
current_close = df['close'].iloc[-1]
current_ma = df['ma_price'].iloc[-1]
# 3. 绘制指标到图表 (方便回测观察)
# 绘制 VOSC (在副图显示)
ContextInfo.paint('VOSC', current_vosc, -1, 0)
ContextInfo.paint('Zero', 0, -1, 0, 'gray') # 0轴线
# 4. 交易逻辑判断
# 必须确保不是 NaN 值
if np.isnan(current_vosc) or np.isnan(current_ma):
return
# 信号定义
# VOSC 金叉 0 轴: 上一根 < 0, 当前 > 0
vosc_cross_up = prev_vosc < 0 and current_vosc > 0
# VOSC 死叉 0 轴: 上一根 > 0, 当前 < 0
vosc_cross_down = prev_vosc > 0 and current_vosc < 0
# 价格趋势判断
is_uptrend = current_close > current_ma
# 获取当前持仓
position = get_position(ContextInfo.account_id, ContextInfo.stock_code)
# --- 买入逻辑 ---
# 条件: VOSC 金叉 0 轴 且 价格在均线之上
if vosc_cross_up and is_uptrend:
if position == 0:
# 全仓买入 (示例逻辑,实盘请做好资金管理)
# 注意:实盘中 passorder 的 quickTrade 参数需根据需求设置 (0: K线走完下单, 1: 立即下单)
# 这里演示使用 order_target_percent 调仓函数,更方便
order_target_percent(ContextInfo.stock_code, 1.0, "FIX", current_close, ContextInfo, ContextInfo.account_id)
print(f"[{timetag_to_datetime(realtime, '%Y-%m-%d %H:%M:%S')}] 买入信号: VOSC金叉且趋势向上. 价格:{current_close}")
# --- 卖出逻辑 ---
# 条件: VOSC 死叉 0 轴 或 价格跌破均线
elif (vosc_cross_down or not is_uptrend):
if position > 0:
# 清仓
order_target_percent(ContextInfo.stock_code, 0.0, "FIX", current_close, ContextInfo, ContextInfo.account_id)
print(f"[{timetag_to_datetime(realtime, '%Y-%m-%d %H:%M:%S')}] 卖出信号: VOSC死叉或趋势破坏. 价格:{current_close}")
def get_position(account_id, stock_code):
"""
辅助函数:获取特定股票的持仓数量
"""
positions = get_trade_detail_data(account_id, 'STOCK', 'POSITION')
for pos in positions:
if pos.m_strInstrumentID + '.' + pos.m_strExchangeID == stock_code:
return pos.m_nVolume
return 0
def timetag_to_datetime(timetag, format_str):
"""
辅助函数:时间戳转字符串
"""
import time
return time.strftime(format_str, time.localtime(timetag / 1000))
3. 代码关键点解析
-
get_market_data_ex:- 这是 QMT 获取行情的推荐接口。
subscribe=True非常重要,它确保在实盘运行时,策略能接收到最新的 Tick 推送并驱动handlebar运行。- 返回的数据是
pandas.DataFrame格式,方便直接使用rolling函数计算均线。
-
VOSC 计算:
- 代码中使用了
(MA_short - MA_long) / MA_long * 100的百分比算法。这比单纯的差值更能反映成交量变化的相对强度,便于不同股票之间的比较。
- 代码中使用了
-
ContextInfo.paint:- 该函数用于在 QMT 的 K 线图界面上绘制指标线。
- 在回测模式下,你可以直观地看到 VOSC 曲线和 0 轴,验证策略信号是否符合预期。
-
交易函数:
- 使用了
order_target_percent。这是一个封装好的便捷函数,意为“将仓位调整至目标百分比”。 1.0代表满仓买入,0.0代表清仓卖出。- 在实盘中,建议结合
ContextInfo.get_available_cash()进行更精细的资金管理。
- 使用了
-
趋势过滤:
- 单纯依靠成交量指标容易在震荡市中产生虚假信号。代码中加入了
is_uptrend = current_close > current_ma作为过滤器,只有在价格处于均线之上时才允许买入,这能有效提高胜率。
- 单纯依靠成交量指标容易在震荡市中产生虚假信号。代码中加入了
4. 如何运行
- 打开 QMT 客户端,进入【模型研究】或【策略编辑器】。
- 新建一个 Python 策略,将上述代码复制进去。
- 修改
ContextInfo.account_id为你的实际资金账号。 - 点击【编译】检查是否有语法错误。
- 在右侧选择回测区间和标的(如 000001.SZ),点击【回测】查看历史表现。
- 确认无误后,可切换到【实盘】模式运行。
Q&A
Q: 为什么 VOSC 指标有时候会产生很多噪音信号?
A: 成交量本身波动较大。如果短期周期(如12)设置得太短,VOSC 会频繁穿越 0 轴。建议适当拉长周期,或者像本策略一样,增加价格均线作为过滤条件。
Q: get_market_data_ex 和 get_market_data 有什么区别?
A: get_market_data_ex 是新版接口,返回的数据结构更统一(字典包含 DataFrame),且支持更灵活的订阅机制。建议新策略都使用 _ex 后缀的接口。
Q: 如何在实盘中让策略立即下单,而不是等 K 线走完?
A: 在 passorder 或 order_target_percent 等交易函数中,有一个参数通常叫 quickTrade(在封装函数中可能不可见,需查看底层实现)。或者在实盘运行时,利用 ContextInfo.subscribe_quote 订阅 Tick 数据,在 Tick 回调中进行更实时的判断。但在 handlebar 机制下,通常是 K 线结束或新 K 线开始时触发信号。