🚀 AI 一键生成 joinquant 策略代码

JoinQuant 技术分析:如何计算 BBI 指标并构建多空策略?

joinquant | 发布时间: 2025/12/22
以下内容由 EasyQuant 生成。

问题描述

如何计算 BBI 多空指数并用于量化策略?

解决方案

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. 代码解析

  1. 数据获取 (attribute_history):

    • BBI 需要计算 3、6、12、24 日均线,因此我们需要获取至少过去 24 个交易日的收盘价数据。
    • attribute_history(security, 24, '1d', ['close']) 返回的是一个 DataFrame,包含了过去 24 天的收盘价。
  2. 均线计算:

    • 利用 Pandas 的切片功能(如 [-3:])和 .mean() 方法,可以快速计算出不同周期的移动平均值。
  3. BBI 计算:

    • 直接按照公式将四个均线值相加除以 4。
  4. 交易信号:

    • 策略比较的是当前最新价格基于过去收盘价计算出的 BBI
    • current_price > bbi: 视为趋势向上,执行买入。
    • current_price < bbi: 视为趋势向下,执行卖出。

5. 优化建议

该示例是一个基础的单标的策略,实际应用中可以考虑以下优化方向:

  1. 多标的轮动:将 g.security 替换为一个股票池(如沪深300成分股),循环遍历每只股票进行 BBI 计算和交易。
  2. 参数优化:3、6、12、24 是 BBI 的默认参数,可以通过聚宽的研究模块进行参数扫描,寻找针对特定标的更优的均线组合。
  3. 结合其他指标: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 卖出信号。