Ptrade量化回测策略源码详解:双均线、风控与回测指标
做量化的人应该都绕不过这一步手里有一套想法想验证它到底能不能赚钱总不能真金白银上去试错。所以回测就成了最关键的过滤器。而Ptrade作为国内券商常用的量化交易终端很多朋友最初接触它都是为了快速把策略想法放到历史数据里跑一遍看看收益曲线和最大回撤是不是能扛得住。这篇内容我就把Ptrade量化回测策略源码这块讲透从最简单的双均线策略入手到复杂的仓位管理、止损逻辑、止盈退出再到回测报告里那些指标的真正意义一次性补齐。适合刚接触量化回测、想用Ptrade验证自己交易思路的朋友也适合已经跑通简单策略、但不知道怎么优化和避坑的进阶用户。1. 为什么我在A股量化里选了Ptrade做回测现在市面上一提到量化回测大家最先想到的往往是backtrader、聚宽、掘金、RQAlpha这些框架。确实这些开源或者在线平台各有优势数据免费、社区活跃、策略代码案例多。但我最后还是把Ptrade作为日常回测的主力工具之一原因挺实际的。Ptrade最核心的优势是国内券商直接内置的终端只要你在支持Ptrade的券商开户登录就能用不需要自己搭服务器、不用处理数据源断供的问题。它的行情数据、复权因子、成分股变动都是券商端维护好的回测时你只需要关注策略本身。对大多数个人交易者来说省掉的这些运维成本非常关键。另一个让我比较认可的点是Ptrade的策略逻辑跟实盘交易直接衔接。你在Ptrade里写的策略回测通过之后可以直接切换模拟交易再通过模拟盘的验证最后切到实盘。这条链路非常顺不像自己搭框架那样回测一套代码实盘还要重写一套中间很容易出现逻辑不一致的问题。当然Ptrade也有它的短板。一是它属于券商定制终端策略代码只能在它自己的环境里跑不方便迁移到其他平台二是它面向A股市场的交易细节很稳但如果你想做美股、期货、加密货币的跨品种回测那它的支持就很有限了三是回测周期限制在分钟级如果你是想做tick级回测那Ptrade不太合适。本质上门槛不低但把A股日频、分钟频的常规策略跑通它完全够了。基于这些考虑如果你是做A股量化、资金量在个人级别、又不打算折腾服务器和数据源那Ptrade确实是性价比较高的选择。如果你要研究超高频率交易或跨市场策略那就另选框架不要为难Ptrade。2. 搭建回测环境导入策略的正确姿势与数据检查很多人第一次用Ptrade容易被策略研究和策略交易两个模块搞混。说白了Ptrade分成两层研究环境用来写代码、跑数据探索交易环境用来挂载策略、做回测和模拟盘。你写的策略源码最终需要放进策略交易模块里才能触发回测引擎。2.1 账号权限与终端环境准备先确认你用的Ptrade是不是券商版本。不同券商对Ptrade终端的开放程度不一样有的支持回测和模拟盘有的只能回测有的需要申请权限。我的建议是在回测之前先跟券商的客户经理确认三件事你是否能用策略交易模块也就是实盘挂单的权限是否能使用组合交易这类批量下单功能回测的行情数据包含哪些年份区间有没有包含即时行情和复权因子。这些看起来不起眼但它直接决定了你回测出来的曲线可信度。我之前就遇到过券商版本数据只更新到某一年导致回测结果完全失真收益曲线年年翻倍实际上只是数据切片不同。2.2 开始回测前的数据完整性检查回测的第一道工序不是写策略而是检查数据。Ptrade内置了get_history、get_current_data等数据接口但如果你没有确认数据是否存在、是否复权、是否有停牌数据空缺那么策略再严谨也没用。最常见的坑不复权数据导致的假信号。A股除权除息很频繁如果Ptrade里没有设置前复权你在回测中算出的价格曲线会在除权日出现跳空下跌然后策略可能因此误判为趋势反转产生大量错误交易。所以数据准备阶段必须用前复权数据保证价格连续。数据就绪检查脚本可以这样做def check_data_ready(code, start_date, end_date): df get_history(20, 1d, [close, high, low, volume], code, start_date, end_date, fqpre) if df is None or len(df) 0: log.warn(数据为空%s % code) return False if df[close].isnull().any(): log.warn(存在缺失价格%s % code) return False log.info(数据检查通过%s, 共%d条记录 % (code, len(df))) return Truefq参数就是复权设置pre代表前复权。每次做长周期回测前我至少抽查三到五只核心持仓股票确保数据正常再跑整个策略。2.3 把握回测时间段与手续费滑点设置手续费和滑点设计直接决定回测的靠谱程度。Ptrade回测时默认的佣金和印花税可能与真实交易有差异A股目前单边印花税是卖出时收0.05%现阶段政策注意后续调整佣金一般万1到万3最低5元。设置时可参考你的真实费率。滑点方面建议单边至少设置0.1%到0.2%尤其做突破策略时止损单和市价单的冲击成本比想象中大。过于乐观的滑点设置会让回测夏普比率虚高实盘一跑就露馅。这些做完才算是真正能开始写策略源码。接下来我用一个完整的双均线策略来演示Ptrade回测代码的写法骨架和细节都会拆开讲清楚。3. 一个完整可跑的均线策略源码拆解很多平台上的回测示例代码为了简单只写了买入和卖出两句逻辑这对实际使用是完全不够的。真实的策略源码至少要包含初始化、交易信号判断、买入执行、卖出执行、风控检查、每日日志输出以及收盘后的持仓信息更新。以下是我在Ptrade里反复打磨过的一个双均线策略源码可以直接复制到策略交易模块中回测# 双均线策略示例仅供学习 def initialize(context): # 设置股票池使用沪深300成分股中比较活跃的标的作为示例 g.stock 600519.SS g.fast 5 g.slow 20 g.max_hold 200 # 单票最大持仓金额元 g.stop_loss 0.08 # 止损线 8% g.take_profit 0.15 # 止盈线 15% # 设置手续费与滑点 set_commission(0.0003) set_slippage(0.001) # 初始化时订阅股票便于盘中获取行情 subscribe(g.stock) log.info(策略初始化完成) def handle_data(context, data): # 每日数据处理 if context.current_dt.weekday() 5: return # 取最近 slow1 个交易日的数据保证均线计算完整 hist get_history(g.slow 1, 1d, [close], g.stock, context.current_dt, context.current_dt, fqpre) if hist is None or len(hist) g.slow 1: return close_list hist[close].values fast_ma close_list[-g.fast:].mean() slow_ma close_list[-g.slow:].mean() current_position context.portfolio.positions.get(g.stock, None) current_price data.current(g.stock, price) # 金叉买入 if fast_ma slow_ma and (current_position is None or current_position[closeable_amount] 0): cash context.portfolio.cash if cash g.max_hold: buy_amount min(g.max_hold, cash * 0.2) order_target_value(g.stock, buy_amount) log.info(金叉买入 %s价格 %.2f金额 %.2f % (g.stock, current_price, buy_amount)) # 死叉卖出 止损止盈 must_sell False if current_position is not None and current_position[closeable_amount] 0: avg_cost current_position[avg_cost] if fast_ma slow_ma: must_sell True log.info(死叉卖出信号%s 当前价格 %.2f % (g.stock, current_price)) if current_price avg_cost * (1 - g.stop_loss): must_sell True log.info(止损信号%s 亏损 %.2f%% % (g.stock, (current_price / avg_cost - 1) * 100)) if current_price avg_cost * (1 g.take_profit): must_sell True log.info(止盈信号%s 盈利 %.2f%% % (g.stock, (current_price / avg_cost - 1) * 100)) if must_sell: order_target_value(g.stock, 0)这段代码你直接跑大概率能收敛出一条不错的净值曲线。但我建议你多看几个细节不要只当成能跑就完事了。3.1 均线参数选择背后的逻辑fast5、slow20是我回测下来比较顺手的默认参数但它在不同股票上的表现差异很大。这里想提醒的是均线策略的核心不在于参数是5和20而在于它本质上是趋势跟踪思路。快线上穿慢线代表短期动量转正下穿则说明趋势可能结束。如果你换一只股票流动性差、波动率低那这个参数组合效果会很差。比较务实的做法是先对股票池做波动率排序从中挑选那些日均波动在2%左右的活跃标的再套用趋势策略比直接在固定股票上优化参数要有效得多。3.2 为什么用了order_target_value而不是orderorder_target_value是目标持仓金额下单Portfolio会计算出需要买入或卖出的数量并且和当前持仓做差值。这个函数的好处是天然支持部分调仓和严格仓位控制。而order是直接指定股数灵活性弱很多容易在资金不足时废单或者买入仓位超过预期。个人建议除非你有很特殊的需求比如限价单精确到一分钱否则在Ptrade里统一用order_target_value来管理仓位。3.3 止损止盈的取舍我在策略里加了8%止损、15%止盈本质上是为了限制单笔亏损幅度同时防止利润回吐。但必须承认的是这两个阈值在不同市场状态下差别很大。A股震荡市里15%止盈很容易被触发但后续股价可能继续上涨你反而卖飞了趋势市里8%止损可能太紧一根大阴线直接把仓位洗出去。所以止损止盈参数不要死记回测时要多试几组观察最大回撤和胜率之间的权衡再确定适合当前品种的数值。4. 回测报告的四个关键指标到底该怎么读Ptrade跑完回测后会生成一份报告里面有收益曲线、回撤曲线、夏普比率、胜率、盈亏比等一堆数字。很多人把目光只盯在总收益率上这是新手最容易犯的错误。你真正该看的是这四样东西。4.1 最大回撤先保命再谈收益最大回撤这个指标描述的是在策略运行过程中从任意一个高点跌到随后的最低点最大的跌幅是多少。举个例子你的策略净值从1涨到1.5然后掉到1.1之后又涨到2.0再跌到1.6那么最大回撤就是从2.0到1.6的那个26.7%粗略按净值算的区间也可以说是从1.5跌到1.1的26.7%看具体定义和窗口。最大回撤的意义在于它决定了这个策略你能不能实际拿得住。如果回撤达到50%策略最终收益再好大部分实盘资金也早就在中途割肉离场了。我自己的标准是单策略最大回撤尽量控制在20%以内超过这个数字的需要认真审视策略逻辑是不是过度集中或杠杆过高。4.2 夏普比率收益要除以波动才有意义夏普比率 (策略年化收益率 - 无风险利率) / 策略年化波动率。简单理解每一份风险你换来了多少收益。A股日频策略里夏普大于1算合格大于2已经算不错了大于3的你可以怀疑一下是不是过拟合了。常见的问题是回测频率是日频但夏普计算时用的是年化无风险利率还是日化不同平台口径会不同。Ptrade的报告里一般会用年化口径你在对比不同策略时要统一用同一份报告的口径不要跨平台拿数字硬比那没有参考价值。4.3 胜率 vs 盈亏比小赚大亏和大赚小亏哪个更好胜率是指所有已平仓交易中盈利交易的比例。盈亏比是平均盈利除以平均亏损。这两个是跷跷板关系。比如均线趋势策略胜率一般不高可能只有40%到45%但盈亏比能到2比1以上长期下去依然是正期望。不要迷信胜率。一个胜率70%的策略如果每次赚200元、亏1000元最终是亏钱的反过来胜率35%每次赚800元、亏400元账户曲线却能稳定向上。看策略报告时一定把两个指标放在一起算期望收益而不是单独看胜率数字。4.4 策略换手率成本的最小敌人换手率等于成交金额除以持仓市值Ptrade报告里通常也有。换手率越高手续费和滑点对净值的侵蚀越明显。我见过很多策略在忽略滑点和手续费时收益不错一旦加上真实费率净利润直接归零甚至变负。所以读报告时如果换手率超过每日20%你要额外警惕建议自己手动用总成交额 * 0.13%成本再算一遍看看真实收益还剩多少。# 估算交易成本 total_turnover report_stats[total_turnover] # 从回测接口获取 estimated_cost total_turnover * 0.0013 # 佣金过户费滑点等综合估算跑回测时我自己习惯把综合成本设置在0.15%到0.2%之间尤其对高频换手策略这会非常影响最终净值。5. 从回测到模拟盘Ptrade实盘衔接的真实门槛回测曲线再好看也只是历史数据的幸存者。真正让策略上战场中间还隔着模拟盘这道关。Ptrade里从回测切换到模拟交易非常简单只需要创建模拟交易实例挂载同一份策略代码选择日期从当天开始即可。但实际操作中有几个非常隐蔽又关键的坑值得提前避掉。5.1 回测与模拟盘在数据上的时间差回测使用的是历史数据而模拟盘接收的是实时行情。同一个handle_data函数里你在回测中用的data.current获取价格在模拟盘中会变为实时价格逻辑上没有区别但如果你用到了get_history要特别留意在盘中调用时最新的历史数据可能还没有包含当天的实时K线Ptrade默认get_history是取已经形成的K线当天未收盘不算进去。这会导致盘中均线指标和收盘后计算的数值有偏差。我的做法是在实盘/模拟盘中额外订阅行情快照自己维护一个临时的实时价格池而不是只依赖get_history收盘值。def handle_data(context, data): # 模拟盘中获取实时价格做辅助判断 tick_price data.current(g.stock, price)5.2 撮合方式模拟盘的成交假设比回测保守Ptrade模拟盘是按真实行情撮合但成交不一定立即全部成交。大单买入时可能只按当前盘口部分成交剩下的挂单等待后续行情。而回测引擎中同等金额订单是按当时价格即时成交的。这种差异在流动性好的蓝筹股上不明显但在小盘股上会非常明显可能一个买入订单要拆成好几笔才成交完。如果你计划做小盘股策略建议在回测时就把单笔下单金额压低比如控制在日成交额的1%以下这样回测结果才更接近模拟盘的真实表现。5.3 隔夜风险与涨跌停的极端处理A股有涨跌停制度涨停时买不进、跌停时卖不出。Ptrade回测引擎对涨跌停的处理相对理想化但实盘和模拟盘中涨停板的封单量可能让你完全买不到跌停板的封单则让你卖不掉。这类流动性风险对回测收益曲线的贡献常常被高估。我建议在策略里显式加入涨跌停过滤def is_limit_up(code, data): cp data.current(code, price) up_limit data.current(code, high_limit) if cp up_limit * 0.998: return True return False在handle_data中买入前检查是否接近涨停卖出前检查是否接近跌停可以显著减少回测与实盘之间的偏差。5.4 心理层面的模拟盘考验还有一点看起来不是技术问题其实很关键模拟盘的时间跨度。很多人回测出漂亮曲线后直接跳到模拟盘跑了几天发现收益不如回测动辄翻倍就开始频繁调参。这是错误的使用方式。模拟盘至少应该跑一个完整的市场周期比如两到三个月覆盖涨、跌、震荡三种状态才能评估策略的适应性。回测优化出来的参数在模拟盘上表现波动实属正常不要一有回撤就推翻重来。6. 我踩过的几个回测坑希望你绕开最后这部分我梳理一下自己过去实操中真正踩过的坑。这些东西平台文档一般不会写但影响非常大。6.1 未来函数回测里偷看了不该看的行情未来函数是所有回测中的第一杀手。常见的有两种一是用当天的收盘价计算信号再假设你在当天的开盘价成交这在Ptrade中如果你用data.current拿到的是当日最新价然后直接order就可能存在这种信号和成交同价位的偷价嫌疑二是使用了整个区间数据计算参数比如把未来一年的数据放进均值回归模型里拟合出最优参数再用同一段区间验证。Ptrade回测里避免未来函数的核心原则是所有下单依据必须基于当前时刻已经发生的数据。以均线为例你必须使用已经收盘的历史K线计算均线而不是当天还没收盘的实时价格。更稳妥的方式是信号在今日收盘计算次日开盘再下单成本稍高但逻辑干净。6.2 幸存者偏差股票池里全是活下来的牛股很多回测案例喜欢用当下的沪深300成分股做股票池然后回测过去三年。但你知道吗三年前的沪深300成分股和现在的成分股差别很大那些被调出的股票往往是下跌趋势中的股票。如果你只用现在的成分股回测就会天然剔除掉一批表现差的股票造成收益虚高。补救方法使用历史成分股做回测。Ptrade大部分版本支持指定回测时间段内的指数成分但你需要主动去获取当时的股票列表而不是直接用当下的代码。如果你拿不到历史成分退而求其次的办法是扩大股票池比如用全A至少降低幸存者偏差的影响。6.3 参数过拟合曲线完美是陷阱每优化一个参数回测的拟合程度就更高但也离真实规律更远。你一定会遇到那种情况把均线参数从5/20换成8/25回测收益从30%变成60%看起来找到了圣杯。实际上这很可能只是把随机噪声拟合得更好了。我的习惯是参数必须成组调整而不是单点调优最优参数附近要有平滑区才算稳健如果参数稍微一动收益就剧烈变化说明策略不稳定用样本外数据验证比如前70%时间段调参后30%时间段做验证两段都表现不错才敢上模拟盘。在Ptrade里你可以设置不同的回测起止时间把2019到2021作为训练段2022到2023作为验证段分别跑一遍对比曲线。6.4 忽略停牌和数据毛刺A股停牌很常见回测时如果股票停牌但你的策略还给它一个目标仓位Ptrade通常会自动跳过或按旧数据撮合这会导致持仓比例失真。更麻烦的是数据毛刺异常价格可能让均线瞬间触发信号。处理办法在信号计算前剔除成交量异常的样本比如成交量为0但价格有变化的记录并在买入前判断股票是否停牌使用context.portfolio.positions里的closeable_amount字段它代表可卖数量如果持仓里可卖数量为0说明大概率停牌。if current_position is not None and current_position[closeable_amount] 0: log.info(持仓停牌跳过卖出) return6.5 回测区间选择过度偏多动量类策略在单边上涨区间的回测曲线一定非常好看但这不能说明策略在下跌区间也能扛住。我建议每位做回测的朋友都强制自己在回测区间里包含一段明显的熊市或长期横盘震荡市。以A股为例2018年、2022年都是不错的压力测试区间。如果你发现策略在这些区间亏损严重但回撤可控就没那么可怕如果直接腰斩那就要重新审视策略逻辑了。回测不是为了让历史曲线好看而是为了搞清楚一个策略在什么市场环境下会赚钱、在什么环境下会亏钱。带着这个认知去用Ptrade你看到的报告就不只是收益率数字而是策略的真实性格。这套源码和思路你可以直接拿去跑但更建议先跑一段模拟盘观察它在你关心的市场环境下表现如何再决定要不要投入更多的精力去迭代它。