简介:这份文档面向对量化投资与Python股票分析感兴趣的投资者,聚焦网格交易策略中网格大小固定的痛点,讲解如何让网格随市场波动自动调整。内容围绕两条主线展开:一是以收盘价、最高价和最低价的标准差衡量波动程度,波动剧烈时放大网格、平缓时缩小网格;二是将买入与卖出网格差异化设置,并与KDJ指标中的RSV值挂钩,上升趋势中买入网格变小、卖出网格变大,下降趋势则相反,从而减少过早满仓或空仓的风险。文中还给出初始网格0.5元、RSV为0.85时买入网格0.15元、卖出网格0.85元的具体算例,便于对照理解。资源包为1个docx文档,约165KB,轻量易读,适合作为策略思路参考与量化入门学习材料。目前已有6372人学习下载,可帮助读者理解自适应网格的构建逻辑,并将其迁移到自己的Python股票分析实践中。
1. 固定网格为什么会在单边行情里翻车
做网格交易的人大多有过这种体验:震荡市里每天挂单成交,账户曲线稳步向上,感觉自己找到了提款机;可一旦行情走出单边趋势,要么早早把筹码卖光看着股价继续涨,要么一路补仓补到满仓被套。问题不在网格策略本身,而在于网格大小是写死的。这份资源给出的方案,就是让网格大小跟着波动率和趋势自动调整,用标准差控制整体网格宽度,用 RSV 值拆分买入网格和卖出网格。它适合已经懂网格基本逻辑、会用 Python 跑回测、想解决极端行情下过早满仓或空仓问题的从业者。整套思路不复杂,但落地时有几个参数边界必须搞清楚,否则调出来的网格还不如固定值。
2. 自适应网格的两层逻辑:标准差定宽度,RSV 定买卖不对称
2.1 用标准差衡量波动烈度,决定网格总宽度
固定网格最根本的毛病,是把市场波动当成常数。实际上,一只股票在不同阶段的日均振幅可能差好几倍。资源里的做法是取近一段时期收盘价、最高价、最低价的标准差作为波动烈度的代理变量,标准差大就放大网格,标准差小就收窄网格。这和布林带的思想同源——布林带宽度随价格波动伸缩,网格宽度也应该如此。
具体实现上,我一般会取最近 N 个交易日的最高价、最低价、收盘价三列,分别算标准差再合成一个综合波动值,或者直接用典型价格((H+L+C)/3)的标准差。资源没有限定 N 的取值,从实操角度看,N 太小会让网格频繁跳变,N 太大又反应迟钝,20 到 60 个交易日是比较常见的区间。下面这段代码演示如何从日线数据算出波动系数,并映射成基础网格宽度:
import numpy as np import pandas as pd def calc_base_grid(df, window=30, base_grid=0.5, vol_ref=None): """ df: 含 high/low/close 列的日线数据,按日期升序 window: 滚动窗口长度,常用 20~60 base_grid: 基准网格宽度(元),波动处于参考水平时的网格大小 vol_ref: 参考波动率,默认取全样本标准差的中位数 """ typical = (df['high'] + df['low'] + df['close']) / 3.0 # 滚动标准差,反映近期波动烈度 roll_std = typical.rolling(window=window, min_periods=window).std() if vol_ref is None: vol_ref = roll_std.median() # 用中位数做基准,避免极端值拉偏 # 网格宽度与波动率成正比,并做上下限约束 grid = base_grid * (roll_std / vol_ref) grid = grid.clip(lower=base_grid * 0.3, upper=base_grid * 3.0) return grid这段代码的关键在三个参数。window决定网格对波动的敏感度,30 日左右能兼顾响应速度和稳定性;vol_ref是基准波动水平,用全样本中位数而不是均值,是因为极端行情会把均值拉高,导致常态下网格被压得过窄;clip的上下限是血泪经验——不设约束的话,遇到财报或突发事件,标准差瞬间放大,网格可能被拉到基准值的五六倍,挂单价格直接飞出合理区间。上限 3 倍、下限 0.3 倍是我常用的保守设置,具体品种可以微调。
提示:标准差算的是绝对价格波动,高价股和低价股不可直接比较。如果要做多标的统一参数,先把价格序列做归一化,或者改用收益率标准差再乘回价格。
2.2 RSV 拆分买卖网格,让母仓随趋势浮动
光有总宽度还不够。资源里第二个方法是把买入网格和卖出网格拆开,两者之和等于第一步算出的基础网格,但各自大小由 RSV 值决定。RSV 的公式是(收盘价 - 最低价)/(最高价 - 最低价),取值在 0 到 1 之间。上涨趋势中收盘价贴近最高价,RSV 趋近 1;下跌趋势中收盘价贴近最低价,RSV 趋近 0。
映射规则是:买入网格 = 总宽度 × (1 - RSV),卖出网格 = 总宽度 × RSV。RSV 高说明趋势偏多,买入网格变小、卖出网格变大,策略变得易买难卖,母仓能跟着趋势往上走;RSV 低则反过来,卖出网格变小、买入网格变大,避免在下跌中过早满仓。资源里举的例子是总宽度 1 元、RSV 0.85 时,买入网格 0.15 元、卖出网格 0.85 元。
def split_grid(base_grid, rsv, min_ratio=0.1): """ base_grid: 2.1 算出的基础网格宽度 rsv: 当前 RSV 值,0~1 min_ratio: 单侧网格最小占比,防止一侧被压到几乎为零 """ rsv = np.clip(rsv, min_ratio, 1 - min_ratio) # 约束 RSV,避免极端值 buy_grid = base_grid * (1 - rsv) sell_grid = base_grid * rsv return buy_grid, sell_grid这里有个容易被忽略的坑:RSV 原始值可能正好是 0 或 1,比如一字涨停或跌停时,最高价等于最低价,分母为零。代码里用clip把 RSV 约束在 0.1 到 0.9 之间,保证任何一侧网格都不会小到无法成交。min_ratio取 0.1 意味着单侧网格至少占总宽度的 10%,这个值太小会让挂单密集到手续费吃掉利润,太大又失去了不对称的意义。另外 RSV 本身是 KDJ 指标的中间量,计算时通常用 9 日窗口,资源没有明确窗口长度,我一般沿用 9 日,和 KDJ 的习惯保持一致,便于对照。
2.3 把两层逻辑串成一条可执行的挂单链
单独看两个公式都简单,真正落地是把它们串起来:每个交易日收盘后,先算滚动标准差得到基础网格,再算当日 RSV 拆出买卖网格,然后以当前价为锚,向下按买入网格挂买单、向上按卖出网格挂卖单。下面是一个最小可运行的挂单价格生成函数:
def gen_grid_orders(price, base_grid, rsv, levels=5): """ price: 当前锚定价(常用最新收盘价或母仓成本价) levels: 上下各挂几档 """ buy_grid, sell_grid = split_grid(base_grid, rsv) buys = [round(price - buy_grid * i, 2) for i in range(1, levels + 1)] sells = [round(price + sell_grid * i, 2) for i in range(1, levels + 1)] return {'buy': buys, 'sell': sells} # 示例:基础网格 1 元,RSV 0.85,当前价 11 元 print(gen_grid_orders(11.0, 1.0, 0.85)) # 买入档约 10.85/10.70/...,卖出档约 11.85/12.70/...注意买入档间距是 0.15 元、卖出档间距是 0.85 元,这正是资源例子里“跌一毛五买、涨八毛五卖”的由来。levels控制挂单深度,档数越多资金占用越大,一般 3 到 5 档够用。价格取整到分是 A 股报价习惯,其他市场按最小变动价位调整。这套链条每天只需跑一次,盘后生成次日挂单,不需要盘中高频计算,对散户的算力和精力都友好。
3. 参数标定与回测验证:别拿默认值直接上实盘
3.1 三个核心参数的标定顺序
自适应网格看着参数不多,但window、base_grid、vol_ref三者互相牵制,乱调一通很容易过拟合。我的习惯是按固定顺序标定:先定base_grid,再定window,最后校准vol_ref。
base_grid应该和标的的日常波动匹配。一个粗略的起点是取近一年日均振幅的一半左右,比如某标的日均振幅 2%、价格 10 元,日波动约 0.2 元,基础网格可以从 0.2 到 0.3 元起步。设得太大,震荡市里几天不成交;设得太小,手续费和滑点吃掉利润。window影响网格调整的灵敏度,可以用回测扫描 20、30、40、60 几个值,看哪个在样本外表现稳定。vol_ref决定常态下网格是放大还是收窄,用样本中位数是稳妥选择,但如果标的经历过结构性变化(比如从高波动变成低波动),应该用近一年的中位数而不是全历史。
| 参数 | 含义 | 常用取值 | 调大后的影响 | 调小后的影响 |
|---|---|---|---|---|
| window | 滚动标准差窗口 | 20~60 日 | 网格调整迟钝、稳定 | 网格频繁跳变 |
| base_grid | 基准网格宽度 | 日均振幅的 0.5~1 倍 | 成交稀疏、单笔利润厚 | 成交密集、手续费占比高 |
| vol_ref | 参考波动水平 | 近一年中位数 | 常态网格偏窄 | 常态网格偏宽 |
| min_ratio | 单侧网格最小占比 | 0.1~0.2 | 不对称性减弱 | 单侧挂单过密 |
3.2 回测里必须看的两个指标
回测不能只看总收益。自适应网格的价值在于控制极端行情下的回撤,所以除了收益率,必须单独统计最大回撤和满仓/空仓持续时间。我一般会构造三段行情做分段回测:一段单边上涨、一段单边下跌、一段宽幅震荡,分别看策略在三段里的表现。固定网格在单边段往往出现“上涨段早早空仓、下跌段早早满仓”,自适应网格如果参数合理,满仓和空仓的天数应该明显缩短。
def backtest_segments(df, grid_func, init_cash=500000, init_pos_ratio=0.4): """ 简化回测框架:按日生成挂单,用次日高低价判断成交 grid_func: 输入当日数据,返回 (buy_grid, sell_grid) """ cash = init_cash pos = init_cash * init_pos_ratio / df['close'].iloc[0] equity_curve = [] for i in range(1, len(df)): row = df.iloc[i] prev_close = df['close'].iloc[i - 1] buy_g, sell_g = grid_func(df.iloc[:i]) # 最低价触及买入档则买入,最高价触及卖出档则卖出 if row['low'] <= prev_close - buy_g and cash > 0: buy_amt = min(cash, init_cash * 0.1) pos += buy_amt / (prev_close - buy_g) cash -= buy_amt if row['high'] >= prev_close + sell_g and pos > 0: sell_shares = pos * 0.1 cash += sell_shares * (prev_close + sell_g) pos -= sell_shares equity_curve.append(cash + pos * row['close']) return equity_curve这个框架刻意做得很粗,目的是快速对比不同参数下的相对表现,不是精确复现成交。真实回测要考虑涨跌停无法成交、停牌、滑点和手续费,这些细节会显著改变结果。分段回测时,把三段数据分别喂进去,看权益曲线在单边段的最大回撤,比看总收益更有参考价值。如果自适应版本在单边下跌段的回撤反而比固定网格大,多半是min_ratio设得太小或者window太长,导致网格没及时放大。
3.3 从回测到模拟盘的过渡
回测跑通不代表能上实盘。参数在历史数据上表现好,可能只是拟合了那段行情的波动特征。稳妥的做法是先用模拟盘跑一到两个月,每天记录生成的挂单和实际成交,重点观察两件事:一是网格宽度在波动突变时有没有及时调整,二是买卖不对称在趋势转换点是否造成连续单边成交。模拟盘期间不要改参数,跑完再统一评估。这一步很多人跳过,结果实盘第一周就遇到参数不适配的行情,后悔药没处买。
4. 避坑与排查:五个真实会翻车的地方
4.1 标准差窗口内数据不足导致网格异常
现象:新上市标的或数据缺失的标的,前几十个交易日网格宽度忽大忽小,挂单价格离谱。原因:滚动标准差在窗口未满时返回 NaN 或基于极少样本计算,波动估计极不稳定。解决:rolling里设min_periods=window,窗口未满时不生成挂单,或者用固定网格兜底,等数据够了再切换自适应模式。
4.2 RSV 分母为零引发除零错误
现象:一字板或极端行情下程序报错或 RSV 变成 inf。原因:最高价等于最低价时,RSV 公式分母为零。解决:计算前判断high - low是否小于一个极小值,是则沿用前一日的 RSV,或者在split_grid里先做clip约束。资源正文没提这个边界,但实盘一定会遇到。
4.3 网格频繁跳变导致挂单反复撤改
现象:每天生成的挂单价格和前一天差很多,盘中撤单改单频繁,滑点成本上升。原因:window太短或vol_ref用了均值,波动估计噪声大。解决:拉长窗口到 30 日以上,vol_ref改用中位数,并对基础网格做平滑处理,比如取近三日网格的移动平均再使用。
4.4 买卖不对称在趋势反转点造成连续亏损
现象:上涨趋势中卖出网格很大、买入网格很小,趋势突然反转下跌时,策略几乎不买,等 RSV 降下来才开始接,错过了低位;或者反过来在下跌转上涨时过早卖光。原因:RSV 是滞后指标,趋势反转时它反应慢半拍。解决:给 RSV 加一个短周期均线做平滑,或者设置趋势过滤条件,比如价格跌破 20 日均线时强制把 RSV 往 0.5 拉,减弱不对称程度。
4.5 资金分配与网格档数不匹配导致满仓过早
现象:明明设了自适应网格,下跌段还是很快满仓。原因:挂单档数levels太多、每档资金占比太大,网格放大带来的缓冲被档数抵消。解决:总仓位上限先定死(比如母仓 40%、加仓资金 60%),再反推每档资金和档数,确保最坏情况下(连续触及所有买入档)也不会打光子弹。这一步是资金管理,和网格算法本身无关,但两者必须一起算。
5. 进阶技巧:把 RSV 平滑和波动分位结合,减少参数敏感度
参数调多了会发现一个尴尬:window和min_ratio稍微一动,回测结果就变脸,说明策略对参数太敏感。我后来习惯做两处改进,能明显降低这种敏感度。
第一处是给 RSV 加平滑。原始 RSV 每天跳,拆出来的买卖网格也跟着跳,挂单不稳定。用 RSV 的 3 日或 5 日移动平均代替当日值,趋势判断更稳,代价是反应慢一两天。对于日频调仓的网格策略,这个代价可以接受。
def smooth_rsv(df, n=9, smooth=3): low_n = df['low'].rolling(n).min() high_n = df['high'].rolling(n).max() rsv = (df['close'] - low_n) / (high_n - low_n) rsv = rsv.replace([np.inf, -np.inf], np.nan).ffill() return rsv.rolling(smooth).mean()第二处是把波动率从绝对值改成历史分位。标准差算出来是个绝对数,不同标的、不同时期不可比,而波动率的历史分位(比如当前滚动标准差在过去两年里排第几)天然归一化到 0 到 1,映射网格宽度时更直观:分位 0.8 说明当前波动高于历史 80% 的时间,网格放大到基准的 1.5 倍左右;分位 0.2 则收窄。这样vol_ref这个参数就不需要了,少一个要调的旋钮。
| 改进项 | 解决的问题 | 代价 |
|---|---|---|
| RSV 平滑 | 挂单频繁跳变 | 趋势反转反应慢 1~2 日 |
| 波动率分位替代绝对值 | 参数跨标的不可比、vol_ref难定 | 需要至少两年历史数据 |
| 网格宽度三日平滑 | 单日异常波动拉偏网格 | 极端行情响应略滞后 |
这两处改完之后,我在模拟盘上对比过,同样的行情段,参数在合理区间内变动时,权益曲线的差异明显收窄,不再出现“窗口从 30 改成 35 结果天差地别”的情况。从那以后我每次标定这类自适应参数,都强制先做平滑和归一化,再谈调参,否则调出来的数字看着漂亮,换一段行情就原形毕露。希望这套拆解能帮到你,少走几段我踩过的弯路。
本文还有配套的精品资源,点击获取