问题描述
解决方案
这是一个基于 布林带(Bollinger Bands) 的均值回归策略。
策略原理
均值回归的核心思想是价格终将回归其价值中枢。在本策略中:
- 中轨:使用过去 N 天的收盘价移动平均线作为价值中枢。
- 上轨/下轨:在中轨基础上加减 K 倍的标准差。
- 买入信号:当股价跌破下轨时,认为股价被低估(超卖),预期将回归均值,执行买入。
- 卖出信号:当股价突破上轨时,认为股价被高估(超买),预期将回调,执行卖出。
策略代码
# -*- coding: utf-8 -*-
import numpy as np
import pandas as pd
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.n = 20 # 布林带计算周期(均线周期)
g.k = 2 # 布林带宽度(标准差倍数)
g.stock_num = 10 # 最大持仓股票数量
# 设定股票池:这里选取沪深300成分股
g.security_list = get_index_stocks('000300.XSHG')
# 每天开盘时运行
run_daily(market_open, time='09:30')
def market_open(context):
"""
每日交易逻辑
"""
# 获取当前持仓
current_positions = context.portfolio.positions
# 获取当前可用资金
cash = context.portfolio.available_cash
# 获取待操作的股票列表(沪深300)
security_list = g.security_list
# 获取当前时刻的标的状态数据(用于判断停牌)
current_data = get_current_data()
# 1. 卖出逻辑:遍历持仓,如果价格突破上轨,则卖出
for security in list(current_positions.keys()):
# 获取过去 N 天的收盘价数据
# 注意:这里取 g.n + 1 天,是为了计算截止到昨天的指标,避免未来函数
hist_data = attribute_history(security, g.n, '1d', ['close'])
if len(hist_data) < g.n:
continue
# 计算布林带
close_prices = hist_data['close']
mid_line = close_prices.mean() # 中轨:均线
std = close_prices.std() # 标准差
upper_band = mid_line + g.k * std # 上轨
# 获取昨日收盘价(作为信号判断依据)
current_price = close_prices[-1]
# 如果昨日收盘价高于上轨,且股票未停牌,则止盈卖出
if current_price > upper_band and not current_data[security].paused:
order_target_value(security, 0)
log.info("股票 %s 突破上轨,触发卖出信号" % security)
# 2. 买入逻辑:遍历股票池,如果价格跌破下轨,则尝试买入
# 统计当前持仓数量
current_hold_count = len(context.portfolio.positions)
# 如果持仓已满,则不再买入
if current_hold_count >= g.stock_num:
return
# 计算每只股票的目标买入资金(简单的等权分配)
# 注意:这里用总资产来计算,以维持仓位平衡
target_value = context.portfolio.total_value / g.stock_num
for security in security_list:
# 如果已经持仓或停牌,跳过
if security in current_positions or current_data[security].paused:
continue
# 如果持仓数量已达上限,停止买入循环
if len(context.portfolio.positions) >= g.stock_num:
break
# 获取历史数据
hist_data = attribute_history(security, g.n, '1d', ['close'])
if len(hist_data) < g.n:
continue
# 计算布林带
close_prices = hist_data['close']
mid_line = close_prices.mean() # 中轨
std = close_prices.std() # 标准差
lower_band = mid_line - g.k * std # 下轨
# 获取昨日收盘价
current_price = close_prices[-1]
# 如果昨日收盘价低于下轨,触发买入信号
if current_price < lower_band:
# 使用可用资金买入
if context.portfolio.available_cash >= target_value:
order_target_value(security, target_value)
log.info("股票 %s 跌破下轨,触发买入信号" % security)
def after_trading_end(context):
"""
盘后处理
"""
pass
常见问题 (Q&A)
Q: 为什么使用 attribute_history 获取数据?
A: attribute_history 是聚宽获取历史行情数据的核心函数。在日频回测中,我们在开盘时(09:30)调用它获取过去 N 天的数据(通常截止到昨天收盘),以此计算技术指标。这避免了使用当天的未来数据(未来函数)。
Q: 策略中的 g.n 和 g.k 是什么意思?
A: 这是布林带的两个核心参数。g.n = 20 表示使用过去20天的移动平均线作为中轨;g.k = 2 表示上下轨距离中轨的宽度是2倍标准差。这是经典的布林带参数设置,您可以根据需要调整,例如改为 g.n=10 使策略更灵敏。
Q: 为什么买入时要判断 current_data[security].paused?
A: 在实盘或回测中,股票可能会停牌。对停牌的股票下单是无效的,且可能导致程序报错或逻辑混乱。使用 get_current_data() 获取标的状态并检查 paused 属性是编写健壮策略的必要步骤。
Q: 如何控制风险?
A: 代码中设置了 g.stock_num = 10,即最多持有10只股票。资金分配采用简单的等权模型(总资产/10)。这是一种基本的分散投资风控手段,防止单只股票波动对账户造成过大影响。