箱体理论及量化实战
所属分类 quant
浏览量 7
箱体理论及量化实战
一 基本概念
箱体理论(Box Theory)由美国分析师 尼古拉斯・达瓦斯 提出,
股价在一段时间内会在一个明确的价格区间(箱体)内波动,上方压力位为箱顶,下方支撑位为箱底。
当股价突破箱体边界且有效站稳后,会进入新的箱体运行,原支撑 / 压力位转换为新的压力 / 支撑位。
股价像在一个 “箱子” 里上下震荡,突破箱子顶部就会进入更高的箱子,跌破底部就会掉入更低的箱子。
二 核心原理
市场情绪周期驱动
股价在箱底时,买方认为价格低估,买入力量增强,推动股价回升;
股价到箱顶时,卖方认为价格高估,卖出力量增强,压制股价回落;
突破箱体则意味着多空力量失衡,趋势形成。
支撑与压力的相互转换
有效突破箱顶 → 原箱顶变为新箱体的支撑位;
有效跌破箱底 → 原箱底变为新箱体的压力位。
三、量化策略结合点
箱体自动识别:
通过编程计算股价的 历史高低点,自动标记箱顶、箱底和箱体区间
Python 可使用 TA-Lib 库计算高低点;
突破信号量化:
设定量化指标
如收盘价突破箱顶 + 成交量 > 5 日均量 1.5 倍 + 站稳 3 天,自动触发交易信号;
回测验证:
用 ETF 历史数据回测箱体策略的胜率、盈亏比,优化箱体参数(如震荡时间、突破确认天数)。
四、风险提示
假突破风险:
需严格执行 “收盘价站稳 + 放量” 的确认条件,避免追高假突破;
趋势反转风险:
当大盘出现系统性风险时,箱体支撑 / 压力位可能失效,需及时止损;
结合其他指标:
单独使用箱体理论胜率有限,建议搭配 MACD、RSI 等指标,提高信号可靠性。
python实现
ETF 箱体策略的量化回测 Python 代码框架,
涵盖箱体识别、突破信号生成、回测分析核心模块
一、环境依赖
pip install tushare pandas numpy matplotlib backtrader # 核心库
tushare:获取金融数据(需注册获取 token);
backtrader:回测框架;
pandas/numpy:数据处理;
matplotlib:可视化。
二、完整代码框架
import tushare as ts
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import backtrader as bt
from datetime import datetime
# ===================== 1. 全局配置 =====================
# Tushare token(替换为自己的)
TS_TOKEN = "你的tushare_token"
ts.set_token(TS_TOKEN)
pro = ts.pro_api()
# 策略参数
BOX_TOP_BARS = 20 # 识别箱顶的周期(连续20天内的高点)
BOX_BOTTOM_BARS = 20 # 识别箱底的周期(连续20天内的低点)
BREAK_CONFIRM_DAYS = 3 # 突破确认天数(站稳N天)
VOL_MULTIPLE = 1.5 # 突破时成交量需大于5日均量的倍数
STOP_LOSS_RATIO = 0.03 # 止损比例(3%)
TAKE_PROFIT_RATIO = None # 止盈比例(默认按新箱体高度)
# ===================== 2. 数据获取函数 =====================
def get_etf_data(ts_code, start_date, end_date):
"""
获取ETF日线数据(Tushare接口)
:param ts_code: ETF代码(如510050.SH)
:param start_date: 开始日期(格式:20200101)
:param end_date: 结束日期(格式:20250101)
:return: 标准化后的DataFrame
"""
df = pro.daily(ts_code=ts_code, start_date=start_date, end_date=end_date)
# 数据格式转换
df['trade_date'] = pd.to_datetime(df['trade_date'])
df = df.sort_values('trade_date').reset_index(drop=True)
# 重命名列(适配backtrader)
df.rename(columns={
'trade_date': 'datetime',
'open': 'open',
'high': 'high',
'low': 'low',
'close': 'close',
'vol': 'volume'
}, inplace=True)
# 补充5日均量
df['vol_5ma'] = df['volume'].rolling(window=5).mean()
return df
# ===================== 3. 箱体识别函数 =====================
def identify_box(df):
"""
识别股价的箱体区间(箱顶/箱底)
:param df: 包含ohlcv的DataFrame
:return: 新增箱顶(box_top)、箱底(box_bottom)列的DataFrame
"""
# 计算N周期内的最高收盘价(箱顶)、最低收盘价(箱底)
df['box_top'] = df['close'].rolling(window=BOX_TOP_BARS).max()
df['box_bottom'] = df['close'].rolling(window=BOX_BOTTOM_BARS).min()
# 填充空值(前N天无箱体数据)
df['box_top'].fillna(method='bfill', inplace=True)
df['box_bottom'].fillna(method='bfill', inplace=True)
# 计算箱体高度(用于止盈)
df['box_height'] = df['box_top'] - df['box_bottom']
return df
# ===================== 4. 突破信号生成 =====================
def generate_break_signal(df):
"""
生成箱体突破/跌破信号
买入信号:放量突破箱顶 + 连续N天站稳
卖出信号:放量跌破箱底 + 连续N天站不稳 / 止损/止盈触发
"""
# 初始化信号列(0:无信号, 1:买入, -1:卖出)
df['signal'] = 0
df['break_confirm'] = 0 # 突破确认标记
# 1. 突破箱顶的初步判断(收盘价>箱顶 + 成交量>5日均量*VOL_MULTIPLE)
df['top_break_pre'] = (df['close'] > df['box_top']) & (df['volume'] > df['vol_5ma'] * VOL_MULTIPLE)
# 2. 跌破箱底的初步判断(收盘价<箱底 + 成交量>5日均量*VOL_MULTIPLE)
df['bottom_break_pre'] = (df['close'] < df['box_bottom']) & (df['volume'] > df['vol_5ma'] * VOL_MULTIPLE)
# 3. 确认突破(连续BREAK_CONFIRM_DAYS天站稳/站不稳)
for i in range(BREAK_CONFIRM_DAYS, len(df)):
# 买入确认:连续N天收盘价>箱顶
if all(df['close'].iloc[i-BREAK_CONFIRM_DAYS:i] > df['box_top'].iloc[i-BREAK_CONFIRM_DAYS:i]):
df.loc[df.index[i], 'break_confirm'] = 1
# 卖出确认:连续N天收盘价<箱底
elif all(df['close'].iloc[i-BREAK_CONFIRM_DAYS:i] < df['box_bottom'].iloc[i-BREAK_CONFIRM_DAYS:i]):
df.loc[df.index[i], 'break_confirm'] = -1
# 4. 生成最终交易信号
df.loc[df['break_confirm'] == 1, 'signal'] = 1 # 买入
df.loc[df['break_confirm'] == -1, 'signal'] = -1 # 卖出
# 5. 止损/止盈信号(持仓时触发)
hold_flag = False # 持仓标记
entry_price = 0 # 入场价
for i in range(len(df)):
if df['signal'].iloc[i] == 1 and not hold_flag:
hold_flag = True
entry_price = df['close'].iloc[i]
elif hold_flag:
# 止损:收盘价 < 入场价*(1-STOP_LOSS_RATIO)
if df['close'].iloc[i] < entry_price * (1 - STOP_LOSS_RATIO):
df.loc[df.index[i], 'signal'] = -1
hold_flag = False
# 止盈:收盘价 >= 入场价 + 原箱体高度
elif TAKE_PROFIT_RATIO is None and df['close'].iloc[i] >= entry_price + df['box_height'].iloc[i]:
df.loc[df.index[i], 'signal'] = -1
hold_flag = False
# 自定义止盈比例
elif TAKE_PROFIT_RATIO and df['close'].iloc[i] >= entry_price * (1 + TAKE_PROFIT_RATIO):
df.loc[df.index[i], 'signal'] = -1
hold_flag = False
elif df['signal'].iloc[i] == -1:
hold_flag = False
return df
# ===================== 5. Backtrader回测策略 =====================
class BoxStrategy(bt.Strategy):
"""箱体策略回测类"""
params = (
('stop_loss', STOP_LOSS_RATIO),
('take_profit', TAKE_PROFIT_RATIO),
)
def __init__(self):
self.dataclose = self.datas[0].close
self.dataopen = self.datas[0].open
self.signal = None # 信号缓存
self.order = None # 订单缓存
self.entry_price = 0 # 入场价
def next(self):
# 避免重复下单
if self.order:
return
# 获取当前信号(从外部数据传入,需提前处理)
current_date = self.datas[0].datetime.date(0)
signal = self.df.loc[self.df['datetime'] == pd.to_datetime(current_date), 'signal'].values[0]
# 买入信号:无持仓时触发
if signal == 1 and not self.position:
self.order = self.buy(size=10000) # 买入10000份(可调整)
self.entry_price = self.dataclose[0]
print(f"买入:{current_date},价格:{self.dataclose[0]}")
# 卖出信号:持仓时触发(突破跌破/止损/止盈)
elif signal == -1 and self.position:
self.order = self.sell(size=self.position.size)
print(f"卖出:{current_date},价格:{self.dataclose[0]},盈亏:{(self.dataclose[0]-self.entry_price)*self.position.size}")
self.entry_price = 0
# ===================== 6. 回测执行函数 =====================
def run_backtest(df):
"""
执行回测并输出结果
"""
# 初始化回测引擎
cerebro = bt.Cerebro()
# 添加策略
cerebro.addstrategy(BoxStrategy)
# 转换数据格式为backtrader兼容格式
df_bt = df[['datetime', 'open', 'high', 'low', 'close', 'volume']].copy()
df_bt.set_index('datetime', inplace=True)
data = bt.feeds.PandasData(dataname=df_bt)
cerebro.adddata(data)
# 设置初始资金
cerebro.broker.setcash(100000.0)
# 设置手续费(ETF手续费极低,按0.0003计算)
cerebro.broker.setcommission(commission=0.0003)
# 运行回测
print('初始资金:%.2f' % cerebro.broker.getvalue())
cerebro.run()
print('最终资金:%.2f' % cerebro.broker.getvalue())
# 绘制回测曲线
cerebro.plot(style='candlestick')
# ===================== 7. 主函数(流程执行) =====================
if __name__ == "__main__":
# 步骤1:获取ETF数据(以沪深300ETF 510300.SH为例)
etf_data = get_etf_data(
ts_code="510300.SH",
start_date="20200101",
end_date="20250101"
)
# 步骤2:识别箱体
etf_data = identify_box(etf_data)
# 步骤3:生成突破信号
etf_data = generate_break_signal(etf_data)
# 步骤4:箱体可视化(可选)
plt.figure(figsize=(12, 6))
plt.plot(etf_data['datetime'], etf_data['close'], label='收盘价', color='blue')
plt.plot(etf_data['datetime'], etf_data['box_top'], label='箱顶', color='red', linestyle='--')
plt.plot(etf_data['datetime'], etf_data['box_bottom'], label='箱底', color='green', linestyle='--')
# 标记买入/卖出信号
buy_signals = etf_data[etf_data['signal'] == 1]
sell_signals = etf_data[etf_data['signal'] == -1]
plt.scatter(buy_signals['datetime'], buy_signals['close'], marker='^', color='red', s=100, label='买入')
plt.scatter(sell_signals['datetime'], sell_signals['close'], marker='v', color='green', s=100, label='卖出')
plt.title('沪深300ETF箱体策略信号')
plt.xlabel('日期')
plt.ylabel('价格')
plt.legend()
plt.show()
# 步骤5:执行回测
run_backtest(etf_data)
三、代码使用说明
1. 基础配置
替换 TS_TOKEN 为自己的 Tushare token
注册地址:https://tushare.pro/register
调整策略参数(如 BOX_TOP_BARS 箱体周期、BREAK_CONFIRM_DAYS 突破确认天数)适配不同 ETF
2. 核心模块解释
模块 功能
get_etf_data
获取 ETF 日线数据,标准化格式并计算 5 日均量
identify_box
滚动计算 N 周期内的高低点,识别箱顶 / 箱底,计算箱体高度
generate_break_signal
结合成交量和站稳天数,生成买入 / 卖出信号,包含止损 / 止盈逻辑
BoxStrategy
Backtrader 策略类,执行下单逻辑,输出交易日志
run_backtest
初始化回测引擎,计算收益,绘制回测曲线
3. 输出结果
可视化图表:包含收盘价、箱顶 / 箱底、买入 / 卖出信号标记;
回测日志:输出每笔交易的买卖日期、价格、盈亏;
核心指标:初始资金、最终资金、收益率(最终资金 / 初始资金 - 1)
四、优化方向
多指标融合:
加入 MACD/RSI 指标过滤假突破(如突破时 RSI<70 避免超买);
参数优化:
用网格搜索遍历不同箱体周期 / 确认天数,找到最优参数组合;
多标的回测:
循环遍历多只 ETF(如 510050、159915),输出策略在不同标的上的表现;
风险指标:
添加最大回撤、胜率、盈亏比等回测指标(可通过 backtrader 的 Analyzer 实现)
五、注意事项
Tushare 免费版数据有频次限制,如需高频回测可改用本地数据(如 CSV 格式);
ETF 无印花税,手续费设置需贴近实际(代码中按 0.03% 计算);
回测结果仅为历史参考,实盘需结合市场环境和资金管理。
上一篇
下一篇
ETF 动量和反转策略
springboot3 应用启动 报错 找不到 com.mybatisflex.core.service.IService
python量化项目
Java 机器学习库