10_放量突破 · 回测实录
成交量放大到20日均量的1.5倍以上,且收盘价突破20日最高价,才买入。
策略类型 :趋势跟踪 / 量价
回测批次 :2026-10-04(本地 QuantStudio 回测引擎)
数据来源 :QuantStudio 本地行情库(etf_daily / stock_daily)
一、策略思路
在03号海龟突破的基础上加了一个"成交量确认"条件。逻辑是:价格突破如果只是缩量创新高,很可能是主力在自弹自唱、没有真实买盘接手;只有放量突破,才说明有增量资金真正进场。出场仍然用跌破10日最低价。多了一道过滤,信号数量会明显减少,但每个信号的"含金量"更高。
关键参数
| 参数 | 取值 |
|---|---|
VOL_MA_PERIOD |
20 |
VOL_MULTIPLIER |
1.5 |
ENTRY_LOOKBACK |
20 |
EXIT_LOOKBACK |
10 |
MAX_HOLDINGS |
3 |
EXPOSURE |
0.98 |
二、回测设置
| 项目 | 设置 |
|---|---|
| 策略文件 | 10_放量突破.py |
| 回测区间 | 2024-01-01 ~ 2026-09-02 |
| 初始资金 | 100000 元 |
| 佣金费率 | 0.00035(最低 5.0 元) |
| 印花税率 | 0.001 |
| 过户费率 | 1e-05 |
| 滑点 | 0.0 |
| 撮合价格 | close |
| 基准 | 000300 |
| 交易日数 | 647 |
三、回测结果

把这张图和03号海龟突破放在一起看:交易的回合数应该更少,胜率可能更高,但总收益不一定更好------"过滤"永远是一个取舍,它筛掉噪音的同时也会筛掉机会。
绩效指标
| 指标 | 数值 |
|---|---|
| 累计收益 | 53.70% |
| 年化收益 | 18.07% |
| 最大回撤 | 19.16% |
| 年化波动率 | 23.04% |
| 夏普比率 | 0.680 |
| 索提诺比率 | 1.116 |
| 卡玛比率 | 0.943 |
| 回合胜率 | 48.94% |
| 日胜率 | 50.39% |
| 盈亏比 | 222.2% |
| 盈利回合数 | 23 |
| 亏损回合数 | 24 |
| 平均持仓天数 | 57.1 |
| 成交笔数 | 104 |
「盈亏比」= 平均盈利 ÷ 平均亏损 > 100%,表示平均每笔赚的比亏的多。
「回合」指一次完整的买入→卖出(
round_trips),「成交笔数」是买卖双方分开计数的原始成交记录条数。
⚠️ 本窗口基准数据不完整 :引擎的基准序列尾部为占位填充值,据此算出的沪深300收益为 0.00%,该数字不可信;净值图中的基准曲线已截断至有效区间(至 2026-07-31)。因此本策略的 α、β、超额收益、信息比率等相对指标一律不可引用,只能用绝对收益与回撤来判断。
分年度收益
| 年份 | 区间收益 | 交易日 |
|---|---|---|
| 2024 | 18.75% | 242 |
| 2025 | 27.41% | 243 |
| 2026 | 2.51% | 162 |
四、交易明细
- 完整交易回合数:47 个(盈利 23 / 亏损 24)
- 单笔平均盈亏:+1128.55 元 ;最大单笔盈利 +31047.40 元 ;最大单笔亏损 -6264.01 元
- 持仓天数:平均 57.1 天 ,最长 521 天 ,最短 7 天
盈利最多的 5 个回合
| 买入日 | 卖出日 | 标的 | 盈亏(元) | 持仓天数 |
|---|---|---|---|---|
| 2025-06-06 | 2025-10-14 | 515880.SH |
+31047.40 | 130 |
| 2024-09-25 | 2024-12-17 | 159915.SZ |
+10505.15 | 83 |
| 2024-09-25 | 2024-11-25 | 512880.SH |
+9968.27 | 61 |
| 2024-09-25 | 2024-11-26 | 515880.SH |
+6294.94 | 62 |
| 2026-07-02 | 2026-08-25 | 512170.SH |
+5598.01 | 54 |
亏损最多的 5 个回合
| 买入日 | 卖出日 | 标的 | 盈亏(元) | 持仓天数 |
|---|---|---|---|---|
| 2026-03-12 | 2026-04-03 | 516160.SH |
-6264.01 | 22 |
| 2024-12-27 | 2025-01-03 | 515880.SH |
-4048.41 | 7 |
| 2024-06-17 | 2024-07-25 | 515880.SH |
-3604.92 | 38 |
| 2024-12-23 | 2025-01-03 | 512480.SH |
-3563.91 | 11 |
| 2026-03-03 | 2026-03-16 | 512660.SH |
-2282.59 | 13 |
五、完整回测代码
文件名:
10_放量突破.py(共 115 行)引擎:QuantStudio 本地回测;全部为公开 API,无平台专有调用。
python
# -*- coding: utf-8 -*-
"""
10_放量突破策略 (QuantStudio 本地回测版)
========================================
核心逻辑:成交量 > 20日均量×1.5 且 收盘价突破20日最高价 → 买入。
跌破10日最低价 → 卖出。
量价齐升是趋势启动的经典信号。
多标的独立判断,最多持3只。
"""
import numpy as np
ETF_POOL = [
'510300.SS', '159915.SZ', '510500.SS', '512880.SS',
'512800.SS', '512690.SS', '512170.SS', '512480.SS',
'515880.SS', '512660.SS', '516160.SS', '518880.SS',
]
VOL_MA_PERIOD = 20 # 成交量均线周期
VOL_MULTIPLIER = 1.5 # 放量倍数
ENTRY_LOOKBACK = 20 # 突破周期
EXIT_LOOKBACK = 10 # 出场周期
MAX_HOLDINGS = 3
EXPOSURE = 0.98
def initialize(context):
set_benchmark('000300.SS')
run_daily(context, trade, time='09:30')
log.info('10_放量突破初始化: 池=%d, 量能均线=%d日, 放量倍数=%.1f, 突破=%d日',
len(ETF_POOL), VOL_MA_PERIOD, VOL_MULTIPLIER, ENTRY_LOOKBACK)
def _get_breakout_signal(code):
"""返回 (是否放量突破, 突破幅度, 是否跌破出场线)"""
try:
n = max(VOL_MA_PERIOD, ENTRY_LOOKBACK, EXIT_LOOKBACK) + 2
h = get_history(n, '1d', field=['close', 'high', 'low', 'volume'],
security_list=code, fq='pre', include=False, is_dict=True)
if h is None or code not in h:
return False, 0.0, False
df = h[code]
closes = np.asarray(df['close'], dtype=float)
highs = np.asarray(df['high'], dtype=float)
lows = np.asarray(df['low'], dtype=float)
volumes = np.asarray(df['volume'], dtype=float)
if len(closes) < n:
return False, 0.0, False
current_close = float(closes[-1])
current_vol = float(volumes[-1])
# 成交量均线(不含当日)
vol_ma = float(np.mean(volumes[-(VOL_MA_PERIOD + 1):-1]))
volume_breakout = current_vol > vol_ma * VOL_MULTIPLIER if vol_ma > 0 else False
# 20日最高价(不含当日)
entry_high = float(np.max(highs[-(ENTRY_LOOKBACK + 1):-1]))
price_breakout = current_close > entry_high
# 10日最低价(不含当日)
exit_low = float(np.min(lows[-(EXIT_LOOKBACK + 1):-1]))
price_breakdown = current_close < exit_low
breakout_amp = (current_close - entry_high) / entry_high if entry_high > 0 else 0
is_breakout = volume_breakout and price_breakout
return is_breakout, breakout_amp, price_breakdown
except Exception:
return False, 0.0, False
def trade(context):
held = list(context.portfolio.positions.keys())
# 1. 跌破出场线卖出
for code in held:
_, _, breakdown = _get_breakout_signal(code)
if breakdown:
order_target_value(code, 0)
log.info('放量突破策略卖出 %s: 跌破%d日低点', code, EXIT_LOOKBACK)
# 2. 放量突破买入
held = list(context.portfolio.positions.keys())
if len(held) >= MAX_HOLDINGS:
return
candidates = []
for code in ETF_POOL:
if code in held:
continue
is_breakout, amp, _ = _get_breakout_signal(code)
if is_breakout:
candidates.append((code, amp))
if not candidates:
return
# 按突破幅度排序
candidates.sort(key=lambda x: x[1], reverse=True)
slots = MAX_HOLDINGS - len(held)
buy_list = candidates[:slots]
total_value = context.portfolio.total_value
per_value = total_value * EXPOSURE / MAX_HOLDINGS
for code, amp in buy_list:
order_target_value(code, per_value)
log.info('放量突破买入 %s: 突破幅度=%.2f%%, 目标市值=%.0f',
code, amp * 100, per_value)
def handle_data(context, data):
pass
六、风险提示
放量本身是双刃剑:突破日的巨大成交量,也可能是主力借突破出货。此外成交量数据受停牌复牌、ETF申赎影响,会出现假放量。过滤条件越多,策略对参数的敏感度就越高,1.5倍这个阈值不宜随意调整。
免责声明:本文所有数字来自本地历史回测,回测收益不代表未来表现,也不构成任何投资建议或个股/基金推荐。策略代码仅供研究学习,实盘执行还需额外考虑滑点、冲击成本、流动性、涨跌停与平台差异等因素。投资有风险,决策需谨慎。