问题描述
解决方案
这是一个基于 VR (Volume Ratio,成交量比率) 指标的量化交易策略。
策略原理
VR 指标主要通过分析股价上升日和下降日的成交量比率,来判断市场的人气和资金流向,从而辅助判断股价的顶部和底部。
VR 计算公式:
$$VR = \frac{AVS + \frac{1}{2}CVS}{BVS + \frac{1}{2}CVS} \times 100$$
其中:
- $AVS$:N日内股价上涨日(收盘价 > 前收盘价)的成交量之和。
- $BVS$:N日内股价下跌日(收盘价 < 前收盘价)的成交量之和。
- $CVS$:N日内股价平盘日(收盘价 = 前收盘价)的成交量之和。
- $N$:通常取 26 日。
交易逻辑:
- 买入信号:当 VR 值低于 70(低位区域),认为市场超卖,人气低迷,可能是底部,执行买入。
- 卖出信号:当 VR 值高于 250(高位区域),认为市场超买,人气过热,可能是顶部,执行卖出。
- 股票池:选取沪深300成分股。
- 仓位管理:等权重买入,满仓操作。
策略代码 (JoinQuant)
# -*- coding: utf-8 -*-
import jqdata
import pandas as pd
import numpy as np
def initialize(context):
"""
初始化函数,设定基准、手续费、全局变量等
"""
# 设定沪深300作为基准
set_benchmark('000300.XSHG')
# 开启动态复权模式(真实价格)
set_option('use_real_price', True)
# 过滤掉order系列API产生的比error级别低的log
log.set_level('order', 'error')
# 股票类每笔交易时的手续费是:买入时佣金万分之三,卖出时佣金万分之三加千分之一印花税, 每笔交易佣金最低扣5块钱
set_order_cost(OrderCost(close_tax=0.001, open_commission=0.0003, close_commission=0.0003, min_commission=5), type='stock')
# --- 策略参数设置 ---
g.index_security = '000300.XSHG' # 股票池:沪深300
g.vr_window = 26 # VR指标计算周期
g.buy_threshold = 70 # VR低于该值买入 (超卖)
g.sell_threshold = 250 # VR高于该值卖出 (超买)
g.max_hold_count = 10 # 最大持仓数量
# 每天开盘前运行
run_daily(before_market_open, time='before_open')
# 每天开盘时运行
run_daily(market_open, time='open')
def before_market_open(context):
"""
开盘前运行函数
"""
# 获取当天的沪深300成分股
g.security_list = get_index_stocks(g.index_security)
def calculate_vr(security_list, window, end_date):
"""
批量计算VR指标
返回: Series (index为股票代码, value为VR值)
"""
# 获取历史数据:需要 window + 1 天的数据来计算涨跌(比较今天和昨天)
# fields: close, volume
h_data = history(window + 1, '1d', ['close', 'volume'], security_list)
vr_values = {}
for stock in security_list:
try:
close = h_data[stock]['close']
volume = h_data[stock]['volume']
# 如果数据长度不足,跳过
if len(close) < window + 1:
continue
# 计算每日涨跌情况
# diff > 0: 上涨, diff < 0: 下跌, diff = 0: 平盘
price_diff = close.diff().iloc[1:] # 去掉第一个NaN
current_vol = volume.iloc[1:] # 对应的成交量
# AVS: 上涨日成交量
avs = current_vol[price_diff > 0].sum()
# BVS: 下跌日成交量
bvs = current_vol[price_diff < 0].sum()
# CVS: 平盘日成交量
cvs = current_vol[price_diff == 0].sum()
# 计算VR
denominator = bvs + 0.5 * cvs
if denominator == 0:
vr = 1000 # 分母为0,说明没有下跌和平盘,极端强势,给一个大数值
else:
vr = (avs + 0.5 * cvs) / denominator * 100
vr_values[stock] = vr
except Exception as e:
# 遇到停牌或其他数据异常跳过
continue
return pd.Series(vr_values)
def market_open(context):
"""
主交易逻辑
"""
# 1. 计算股票池中所有股票的VR值
vr_series = calculate_vr(g.security_list, g.vr_window, context.previous_date)
if vr_series.empty:
return
# 2. 生成买入和卖出列表
# 卖出条件:VR > 250 (超买) 或者 股票不在成分股里了
current_positions = list(context.portfolio.positions.keys())
stocks_to_sell = []
for stock in current_positions:
# 如果股票不在当天的计算结果里(可能停牌或被剔除),或者VR过高
if stock not in vr_series.index:
stocks_to_sell.append(stock)
elif vr_series[stock] > g.sell_threshold:
stocks_to_sell.append(stock)
log.info("卖出 %s, VR值: %.2f (触发超买阈值)" % (stock, vr_series[stock]))
elif stock not in g.security_list:
stocks_to_sell.append(stock)
log.info("卖出 %s, (被剔除出成分股)" % (stock))
# 执行卖出
for stock in stocks_to_sell:
order_target_value(stock, 0)
# 3. 买入逻辑
# 筛选出 VR < 70 的股票
buy_candidates = vr_series[vr_series < g.buy_threshold].sort_values()
# 当前持仓数量
current_hold_count = len(context.portfolio.positions)
# 剩余可买位置
slots_available = g.max_hold_count - current_hold_count
if slots_available > 0 and not buy_candidates.empty:
# 获取可用资金
cash = context.portfolio.available_cash
# 每只股票分配资金 (简单平均)
cash_per_stock = cash / slots_available
for stock in buy_candidates.index:
if slots_available <= 0:
break
if stock not in current_positions:
order_value(stock, cash_per_stock)
log.info("买入 %s, VR值: %.2f (触发超卖阈值)" % (stock, vr_series[stock]))
slots_available -= 1
策略说明
- 数据获取:使用
history函数获取过去N+1天的收盘价和成交量。我们需要N+1天是因为计算当天的涨跌需要对比前一天的收盘价。 - VR 计算:
- 利用 Pandas 的
diff()方法快速计算价格变化。 - 利用布尔索引筛选出上涨、下跌和平盘的成交量并求和。
- 处理了分母为 0 的极端情况。
- 利用 Pandas 的
- 买卖执行:
- 卖出:优先检查持仓股票,如果 VR 值过高(>250)或股票被调出沪深300成分股,则清仓。
- 买入:在卖出操作释放资金后,筛选 VR 值过低(<70)的股票。如果有多个标的符合条件,按 VR 值从小到大排序(优先买入超卖最严重的),直到达到最大持仓数量限制。
- 风险控制:设置了最大持仓只数 (
g.max_hold_count),防止资金过于集中在某一只股票上。
Q&A
Q: 为什么 VR 指标的参数通常设置为 26?
A: 26 是技术分析中常用的参数(如 MACD、Ichimoku Cloud 等也常用 26),代表大约一个月的交易日(加上周末)。这是一个经验值,您可以根据回测结果调整为 12 或 24。
Q: 这个策略适合什么市场环境?
A: VR 策略属于反转策略(Mean Reversion)。它在震荡市中表现较好,能够捕捉到底部反弹和顶部回调。在单边极强的牛市中,VR 可能会过早发出卖出信号(钝化);在单边熊市中,可能会过早抄底(接飞刀)。建议结合均线或其他趋势指标过滤信号。
Q: 如何优化这个策略?
A:
- 增加趋势过滤:例如,只在股价位于 60 日均线之上时才允许买入(即使 VR 很低),以避免在崩盘式下跌中抄底。
- 调整阈值:不同板块的 VR 波动范围不同,可以针对不同行业设置动态阈值。
- 止损:代码中目前仅依靠 VR 高位止盈,建议加入固定比例止损(如亏损 10% 止损)。