问题描述
解决方案
BBI 多空指数策略设计与实现
BBI(Bull And Bear Index)多空指数是一种将不同周期的移动平均线加权平均之后的综合指标。它结合了短期(3日、6日)和长期(12日、24日)移动平均线的优势,用于判断市场的多空趋势。
策略逻辑
-
指标计算:
BBI = (MA(3) + MA(6) + MA(12) + MA(24)) / 4
其中 MA(N) 代表最近 N 日的收盘价算术平均值。 -
交易信号:
- 买入信号:当股价(收盘价)上穿 BBI 指标线时,视为多头市场,执行买入。
- 卖出信号:当股价(收盘价)下穿 BBI 指标线时,视为空头市场,执行卖出。
-
资金管理:
- 买入时使用当前所有可用现金。
- 卖出时清空该标的所有持仓。
PTrade 策略代码实现
以下是基于 PTrade 框架的完整策略代码。代码兼容 Python 3.5 语法,可以直接在 PTrade 回测或交易环境中运行。
import numpy as np
import pandas as pd
def initialize(context):
"""
初始化函数,设置股票池、基准和全局变量
"""
# 设定要交易的股票,这里以 600570.SS (恒生电子) 为例
# 实际使用时可修改为其他股票或通过 get_index_stocks 获取股票池
g.security = '600570.SS'
set_universe(g.security)
# 设置基准为沪深300
set_benchmark('000300.SS')
# 设置佣金费率(可选,回测常用设置)
set_commission(commission_ratio=0.0003, min_commission=5.0)
# 设定 BBI 计算所需的周期参数
g.ma_periods = [3, 6, 12, 24]
# 打印策略启动日志
log.info("BBI 策略初始化完成")
def handle_data(context, data):
"""
核心策略逻辑,每个运行周期(日线或分钟)执行一次
"""
security = g.security
# 1. 获取历史数据
# BBI 最大周期为 24,我们需要获取足够的数据来计算 MA24
# 为了保险起见,获取过去 30 天的数据,避免停牌导致数据不足
# include=False 表示不包含当前周期(即只取历史收盘价),避免未来函数
history_count = 30
h_data = get_history(history_count, frequency='1d', field='close', security_list=security, include=False)
# 检查数据长度是否足够,如果不足 24 天无法计算 BBI,直接返回
if len(h_data) < 24:
log.warning("历史数据不足,跳过本次计算")
return
# 提取收盘价序列 (Series)
close_prices = h_data['close']
# 2. 计算 BBI 指标
# 分别计算 3日, 6日, 12日, 24日 均线
# 使用 iloc[-N:] 获取最后 N 个数据,然后求 mean()
ma3 = close_prices.iloc[-g.ma_periods[0]:].mean()
ma6 = close_prices.iloc[-g.ma_periods[1]:].mean()
ma12 = close_prices.iloc[-g.ma_periods[2]:].mean()
ma24 = close_prices.iloc[-g.ma_periods[3]:].mean()
# 计算 BBI 值
bbi = (ma3 + ma6 + ma12 + ma24) / 4.0
# 3. 获取当前市场数据
# 获取当前最新价格
current_price = data[security]['close']
# 获取当前持仓数量
position = get_position(security).amount
# 获取当前可用现金
cash = context.portfolio.cash
# 4. 生成交易信号并执行
# 信号逻辑:
# 如果 当前价格 > BBI 且 空仓 -> 买入 (多头趋势)
# 如果 当前价格 < BBI 且 持仓 -> 卖出 (空头趋势)
if current_price > bbi and position == 0:
# 全仓买入
order_value(security, cash)
log.info("买入信号触发: 价格(%.2f) > BBI(%.2f), 全仓买入 %s" % (current_price, bbi, security))
elif current_price < bbi and position > 0:
# 清仓卖出
order_target(security, 0)
log.info("卖出信号触发: 价格(%.2f) < BBI(%.2f), 清仓卖出 %s" % (current_price, bbi, security))
代码解析
-
数据获取 (
get_history):- 我们设置
count=30并使用include=False。这意味着我们使用昨天及之前的收盘价来计算 BBI 指标。这样做符合技术分析中“基于历史数据判断当前走势”的逻辑,且在回测中更严谨。 frequency='1d'表示使用日线数据。
- 我们设置
-
指标计算:
- 利用 Pandas 的切片功能
iloc[-N:]快速获取最近 N 天的数据,并使用.mean()计算平均值。 - BBI 是这四个均值的算术平均。
- 利用 Pandas 的切片功能
-
交易逻辑:
- 策略比较的是
current_price(当前最新价)与bbi(基于历史收盘价计算的支撑/阻力位)。 - 买入:当价格站上 BBI 线,且当前没有持仓时,使用
order_value买入。 - 卖出:当价格跌破 BBI 线,且当前持有股票时,使用
order_target(security, 0)卖出所有持仓。
- 策略比较的是
-
兼容性:
- 使用了
%格式化字符串(如"%s" % var),而不是 f-string,确保在 Python 3.5 环境下也能正常运行。
- 使用了
常见问题 (Q&A)
Q1: 如何修改 BBI 的计算周期?
A1: 在 initialize 函数中,修改 g.ma_periods = [3, 6, 12, 24] 列表中的数字即可。例如,如果你想计算更长周期的 BBI,可以改为 [6, 12, 24, 48],同时记得增加 handle_data 中 get_history 获取的天数(history_count)。
Q2: 这个策略可以用于分钟级别回测吗?
A2: 可以。只需将 get_history 中的 frequency='1d' 改为 frequency='1m' 或其他分钟周期,并将策略回测频率设置为分钟级。但在分钟级别下,BBI 的参数(3, 6, 12, 24)代表的是分钟数的均线,可能需要根据实际情况调整参数大小。
Q3: 为什么使用 include=False?
A3: 在计算均线类指标时,通常使用已收盘的 K 线数据。如果在盘中实时将当前未收盘的价格计入 MA 计算,会导致指标在盘中不断跳动(Repainting),造成信号不稳定。使用 include=False 锁定昨天的 BBI 值作为今日的参考线是比较稳健的做法。
Q4: 如何对多只股票运行此策略?
A4: 你可以在 initialize 中使用 set_universe 设置多只股票列表,然后在 handle_data 中使用 for 循环遍历 g.security 列表,对每一只股票分别执行计算和交易逻辑。