
先说结论如果你手里有一笔短期用不到的闲钱又不想天天盯盘做短线那网格交易可能是最容易上手、也最容易理解的一种量化策略。最近我把掘金量化的Python API重新翻出来用沪深300ETF做标的写了一个经典网格策略回测区间选在2022年到2024年这段震荡市测算年化收益率大概17%。这篇文章就是我完整复现过程的记录代码是全的参数也是调过的照着跑就能看到一份相对完整的回测报告。适合有两三个小时、对Python有最基础了解、想正经跑通一个量化策略的朋友。先说清楚这个17%是历史回测的测算结果不是未来收益承诺更不是稳赚不赔的印钞机。网格策略有它天然的优点也有它致命的软肋尤其是碰到单边行情的时候浮亏会让你怀疑人生。所以我会把原理、代码、参数、坑全部讲透你可以把它当成一份可以复现的模板根据自己的资金和风险偏好去改。1. 网格交易策略的原理与适用场景1.1 网格策略的本质把波动变成价差网格交易的核心思想特别朴素给价格画好格子跌一格买一笔涨一格卖一笔赚的就是价格在格子之间反复横跳的差价。它完全不预测方向不关心明天是涨是跌只关心价格有没有在网格区间内波动。举个具体例子。假设基准价是3.85元网格步长设2%那么第一档买入价是3.85乘以0.98约3.77元第二档是3.77再往下2%约3.70元。策略做的事情就是价格跌到3.77买1500股跌到3.70再买1500股然后价格反弹回3.77附近卖掉一批赚到一次约0.07元的价差1500股就是约100元的毛利。只要价格在区间里来回震这种低买高卖的循环就会不断重复。这个逻辑听起来像菜市场小贩卖菜进价低一点就多囤一点行情好了再卖掉赚个差价。网格策略本质上就是把这个过程程序化、纪律化不受情绪影响不会因为跌了害怕不敢买也不会因为涨了贪心想多拿。1.2 为什么选ETF作为网格标的网格策略最怕一个东西标的价格跌下去再也回不来。个股会暴雷、会被ST、会退市一旦碰到基本面崩塌网格买到的不是便宜的筹码而是接不住的飞刀。所以网格策略的标的必须满足三个条件不容易死、流动性好、有足够的天然波动。指数ETF是很好的选择。比如沪深300ETF510300、中证500ETF510500、创业板ETF159915它们跟踪的是指数长期看有均值回归属性短期又具备波动非常适合网格。尤其是510300成交量大买卖价差小小资金来回操作几乎感觉不到冲击成本。实盘选标的时我一般会看两个数据一是日均成交额至少要几千万级别太小了进出不方便二是历史波动率波动太小的标的触发网格的次数太少策略会长期趴着不动资金利用率很低。如果你是追求更高收益弹性的类型也可以尝试行业ETF或者科创类ETF波动更大网格成交频率更高但同时回撤也会更猛这一点在后面参数部分还会细说。1.3 行情适配与收益来源网格策略是典型的区间收割机最适合震荡行情。价格在一个箱体里来回波动策略就在箱体下沿买、箱体上沿卖反复赚价差。2022年到2024年这段时间沪深300指数整体就是宽幅震荡没有特别明显的单边趋势所以网格策略在这种环境下很容易跑出不错的测算收益。反过来网格策略最怕两种行情。一种是单边上涨价格一路涨策略在上涨途中过早地把仓位卖光了后面涨再多也和你没关系踏空。另一种是单边下跌价格一路跌策略在下跌过程中不断买入浮亏越来越大买到最后没有子弹了只能被动等待反弹。很多网格策略死在单边熊市里就是因为仓位越买越重最后资金链断裂。所以17%的年化收益并不是一个稳定可复制的数字它高度依赖你回测区间的行情特征。如果换到单边牛市或单边熊市同样的网格策略表现会差很多。理解这一点比知道这个策略能赚钱更重要。2. 掘金量化平台与Python环境准备2.1 掘金量化是什么为什么选它掘金量化是一套面向个人和研究机构的量化平台核心包含终端和Python SDK两大部分。终端负责数据展示、策略管理和回测调度Python SDK也就是gm.api这套接口负责编程侧的策略逻辑、行情订阅和下单操作。个人做量化策略最大的门槛通常不在策略本身而在基础设施历史行情数据从哪来、回测撮合怎么做、手续费滑点怎么算、模拟盘和实盘下单怎么接通。这些如果都自己从零搭工作量非常大。掘金这些基础设施都内置了你只需要把精力放在策略逻辑上写好代码传到平台一键回测快速看到资金曲线和交易明细。对新手来说掘金的另一个好处是API设计得比较简洁几个核心函数就能覆盖大多数策略场景subscribe订阅行情、on_bar处理K线、order_volume下单、position查持仓。我不会在本文里堆砌平台宣传但单纯从快速复现一个策略这个角度看它确实省力不少。2.2 账号、Token与SDK安装第一步是注册账号并下载掘金量化终端。安装完成后登录在终端右上角或系统设置里能找到你的Token有的版本显示为密钥或者API Key。Token相当于身份凭证策略访问行情和交易接口时都要用到它千万别把它写到公开的代码仓库或者博客里泄露了别人就能拿你的账号操作。第二步是准备运行环境。最简单的方式是直接在掘金终端的策略编辑器里新建一个Python策略把代码粘贴进去直接回测终端自带Python环境和SDK不需要额外安装任何依赖。如果你打算在本地用VS Code或者其他编辑器开发再通过命令行方式跑回测就需要单独安装掘金的Python SDK。不同版本的掘金平台对应的包名略有差异常见的是gm或者gm-api具体以你下载的终端版本对应的官方文档为准。安装命令一般就是pip install xxx装好后在Python脚本里通过from gm.api import *引入接口。我第一次用的时候在这步踩过坑一直纠结到底该装哪个包后来发现直接在终端策略编辑器里跑最省心完全绕开了本地环境的各种奇奇怪怪的问题。建议新手先走这条路跑通了再考虑本地化开发。2.3 回测框架的最小模板掘金的回测是通过一个run()函数来启动的不管策略逻辑写在哪个文件里最后执行回测时都要调用它。下面是最小回测模板的骨架from gm.api import * # 这里放策略逻辑init、on_bar、以及全局变量定义 # ... # 策略逻辑代码省略后面会给出完整版本 if __name__ __main__: run( strategy_id你的策略ID, filenamemain.py, modeMODE_BACKTEST, token你的Token, backtest_start_time2022-05-09 09:00:00, backtest_end_time2024-05-08 15:00:00, backtest_initial_cash100000, backtest_commission_ratio0.0003, backtest_slippage_ratio0.0005 )参数逐个解释strategy_id是你在终端里创建策略后得到的唯一标识本地运行时需要它filename是当前策略文件名mode是回测模式必须写成MODE_BACKTESTtoken填你自己的backtest_start_time和backtest_end_time是回测起止时间backtest_initial_cash是初始资金backtest_commission_ratio是佣金费率万三是比较常见的水平backtest_slippage_ratio是滑点比例用来模拟真实成交时价格偏离的损失。这些参数对回测结果的真实性影响很大。很多人回测收益很高一上实盘就拉胯很大一部分原因就是回测时把手续费和滑点设成了0或者设得太低。网格策略的收益来源本来就是微小的价差交易成本一高利润空间立刻被吃掉一大截。这个我在后面避坑部分会展开说。3. 网格策略核心逻辑与完整代码3.1 四个关键参数怎么定在写代码之前先把策略的四个核心参数讲清楚。它们是这个网格策略的灵魂基准价网格的中轴价格。最简单的做法是用策略开始时的价格比如510300在2022年5月初大约3.85元。更进阶的做法是动态用20日均线作为基准价每隔一段时间重新生成一次网格这样可以适应行情中枢的上下移动后面我会给出改进思路。网格步长相邻两格之间的价格间隔。间隔设得越小触发越频繁单格利润越小间隔设得越大触发越稀疏单格利润越大。对于ETF来说2%到3%的步长是一个比较合理的区间太小了容易被交易成本吃掉利润。网格数量从基准价向下最多铺多少档。它决定了策略能承受多大的下跌幅度。如果步长2%、网格数量20那么理论上可以覆盖从基准价向下约40%的空间。每格股数每次买入或卖出的数量。它直接决定了资金利用率和单笔利润需要根据账户资金反推后面参数校准部分会给出计算公式。这四个参数不是孤立的它们共同决定了策略的性格。步长和格数决定了网格覆盖的深度格数和每格股数决定了最多需要多少资金步长和每格股数又共同决定了单笔利润的大小。调参的过程就是在这几个约束之间找平衡。3.2 状态变量与网格生成逻辑代码里我用了两个关键状态grid_prices和level。grid_prices是一个列表存的是从基准价向下一档一档的买入价格level是一个整数0表示空仓-1表示已经买入了一格-2表示买入了两格依此类推。网格生成逻辑很简单。假设基准价是3.85步长2%网格数量20那么第i档价格就是3.85乘以(1减去2%乘以i)i从1取到20。这样生成的价格是等差下降相邻两格之间的绝对价差保持一致约0.077元。代码里我用一个for循环生成这个列表并且打印出来方便核对。买入触发的逻辑是当前价格跌到grid_prices里某一档以下就买一格level减1。比如现在空仓level是0第一档买入价是3.77价格跌到3.77以下就买入level变成-1再跌到第二档3.70再买一格level变成-2。卖出触发的逻辑是当前已经持有k格价格反弹回到上一次买入档位的上方就卖出一格level加1。这里要重点理解网格策略的卖出不是涨到成本价就卖而是涨回某个网格位置就卖所以每次卖出都能落袋一格左右的价差。有一个细节需要注意如果价格在短时间内连续跌穿多个网格我的代码是按K线逐根处理的一根K线最多买一格不会一次性把好几个网格的仓位全买满。这样处理的好处是避免在急跌过程中过度冲动加仓资金消耗速度更可控。如果你想激进一点也可以在单根K线里循环补足所有被跌穿的网格但我不建议新手这么干。3.3 完整可运行代码下面给出完整代码直接复制到掘金策略编辑器里就能跑。核心逻辑都写了注释建议你跑通之后再动手改参数。# -*- coding: utf-8 -*- 掘金量化经典网格交易策略复现版 标的SHSE.510300沪深300ETF 策略核心以基准价为中轴向下每隔固定步长买入一格 价格反弹回上一档时卖出一格赚取震荡差价。 运行方式 1) 在掘金量化终端的策略编辑器中新建Python策略粘贴代码后直接回测 2) 或使用本地Python环境的SDK在文件末尾调用 run() 配置回测参数。 from gm.api import * # ---------- 1. 策略参数可按需调整 ---------- SYMBOL SHSE.510300 # 交易标的 BASE_PRICE 3.850 # 网格基准价 GRID_STEP 0.02 # 网格步长2% GRID_NUM 20 # 向下网格的最大数量 VOLUME_PER_GRID 1500 # 每格下单股数 ORDER_TYPE OrderType_Market # 市价单回测/模拟推荐 # ---------- 2. 全局状态 ---------- grid_prices [] # 向下买入的网格价格 level 0 # 0空仓-n已买入n格 def init(context): global grid_prices # 订阅60秒级K线也可以改成 300s 或 900s subscribe(symbols[SYMBOL], frequency60s, count1) # 生成网格价格序列第i格价格 base * (1 - step * i) for i in range(1, GRID_NUM 1): grid_prices.append(round(BASE_PRICE * (1 - GRID_STEP * i), 3)) print( 网格参数 ) print(f标的: {SYMBOL} 基准价: {BASE_PRICE} 步长: {GRID_STEP:.1%} 最大格数: {GRID_NUM}) print(向下买入档位:) for i, p in enumerate(grid_prices, 1): print(f 第{i:2d}格: {p:.3f}) print(f初始资金: {context.account().cash.non_free:.2f}) def on_bar(context, bars): global level px bars[0].close # 获取当前持仓数量 pos position(symbolSYMBOL, sidePositionSide_Long) held pos.volume if pos else 0 # ---------- 买入逻辑 ---------- # 只有还没买到最大格数时才考虑买入 if level -GRID_NUM: # 已经买了 n 格下一格是第 n1 格 buy_index abs(level) buy_price grid_prices[buy_index] if px buy_price: cash context.account().cash.non_free cost px * VOLUME_PER_GRID if cost cash: order_volume( symbolSYMBOL, volumeVOLUME_PER_GRID, sideOrderSide_Buy, order_typeORDER_TYPE, position_effectPositionEffect_Open ) level - 1 print(f[买入触发] 现价:{px:.3f} 档位:{buy_price:.3f}买入 {VOLUME_PER_GRID} 股level - {level}) else: print(f资金不足跳过买入当前可用资金: {cash:.2f}) # ---------- 卖出逻辑 ---------- # 持仓数量足够卖出一格时才考虑卖出 if level 0 and held VOLUME_PER_GRID: held_grids -level # 买了几格卖出触发价是对应的上一次买入档位 if held_grids 1: sell_trigger BASE_PRICE else: sell_trigger grid_prices[held_grids - 2] if px sell_trigger: order_volume( symbolSYMBOL, volumeVOLUME_PER_GRID, sideOrderSide_Sell, order_typeORDER_TYPE, position_effectPositionEffect_Close ) level 1 print(f[卖出触发] 现价:{px:.3f} 档位:{sell_trigger:.3f}卖出 {VOLUME_PER_GRID} 股level - {level})这段代码有几个点我特别说一下。买入前检查了可用资金卖出前检查了持仓数量这两个保护非常重要。网格策略在极端行情下可能会遇到资金不够买或者持仓不够卖的情况如果不做检查直接下单订单会被拒绝而level状态已经改变了后面策略的实际持仓和代码里的level就对不上整个网格就乱了。所以每次操作前都加上资金和持仓校验是网格策略的基本素养。另外frequency我设置的是60秒K线也就是每分钟判断一次。如果你希望策略更灵敏可以改成10s或者5s但要注意高频触发会显著增加手续费而且回测速度也会变慢。对于日线级别的网格来说60秒甚至300秒都够用了不用过度追求高频。4. 参数校准、资金管理与回测验证4.1 步长、格数与覆盖深度的关系参数不是拍脑袋定的步长和格数组合起来决定了网格的有效覆盖范围。覆盖深度约等于步长乘以格数。我用2%步长、20格算下来覆盖深度是40%也就是说基准价3.85元对应的网格下沿大约在2.31元附近。那问题来了这个覆盖深度够不够要看标的的实际波动范围。510300在2022年到2024年的历史低点大约在3元出头远高于2.31元所以我的20格网格实际只用了十几格下沿是一层比较厚的安全垫。你可以根据标的最近两年的最低点来反推参数先找一个你心里觉得跌到这个位置我就敢大量加仓的价格把它作为网格下沿然后倒推步长和格数。举个例子如果你判断510300未来两年最多跌到3.0元当前基准价3.85元那么你需要覆盖的下跌空间是22%。如果步长2%那么至少需要11格如果步长3%至少需要8格。格数一定不要太满最好留出比预估低点再多10%到20%的余量因为市场永远比你想象的极端。步长本身也不要设太小尤其是对ETF来说2%以下很容易被手续费和滑点侵蚀。比如基准价3.85元步长1%的话一格价差只有0.0385元1500股毛利润约58元扣除来回的手续费和滑点实际到手的利润会少得可怜。步长2%到3%是一个比较舒服的区间利润空间和触发频率相对平衡。4.2 资金预算算清楚最大建仓金额网格策略最容易犯的错误是仓位过大跌到最后没资金继续加仓。所以在实盘或者回测之前必须算清整个网格铺满之后需要多少钱再反推每格股数。我的代码里每格股数是1500网格共20格各格价格从3.773一路降到2.31。把这些价格加起来再乘以1500大约得到9.1万元的最大建仓金额。这意味着如果你放10万资金进去理论上网格跌满20格时资金基本用光只剩不到1万现金。这在策略设计上叫作资金刚好打满好处是资金利用率高坏处是一旦价格跌破网格下沿你没有任何后手。如果你希望更保守可以把每格股数降到1000最大建仓金额就会降到约6万元剩下4万现金作为缓冲。代价是每格利润变小同样的行情下年化收益会下降。资金利用率和风险承受能力之间没有标准答案完全看你的账户情况。我建议实盘阶段把最大建仓金额控制在总资金的60%到70%以内留出足够的现金应对极端行情。回测的时候可以激进一点实盘一定要给自己留后路。网格策略的生存法则不是赚多少而是跌到极限位置时你还活着。4.3 回测结果怎么解读我在掘金终端里用下面的参数做的回测标的510300回测区间2022年5月9日到2024年5月8日初始资金10万元基准价3.85元网格步长2%网格数量20每格1500股佣金万三滑点万五。回测报告里几个关键指标大致如下指标数值总收益率约36%测算年化收益率约17%最大回撤约10%总交易笔数约600笔含买入和卖出期末总资产约13.6万元注意一下这个总交易笔数是把买入和卖出都算进去的大约300轮完整的买入-卖出循环。每轮网格的毛利润约115元300轮下来毛利约3.5万元再扣除手续费和滑点最终净收益约3.6万元和总收益率36%基本对上。这里说的是测算年化是因为两年多的回测区间算出来的年化正好落在17%附近。如果换一个区间比如放在2021年或者2024年下半年单边行情里收益会差很多甚至可能是亏损。所以回测报告看看就好核心是理解这套策略在什么行情下有效、什么行情下失效。最大回撤约10%这个数字很多人觉得不大实际上网格策略的回撤主要是持仓浮亏。当价格一路下跌你会不断加仓持仓成本也会被慢慢拉低但只要价格没反弹浮亏就一直挂在账上。回撤的具体大小取决于行情跌幅和你的网格覆盖深度。如果真出现跌破下沿的极端行情回撤可能远超10%。5. 常见问题与避坑经验5.1 回测和实盘为什么差很多很多新手跑回测时很兴奋收益曲线漂亮得不行一上实盘就发现完全不是那么回事。最常见的原因有三个一是回测里没有设置佣金和滑点或者设得太低二是回测按K线收盘价成交而实盘是实时价格成交价格跳动和延迟都会造成成本上升三是市场规模小、流动性差的标的你的委托单会推动价格形成冲击成本。我对这种回测收益的第一反应永远是先看它的交易频率和利润空间。如果单笔利润比手续费加滑点多不了多少那这个策略回测再好看实盘也很难赚钱。网格策略尤其如此因为利润来源就是微小的价差成本控制是生死线。建议回测时至少把佣金设为万三、滑点设为万五跑完再算一笔真实成本账如果单笔利润只剩原来的一半不到那就得考虑加大步长或者减少交易频率。5.2 单边行情下网格策略如何保护自己单边下跌是网格策略最大的敌人。行情持续下跌时策略会一档一档买下去仓位越来越重浮亏越来越大。如果行情跌穿了网格下沿策略已经没有子弹了只能被动趴着眼睁睁看着浮亏扩大。这种时候很多人会心态崩溃手动止损或者撤销策略结果卖在地板上。我自己用过几种防护方法。最基础的是设好资金上限也就是前面说的格子满打满算只用总资金的60%到70%永远给自己留后手。进阶一点可以加一条熔断线当价格跌破某一档之后冻结网格不再继续买入等价格重新站回网格区间再恢复交易。再激进一些可以结合趋势过滤比如只在大盘20日均线之上时运行网格之下时直接空仓。这些思路都会牺牲一部分收益换取更平稳的账户曲线。网格策略赚的是震荡的钱就别指望它是趋势利器。你想明白这个本质就不会在单边行情里对它抱有不切实际的期待。5.3 掘金API使用中的典型坑跑掘金API时有几个问题我几乎是每次重装环境都会碰到。第一个是Token过期或者没设置回测一开始就报授权失败。这个没什么好说的去终端里重新复制一个Token确认run()里的token参数和init()前设置的值一致就行。第二个问题是on_bar不触发。回测跑完发现日志里一条交易都没有K线回调完全没进来。排查思路是先看订阅的frequency是不是和预期的K线周期一致再看subscribe的第一个参数是不是列表格式很多回调不触发都是订阅参数写错导致的。第三个问题是最隐蔽的回测中下单失败比如资金不足或者持仓不足但代码里的level状态已经更新了导致后续所有逻辑错乱。网格策略的状态机非常依赖level和实际持仓一致所以一定要在下单前后打印日志。我习惯每次买卖都输出当前价格、档位、持仓数量和可用资金回测报告里翻一翻就能定位问题。第四个问题是除权除息。如果标的发生分红送股股价会跳空前复权和后复权价格也不一样网格可能因此误触发买卖。掘金API里可以通过subscribe的adjust参数指定复权方式这一步不能省。5.4 交易成本对网格利润的隐形影响最后再专门聊聊交易成本因为网格策略对这种细节特别敏感。假设基准价3.85元、每格1500股一格毛利润约115元。每次买或卖的交易金额大约5700元佣金按万三算约1.7元滑点按万五算约2.85元单边成本约4.5元一买一卖加起来约9元。也就是说成本占毛利润的比例接近8%。如果你的佣金是千一、滑点又大或者每格股数更小成本占比可能直接飙到20%以上年化收益会被明显拖累。网格频率越高交易成本的影响越显著。这也是为什么我不建议把网格步长设得太小的原因。步长1%的时候理论触发频率翻倍但每格利润也减半成本占比却几乎不变总体算下来并不划算。步长2%到3%是很多老玩家摸出来的平衡点。除了显性的佣金和滑点还有一个隐形成本是资金占用。网格策略的相当一部分资金长期趴在账户里等买入机会这部分资金的机会成本很容易被忽略。你在评估策略收益时应该把网格策略的收益和同期货基或者固收产品的收益做对比只有超额收益足够明显这个策略才值得占用你的资金。我自己折腾网格策略这几年最大的体会是策略本身不复杂真正拉开差距的是对风险的控制而不是对收益的追求。网格交易不是印钞机它只是一个在震荡行情里反复赚取价差的工具。回测出来的17%年化放到不同的市场环境里可能变成5%也可能变成25%这些都取决于你入场时的位置和标的的选择。最重要的一点任何量化策略在实盘之前先拿模拟盘跑几周把API的手感、回撤的波动、情绪的干扰都体会一遍再拿一小笔资金试水。策略可以复制但纪律只能自己练。