问题描述
解决方案
BBI 多空指数计算与量化策略实现
BBI(Bull And Bear Index)多空指数是一种将不同天数的移动平均线加权平均之后的综合指标,属于均线型指标。它结合了短期、中期和长期均线的优势,用于判断市场的多空趋势。
1. BBI 计算公式
BBI 的标准计算公式如下:
$$ BBI = \frac{MA(3) + MA(6) + MA(12) + MA(24)}{4} $$
其中:
- $MA(N)$ 代表最近 $N$ 日的收盘价算术移动平均值。
- 通常选用的周期为 3日、6日、12日、24日。
2. 策略逻辑
本策略基于 BBI 指数进行趋势跟随交易:
- 买入信号:当股价(当前价格)上穿 BBI 指数时,视为多头市场,执行买入。
- 卖出信号:当股价(当前价格)下穿 BBI 指数时,视为空头市场,执行卖出。
3. 聚宽 (JoinQuant) 策略代码实现
以下是一个完整的 BBI 策略代码。该策略选取单只股票(如平安银行)进行回测,使用 attribute_history 获取历史数据手动计算 BBI 指数。
# -*- coding: utf-8 -*-
import jqdata
import numpy as np
def initialize(context):
"""
初始化函数,设定基准、股票池、费用等
"""
# 设定要操作的股票,这里以平安银行为例
g.security = '000001.XSHE'
# 设定沪深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')
# 每天开盘时运行
run_daily(market_open, time='09:30')
def market_open(context):
"""
每日交易逻辑
"""
security = g.security
# 获取过去24天的数据(因为BBI最大周期是24)
# 注意:attribute_history 获取的是不包含当前时刻的过去数据
# 如果是按天回测,这里获取的是截止到昨天的收盘数据
n_days = 24
hist_data = attribute_history(security, n_days, '1d', ['close'])
# 如果数据不足24天(例如新股),则跳过
if len(hist_data) < n_days:
return
# 获取收盘价序列
close_prices = hist_data['close']
# 1. 计算各周期均线
# MA3: 最后3天的均值
ma3 = close_prices[-3:].mean()
# MA6: 最后6天的均值
ma6 = close_prices[-6:].mean()
# MA12: 最后12天的均值
ma12 = close_prices[-12:].mean()
# MA24: 最后24天的均值
ma24 = close_prices[-24:].mean()
# 2. 计算 BBI 指数
bbi = (ma3 + ma6 + ma12 + ma24) / 4.0
# 3. 获取当前最新价格
# 在日级回测中,context.portfolio.positions[security].price 为最新价
# 或者使用 get_current_data()[security].last_price
current_data = get_current_data()
current_price = current_data[security].last_price
# 获取当前可用资金
cash = context.portfolio.available_cash
# 获取当前持仓数量
curr_position = context.portfolio.positions[security].closeable_amount
# 4. 交易逻辑判断
# 打印日志方便调试
# log.info("日期: %s, 当前价: %.2f, BBI: %.2f" % (context.current_dt, current_price, bbi))
# 信号:价格高于 BBI 且当前无持仓 -> 买入
if current_price > bbi and curr_position == 0:
# 全仓买入
order_value(security, cash)
log.info("买入信号触发:价格(%.2f) > BBI(%.2f),全仓买入 %s" % (current_price, bbi, security))
# 信号:价格低于 BBI 且当前有持仓 -> 卖出
elif current_price < bbi and curr_position > 0:
# 清仓
order_target(security, 0)
log.info("卖出信号触发:价格(%.2f) < BBI(%.2f),清仓卖出 %s" % (current_price, bbi, security))
4. 代码解析
-
数据获取 (
attribute_history):- BBI 需要计算 3、6、12、24 日均线,因此我们需要获取至少过去 24 个交易日的收盘价数据。
attribute_history(security, 24, '1d', ['close'])返回的是一个 DataFrame,包含了过去 24 天的收盘价。
-
均线计算:
- 利用 Pandas 的切片功能(如
[-3:])和.mean()方法,可以快速计算出不同周期的移动平均值。
- 利用 Pandas 的切片功能(如
-
BBI 计算:
- 直接按照公式将四个均线值相加除以 4。
-
交易信号:
- 策略比较的是当前最新价格与基于过去收盘价计算出的 BBI。
current_price > bbi: 视为趋势向上,执行买入。current_price < bbi: 视为趋势向下,执行卖出。
5. 优化建议
该示例是一个基础的单标的策略,实际应用中可以考虑以下优化方向:
- 多标的轮动:将
g.security替换为一个股票池(如沪深300成分股),循环遍历每只股票进行 BBI 计算和交易。 - 参数优化:3、6、12、24 是 BBI 的默认参数,可以通过聚宽的研究模块进行参数扫描,寻找针对特定标的更优的均线组合。
- 结合其他指标:BBI 本质是均线策略,在震荡市中容易产生频繁的假信号(反复止损)。可以结合 MACD 或 KDJ 等指标进行信号过滤。
Q&A
Q1: 为什么使用 attribute_history 而不是 get_price?
A1: 在回测的 handle_data 或定时运行函数中,attribute_history 是获取回测当前时间点之前历史数据的专用高效 API,它会自动处理复权和停牌填充,且不会引入未来数据(默认不包含当前未收盘的数据)。
Q2: BBI 指数适合什么类型的市场?
A2: BBI 是趋势型指标,最适合单边上涨或下跌的趋势行情。在横盘震荡整理的行情中,BBI 可能会频繁发出买卖信号导致磨损成本增加。
Q3: 如何在策略中引入止损?
A3: 可以在 market_open 函数中增加逻辑,记录开仓成本 context.portfolio.positions[security].avg_cost,当 current_price < avg_cost * 0.95(亏损5%)时强制卖出,而不必等待 BBI 卖出信号。