问题描述
解决方案
这是一个基于 BBI (Bull And Bear Index) 多空指数 的量化交易策略。
策略原理
BBI 多空指数是一种将不同天数的移动平均线加权平均之后的综合指标,属于均线型指标。它结合了短期、中期和长期均线的优势。
计算公式:
$$ BBI = \frac{MA(3) + MA(6) + MA(12) + MA(24)}{4} $$
交易逻辑:
- 买入信号:当收盘价 上穿 BBI 指标线时,视为多头市场,执行买入。
- 卖出信号:当收盘价 下穿 BBI 指标线时,视为空头市场,执行卖出(清仓)。
策略代码实现
# -*- coding: gbk -*-
import pandas as pd
import numpy as np
def init(ContextInfo):
"""
策略初始化函数
"""
# 1. 设置股票池 (这里以 600000.SH 浦发银行为例,可根据需要修改)
ContextInfo.stock_code = '600000.SH'
ContextInfo.set_universe([ContextInfo.stock_code])
# 2. 设置资金账号 (请替换为您真实的资金账号)
ContextInfo.account_id = 'YOUR_ACCOUNT_ID'
ContextInfo.set_account(ContextInfo.account_id)
# 3. 设置BBI计算参数 (3日, 6日, 12日, 24日均线)
ContextInfo.ma_periods = [3, 6, 12, 24]
# 4. 设置回测参数 (仅回测有效)
ContextInfo.set_slippage(1, 0.002) # 设置滑点
ContextInfo.set_commission(0, [0.0003, 0.0013, 0.0003, 0.0003, 0.0003, 5]) # 设置手续费
def handlebar(ContextInfo):
"""
K线周期运行函数
"""
# 获取当前正在处理的股票代码
stock = ContextInfo.stock_code
# 获取当前K线的时间
index = ContextInfo.barpos
timetag = ContextInfo.get_bar_timetag(index)
date_str = timetag_to_datetime(timetag, '%Y-%m-%d %H:%M:%S')
# 1. 获取历史行情数据
# 我们需要足够的数据来计算 MA24,所以至少取 30 根K线
# dividend_type='front' 使用前复权数据,保证信号准确
data_map = ContextInfo.get_market_data_ex(
['close'],
[stock],
period='1d',
count=30,
dividend_type='front'
)
if stock not in data_map:
return
df = data_map[stock]
# 数据长度校验,不足以计算MA24则跳过
if len(df) < 24:
return
# 2. 计算 BBI 指标
# 计算各周期均线
ma3 = df['close'].rolling(window=3).mean()
ma6 = df['close'].rolling(window=6).mean()
ma12 = df['close'].rolling(window=12).mean()
ma24 = df['close'].rolling(window=24).mean()
# 计算 BBI
bbi = (ma3 + ma6 + ma12 + ma24) / 4
# 获取最新一根K线的收盘价和BBI值
current_close = df['close'].iloc[-1]
current_bbi = bbi.iloc[-1]
# 获取前一根K线的收盘价和BBI值 (用于判断交叉)
prev_close = df['close'].iloc[-2]
prev_bbi = bbi.iloc[-2]
# 3. 获取当前持仓
positions = get_trade_detail_data(ContextInfo.account_id, 'stock', 'position')
current_holding = 0
for pos in positions:
if pos.m_strInstrumentID + '.' + pos.m_strExchangeID == stock:
current_holding = pos.m_nVolume
break
# 4. 生成交易信号
# 信号逻辑:
# 金叉:前一根K线收盘价 <= 前BBI,且 当前收盘价 > 当前BBI
# 死叉:前一根K线收盘价 >= 前BBI,且 当前收盘价 < 当前BBI
is_golden_cross = (prev_close <= prev_bbi) and (current_close > current_bbi)
is_death_cross = (prev_close >= prev_bbi) and (current_close < current_bbi)
# 5. 执行交易
# 如果不是最后一根K线(回测中),或者在实盘的最新K线
if ContextInfo.is_last_bar():
print(f"时间: {date_str}, 收盘价: {current_close:.2f}, BBI: {current_bbi:.2f}")
if is_golden_cross:
if current_holding == 0:
print(f"{date_str} 触发买入信号 (价格上穿BBI)")
# 全仓买入 (目标仓位调整为 100%)
order_target_percent(stock, 1.0, ContextInfo, ContextInfo.account_id)
elif is_death_cross:
if current_holding > 0:
print(f"{date_str} 触发卖出信号 (价格下穿BBI)")
# 清仓卖出 (目标仓位调整为 0%)
order_target_percent(stock, 0.0, ContextInfo, ContextInfo.account_id)
代码说明
-
初始化 (
init):- 设置了单一标的
600000.SH。 - 定义了 BBI 所需的均线周期列表
[3, 6, 12, 24]。 - 注意:请务必将
YOUR_ACCOUNT_ID替换为您实际的 QMT 资金账号。
- 设置了单一标的
-
数据获取 (
handlebar):- 使用
get_market_data_ex获取前复权的日线数据。 - 取最近 30 根 K 线以确保有足够的数据计算 24 日均线。
- 使用
-
指标计算:
- 利用
pandas的rolling().mean()快速计算各周期均线。 - 根据公式计算 BBI 值。
- 利用
-
信号判断:
- 策略采用了交叉判断法:对比“当前时刻”和“上一时刻”的价格与 BBI 的关系。
- 金叉:价格从下方穿过 BBI 线,视为买点。
- 死叉:价格从上方穿过 BBI 线,视为卖点。
-
交易执行:
- 使用
order_target_percent函数。 - 买入时设置为
1.0(即 100% 仓位)。 - 卖出时设置为
0.0(即 0% 仓位,清仓)。
- 使用
Q&A
Q: 为什么使用 get_market_data_ex 而不是 get_market_data?
A: get_market_data_ex 是 QMT 推荐的新版接口,返回的数据结构更清晰(字典包含 DataFrame),且支持更多参数配置(如复权方式、订阅开关等),处理效率更高。
Q: 如何在实盘中避免信号闪烁(Repainting)?
A: 在实盘中,当前 K 线尚未走完时,收盘价是变动的,可能导致 BBI 信号在盘中反复触发。
- 解决方法 1:本代码逻辑是在
handlebar中运行。如果是日线周期,盘中每个 tick 都会计算。如果希望只在收盘确认后交易,可以判断时间是否接近 14:55 或 15:00。 - 解决方法 2:使用
ContextInfo.is_new_bar()逻辑,仅在下一根 K 线开盘的瞬间,根据上一根已完成的 K 线信号进行下单(do_order配合)。
Q: 这个策略适合什么周期?
A: BBI 是一个趋势指标。
- 日线:适合捕捉中短期趋势,信号较稳。
- 分钟线 (如 15m, 60m):信号会更频繁,但在震荡市中容易产生假信号导致频繁止损。建议配合其他过滤器(如成交量或 MACD)使用。