简介:一部Python量化交易实战指南,以聚宽平台为主线讲解量化策略编写,适合刚接触量化的Python新手,也适合希望系统落地策略的进阶开发者。内容从量化交易概念与Python优势入手,逐步展开平台注册认证、SDK安装、开发环境配置、行情与财务数据获取及清洗;随后介绍策略整体框架,涵盖技术指标计算、信号生成、交易执行和风险控制模块,并以趋势跟踪、均值回归、配对交易等典型策略为例给出实现思路。书中还讨论了策略回测指标、参数优化与过拟合防范,以及实盘中的风险管理,并延伸到多因子模型和机器学习策略,最终通过完整案例串起策略构思、编写、回测、实盘的整个流程,边学边练的价值感很强。资源包共1个PDF文件,容量4.58MB,目录分为十二章且层级清晰,方便定位内容;已有910人学习下载,对想借助聚宽平台实现量化想法的读者尤为实用。
1. Python量化交易,从聚宽平台策略编写开始:先把回测跑通,再谈理论
做Python量化交易的人很多,真正跑通一个策略的人很少。我见过某开发者在聚宽上写了个动量策略,回测年化收益接近50%,曲线平滑得像教科书案例,结果一开模拟盘,两个月就把回测利润吐回去一半。问题不在策略思路,而在平台机制和代码细节没吃透——这也是大多数人学量化最容易卡住的地方。
聚宽这类平台把行情数据、复权处理、撮合规则和绩效统计都封装好了,是你验证策略想法成本最低的入口。这篇笔记沿着聚宽平台策略编写的主线展开:平台的数据接口与调度机制、一个最小可回测的动量策略怎么写、回测参数和绩效指标怎么读,以及从回测到模拟盘之间最容易踩的那些坑。适合有Python基础但没写过交易逻辑的从业者,也适合想快速验证策略想法的熟手。
2. 聚宽数据与运行机制:选平台写策略的三个理由
在聚宽写策略,你最先接触的不是策略逻辑,而是数据接口和调度机制。这两块决定了后面所有代码怎么写,也是新手最容易产生"为什么我的回测曲线和别人不一样"困惑的地方。我的经验是动手写策略前,先花半天把数据接口和调度机制过一遍,后面返工成本会低很多。
2.1 数据接口的最小用法:get_price 与财务数据怎么取
量化策略的第一步永远是获取数据。在本地自建环境里,你得先下载行情、处理复权、对齐财务数据,一套流程走下来至少两周;而聚宽把这一步简化成一个函数调用。下面这段代码可以在策略或研究环境里直接运行,取某只股票最近一段时间的日线数据:
from jqdata import * # 获取某只股票最近20个交易日的日线数据 df = get_price( '000001.XSHE', # 股票代码,需带交易所后缀 start_date='2024-01-01', # 起始日期,含当天 end_date='2024-03-01', # 结束日期 frequency='daily', # 数据频率,可选 daily / minute fields=['open', 'close', 'high', 'low', 'volume'], # 需要的行情字段 fq='post', # 复权方式:None 不复权,'pre' 前复权,'post' 后复权 ) print(df.head())这段代码的重点其实不在取数本身,而在三个参数的理解。第一个是股票代码必须带交易所后缀,A股代码要写成000001.XSHE这种形式,XSHE代表深交所,XSHG代表上交所,漏掉后缀平台无法识别。第二个是fq复权参数,很多人刚开始不传它,拿到的是未复权价格,遇到分红除权时股价会跳空,计算收益率时会出现假信号。第三个是count参数,如果不想按日期段取数,可以直接count=20表示取最近20根K线,这在策略里更常用,因为你不需要关心具体日期,只需要"最近多少个交易日"的数据。
除了行情,聚宽还封装了财务数据接口。常见的写法是通过get_fundamentals查询估值、利润和资产负债数据,平台已经帮你把报告期和交易日对齐了,不用处理财报发布日期和实际交易日之间的时滞。相比本地自建时到处找数据源、自己清洗财报,平台把这些脏活都干了。我一般建议新手先在这里把策略跑通,再考虑要不要自己搭数据体系。
2.2 回测引擎的调度逻辑:initialize、before_trading_start 与 handle_data 的分工
数据拿到手,接下来要理解平台的回测引擎在什么时间点调用你的代码。很多在本地写Python的人习惯了顺序执行,第一次用聚宽会很困惑:为什么print一天打了几十次?因为回测引擎不是把你的策略从头到尾跑一遍就结束,而是按照交易日的节奏反复调用你注册的函数。下面是一个标准的策略骨架:
def initialize(context): # 策略启动时只调用一次,用来做全局限量初始化 set_benchmark('000300.XSHG') # 设置基准指数 set_option('use_real_price', True) # 使用真实价格撮合 g.stock_count = 5 # 计划持仓的股票数量 g.lookback = 20 # 动量回看天数 run_monthly(rebalance, 1, time='open') # 每月第一个交易日开盘时调仓 def before_trading_start(context): # 每个交易日盘前调用一次,适合更新股票池、打印当日信息 log.info('交易日: %s', context.current_dt.date()) def handle_data(context, data): # 按回测频率逐根K线调用,按天回测每天一次,按分钟回测每分钟一次 pass聚宽策略的运行节奏可以拆成三层来理解。initialize只在回测开始时执行一次,全局变量、基准、手续费和调仓计划都在这里设置,它相当于整个策略的构造函数。before_trading_start在每天早上开盘前被调用,适合做那些需要当天最新信息、又不想干扰盘中信号的准备工作,比如过滤掉当天停牌的股票、更新股票池。handle_data则按照回测频率被高频调用,按天回测每天一次,按分钟回测每分钟一次。
这里我要强调一个很容易被忽视的点:交易逻辑尽量不要直接写在handle_data里。我一般会把它留空,所有买卖操作都放进通过run_monthly注册的rebalance函数中。原因是调度逻辑越简单,回测结果越容易复现。如果你把信号判断写在handle_data里,一个信号可能在一天内被触发几十次,手续费被反复收取,回测收益里掺入大量成交噪音,实盘根本对不上。用run_monthly或run_daily注册具体函数,让调仓节奏明确、可预期,这是平台上写策略最省心的习惯。
2.3 平台封装与本地自建的差异:先平台后本地的理由
第三个理由和工程成本有关。聚宽把数据、撮合、财务、绩效统计全部做成了标准服务,策略写完直接回测,不需要自己搭交易模拟器。我自己早期试过在本地自建,光是日线数据下载、按股票代码分库、处理停牌和复权更新,就耗掉了两周时间,而这还没开始写任何策略逻辑。下面这张表是我对平台封装和本地自建的差异梳理,核心结论是:起步和迭代阶段选平台,等策略验证完再考虑搬本地。
| 环节 | 聚宽平台 | 本地自建 |
|---|---|---|
| 行情数据 | 内置接口,复权参数可选 | 需要自行下载、清洗、维护 |
| 财务数据 | 按报告期对齐,查询方便 | 需要处理采集与公告时点 |
| 撮合规则 | 内置涨跌停、停牌、滑点模型 | 需要自己写撮合逻辑 |
| 绩效统计 | 回测报告自动生成 | 需要自己算回撤、夏普等指标 |
| 运维成本 | 浏览器里写代码即可 | 需要服务器、数据库、定时任务 |
这张表不是说本地自建没有价值。当策略已经稳定盈利,需要更精细的成交模拟或更高频的数据精度时,本地自建是必经之路。但在起步阶段,平台封装修掉的那些环节,恰恰是新手最容易写出隐蔽错误的地方。选平台的核心理由就一句话:把有限的精力放在策略逻辑上,而不是消耗在跟交易无关的基础设施上。等策略逻辑充分验证,你再决定要不要搬到本地做更精细的优化。
3. 写一个可回测的动量策略:从信号生成到订单成交
理解了平台机制,就可以动手写第一个完整策略。我习惯用动量策略做演示,因为它逻辑直白、代码量小,但已经覆盖了"取数、算信号、下单、调仓"的完整链路。下面这段代码是我在聚宽上常用的最小可跑版本,你可以直接复制到平台上新建策略跑一遍,再逐步改参数看效果。
3.1 最小可跑策略:月度调仓的完整代码骨架
动量策略的思想很简单:过去一段时间涨幅最大的股票,未来一段时间更可能延续趋势。我们取中证500成分股作为股票池,计算每只股票过去20个交易日的累计涨幅,选涨幅最大的5只持有,每月调仓一次。完整代码如下:
# 聚宽平台:月度动量策略,每月第一个交易日开盘调仓 def initialize(context): set_benchmark('000905.XSHG') # 基准设为中证500 set_option('use_real_price', True) # 按真实价格撮合 g.select_count = 5 # 每次调仓持有5只股票 g.lookback = 20 # 用过去20个交易日涨幅排序 g.stock_pool = get_index_stocks('000905.XSHG') # 中证500成分股 run_monthly(rebalance, 1, time='open') # 每月第1个交易日开盘调仓 def rebalance(context): # 第1步:获取股票池过去20个交易日的收盘价,宽表形式 close_df = get_price( g.stock_pool, count=g.lookback, # 取最近20根日线 frequency='daily', fields=['close'], fq='post', # 使用后复权,避免除权跳空干扰 ) # 第2步:计算每只股票的累计涨幅 ret = close_df.iloc[-1] / close_df.iloc[0] - 1 # 第3步:剔除缺失数据的股票,选出涨幅最大的N只 ret = ret.dropna() selected = ret.nlargest(g.select_count).index.tolist() # 第4步:卖出持仓中不在目标列表里的股票 for stock in list(context.portfolio.positions.keys()): if stock not in selected: order_target(stock, 0) # 目标股数设为0,即清仓 # 第5步:用可用现金等权买入目标股票 cash_per_stock = context.portfolio.available_cash / len(selected) for stock in selected: order_value(stock, cash_per_stock) # 按金额下单,自动换算股数 log.info('调仓完成,目标持仓: %s', selected)这段代码的运行逻辑分为五步。第一步获取数据时,get_price一次性传入整个股票池的代码列表,返回的是一个宽表,行是交易日,列是每只股票的收盘价,这种取数方式比循环单只股票快得多。第二步用iloc[-1]取最新价格、iloc[0]取最早价格,相除减1得到区间涨幅。第三步用nlargest选出涨幅最大的5只,dropna是为了剔除因为停牌或上市时间不足导致缺失数据的股票。第四步和第五步是调仓的核心,先清仓不在目标列表里的股票,再按可用资金等权买入。
这里有两个关键参数说明。第一个是run_monthly(rebalance, 1, time='open'),它表示每月第一个交易日开盘时执行调仓,平台会自动跳过非交易日,不需要手动判断节假日。第二个是set_option('use_real_price', True),它告诉回测引擎在模拟撮合时使用当时的真实价格,而不是用数据接口返回的收盘价近似成交。如果不开这个选项,回测里的成交价会失真,低流动性股票的收益会被虚高。
提示:第一次跑策略时,建议把回测区间设为5到10年,初始资金100万,先不看收益,重点确认调仓日志打印是否正常、持仓数量是否符合预期。
3.2 信号生成的细节:向量化计算而不是循环
上面代码里计算涨幅只用了一行close_df.iloc[-1] / close_df.iloc[0] - 1,这背后是pandas的向量化计算。很多从零开始写策略的人,第一反应是用循环逐只股票取数、逐只计算,我见过有人真这么写过,结果回测速度慢到无法接受。对比一下两种写法:
# 错误示范:循环逐只股票计算,速度慢且代码冗长 rets = {} for stock in g.stock_pool: hist = get_price(stock, count=g.lookback, frequency='daily', fields=['close'], fq='post') rets[stock] = hist['close'].iloc[-1] / hist['close'].iloc[0] - 1这段循环代码的问题不在于逻辑错误,而在于性能。中证500成分股有500只,循环写法意味着发起了500次get_price请求,每次请求都要经过网络传输和平台数据读取;而向量化写法是一次请求拿回全部数据,效率差距通常在几十倍以上。回测本身已经需要模拟多年交易,如果信号生成这一步就慢得离谱,整个回测会变成煎熬。
向量化另一个好处是代码不易出错。close_df是宽表,所有股票的时间轴完全对齐,iloc[-1]和iloc[0]天然对应同一时间段的最新技术和初始价格,不存在循环里因为某只股票停牌导致时间轴错位的问题。选股用nlargest(g.select_count)也非常直观,它返回按涨幅降序排列的Series,取index.tolist()就得到了目标股票代码列表。在策略里我几乎不用循环处理信号,除了最后下单环节遍历持仓,那是不可避免的订单操作。
3.3 订单函数的差异与滑点预期:order_value、order_target 怎么选
策略的交易逻辑离不开下单函数,聚宽提供了好几个订单接口,新手最常见的困惑是:什么时候用order_value,什么时候用order_target?这三者的区别用一个表就可以说清楚:
| 函数 | 作用 | 适用场景 |
|---|---|---|
| order_value(stock, money) | 按金额买入,自动换算股数 | 买入固定金额时用 |
| order_target(stock, amount) | 把该股票调整到指定股数 | 清仓或调整到特定持仓量 |
| order_target_value(stock, value) | 把该股票调整到指定市值 | 按目标仓位调仓时用 |
我自己的习惯是:调仓时用order_target(stock, 0)清仓,用order_value买入新目标。回测代码里对应第4步和第5步。order_target(stock, 0)会把这只股票的持仓全部卖出,不用手动计算当前持有多少股;而买入时用order_value(stock, cash_per_stock),平台会按现价自动把金额换算成股数。需要注意的是,A股买入有100股整数倍限制,如果可用资金不足100股对应金额,订单会被拒单,这也是回测中异常日志里常见的提示。
订单类型上,默认下的是市价单,回测引擎会按当时的价格加上滑点撮合。如果你想模拟限价单的成交逻辑,可以显式传入limit_price:
# 限价单示例:以低于当前价的2%挂单,不会立即成交 current_price = get_current_data()[stock].last_price order(stock, 100, limit_price=current_price * 0.98)限价单在回测里和市价单的差异非常大。回测引擎对限价单的处理是:若限价高于当前价则立即成交,低于当前价则挂在订单簿上等待后续行情触及。新手经常在这里产生误解,以为限价单像实盘一样会一直挂着,实际上分钟级回测里时间粒度不够细,限价单的成交模拟会和实盘有明显偏差。所以我的建议是:初期策略一律用市价单,把精力放在信号正确性上,等策略稳定后再用限价单优化成交价格。
4. 回测参数与绩效读数:让结果不骗你的关键设置
策略代码写完只是一半,另一半是回测参数和绩效指标。同样的策略,手续费设成0和设成实盘费率,收益率可能相差几个百分点;滑点设成0和设成千分之二,高换手策略的收益可能直接被吃掉大半。这一章讲的不是策略怎么优化,而是怎么让回测结果可信。
4.1 手续费、滑点、基准:回测参数怎么设才贴近实盘
聚宽回测面板上有几个参数,很多人直接沿用默认值,这在策略初期可以,但到了要看真实收益时,必须改成贴近自己实盘交易的数字。我常用的设置如下:
| 参数项 | 常见默认值 | 我建议的实盘口径 | 影响程度 |
|---|---|---|---|
| 佣金费率 | 平台默认万3左右 | 按自己券商实际费率填写 | 高换手策略差异明显 |
| 印花税 | 卖出收千1 | 确认是否已包含在手续费中 | 卖出成本的主要来源 |
| 滑点 | 按比例默认值 | 0.1%到0.2% | 小市值、高换手更敏感 |
| 基准指数 | 沪深300 | 与股票池对应的指数 | 影响超额收益的解读 |
| 初始资金 | 100万 | 按真实计划投入资金 | 影响小市值股票的买入股数取整 |
这些参数在代码里也可以显式设置。聚宽提供了手续费和滑点接口,我一般会在initialize里写成这样:
def initialize(context): # 手续费按双边万3、卖出加印花税千1设置 set_order_cost(OrderCost( open_commission=0.0003, # 买入佣金万3 close_commission=0.0003, # 卖出佣金万3 close_tax=0.001, # 卖出印花税千1 )) # 滑点设为成交价的千2,模拟真实冲击成本 set_slippage(slippage_rate=0.002)设置这些参数时我吃过一次亏,先说明一下。某次用默认参数回测一个高频调仓策略,年化看着有25%,把手续费改成实盘费率后直接掉到15%以下,这就是隐藏成本吞噬利润的典型情况。聚宽不同版本的手续费接口名称可能变化,如果你用的版本里set_order_cost已弃用,换成set_commission即可,参数含义不变。关键是回测配置里的手续费、滑点要和你的真实交易成本对齐,否则绩效指标全是幻觉。
4.2 绩效指标怎么互为印证:年化、回撤、夏普与胜率
回测报告生成后,一堆数字摆在面前,新手最容易犯的错误是只看年化收益。年化收益高不代表策略好,它必须和回撤、夏普、胜率放在一起看才有意义。我自己判断一个策略是否值得继续投入,会看四个指标的组合:
| 指标 | 含义 | 我的判断标准 |
|---|---|---|
| 年化收益 | 资金按年复利增长的收益率 | 至少覆盖资金成本再谈 alpha |
| 最大回撤 | 资金曲线从最高点到最低点的跌幅 | 超过30%的策略很难拿住 |
| 夏普比率 | 每单位风险的超额收益 | 大于1才值得继续优化 |
| 胜率 | 盈利交易次数占比 | 必须和盈亏比一起看 |
举一个具体的例子。某策略年化收益25%,但最大回撤有20%,说明你想赚这25%的收益,中途可能要承受20%的账面浮亏,绝大多数人会在回撤最深的时候止损离场,最终拿不到年化收益。此时再看夏普比率,如果只有0.8,说明收益的品质一般,风险调整后并不吸引人。反过来,一个策略年化收益只有15%,但最大回撤控制在8%以内,夏普在1.5以上,实际持有体验反而更好。
胜率这个指标最容易误导人。一个策略如果胜率只有35%,但盈利交易的平均收益是亏损交易的3倍以上,它依然是好策略。我一般会把胜率和盈亏比配对看,而单独看任何一个都会得出错误结论。在实际复盘时,我还会关注交易次数:如果回测五年只有不到20次调仓,统计意义上的胜率参考价值不大;至少要几十次以上交易,绩效指标才具有稳定性。
4.3 参数灵敏度:别让一组参数决定策略生死
动量策略里的lookback、select_count都是人为设定的参数。新手调参最常犯的错是:改一个参数,回测收益暴涨,就觉得找到了圣杯。实际上这很可能是在过拟合历史数据。我常用的方法是对关键参数做灵敏度扫描,观察结果是否剧烈跳变。聚宽支持把参数变成全局变量然后逐次回测,做法是这样的:
# 在策略里把待扫描参数定义成全局变量 g.lookback = 20 # 依次手动改成 10、20、30、60,分别记录年化、回撤、夏普扫描的目的是看策略对参数的依赖程度。一个稳健的策略,参数在合理范围内变化时,收益水平应该相对平滑。如果lookback从20改成21,回测收益从25%跌到8%,那这个策略大概率是靠某个特定参数拟合出来的,实盘换一个时间段就会失效。我一般会对每个关键参数取4到5个值做扫描,把结果列成一张表,参数区间内收益平稳的才放心进入模拟盘。
除了参数灵敏度,还要做时间段的稳健性检验。把回测区间拆成两段,比如2018到2020年作为训练段,2021到2023年作为验证段,在训练段调好参数后,不修改任何参数直接跑验证段。如果验证段收益明显垮掉,说明策略只在特定市场环境下有效,不具备外推能力。这个习惯比任何参数优化都重要,也是我从多次回测翻车中总结出的最深刻教训。
5. 策略编写常见坑与排查:回测翻车现场复盘
这一章讲的是我从回测和模拟盘对比中踩过、也帮别人排查过的坑。每一个都是"现象、原因、解决"三段式的处理方式,碰到类似情况时可以直接照这个思路排查。
5.1 未来函数:当天数据产生当天信号
现象:策略回测收益高得惊人,资金曲线一路向北,但模拟盘却稳定亏损,怎么都对不上回测。原因:策略代码里使用了当天收盘后才知道的数据来生成当天的买卖信号,这在回测引擎看来等于"提前知道答案"。常见出现在两种写法里:一是在handle_data里直接取当前这根K线的收盘价做判断,二是取数时没有意识到get_price的结果里包含当前回测日当天的数据。解决:保证信号生成只使用截至前一交易日的数据。安全的写法是多取一根K线,然后把最后一根丢弃:
# 用截至前一交易日的数据计算信号,避免用到当天未走完的行情 close_df = get_price( g.stock_pool, count=g.lookback + 1, # 多取一天 frequency='daily', fields=['close'], fq='post', ) close_df = close_df.iloc[:-1] # 丢弃最后一天(当前交易日)数据 ret = close_df.iloc[-1] / close_df.iloc[0] - 1这里的关键判断是:策略在开盘时执行调仓,而开盘时当天的日线K线还没形成,get_price在开盘时刻的切片里不应该包含当天完整数据。但如果你在回测中用的是按分钟频率,盘中时当前bar已经部分形成,取数结果里就会混入未来信息。最简单稳妥的做法就是上面这种"多取一天再丢弃",无论平台如何调度,信号永远基于截至昨天的数据生成。
5.2 复权方式导致的信号偏移
现象:同一套策略,把复权参数从fq='pre'改成fq='post',回测信号和收益发生明显变化。原因:前复权会随着每次新的除权事件,把历史价格全部重新计算,导致同一只股票的涨幅序列在不同时间点看到的结果不同;未复权价格则在除权除息日出现跳空,可能让一只实际下跌的股票因为分红除权被误判为暴跌。解决:策略中计算收益率统一用后复权fq='post'。后复权的特点是历史价格不会因后续除权事件而改变,信号计算的结果稳定可复现。回测和实盘信号生成要使用同一个复权口径,否则两边对不上。
5.3 涨跌停板与停牌股的成交陷阱
现象:回测结果显示某只股票买入成功,持仓收益在涨,但模拟盘里这笔订单根本没有成交,资金也没有减少。原因:平台撮合规则里,停牌股票任何订单都不会成交,涨停股票买单无法一定成交,跌停股票卖单可能排到收盘也卖不出去。回测报告不会每次都明确提示这些拒单,容易被忽略。解决:调仓前显式过滤掉停牌和涨跌停股票,不能交易的直接排除出目标列表:
def can_trade(stock): """判断股票当前是否可正常交易""" cur = get_current_data()[stock] if cur.paused: # 停牌 return False if cur.high_limit == cur.low_limit: # 一字涨停或跌停 return False return True # 在选股完成后过滤 selected = [s for s in selected if can_trade(s)]加了这层过滤后,回测里的成交逻辑更接近实盘。不过要注意:过滤后目标股票数量可能减少,买入资金分配要重新计算,否则cash_per_stock = available_cash / len(selected)会因为除零或数量过少而出问题。我通常会在过滤后加一个判断,如果可交易股票数为0就本次不调仓。
5.4 手续费和滑点设太低:回测高收益的隐藏来源
现象:策略换手率很高,回测收益每年20%以上,实盘一跑,手续费和冲击成本直接吃掉大部分利润。原因:回测参数里手续费、滑点设得比真实交易低很多,等于假设你能以最优价格无限成交。高换手策略对这类假设极为敏感,换手率越高,隐藏成本越大。解决:把手续费、滑点改成和实盘一致再回测,然后重新评估策略是否值得继续。简单算一笔账:月换手200%,双边费率加滑点合计0.15%,一年的交易成本就是200%乘以12再乘以0.15%,约3.6%,还没算印花税和冲击成本,一个年化20%的策略可能实际收益只剩10%出头。
5.5 print 日志拖慢回测:一个不起眼的性能杀手
现象:分钟级回测里,代码在handle_data中写了print,回测从几分钟变成一个多小时,代码逻辑完全没问题但就是慢得离谱。原因:控制台输出是回测中最容易被低估的开销,逐分钟打印时,输出量轻松达到数万行,平台日志系统需要处理大量I/O。解决:不要用print,改用平台提供的log.info,并且只在需要的时候输出:
# 只在每月调仓日打印,日志明显减少 if context.current_dt.month != getattr(g, 'last_month', None): log.info('调仓目标: %s', selected) g.last_month = context.current_dt.month我排查过某开发者的回测卡顿问题,把他的handle_data里逐股票print全删掉后,回测时间从70分钟降到8分钟,策略收益完全没变。这个坑不会影响结果正确性,但会严重影响迭代速度。回测里保留必要日志就够了,运行过程中想看详细输出,平台有单独的执行日志面板,不需要自己在代码里追加打印。
6. 从回测到模拟盘:验证策略值不值得投入
回测指标满意后,别急着投入真金白银,先走模拟盘验证这一步。聚宽的模拟交易设置很简单:新建一个模拟组合,选择你的策略,设置初始资金和开始日期,平台会从下一个交易日开始用实时行情驱动你的策略自动调仓。这一步的价值在于,模拟盘用的是真实行情、真实撮合规则,它能暴露所有回测阶段隐藏的问题:信号用错了数据、成交价偏离预期、订单被拒单、参数在实盘行情下失效。
我的习惯是模拟盘至少跑一个月,期间只做观察和记录,不修改任何参数。重点对比两件事:第一,模拟盘的成交价格和回测模拟价格有没有系统性偏差,如果每次买入都比回测贵、卖出都比回测便宜,说明滑点设置偏低;第二,调仓是否按预期执行,有没有反复出现停牌、涨跌停导致的调仓失败。一个月后,把模拟盘收益曲线和回测收益曲线叠在一起看,二者偏差在合理范围内,才进入下一步。
验证策略值不值得投入,还要做一次样本外检验。我常用的方法是把回测区间切成两段,前段调参,后段不动参数直接跑。如果后段收益明显恶化,说明策略在市场风格切换后失效,不值得继续投入;如果后段收益和前段接近,策略才具备真正的外推能力。配合参数灵敏度扫描,把几个关键参数各偏移一点看结果是否剧烈变化,如果轻微扰动就大变,说明策略过拟合严重,实盘大概率翻车。
这里的血泪经验来自我自己:某次我把动量策略的lookback调成5天,历史回测收益翻了一倍,当时以为找到了圣杯,结果模拟盘两周就把回测利润吐回去大半。后来我给自己定下规矩:任何参数修改都必须做灵敏度扫描,模拟盘跑满一个月再谈实盘。策略编写不靠灵感,靠的是可复现的流程和纪律。希望这篇笔记能帮你少走一段弯路,把精力真正花在策略逻辑上。
本文还有配套的精品资源,点击获取