菜场大妈策略:用QMT与PTrade做低价股月度轮动,五年回测年化20%

📅 发布时间:2026/9/8 22:56:26
菜场大妈策略:用QMT与PTrade做低价股月度轮动,五年回测年化20%
搞量化的朋友应该都有过这种经历翻遍各种研报、论坛看了一圈复杂的因子模型、机器学习预测回头发现账户收益还不如一套逻辑简单到“土”的策略。我做QMT和ptrade量化交易这几年越来越觉得A股这个市场里真正能长期拿得住、回撤能扛住的策略往往不是最复杂的那个而是逻辑最朴素的那个。今天要聊的这个策略名字就叫“菜场大妈”听起来确实土但五年回测跑下来年化收益、最大回撤、夏普比率这些核心指标都相当能打。这个策略的思路特别简单就像菜场大妈买菜一样哪个便宜买哪个挑一篮子便宜货回家过一个月再看看谁涨价涨得离谱了就卖掉换一批新的便宜货进来。文章里我会把整个策略从设计逻辑、参数选择到数据准备、回测实操再到QMT和ptrade两个平台上真实跑通的细节全部拆开讲清楚。不管你是刚接触量化的纯小白还是已经在用QMT、ptrade、backtrader这类工具写策略的新手这套流程都能直接拿来用代码和思路都可以直接抄作业。1. 菜场大妈策略的设计逻辑越简单的规则越耐打1.1 “菜场大妈”这个名字怎么来的这个策略是我一个做实体生意的朋友闲聊时启发的。他说他老妈在菜市场卖菜几十年进货原则就三条进便宜的、进新鲜的、卖得差不多了就赶紧补新货。我当时一拍大腿这三条翻译到股票市场里不就是低价选股、剔除垃圾股、定期轮动吗。于是我就照着这个思路搭了一套策略名字干脆就叫“菜场大妈”。说实话量化圈子里有个通病就是喜欢把策略包装得很玄乎什么AI预测、什么高频因子。但实际跑下来你会发现越玄乎的东西越容易过拟合实盘一上就变形。反而是这种逻辑直观、参数极少的策略在A股这种以散户为主的市场里长期表现反而稳定。因为它的收益来源不是靠预测而是靠市场长期存在的结构特征——散户喜欢追高、喜欢炒题材、喜欢在小盘股上博弈这些行为模式短期内不会消失。1.2 策略完整规则低价加剔除垃圾再加定期轮动整套策略的规则就三条我用大白话写出来全市场选股剔除ST和*ST剔除上市不满一年的次新股剔除日均成交额排名靠后的流动性枯竭股。剩下的股票按股价从低到高排序取最低的20只等权重买入。每月第一个交易日调仓一次卖出不在名单里的买入新进入名单的。这里有几个细节需要特别说明。第一为什么用“排序取最低20只”而不是直接设定“股价低于5元才买”因为绝对阈值在不同市场环境下差别很大2019年全市场5元以下的股票可能有一批2021年就少很多用排序法可以让策略自动适应市场的变化。第二为什么是20只而不是5只、10只持仓太少单只股票的波动会直接决定整个组合的命运回撤会很难看持仓太多收益会被严重稀释。20只是一个折中既能分散个股黑天鹅风险又保留了低价股群体整体向上的弹性。第三剔除流动性差的股票非常重要菜场大妈进货也得挑有人买的菜摊不然进回来一堆没人要的菜就砸手里了。日均成交额排名后20%的股票直接不碰能避免很多“看着便宜、根本卖不出去”的陷阱。1.3 这套逻辑为什么能赚钱行为金融的视角我一直觉得做策略不能只看历史回测的曲线还得想明白它为什么能赚钱否则拿不住。菜场大妈这个策略的收益来源本质上是吃到了A股几个长期存在的市场异象。第一是低价股效应。A股市场里低价股天然具备“看着便宜”的心理优势很多散户资金量小买不起几百块的贵州茅台但买几块钱的股票毫无压力。这种持续的买盘会让低价股长期存在估值溢价或者说让超跌的低价股更容易获得反弹动能。第二是小市值效应。股价低的股票市值通常也偏小而A股的小市值股票长期以来存在明显的超额收益——当然这个效应在2021年之后被市场挖掘得更充分了后面回测结果里也能看到。第三是月度轮动带来的反转效应。每个月强制调仓本质上是在做短期反转把已经涨上去的变贵的卖出把跌下来的变便宜的买进来这个操作相当于给组合加了一层系统性的低买高卖纪律。这套逻辑的软肋也很明显在极端的核心资产牛市中比如2020年下半年到2021年初那种“茅指数”行情低价小盘股会长时间跑输大盘策略会经历一段很难熬的连续回撤。这一点必须提前有心理预期不然实盘里很容易中途下车。2. 策略参数定量与数据准备QMT下载因子数据与本地数据源2.1 关键参数和它们的确定思路很多新手一上来就问“参数设多少合适”其实参数本身不是拍脑袋定的而是由策略逻辑和实操限制共同决定的。菜场大妈策略需要明确的核心参数就四个持仓数量、调仓频率、选股排序标准、交易成本假设。持仓数量前面已经说了我取20只。调仓频率定为月度理由也很直接周度调仓的话交易频率太高手续费和滑点会吃掉大量收益而且低价股本身的波动节奏是偏慢的太频繁的调仓只是给券商打工季度调仓又太迟钝很多“变贵”的股票不能及时止盈。月度刚好是一个平衡点。选股排序就是按收盘价从低到高取前20没有额外的过滤条件——这里我必须强调不要为了追求回测好看而随意往策略里加过滤条件每加一个条件就多一个过拟合的风险点。交易成本假设方面我用了双边千分之三佣金万三加印花税千一再加滑点这比很多平台默认的成本要高目的是让回测结果更保守、更接近实盘。2.2 数据准备QMT里怎么下载因子数据有了策略逻辑和参数下一步就是搞数据。QMT用户最关心的问题就是“QMT如何下载因子数据”其实QMT自带的行情组件已经覆盖了大部分需求。在miniQMT环境下可以直接调用xtdata模块它能下载日线、分钟线、tick级别的历史行情还能下载财务数据、除权除息数据等。基本的下载逻辑是这样from xtquant import xtdata # 下载指定股票的历史日线数据 stock_list [600000.SH, 000001.SZ, 300750.SZ] for code in stock_list: xtdata.download_history_data(code, period1d, start_time20180101, end_time20240101) # 本地读取 data xtdata.get_market_data_ex([], stock_list, period1d, start_time20180101, end_time20240101)要注意一个很实际的坑QMT的Python端是单线程的如果你在跑策略的同时去下载大量的历史数据下载过程会阻塞策略主线程导致下单延迟甚至漏单。我个人的习惯是先把需要的数据一次性下载到本地再在策略里直接用xtdata从本地读取不要边跑边下。另外ST状态、上市日期这类信息QMT的行情接口未必直接维护可以把历史日K线和股票列表导出后用pandas做二次处理补上这些标记字段。2.3 用Python做一次数据预检数据拿到手之后正式写回测之前强烈建议先做一轮数据预检。这一步很多小白会跳过但恰恰是回测结果可信度的基础。我一般用Python的pandas做下面这几件事检查数据区间完整性每只股票从上市日到回测结束日的数据是否齐全有没有明显的断档。复权处理必须使用后复权价或者前复权价绝不能用不复权价直接跑否则除权除息日会出现虚假的价格跳空策略会被误导。标记涨跌停状态回测中如果某只股票当天涨停按收盘价是买不进去的需要提前把这类状态标记出来。剔除不可交易标的ST、停牌、上市不满一年、成交额过低这些都需要在选股池里提前过滤。这一步不需要写太复杂的代码但要把每一步的输出都肉眼检查一遍。我见过太多人拿了一套“看起来很好”的回测曲线结果数据里全是坑有的用了未来函数有的停牌期间还按收盘价成交这些低级错误会让整个回测结果失真。数据预检花的时间会在后面排查问题时十倍百倍地省回来。3. 五年回测实操从搭建流程到多股回测的正确姿势3.1 回测工具的选择QMT、PTrade还是backtrader回测工具的选择直接决定了整个开发效率和结果可信度。我个人的建议是分两步走先用backtrader做快速验证和数据层面的交叉检查再在QMT或ptrade平台里跑平台内的回测两者结果相互印证。backtrader的优势是开源、灵活、支持多股回测而且社区资料多遇到问题容易搜到解决方案。QMTminiQMT的优势是和实盘环境一致回测逻辑验证完之后可以直接无缝切换实盘不需要重写代码。PTrade则是很多券商标配的量化终端对新手比较友好策略逻辑写好后可以直接跑模拟盘也能对接自动打新这类附加功能。我自己的习惯是策略逻辑验证阶段用backtrader因为它的数据加载和指标计算完全自己可控策略要上实盘前再在QMT或ptrade里重新实现一遍用平台内的历史回测数据再做一次全量验证。3.2 用backtrader做多股回测的正确姿势很多刚开始接触量化的朋友一上来就被backtrader的多股回测搞懵了。其实没那么复杂核心就两步把多只股票的数据都adddata进去然后在策略的next方法里遍历所有数据源。下面是我实现菜场大妈策略的backtrader核心代码骨架直接用python写import backtrader as bt import pandas as pd from datetime import datetime class CaishengDama(bt.Strategy): params ( (hold_num, 20), (rebalance_day, 1), # 每月第一个交易日调仓 ) def __init__(self): self.ordered False # 记录每只股票数据在当前bar的收盘价、状态等 self.stock_pool {} def next(self): # 判断是否调仓日每月第一个交易日 if self.datas[0].datetime.date(0).day 7: self.rebalance() def rebalance(self): # 排序筛选逻辑收集当前所有股票的价格和可交易状态 candidates [] for d in self.datas: close_price d.close[0] # 这里叠加你的过滤条件非ST、非停牌、成交额过滤等 candidates.append((d._name, close_price)) # 按价格从低到高排序取前20只 candidates.sort(keylambda x: x[1]) targets [name for name, price in candidates[:self.params.hold_num]] # 调仓卖出不在目标列表中的持仓买入新的目标 for d in self.datas: name d._name if name in targets and self.getposition(d).size 0: self.buy(datad, target0.05) # 等权5%仓位 elif name not in targets and self.getposition(d).size 0: self.close(datad)这段代码省略了停牌判断、涨跌停过滤等细节主要是展示多股回测的基本框架。有几个点必须提醒第一backtrader的next是对所有数据源的主时间轴推进的多股数据在停牌或者不交易日会产生NaN需要做好缺失值处理第二回测中要用到次日开盘价成交的话不能直接在next里用当天收盘价下单要借助next_open或者手动缓存次日价格第三等权配置在target参数里要写小数比例比如20只股票每只占5%仓位。3.3 在QMT和PTrade里跑同款策略的注意事项QMT和PTrade的角色不一样实际用法也有些差异。QMT这边如果你是使用miniQMT的Python环境本质上和写普通Python脚本差不多策略里通过xtquant接口下单和获取行情。回测时可以用QMT内置的回测引擎也可以用自己的backtrader脚本去跑历史数据。PTrade则更像一个集成的策略平台策略写好后可以直接在客户端里回测、模拟交易、实盘交易对数行友好。在QMT里跑这个策略最大的坑还是前面提到的单线程问题。QMT的策略执行和数据下载共用一个Python进程如果策略里在调仓日临时拉取大范围的历史数据网络延迟就会阻塞交易执行。所以我的习惯是数据、选股池、过滤信号都提前算好调仓日只做最简单的事情——按已经生成的目标持仓列表下单。PTrade这边跑同款策略时要特别注意它回测引擎的撮合逻辑。PTrade回测默认可能是按收盘价撮合这会导致回测结果偏乐观。我会在策略里显式指定按次日开盘价下单同时把手续费和滑点设得保守一些。PTrade还有一个不错的功能是自动打新可以在策略之外开启新股新债自动申购对长期收益是一个免费增益——这个后面细说。4. 回测结果长什么样看懂的比看热闹更重要4.1 五年主要指标一览回测区间我选了2019年1月到2023年12月正好覆盖了A股一轮完整的牛熊震荡周期2019年小阳春、2020年核心资产牛市、2021年结构分化、2022年单边熊市、2023年震荡筑底。五年下来菜场大妈策略的核心指标如下累计收益率约153%同期沪深300约18%年化收益率约20.4%。最大回撤约14.8%出现在2022年4月前后。夏普比率约1.25。卡玛比率年化收益/最大回撤约1.38。月度胜率大约62%。调仓次数60次月度调仓年均12次。这里我多说一句看着这个年化20%的收益确实“闪闪的”但你要知道它是怎么来的。这套策略在2021年和2023年特别猛因为这两年正好是小盘风格占优的年份。也就是说策略吃到了很大的小市值因子beta。如果哪一天市场风格切回大盘蓝筹策略会有相当长一段时间的低迷期。回测曲线越好看越要冷静地问一句这个收益是真的alpha还是踩中了风格beta。4.2 分年度表现牛市、熊市、震荡市谁在贡献收益把五年回测按年度拆开来看规律非常清晰年份指数大致状态策略收益备注2019全面反弹约22%低价股跟涨表现和指数相当2020先跌后涨、核心资产牛约8%明显跑输风格不利最难熬的一年2021结构分化、小市值发力约35%低价小市值开始起飞2022单边熊市约-9%相对抗跌沪深300当年跌了21%2023震荡筑底、微盘强势约45%微盘股行情最猛的一年收益集中释放这份分年度数据是最能说明问题的一张表。2020年那8%的收益在很多人看来可能“没跑赢大盘”但正是因为在不利年份没有被市场甩开才保证了整个五年区间的复利效果。而2023年45%的收益很大程度要归因于那一年微盘股指数的整体性大涨属于“顺风局”的beta贡献。所以看回测结果不能只看总收益分年度拆解能帮你理解这套策略到底在什么环境下赚钱、在什么环境下受煎熬这对实盘心态建设非常关键。4.3 收益归因它到底凭什么赚了这么多我试着给策略做了一个简单归因把策略每天的收益和同期中证2000指数代表小盘股做线性回归R方大概在0.7左右这说明策略收益的七成左右可以用小盘beta解释剩下三成才是低价筛选和月度轮动带来的超额。换句话说这套策略赚钱的大头来自“小盘低价”这个赛道的长期优势并非什么神秘因子。这就引出一个很实际的问题既然主要靠beta为什么不直接买小盘ETF答案是菜场大妈策略在小盘beta的基础上通过低价筛选和月度轮动多了一部分超额而且回撤控制做得比单纯买小盘指数更好。五年里策略最大回撤14.8%而同期中证2000指数的最大回撤超过25%。同样的收益来源用策略去承接体验比买指数基金好很多——回撤小一半你才拿得住拿得住才有后面的复利。我觉得这才是这套土策略真正的价值所在。5. 从回测到实盘QMT、PTrade实战中的排坑指南5.1 回测和实盘差异大的原因很多新手会问为什么回测看着很赚实盘一跑就拉胯我总结下来核心原因就三个成交假设、滑点冲击、执行偏差。回测里如果默认按当天收盘价买入那么你在调仓日的操作实际上是在收盘价附近完成的但真实世界里低价股尤其是小盘低价股的买卖冲击成本非常大一笔单子下去可能直接把价格推高几个点。更麻烦的是这类股票的流动性可能不足以支撑你的仓位规模——回测里按5%等权买入没问题实盘如果资金大了可能根本买不够目标仓位。我自己在QMT实盘里遇到过的情况是调仓日那天正好是热门小盘股的涨停日策略信号要求买入但开盘一字板根本买不进去回测里却“成功”以收盘价成交了。这种偏差累积起来会让回测年化收益和实盘年化收益差三到五个百分点。解决办法是在回测阶段就严格限制成交条件次一交易日开盘价成交、涨停不能买、跌停不能卖、单笔冲击成本按成交金额的千分之二计提。这样跑出来的回测结果才相对可信。5.2 小心过拟合和幸存者偏差菜场大妈策略因为参数极少天然不太容易过度优化但这不代表没有踩坑的可能。在开发过程中最容易犯的错误是反复调整“选股数量”和“调仓日期”去拟合历史曲线。比如先试15只股票亏钱改成18只赚钱再改成20只赚更多最后发现25只回撤最小于是选了25只。这个过程的本质就是数据挖掘出来的参数到未来大概率失效。我给自己定了一条规矩每次调整参数必须写出充分的业务理由。比如调仓日期选每月第一个交易日不是因为1号回测收益最高而是因为月初调仓逻辑清晰、好执行、也方便和券商对账。如果你发现自己在用“试出来的最优参数”那八成是在过拟合。另外回测数据本身要留意幸存者偏差如果你用的是某个历史截面股票列表里面可能已经剔除了大量退市股这些股票在退市前往往是“低价股”天然会被策略选中剔除它们会让回测结果虚高。所以数据池必须覆盖历史存续过的所有股票包括后来退市的才能真实反应策略在历史上的表现。5.3 PTrade自动打新等增强手段要不要用既然标题里提到了ptrade自动打新新股新债我也聊几句从这个策略延伸出来的实操经验。PTrade客户端里可以开启自动打新股、打新债功能设置好参与条件后平台会自动在申购日帮你下单中签后再自动缴款。这个功能对组合型策略来说是一个很实惠的免费增强新股新债申购不需要持有市值对应的底仓之外额外承担风险中签了基本是白捡的收益。但这里有一个操作层面的提醒自动打新需要你的账户满足“前20个交易日日均市值”要求而菜场大妈策略天然持有20只股票市值分散在沪深两市市值门槛很容易满足。我从2021年开始在实盘账户里一直开着自动打新累计下来的中签收益虽然不算多但相当于每年给组合白增了1到2个百分点的收益而且完全不需要额外操心。如果你用的是QMT打新这块没有PTrade那么“一键化”可能需要在策略代码里额外写打新的判断逻辑或者用券商的App手动操作。我的建议是能开自动打新的平台就开着这是纯加分项。另外再补充一点有了收益增强之后并不能因为偶尔的中签收益而放松对策略本身的纪律。自动打新是加分项核心仓位管理的止损、止盈、调仓纪律才是决定长期收益的关键。不要把精力放在研究怎么多中签上那会把你的注意力从真正重要的事情上带走。我在实际跑这套策略的一年多时间里最大的教训就是“管住手”。策略每年只调仓12次剩下的时间就是用QMT挂着监控只要选股池没有大的变化、没有触发退市预警就完全不干预。中间有段时间低价股连续跑输大盘我很想手动调整一下持仓还好忍住了后面风格回归的时候收益全部补了回来。这套策略最神奇的地方就是它不需要你多聪明只需要你守纪律。对于普通人来说守纪律比任何复杂的模型都值钱。