05_网格交易 · 回测实录
单标的(沪深300ETF)5格网格,每格3%,跌一格买一份、涨一格卖一份。
策略类型 :震荡策略 / 网格
回测批次 :2026-10-06(本地 QuantStudio 回测引擎)
数据来源 :QuantStudio 本地行情库(etf_daily / stock_daily)
一、策略思路
网格交易是"震荡市神器"这一说法的原型。做法很机械:先取沪深300ETF近60日收盘价的中位数当基准线,上下各设5格、每格3%,共11档价格;首次运行时先建一半仓位的底仓,保证手里有货可卖;此后价格每跌破一格就买入一份(约总资金的10%),每涨过一格就卖掉持仓的20%。它不判断趋势、不预测方向,只希望价格在区间内来回震荡,好让它一格一格地把价差收进口袋。
关键参数
| 参数 | 取值 |
|---|---|
SECURITY |
510300.SS |
GRID_COUNT |
5 |
GRID_PCT |
0.03 |
BASELINE_LOOKBACK |
60 |
EXPOSURE |
0.95 |
二、回测设置
| 项目 | 设置 |
|---|---|
| 策略文件 | 05_网格交易.py |
| 回测区间 | 2022-01-01 ~ 2026-09-01 |
| 初始资金 | 99999 元 |
| 佣金费率 | 0.0(最低 5.0 元) |
| 印花税率 | 0.001 |
| 过户费率 | 1e-05 |
| 滑点 | 0.0 |
| 撮合价格 | close |
| 基准 | 000300 |
| 交易日数 | 1130 |
三、回测结果

这张图要看两件事:一是净值曲线长期是平的还是向下的。本窗口的结果是略微亏损(-3.09%)------在一段既有大涨也有大跌的四年半里都赚不到钱,说明"震荡套利"的收益被单边行情和交易摩擦吃掉了。二是回撤图中的坑:网格策略的坑通常是缓慢加深、然后长期不回填的那种,本图正是如此。
绩效指标
| 指标 | 数值 |
|---|---|
| 累计收益 | -3.09% |
| 年化收益 | -0.69% |
| 最大回撤 | 17.52% |
| 年化波动率 | 6.97% |
| 夏普比率 | -0.550 |
| 索提诺比率 | -0.741 |
| 卡玛比率 | -0.039 |
| 回合胜率 | 55.86% |
| 日胜率 | 51.42% |
| 盈亏比 | 79.3% |
| 盈利回合数 | 81 |
| 亏损回合数 | 64 |
| 平均持仓天数 | 48.0 |
| 成交笔数 | 292 |
「盈亏比」= 平均盈利 ÷ 平均亏损 > 100%,表示平均每笔赚的比亏的多。
「回合」指一次完整的买入→卖出(
round_trips),「成交笔数」是买卖双方分开计数的原始成交记录条数。
注:上表「盈利/亏损回合数」为引擎口径(合计 145 笔),其中包含回测末日尚未平仓的持仓;「交易明细」中按已平仓成交统计的回合数为 142 笔。两者不影响收益率与回撤,仅统计范围不同。
⚠️ 本窗口基准数据不完整 :引擎的基准序列尾部为占位填充值,据此算出的沪深300收益为 0.00%,该数字不可信;净值图中的基准曲线已截断至有效区间(至 2026-07-31)。因此本策略的 α、β、超额收益、信息比率等相对指标一律不可引用,只能用绝对收益与回撤来判断。
分年度收益
| 年份 | 区间收益 | 交易日 |
|---|---|---|
| 2022 | -12.59% | 242 |
| 2023 | -3.27% | 242 |
| 2024 | 3.95% | 242 |
| 2025 | 9.83% | 243 |
| 2026 | 1.35% | 161 |
四、交易明细
- 完整交易回合数:142 个(盈利 78 / 亏损 64)
- 单笔平均盈亏:-40.18 元 ;最大单笔盈利 +664.89 元 ;最大单笔亏损 -6966.51 元
- 持仓天数:平均 48.0 天 ,最长 112 天 ,最短 1 天
盈利最多的 5 个回合
| 买入日 | 卖出日 | 标的 | 盈亏(元) | 持仓天数 |
|---|---|---|---|---|
| 2024-08-05 | 2024-09-27 | 510300.SH |
+664.89 | 53 |
| 2024-07-30 | 2024-09-26 | 510300.SH |
+582.81 | 58 |
| 2025-10-10 | 2025-11-06 | 510300.SH |
+561.35 | 27 |
| 2025-09-04 | 2025-09-11 | 510300.SH |
+511.74 | 7 |
| 2025-08-06 | 2025-08-22 | 510300.SH |
+495.42 | 16 |
亏损最多的 5 个回合
| 买入日 | 卖出日 | 标的 | 盈亏(元) | 持仓天数 |
|---|---|---|---|---|
| 2022-01-28 | 2022-03-15 | 510300.SH |
-6966.51 | 46 |
| 2022-08-18 | 2022-11-01 | 510300.SH |
-1084.48 | 75 |
| 2022-08-03 | 2022-10-12 | 510300.SH |
-995.93 | 70 |
| 2022-01-05 | 2022-02-07 | 510300.SH |
-880.15 | 33 |
| 2022-01-14 | 2022-02-15 | 510300.SH |
-737.89 | 32 |
五、完整回测代码
文件名:
05_网格交易.py(共 129 行)引擎:QuantStudio 本地回测;全部为公开 API,无平台专有调用。
python
# -*- coding: utf-8 -*-
"""
05_网格交易策略 (QuantStudio 本地回测版)
========================================
核心逻辑:在固定价格区间内设置网格,价格每下跌一格买入一份,每上涨一格卖出一份。
震荡市神器,单边市会被套牢或卖飞。
单标的(沪深300ETF),5格网格,每格3%。
"""
import numpy as np
SECURITY = '510300.SS'
GRID_COUNT = 5 # 网格数量
GRID_PCT = 0.03 # 每格幅度(3%)
BASELINE_LOOKBACK = 60 # 基准价回看天数
EXPOSURE = 0.95
def initialize(context):
set_benchmark('000300.SS')
g.baseline_price = None
g.grid_levels = []
g.last_grid_index = None
run_daily(context, trade, time='09:30')
log.info('05_网格交易初始化: 标的=%s, 格数=%d, 每格=%.0f%%',
SECURITY, GRID_COUNT, GRID_PCT * 100)
def _calc_baseline(code):
"""计算基准价(60日收盘价中位数)"""
try:
h = get_history(BASELINE_LOOKBACK, '1d', field=['close'],
security_list=code, fq='pre', include=False, is_dict=True)
if h is None or code not in h:
return None
closes = np.asarray(h[code]['close'], dtype=float)
closes = closes[np.isfinite(closes)]
if len(closes) < 20:
return None
return float(np.median(closes))
except Exception:
return None
def _get_current_price(code):
"""获取当前价格(用昨日收盘价代理,日线模式下)"""
try:
h = get_history(1, '1d', field=['close'],
security_list=code, fq='pre', include=True, is_dict=True)
if h is None or code not in h:
return None
closes = np.asarray(h[code]['close'], dtype=float)
if len(closes) == 0:
return None
return float(closes[-1])
except Exception:
return None
def trade(context):
# 每日重新计算基准价和网格
baseline = _calc_baseline(SECURITY)
if baseline is None or baseline <= 0:
return
g.baseline_price = baseline
# 生成网格价格(基准价上下各GRID_COUNT格)
g.grid_levels = []
for i in range(-GRID_COUNT, GRID_COUNT + 1):
price = baseline * (1.0 + i * GRID_PCT)
g.grid_levels.append((i, price))
current_price = _get_current_price(SECURITY)
if current_price is None:
return
# 确定当前价格在哪个网格
current_index = 0
for idx, price in g.grid_levels:
if current_price >= price:
current_index = idx
# 首次运行:记录当前网格
if g.last_grid_index is None:
g.last_grid_index = current_index
# 初始建仓:按当前网格位置建底仓
pos = get_position(SECURITY)
if pos is None or pos.amount == 0:
# 底仓 = 总资金的50%
target_value = context.portfolio.total_value * 0.5
order_target_value(SECURITY, target_value)
log.info('网格初始建仓: 基准=%.3f, 当前=%.3f, 网格=%d, 底仓=%.0f',
baseline, current_price, current_index, target_value)
return
# 网格变化:下跌买入,上涨卖出
if current_index < g.last_grid_index:
# 价格下跌,买入
grids_dropped = g.last_grid_index - current_index
cash = context.portfolio.cash
if cash > 1000:
# 每下跌一格买入总资金的10%
buy_value = context.portfolio.total_value * 0.10 * grids_dropped
buy_value = min(buy_value, cash * 0.95)
if buy_value > 100:
order_value(SECURITY, buy_value)
log.info('网格买入: 价格=%.3f, 网格%d→%d, 买入=%.0f',
current_price, g.last_grid_index, current_index, buy_value)
elif current_index > g.last_grid_index:
# 价格上涨,卖出
grids_rised = current_index - g.last_grid_index
pos = get_position(SECURITY)
if pos is not None and pos.amount > 0:
# 每上涨一格卖出持仓的20%
current_value = pos.amount * current_price
sell_value = current_value * 0.20 * grids_rised
sell_value = min(sell_value, current_value * 0.9)
if sell_value > 100:
order_target_value(SECURITY, current_value - sell_value)
log.info('网格卖出: 价格=%.3f, 网格%d→%d, 卖出=%.0f',
current_price, g.last_grid_index, current_index, sell_value)
g.last_grid_index = current_index
def handle_data(context, data):
pass
六、风险提示
这是本批里最需要警惕的一类。网格的收益结构是"很多次小赚 + 一次大亏":回合胜率高达55.9%,但最大单笔盈利只有665元,最大单笔亏损却有6967元------一次单边下跌就能吞掉十次盈利。更深层的问题在仓位方向:上涨卖出后仓位越来越轻、下跌买入后仓位越来越重,等于在下跌趋势里自动"越亏越加仓",与趋势跟踪的"截断亏损"正好相反。另外本回测的佣金费率被设为0(只按每笔最低5元计,四年半共1460元)------由于本策略单笔金额小、笔数多,"最低5元"其实比费率制更贵,所以这次的亏损并非费用低估造成的;真正的缺口在于未计入的冲击成本与滑点。
免责声明:本文所有数字来自本地历史回测,回测收益不代表未来表现,也不构成任何投资建议或个股/基金推荐。策略代码仅供研究学习,实盘执行还需额外考虑滑点、冲击成本、流动性、涨跌停与平台差异等因素。投资有风险,决策需谨慎。