简介这份文档围绕网格交易中网格大小固定的痛点面向已有一定Python基础的量化投资者与股票分析爱好者讲解一种按市场波动自动调整网格的实战方法。内容以收盘价、最高价和最低价的标准差衡量波动程度并借助KDJ指标中的RSV值对买入网格与卖出网格做差异化设置文中还给出RSV为0.85时买入网格0.15元、卖出网格0.85元的计算示例便于理解上升与下降趋势下如何减少过早满仓或空仓。包内仅含1个docx文件压缩包约165KB轻量便携适合直接阅读、对照实践也可作为用Python构建股票分析工具的参考。已有6377人学习反映该主题在量化社区具有一定关注度。读者可从中获得动态网格的完整思路、参数联动逻辑与示例推演用于优化网格策略的适应性并控制回撤风险。1. 网格交易为什么固定网格会死在趋势里网格交易最反直觉的一点是它的钱几乎都亏在单边行情上而不是震荡。震荡市里这套策略近乎必胜价格每来回一趟账户就多一份差价。可一旦网格间距被写死成一个常数策略就在两个方向上同时失去了判断力——大涨时几根阳线穿过所有网格仓位被清空后面的主升浪完整踏空大跌时同样几根阴线把预留的子弹全部打光浮亏叠着满仓亏损。这份资源要解决的问题非常具体让网格大小自己跟着波动走剧烈时把胃口撑大平静时把网眼收细再用 RSV 把买入和卖出两个方向的间距拆开让母仓能随价格趋势上下浮动。搞懂它等于搞懂了网格交易从「固定刻度」跨到「自适应刻度」的那一步也顺带看清了量化投资里一个反复出现的套路把尺度参数和方向参数拆开来分别处理。2. 标准差定宽把网格从固定刻度换成波动率刻度整套自适应方案拆成两步第一步决定「网格有多宽」第二步决定「这宽度在买入和卖出之间怎么分」。两步的输入完全独立第一步只吃价格序列第二步只吃 RSV。这种拆分不是随手为之它对应着两个正交的问题——市场现在有多大的波动空间以及价格现在站在这个空间的哪一侧。先看第一步。2.1 网格间距的本质是价格尺度固定网格的隐式假设是「价格尺度和时间无关」这在一只股票上几乎从不成立。同一只票横盘半年时日振幅可能只有 0.8%财报出来那周能到 4%。用同一个 0.5 元去切这两种状态前者可能一周都不成交一次后者一天击穿三层网格。问题不在参数选得不好在于尺子本身是错的。用标准差定宽本质是把网格从「绝对价格空间」映射到「波动率归一化空间」。设近 N 日价格的标准差为 σ网格宽度取 G k·σ翻译过来就是网格线之间的距离恒等于 k 个标准差。价格波动放大σ 变大G 跟着变大网格自动稀疏波动收敛G 自动收细。这和布林带的思路是同一个统计内核——布林带用 MA ± m·σ 画通道带宽随波动伸缩网格用 k·σ 定间距间距也随波动伸缩。区别只在于布林带描述的是「价格大概率落在哪」网格描述的是「价格每走多远我出手一次」。这个映射还有一个附带好处σ 是绝对价格单位股价从 10 元涨到 20 元σ 通常也会跟着放大网格宽度自动跟着股价走。省掉了「股价翻倍后要手动把网格乘 2」这种纯体力活也避免了低价股网格过密、高价股网格过疏的经典错配。2.2 为什么样本里要放最高价和最低价常规的波动率计算只吃收盘价因为收盘价噪声小、连续性好。但网格策略的成交判定靠的是日内价格穿越网格线真正决定「今天会不会触发」的是最高价和最低价收盘价反而没那么重要。如果只用收盘价算 σ日内那些插针行情会被系统性地低估——某天低开 5% 又被拉回收盘价看着纹丝不动σ 认为一切正常可网格已经被打穿了两层。把最高价、最低价一起纳入样本就是把日内极值信息喂给了波动尺度。常见的实现有两种一种是把 close、high、low 三个序列在窗口内的值直接拼成一个样本集合求标准差另一种是三个序列各自求滚动标准差再取平均。两者结果不同直接拼接会让 σ 系统性偏大因为三个序列高度相关趋势项被重复计入三次相当于把方向性涨跌误当成了波动。取平均更接近「真实的日内波动幅度」代价是计算三次、慢一点。做日线级别的网格这个开销完全可以接受。提示窗口长度一般取 20 个交易日约等于一个自然月。窗口太短σ 会被单日暴涨暴跌带偏窗口太长对波动率切换的反应会滞后半个月等于网格永远慢市场半拍。2.3 从 σ 到网格宽系数、窗口和上下限k 是唯一一个纯人工的系数控制「网格相对波动率是密还是疏」。k 取 1.0 意味着网格间距等于一个标准差在这个尺度上价格一天之内穿越网格的概率不算高也不算低属于中性设定。k 小于 1 网格变密、成交频繁、手续费压力大k 大于 1 网格变疏、单次成交利润厚、但可能长期不成交。实战里这个值通常落在 0.5 到 1.5 之间而且它对最终收益的影响比想象中大得多后面第 6 章会专门讲这个敏感度问题。光有 k·σ 还不够必须加两个硬边界。下限来自交易成本一次完整的买入加卖出双边佣金加印花税加滑点保守估计 0.15% 起步网格宽度如果小于这个数的两倍做一次亏一次做得越勤亏得越快。所以网格宽度的下限通常设成现价的 1.5% 到 2%具体取决于券商费率和标的流动性。上限来自成交频率如果 σ 因为某段极端行情被撑得很大网格宽到 20%一年可能都不触发一次策略就退化成了买入持有。上限一般设在现价的 8% 到 12%。参数含义典型取值调大的后果n波动率统计窗口20 个交易日反应变慢对波动切换不敏感k网格宽度相对 σ 的倍数0.5 ~ 1.5网格变疏成交频率下降min_pct网格宽度占现价的下限1.5% ~ 2%单次利润变厚但小额波动被浪费max_pct网格宽度占现价的上限8% ~ 12%极端行情下仍能保持成交2.4 代码实现与平滑处理把上面的推导落到 pandas 上核心就十几行。注意三个序列各自求标准差再平均而不是拼接后求一次。import numpy as np import pandas as pd def grid_width(close, high, low, n20, k1.0, min_pct0.015, max_pct0.10): 返回每日的网格宽度绝对价格单位 close/high/low: 等长的 Series按日期升序且必须是前复权数据 n: 波动率统计窗口 k: 网格宽度相对标准差的倍数 min_pct: 网格宽度占现价的下限用来兜住手续费和滑点 max_pct: 网格宽度占现价的上限防止极端行情下长期不成交 # 三个序列分别求滚动标准差直接拼接会重复计入趋势项σ 会系统性偏大 s_close close.rolling(n, min_periodsn).std(ddof1) s_high high.rolling(n, min_periodsn).std(ddof1) s_low low.rolling(n, min_periodsn).std(ddof1) sigma (s_close s_high s_low) / 3.0 raw k * sigma # 核心尺度项网格等于 k 个标准差 lo close * min_pct # 成本地板 hi close * max_pct # 频率天花板 return raw.clip(lowerlo, upperhi)这段代码里有两个容易忽略的点。min_periodsn保证了前 n-1 天返回 NaN 而不是拿半截样本硬算否则回测开头几天的网格宽度会莫名其妙地偏小。clip的上下界是乘在现价上的而不是乘在 σ 上因为成本的绝对值和股价挂钩跟波动率没关系。但直接拿这个结果去排网格线会出问题σ 是逐日滚动的每天窗口滑一格G 就会小幅跳动。价格纹丝不动网格线自己却挪了位置可能刚好挪到现价附近凭空触发一次假成交。所以 G 在进入交易逻辑之前通常还要过一层平滑def smooth_width(width, alpha0.3, max_step0.2): 对网格宽度做限幅 指数平滑避免网格线自己跳到价格上 out width.copy() prev width.iloc[0] for i in range(1, len(width)): target width.iloc[i] # 单日变动幅度限幅最多上下浮动 20% target np.clip(target, prev * (1 - max_step), prev * (1 max_step)) # 指数平滑alpha 越小越迟钝 prev alpha * target (1 - alpha) * prev out.iloc[i] prev return outmax_step限幅解决的是「跳变」alpha平滑解决的是「抖动」。两个参数都在拿响应速度换稳定性取值太激进等于没做自适应取值太保守又追不上波动率切换。经验上 alpha 取 0.2 到 0.3、max_step 取 0.2 是个不容易翻车的起点。3. RSV 拆格买卖间距不对称母仓怎么跟着价格漂第一步算出的 G 只是「一格有多宽」如果买卖两侧都用这个宽度得到的就是标准的对称网格——涨一格卖一份跌一格买一份仓位围绕母仓上下摆动长期均值回归。这份资源真正的巧思在第二步把总宽度 2G 在买入侧和卖出侧重新分配比例由 RSV 决定。分配完之后策略的行为就不再对称了。3.1 RSV 的几何含义收盘价在区间中的位置RSV 是 KDJ 指标计算链条上的第一个量定义很朴素RSV 收盘价 − N 日最低价/N 日最高价 − N 日最低价。它度量的是「今天的收盘价站在最近 N 天价格区间的百分之几位置」取值天然落在 0 到 1 之间。N 在 KDJ 里的默认值是 9原文没写窗口但这个量的分母就是区间高度窗口必须和前后的最高最低价对齐。这个量的几何意义比它的名字重要得多。RSV 趋近 1说明收盘价贴着区间上沿价格在过去九天里一直在往上顶RSV 趋近 0说明收盘价贴着下沿价格在往下砸。它不是一个预测指标而是一个位置指标——描述当前价格在近期波动区间的相对高度。用它来分配买卖网格等于承认了一个事实网格宽度应该随价格在区间中的位置而变化而不是随时间的推移而变化。和第一步的标准差对比一下会更清楚。σ 描述的是「区间有多高」RSV 描述的是「价格站在区间多高的位置」。前者是尺度后者是位置两者恰好构成一个完整的坐标描述。3.2 买卖网格的守恒分配2G 怎么切原文给的分配规则是设第一步算出的网格宽度为 G则买入网格与卖出网格之和恒等于 2G。RSV 为 r 时买入网格 2G × (1 − r) 卖出网格 2G × r拿原文的例子验算G 0.5 元2G 1 元r 0.85则买入网格 1 × 0.15 0.15 元卖出网格 1 × 0.85 0.85 元。此时股价每跌 0.15 元买一份每涨 0.85 元才卖一份。这个 2G 的系数不是随手定的它保证了一个重要性质当 r 0.5 时买入网格 卖出网格 G策略精确退化为对称网格。也就是说自适应机制在 RSV 中性时是「透明」的不会凭空改变策略的基本行为。r 偏离 0.5 越多不对称越明显但两侧之和始终锁死在 2G总的波动尺度完全由第一步的 σ 控制。这是两个模块正交的关键——第二步只搬运宽度不创造宽度。RSV 值市场位置买入网格卖出网格策略倾向0.85贴近区间上沿0.15 元0.85 元密集买入稀疏卖出0.50区间中部0.50 元0.50 元对称网格0.15贴近区间下沿0.85 元0.15 元稀疏买入密集卖出3.3 方向权重的实际效果母仓随价格漂移单纯看公式容易觉得这只是「参数调了一下」但把它放到趋势行情里跑一遍效果就出来了。上涨趋势中收盘价持续贴着区间上沿RSV 长期在 0.8 以上。买入网格被压到 0.15意味着价格从当前位置回撤 0.15 元就补一份仓卖出网格被撑到 0.85意味着要涨 0.85 元才减一份。结果就是小幅回撤就加仓大涨才减仓净持仓在上涨过程中不降反升。这正好解掉了固定网格最疼的那个场景——价格从 11 块一路涨到 15 块固定网格在 11.5、12、12.5 一路卖光后面 2.5 块的涨幅一股没吃到。自适应网格在这个过程里卖得极慢母仓被价格「抬」着往上走。下跌趋势中一切反过来。RSV 长期在 0.2 以下买入网格被撑到 0.85卖出网格压到 0.15。价格跌 0.85 元才买一份反弹 0.15 元就卖一份净持仓持续下降。这解掉的是另一个场景——从 11 块跌到 7 块固定网格在 10.5、10、9.5 一路买满抄底抄在半山腰剩下 3 块钱的跌幅只能干看着。自适应网格在下跌初期就开始减仓买入节奏被拉得很慢满仓点被大幅推后。原文把这叫「母仓在一定程度上随价格上下浮动避免爆仓和空仓」更技术化的说法是RSV 在这里充当了一个动量信号用趋势跟踪的逻辑去调制一个均值回归策略。两种假设本来互斥但作用在不同尺度上就不冲突了——网格负责在日内到周级别的波动里薅差价RSV 负责在月级别的趋势里调整仓位中枢。3.4 代码实现与参数含义把两步接起来计算每日买卖网格的函数如下。注意 RSV 必须做截断原因下一章会详细讲。def split_grids(total_width, rsv, rsv_floor0.2, rsv_cap0.8): 把总宽度 2G 分配到买入侧和卖出侧 total_width: 第一步算出的网格宽度 G rsv: (close - low_n) / (high_n - low_n)窗口默认 9 rsv_floor/cap: 对 RSV 做截断防止趋势末端把某一侧压成 0 r np.clip(rsv, rsv_floor, rsv_cap) buy_grid 2 * total_width * (1 - r) # 价格每跌这么多买一份 sell_grid 2 * total_width * r # 价格每涨这么多卖一份 return buy_grid, sell_grid def grids_today(df, i, k1.0, n20, rsv_n9): 给定第 i 行返回当日的买入网格和卖出网格 close, high, low df[close], df[high], df[low] g grid_width(close, high, low, nn, kk).iloc[i] hh high.iloc[i - rsv_n 1:i 1].max() ll low.iloc[i - rsv_n 1:i 1].min() # 区间高度为 0 时连续一字板给中性值避免除零 rsv (close.iloc[i] - ll) / (hh - ll) if hh ll else 0.5 return split_grids(g, rsv)三个参数值得单独说。rsv_n取 9 是 KDJ 的惯例窗口越短 RSV 越敏感、越容易在 0 和 1 之间来回跳窗口越长越平滑、但反应越慢。rsv_floor和rsv_cap是这套实现里最容易被忽略的防御措施把 RSV 限制在 0.2 到 0.8等价于给买卖网格各设一个 0.4G 的最小值防止某一侧被压成零宽度——零宽度的网格意味着「任何微小波动都触发」那基本等于失控。4. 锚点与记账自适应网格真正容易翻车的地方公式看起来很干净但从公式到能跑的代码之间隔着一层东西网格线到底画在哪里。固定网格里这个问题不存在母仓成本 11 块、网格 0.5 元网格线就是 10.5、11.5、10、12 一路排下去写死即可。自适应网格里 G 每天都变如果还按「母仓成本 ± 固定倍数」来排线第二天宽度一变所有网格线全部重排可能瞬间全部跳到现价下方或上方触发一堆莫名其妙的成交。4.1 锚点自适应网格比固定网格多出来的一个状态量标准解法是引入一个滚动锚点网格线不围绕成本价排而是围绕「上一次成交价」排。买入线 锚点 − buy_grid卖出线 锚点 sell_grid每次成交后把锚点更新成这次成交价。这样网格线永远贴着价格走宽度变化只影响「下一格有多远」不影响「当前站在哪」。这个改动看起来小实际改变了策略的性质。固定网格是路径无关的——不管价格怎么走网格线位置不变。滚动锚点的网格是路径相关的——锚点记录了策略上一次出手的位置成交历史会影响后续所有网格线的位置。回测时如果每天重算网格却忘了维护锚点得到的收益曲线会和实盘完全对不上这是这套策略最容易出问题的地方之一。注意锚点必须用「成交价」更新不能用「当日收盘价」更新。用收盘价更新会引入前视偏差回测里看起来能精准踩点实盘时根本复现不出来。4.2 成交判定与价格假设判定逻辑本身不难日内最高价 ≥ 卖出线就卖出日内最低价 ≤ 买入线就买入。难点在成交价怎么取。如果直接假设「卖在卖出线、买在买入线」回测会系统性高估收益。原因很简单价格能穿越网格线通常意味着当天有动能跳空开盘就是典型情况。某天直接高开 4%卖出线在 1% 的位置实际成交价是开盘价而不是卖出线你并没有占到那 3% 的便宜。保守的取法是# 卖出跳空高开时按开盘价成交不占便宜 sell_px max(sell_line, open_price) # 买入跳空低开时按开盘价成交也不占便宜 buy_px min(buy_line, open_price)另一个简化是「一天最多成交一次」。真实情况里价格可能在一天内来回穿越多条网格线但日线数据无法还原顺序硬要模拟只能上分钟数据。日线级别做网格一天一单是行业里普遍接受的近似代价是回测的成交次数会略少于实盘属于偏保守的误差方向可以接受。4.3 状态机实现把锚点、持仓、现金、成交判定打包成一个状态机核心循环大概是这样from dataclasses import dataclass dataclass class GridState: anchor: float # 上一次成交价所有网格线以它为基准 shares: int # 当前持仓股数 cash: float # 可用现金 fee: float 0.001 # 单边费率含佣金与印花税的粗估 def try_fill(state, open_, high, low, buy_grid, sell_grid, lot_cash): lot_cash: 每一格投入的金额决定每次买卖的股数 返回 buy / sell / hold sell_line state.anchor sell_grid if high sell_line: px max(sell_line, open_) # 保守成交价 shares int(lot_cash / px) // 100 * 100 # 向下取整到手 shares min(shares, state.shares) # 不允许卖穿 if shares 0: state.cash shares * px * (1 - state.fee) state.shares - shares state.anchor px # 锚点跟随成交价上移 return sell buy_line state.anchor - buy_grid if low buy_line: px min(buy_line, open_) shares int(lot_cash / px) // 100 * 100 cost shares * px * (1 state.fee) if shares 0 and cost state.cash: # 现金不足则放弃这一格 state.cash - cost state.shares shares state.anchor px # 锚点跟随成交价下移 return buy return hold// 100 * 100是 A 股一手 100 股的约束非整手的买单在实盘里根本发不出去回测里不处理会导致收益虚高。min(shares, state.shares)防止持仓算成负数cost state.cash防止现金透支——这两个检查看起来是废话但在参数调得比较激进时网格很密、每格金额很大它们是真的会触发的。lot_cash的确定方式决定了资金曲线。常见做法是总资金先划出一部分作为母仓一次性建仓剩下的现金除以预估的最大加仓格数得到每格金额。这个「最大加仓格数」本身又依赖网格宽度——网格越宽同样跌幅下能触发的次数越少所以每格可以给得更大。这形成了一个闭环也是为什么自适应网格在下跌行情里更抗揍网格自动变宽 → 同样跌幅触发次数减少 → 现金消耗速度下降。5. 避坑排查自适应网格上线后最常见的四类翻车纸面推演再漂亮跑起来该遇到的问题一个都不会少。下面四条是按「症状最明显、排查最费时」排序的实战记录每条都按现象、原因、解决三段来写。5.1 网格宽度日间抖动假信号把手续费吃光现象回测里成交次数是预期的三到五倍胜率看着还行但扣掉手续费后收益接近零甚至转负。逐笔看成交记录很多笔的价格几乎一样隔一两天就买一次卖一次。原因σ 是滚动计算的每天窗口滑一格G 就跳一下网格线跟着跳。价格实际没怎么动网格线自己挪到了价格附近于是触发了成交。窗口越短、k 越小这个抖动越剧烈。解决给 G 加平滑和限幅也就是 2.4 节那个smooth_width。另外把锚点更新严格绑在成交上——只要没成交锚点不动网格线就围绕同一个基准宽度小幅度变化不至于立刻产生穿越。这两条一起加假信号通常能压掉七八成。5.2 RSV 饱和趋势末端单边失控现象在一段单边上涨里买入异常密集现金很快耗尽或者在一段单边下跌里卖出异常密集很早就空仓。回撤曲线在趋势反转的那一刻突然变陡。原因RSV 的取值是 [0, 1]趋势末端会长时间贴边。RSV 逼近 1 时买入网格 2G × (1 − r) 趋近于 0意味着任何微小回撤都触发买入RSV 逼近 0 时反过来卖出网格趋近 0。原文的公式本身没有对极端值做保护。解决对 RSV 截断限制在 0.2 到 0.8 之间等价于给买卖网格各设 0.4G 的下限。这个改动的副作用是趋势末端的仓位漂移变慢但换来的是不会在一天之内把子弹打光或者把仓位清空。截断区间越窄越保守0.2/0.8 是个平衡点如果要更保守可以收窄到 0.3/0.7。5.3 除权与停牌造成的价格断层现象某一天突然触发大量成交之后所有网格线位置都不对了回测收益出现无法解释的跳变。原因用了未复权数据。送股、分红当天股价会出现一个非市场因素的跳空跌 30% 那种。σ 会把这个跳空当成剧烈波动G 瞬间撑到上限锚点如果恰好在那天更新后面所有网格线都偏了。停牌复牌同理停牌一个月出来补跌效果一样。解决全部改用前复权数据这是硬性前提不是可选项。另外加一道保险单日涨跌幅超过阈值比如 11%超过常规涨跌停幅度时判定为数据异常或特殊事件当天不交易并重置锚点为当日收盘价。停牌期间的行数据要么剔除、要么用前值填充后再标记为不可交易日。5.4 回测成交假设过于乐观实盘对不上现象回测年化两位数实盘跑三个月差一大截成交价普遍比回测差。原因回测里默认「价格触及网格线就按网格线成交」实盘里要么没挂单、要么挂单排不上队、要么跳空直接开在别处。另外手续费按万分之几算实际还有印花税、过户费、滑点双边加起来常常是回测假设的两到三倍。解决成交价用前面说的max/min(网格线, 开盘价)保守取法费率按双边 0.15% 以上估再加 0.05% 到 0.1% 的滑点。做完这两件事回测收益通常会掉三成左右但剩下的那部分才是能落地的。判断一套参数能不能用看的是扣完成本之后的曲线不是扣之前的。6. 设计取舍尺度与方向解耦之后还剩什么没解决这套方案最值得学的地方不是那两个公式而是它的分解方式σ 管尺度、RSV 管方向两者相乘之前互不干扰中性点精确退化为对称网格。这种正交拆解让每个参数都有独立的物理含义调参时知道自己在调什么——调 k 是调「网格稀不稀」调 rsv_n 是调「仓位漂移快不快」两者不会互相污染。代价也很实在。RSV 用的是 9 日窗口σ 用的是 20 日窗口两个时间尺度不一致会出现「尺度慢、方向快」的错配波动率还没反应过来RSV 已经在 0 和 1 之间跳了好几轮导致网格宽度还是老样子买卖比例却大幅摆动。这不是 bug是两套统计量各自的最优窗口不同带来的固有摩擦。更根本的问题在于这整套机制是一个精心调过的启发式不是从什么最优控制理论推出来的。k、n、rsv_n、截断区间、平滑系数五个参数全是人工设定参数空间不小过拟合的风险实打实存在。验证的时候我一般会做两件事验证项做法看什么参数敏感性k 从 0.5 扫到 1.5步长 0.1其余固定收益/回撤曲面是否平滑有没有孤立的尖峰同区间对照同样资金、同样母仓比例跑一版固定网格超额收益是否集中在趋势行情段第一项看的是稳定性。如果 k 取 0.9 时年化 15%、取 1.0 时掉到 3%说明这个参数在拟合噪声实盘大概率落在 3% 那一侧。第二项看的是归因。自适应网格相对固定网格的超额应该主要出现在单边行情里如果震荡市里也大幅跑赢那多半是回测成交假设出了问题不是策略真的更聪明。还有一个原文没展开、但绕不开的取舍这套方法只解决了「极端行情下过早满仓或空仓」资金利用率低的问题原封不动。母仓占掉四成、剩余现金还要留足加仓空间实际能动的钱始终只有一半左右。想提高利用率就得加杠杆或者压缩母仓比例而这两条路都会放大回撤和自适应机制降低回撤的初衷直接冲突。从那以后我每次给网格策略调参都会先把固定网格的对照组跑一遍再决定要不要开自适应——如果自适应相对固定网格的超额收益在样本外消失那这个「自适应」就只是把噪声包装成了参数。希望这套拆解能帮到你尤其是那句「先跑对照组」它比任何一组参数都值钱。本文还有配套的精品资源点击获取