☰
波动自适应网格交易策略:用标准差与RSV解决极端行情痛点
2026/10/27 11:09:05 网站建设 项目流程

简介:这份文档面向对量化投资与Python股票分析感兴趣的投资者,聚焦网格交易策略中网格大小固定所导致的过早满仓或空仓问题,讲解一种让网格随市场波动自动调整的改进思路。内容以收盘价、最高价、最低价的标准差衡量波动程度,并借助KDJ指标中的RSV值对买入网格与卖出网格实行差异化设置,配合0.5元初始网格、RSV为0.85时买入0.15元、卖出0.85元等算例,说明如何顺应趋势优化买卖决策。资源包共1个docx文件,约165KB,以图文文档形式完整呈现方法原理与计算逻辑,便于对照阅读和整理笔记。该资源已有6372人学习,适合希望理解网格交易自适应机制、并借助Python构建股票分析工具的读者参考,可从中获取波动率度量、网格差异化设置及回撤控制的具体思路。

1. 网格交易策略的痛点:为什么固定网格总在极端行情里翻车

去年帮一个做量化的朋友复盘他的实盘账户,发现一个很典型的现象:震荡市里网格策略跑得挺欢,一到单边行情就出问题——大涨时早早把仓位卖光,眼睁睁看着后面一大段利润溜走;大跌时又过早满仓,越跌越买,最后子弹打光只能躺平。他自己也承认,问题不在策略逻辑,而在网格大小是写死的。这份资源讲的正是这个痛点:一种让网格大小随市场波动自动调整的方法,核心是用标准差衡量波动剧烈程度、用 RSV 值区分买卖方向,让买入网格和卖出网格不再对称。适合已经跑过基础网格、想解决极端行情下过早满仓或空仓问题的开发者,也适合拿它当 Python 量化分析的练手项目。

2. 策略架构拆解:标准差定基准,RSV 分买卖

这套方法把网格大小的计算拆成两步走,先定一个总网格宽度,再按趋势把总宽度拆成买入和卖出两个不对称的间距。整个计算链路不依赖复杂模型,输入就是收盘价、最高价、最低价这几个基础行情字段,输出是下一次触发买入和卖出各自需要的价格间距。理解这条链路,后面看代码和改参数才不会迷路。

2.1 第一步:用标准差算出总网格宽度

总网格宽度的思路借用了布林带的思想——布林带的宽度随价格波动变化,波动大带宽就宽,波动小带宽就窄。这里把近一段时期的收盘价、最高价、最低价拿来算标准差,标准差越大说明近期价格越活跃,网格就该放大,让策略的“胃口”能吞下大波浪,不至于价格稍微一动就触发满仓或清仓。

常见做法是取一个滚动窗口,比如最近 20 个交易日,把每天的收盘价、最高价、最低价分别纳入计算,得到一个综合的波动度量。窗口长度的选择直接影响策略灵敏度:窗口太短,网格大小会跟着短期噪声剧烈跳动,交易频率失控;窗口太长,反应迟钝,遇到行情切换时网格还停留在旧状态。我一般会先用 20 日左右做基准,再根据标的的波动特性微调。

import numpy as np import pandas as pd def calc_base_grid(df, window=20, scale=1.0): """ 用滚动标准差计算基础网格宽度 df: 含 high/low/close 列的行情 DataFrame,按日期升序 window: 滚动窗口长度,默认 20 个交易日 scale: 缩放系数,用来把标准差映射到实际价格间距 """ # 对收盘价、最高价、最低价分别取滚动标准差 std_close = df['close'].rolling(window).std() std_high = df['high'].rolling(window).std() std_low = df['low'].rolling(window).std() # 三者取均值作为综合波动度量,避免单一字段偏差 combined_std = (std_close + std_high + std_low) / 3 # 乘以缩放系数得到基础网格宽度,单位与价格一致 base_grid = combined_std * scale return base_grid

这段代码的关键在combined_std这一行:单独用收盘价标准差也能跑,但把最高价和最低价的标准差一起平均进来,对日内振幅大的标的更稳。scale参数是留给二次开发的调节旋钮,默认 1.0 时网格宽度约等于一个标准差,想让策略更保守(网格更大、交易更少)就调大,想更激进就调小。注意rolling前window行会是 NaN,实盘里要等数据攒够再开始计算,回测时直接丢掉前window行即可。

2.2 第二步:用 RSV 把总宽度拆成不对称的买卖网格

有了总宽度,接下来解决买卖对称的问题。原文的思路是引入 KDJ 指标里的 RSV 值,公式很直白:RSV =(收盘价 - 最低价)/(最高价 - 最低价)。这个值落在 0 到 1 之间,上涨趋势里收盘价贴近最高价,RSV 趋近 1;下跌趋势里收盘价贴近最低价,RSV 趋近 0。

拆分的逻辑是:假设总网格宽度为 G,当前 RSV 为 r,则买入网格 = G × (1 - r),卖出网格 = G × r。拿原文的例子验证一下,G 为 0.5 元、r 为 0.85 时,买入网格 0.5 × 0.15 = 0.075 元,卖出网格 0.5 × 0.85 = 0.425 元。原文举例时把总宽度说成 1 元、买入 0.15、卖出 0.85,本质是同一个比例关系,只是基准宽度取值不同。核心结论一致:趋势向上时买入间距变小、卖出间距变大,实现“易买难卖”,让母仓能跟着价格往上飘一点,避免一涨就清仓。

def split_grid(base_grid, close, high, low): """ 按 RSV 拆分买入/卖出网格 base_grid: 第一步算出的总网格宽度 close/high/low: 当前周期的收盘、最高、最低价 返回 (买入网格, 卖出网格) """ price_range = high - low if price_range <= 0: # 一字板等极端情况,RSV 无定义,退化为对称网格 return base_grid / 2, base_grid / 2 rsv = (close - low) / price_range # 限制在 0~1,防止异常数据导致网格为负 rsv = min(max(rsv, 0.0), 1.0) buy_grid = base_grid * (1 - rsv) sell_grid = base_grid * rsv return buy_grid, sell_grid

price_range <= 0这个分支是血泪经验:一字涨停或跌停时最高价等于最低价,直接除会抛异常或得到 inf,策略在那种行情下本来也不该挂单,退化成对称网格或者干脆跳过当天更安全。rsv的截断同样必要,行情数据偶有脏值,不截断可能算出负的买入网格,挂单逻辑会直接翻车。

2.3 数据流与模块划分:从行情到挂单的完整链路

把两步串起来,整个策略的数据流是:行情数据进入 → 滚动标准差算基础网格 → 当日 RSV 拆分买卖网格 → 与当前持仓和现金比对 → 生成买入或卖出信号 → 交给交易执行层。对应到代码结构,我一般会拆成三个模块:data_loader负责拉取和清洗行情,grid_engine放上面两个计算函数和信号生成逻辑,executor处理下单和持仓记账。这样拆的好处是回测和实盘能共用grid_engine,只换executor的实现。

信号生成部分要维护一个“上次成交价”的状态,每次价格相对上次成交价变动超过对应方向的网格才触发。买入用买入网格判断,卖出用卖出网格判断,两个间距不同,所以同一个价格变动幅度在上涨和下跌时触发的概率天然不对称,这正是策略想要的效果。

3. 跑起来:环境准备与最小可运行示例

这套方法本身不依赖重型框架,Python 加 pandas、numpy 就能把计算链路跑通。想验证策略效果,再补一个回测循环即可。下面按“装环境 → 造数据 → 跑计算 → 看输出”的顺序走一遍,新手照着敲能出结果,熟手可以直接把函数抠进自己的项目。

3.1 依赖安装与目录约定

依赖只有三个:pandas处理行情表、numpy做数值计算、matplotlib可选,用来把网格宽度随价格变化的曲线画出来直观检查。安装命令很常规:

pip install pandas numpy matplotlib

目录上建议按职责分开放,避免所有代码堆在一个文件里:

modeng_grid/ ├── data/ │ └── sample_ohlc.csv # 行情数据,含 date/open/high/low/close ├── grid_engine.py # 网格计算与信号生成 ├── backtest.py # 简易回测循环 └── run_demo.py # 入口,跑通最小示例

行情数据如果没有现成的,可以先用模拟数据验证逻辑。真实标的的历史数据从常见行情接口导出成 CSV 即可,字段名对齐high/low/close就能直接喂进计算函数。

3.2 用模拟行情跑通网格计算

没有真实数据时,用随机游走造一段带趋势切换的行情,能同时看到网格在震荡段和单边段的不同表现。下面这段代码生成 200 个交易日的数据,前 100 天震荡、后 100 天单边上涨,然后逐日计算买卖网格。

import numpy as np import pandas as pd from grid_engine import calc_base_grid, split_grid np.random.seed(42) n = 200 # 前 100 天震荡,后 100 天单边上涨 returns = np.concatenate([ np.random.normal(0, 0.01, 100), np.random.normal(0.008, 0.01, 100) ]) close = 10 * np.exp(np.cumsum(returns)) high = close * (1 + np.abs(np.random.normal(0, 0.005, n))) low = close * (1 - np.abs(np.random.normal(0, 0.005, n))) df = pd.DataFrame({'close': close, 'high': high, 'low': low}) df['base_grid'] = calc_base_grid(df, window=20, scale=1.0) results = [] for i in range(len(df)): if pd.isna(df.loc[i, 'base_grid']): continue bg = df.loc[i, 'base_grid'] buy_g, sell_g = split_grid(bg, df.loc[i, 'close'], df.loc[i, 'high'], df.loc[i, 'low']) results.append((i, bg, buy_g, sell_g)) res_df = pd.DataFrame(results, columns=['idx', 'base_grid', 'buy_grid', 'sell_grid']) print(res_df.tail(10))

跑完看res_df的尾部,单边上涨段里sell_grid应该明显大于buy_grid,说明策略在上涨中卖出间距拉大、买入间距收窄,符合“易买难卖”的设计。如果两个值长期接近,检查 RSV 计算用的 high/low 是否和 close 对得上,数据错位是这类计算最常见的翻车点。

3.3 简易回测循环:验证网格调整是否真的减少过早满仓

计算正确只是第一步,还得看它对交易行为的影响。写一个最小回测:初始现金 50 万、母仓 20 万,价格相对上次成交价变动超过对应网格就成交固定金额,记录持仓和现金变化。重点观察单边上涨段里仓位是否比固定网格保留得更多。

def run_backtest(df, initial_cash=500000, position_value=200000, trade_amount=10000): cash = initial_cash shares = position_value / df.loc[0, 'close'] last_price = df.loc[0, 'close'] records = [] for i in range(1, len(df)): if pd.isna(df.loc[i, 'base_grid']): continue price = df.loc[i, 'close'] buy_g, sell_g = split_grid(df.loc[i, 'base_grid'], price, df.loc[i, 'high'], df.loc[i, 'low']) if price <= last_price - buy_g and cash >= trade_amount: shares += trade_amount / price cash -= trade_amount last_price = price elif price >= last_price + sell_g and shares * price > trade_amount: shares -= trade_amount / price cash += trade_amount last_price = price records.append((i, price, shares, cash, shares * price + cash)) return pd.DataFrame(records, columns=['idx', 'price', 'shares', 'cash', 'total'])

trade_amount控制单次成交金额,太小会被手续费吃掉,太大则网格还没铺开就满仓了,一般取总资金的 2% 到 5%。last_price是触发判断的锚点,只在成交后更新,这样价格在网格内来回晃不会反复触发。回测结果里对比total曲线和纯持有曲线,能直观看到网格调整在震荡段的增益和单边段的回撤控制。

4. 避坑与排查:实盘前必须过的几道坎

策略逻辑看着简单,真跑起来问题不少。下面几条是我和身边人踩过的,按“现象 → 原因 → 解决”整理,实盘前逐条对一遍能省不少事。

4.1 网格宽度忽大忽小,交易频率失控

现象:回测里某几天连续触发十几次买卖,手续费把利润吃光。原因:滚动窗口太短,标准差被单日异常波动带偏,基础网格瞬间缩到很小。解决:窗口不要低于 15 个交易日,并在calc_base_grid输出上加一个下限,比如base_grid = base_grid.clip(lower=price * 0.005),保证网格至少是价格的 0.5%,防止极端缩水。

4.2 RSV 在涨跌停日算出异常值

现象:一字板当天买入网格变成负数或极大值,挂单逻辑报错。原因:最高价等于最低价,分母为零。解决:就是 2.2 节里那个price_range <= 0分支,退化为对称网格或直接跳过当日信号。实盘里涨跌停本来也难成交,跳过最稳妥。

4.3 回测收益漂亮,实盘一跑就亏

现象:回测年化很好看,实盘上线两周回撤明显。原因:回测按收盘价成交,没考虑滑点和手续费,也没处理停牌和跳空。解决:回测里给每次成交加千分之一到千分之三的滑点,手续费按券商实际费率扣,跳空日按开盘价成交而不是收盘价。这几项加上后收益会缩水一大截,但那才是接近真实的数字。

4.4 母仓和网格资金比例失调导致过早满仓

现象:下跌段没跌多少就满仓了,后面继续跌只能干看。原因:单次成交金额相对总资金太大,或者买入网格在下跌趋势里被 RSV 放大得不够。解决:单次成交金额控制在总资金 2% 到 5%,同时检查下跌段 RSV 是否确实趋近 0、买入网格是否相应变大。如果买入网格没变大,回头查 RSV 公式里 high/low 的取值周期是否和网格计算窗口一致。

5. 二次开发:把网格引擎接进自己的策略框架

这套方法的价值不在照抄,而在把“波动自适应 + 买卖不对称”这两个思路抽出来,接到自己的持仓管理和风控体系里。下面说几个我实际改过的方向,以及验证改动是否有效的办法。

第一个方向是替换波动度量。标准差只是其中一种,换成 ATR(平均真实波幅)往往对跳空更敏感,因为 ATR 把前一日收盘价也纳入计算。改法是把calc_base_grid里的combined_std换成 ATR 序列,接口不变,回测对比两者在跳空频繁的标的上谁的回撤更小。第二个方向是给 RSV 加平滑,原始 RSV 单日跳动大,用 3 日移动平均后的 RSV 拆网格,交易频率会明显下降,适合资金量大、不想频繁操作的场景。

第三个方向是加一层总仓位约束。原方法只调网格间距,不管总仓位上限,极端下跌里仍可能满仓。我一般会在信号生成后加一道检查:当前持仓市值超过总资产 70% 时,买入信号降级为不操作,只保留卖出。这个约束和网格调整是正交的,叠加上去不会破坏原有逻辑。

验证改动是否有效,别只看总收益。我习惯固定看三个指标:最大回撤、单边行情段的仓位保留率、年化换手次数。改动后如果收益涨了但换手翻倍,大概率是过拟合了短期噪声,换一段样本外数据再跑一遍,两个区间都稳才算数。

改造方向改动位置观察指标适用场景
标准差换 ATRcalc_base_grid最大回撤跳空频繁的标的
RSV 加平滑split_grid前处理年化换手次数大资金、低频操作
总仓位上限信号生成后单边段仓位保留率极端行情风控

最后说个教训。早些年我改这类策略,总喜欢一次动好几个参数,回测一好看就上线,结果实盘出问题根本定位不到是哪个改动引起的。从那以后我每次只改一个变量,改完先在样本外区间跑一遍,再拿模拟盘挂两周,确认行为符合预期才碰实盘。这套流程慢,但省下来的排查时间远比多花的多。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询