四个月搭建个人交易系统:从正期望值到回测与风控全复盘
发布时间:2026/9/3 16:22:09 作者:尧图编辑部 阅读量:1,286

做交易的人应该都有过类似体验行情看对了仓位没跟上仓位跟上了止损没执行止损执行了结果行情又回来了。反复几次之后资金曲线不但没有稳定上升反而在震荡中不断缩水。真正的问题往往不在某一次判断的“准不准”而在于你始终没有一套稳定输出、可复盘的交易系统。这篇文章是我用四个月时间搭建个人交易系统的一次完整复盘。我会从正期望值这个概念讲起拆解策略设计、回测验证、仓位管理、风险控制这几个环节并给出一个最小可运行的回测示例。无论你是刚开始接触量化交易还是已经手动交易了一段时间想往系统化方向走这篇文章都值得收藏作为参考。需要先说明的是本文只讨论交易系统的技术构建与验证方法不构成投资建议。市场有风险任何策略在实盘前都必须经过充分测试并严格控制资金风险。1. 背景为什么多数交易系统活不过三个月1.1 交易系统到底指什么很多人把“交易系统”等同于“一个买入指标”。比如均线金叉买入、MACD 底背离买入、RSI 超卖买入。这种理解是不完整的。一个能长期运行的交易系统至少应该包含以下四个闭环环节信号产生层决定什么时候进场、什么时候出场。仓位管理层决定每次交易投入多大比例的资金。风险控制层决定单笔最大亏损、总回撤上限、异常行情如何处理。复盘迭代层记录每笔交易统计系统表现并持续修正。也就是说信号只是系统的输入而不是系统本身。如果你只优化“买入信号”却忽略仓位和风控资金曲线很难稳定。1.2 正期望值的概念“正期望值”听起来很学术其实就是一句话当你把同一套规则重复执行足够多次之后平均每笔交易赚到的钱大于平均每笔亏掉的钱。它不要求你每笔都对。事实上低于 50% 胜率的策略也可以稳定盈利关键是盈亏比要足够高。反过来胜率 90% 的策略也可能爆仓因为少数几次亏损可能吃掉所有利润。期望值的公式非常简单期望值 胜率 × 平均单笔盈利 - 败率 × 平均单笔亏损当这个结果大于 0 时策略就具备正期望值。下文第三部分会专门展开讲这个公式并给出不同胜率和盈亏比下的对比。1.3 为什么四个月是一个合理的迭代周期交易系统的搭建不是一蹴而就的。它需要覆盖趋势行情、震荡行情、极端行情等不同市场环境因此至少需要观察一个季度以上的数据。我在四个月里把过程拆成了四个阶段阶段时间核心任务第一阶段第1个月明确交易规则写清策略边界第二阶段第2个月编写回测代码完成历史数据验证第三阶段第3个月小资金模拟盘记录实盘滑点和心理偏差第四阶段第4个月小额实盘试运行统计系统稳定性后面第 4 部分会按时间线还原这个过程。2. 环境准备与工具选型2.1 运行环境说明本文示例以 Python 为主这是量化交易生态最成熟的编程语言之一。版本和环境如下操作系统Windows 10/11 或 macOS 均可示例代码是跨平台的。Python 版本建议使用 Python 3.9 及以上。核心库pandas、numpy、matplotlib用于数据处理、回测计算和可视化。可选库backtrader 或 vectorbt用于更复杂的回测框架。如果你还没有安装 Python 库可以使用 pip 安装pip install pandas numpy matplotlib关于版本有一点需要提醒不同 pandas 版本的 API 差异并不小比如pct_change()、rolling().mean()的行为基本稳定但某些绘图参数在不同版本下会变化。因此本文示例以“能用”为优先重点演示回测思路具体版本请结合你的实际环境调整。2.2 数据源选择回测必须依赖历史行情数据。常见的数据获取方式有行情软件导出通达信、同花顺等客户端可以导出一段周期的日线数据。开源数据接口如 akshare、tushare 等可以获取 A 股、ETF、期货等行情。自建数据库对于长期迭代的系统建议把清洗后的 K 线数据存入 SQLite 或 MySQL。需要提醒的是不同的数据源在复权价格、停牌处理、涨跌停价格上可能存在差异。这会导致同一套策略在不同数据源上的回测结果不一致。因此建议从第一天开始就固定数据源不要频繁切换。2.3 项目目录结构一个清晰的项目结构可以让你在迭代时少走很多弯路。推荐目录如下trading_system/ ├── data/ # 原始行情数据 ├── backtest/ # 回测代码 │ ├── signal.py # 信号生成 │ ├── engine.py # 回测引擎 │ └── metrics.py # 绩效指标 ├── strategy/ # 策略配置文件 │ └── config.yaml # 参数配置 ├── logs/ # 交易日志 └── reports/ # 分析报告和资金曲线图3. 抓住交易系统的核心期望值公式3.1 期望值的基本公式在交易中期望值的计算公式可以写成E P(win) × avg_win - P(lose) × avg_loss其中P(win)胜率即盈利交易笔数占总交易笔数的比例。avg_win平均每笔盈利交易的盈利金额。P(lose)败率等于 1 - P(win)。avg_loss平均每笔亏损交易的亏损金额。举个例子某策略在 100 笔交易中盈利 40 笔亏损 60 笔。平均盈利 1000 元平均亏损 500 元。那么期望值E 0.4 × 1000 - 0.6 × 500 400 - 300 100每笔交易平均赚 100 元。只要样本量足够大交易次数足够多这个正期望值就能逐步体现出来。这里有一个容易被忽略的前提期望值必须基于“大量的、规则一致的”交易样本。如果只交易了 20 笔就算期望值算出来是正的统计意义也不够强结果很可能来自运气。3.2 胜率与盈亏比的辩证关系很多新手过度追求胜率觉得“10 笔里面必须对 8 笔”才算好系统。但实际上高胜率往往对应低盈亏比二者之间需要达到平衡。我们可以用一个盈亏平衡表来看胜率盈亏比平均盈利/平均亏损期望值30%3.0正40%2.0正50%1.0060%0.8接近080%0.2负从表中可以看出如果胜率只有 30%但当盈利交易平均赚 3 倍于亏损交易平均亏损时系统依然有正期望值。这也是趋势跟踪类策略的常见特征大部分时间小亏少数时间大赚。反过来如果胜率高达 80%但每笔盈利只有 20 元每笔亏损却有 100 元那长期执行下去几乎必然亏钱。因此在设计策略时不要只盯住胜率一个指标而要把胜率和盈亏比放在一起评估。3.3 回撤控制对期望值的二次影响正期望值解决的是“长期能不能赚钱”的问题但资金曲线中间的回撤会直接影响你能不能坚持执行到“长期”到来。举个例子一个策略每笔期望值为正但最大回撤达到 50%。对于 10 万本金意味着要在某个阶段承受 5 万的浮亏。大多数人在这个阶段会因为心理压力而手动干预甚至停止策略最终无法等到正期望值兑现。回撤控制的常见手段包括单笔亏损上限比如单笔最大亏损不超过总资金的 2%。每日/每周亏损熔断当日亏损达到阈值停止开新仓。总回撤熔断资金曲线从高点回撤达到预设比例降低仓位或暂停系统。这部分不是简单的“心态问题”而是系统设计问题。一个没有风控层的系统哪怕信号逻辑正确也很难被长期执行。4. 四个月实战复盘从想法到系统4.1 第一个月明确规则边界我第一周的想法很杂既想做均线趋势跟踪又想叠加 MACD 过滤还想引入成交量确认。结果发现规则越多样本内外表现越不稳定各种条件互相矛盾。后来我强迫自己做减法只保留一条主规则和一条辅助过滤条件。规则用大白话写下来必须满足进场信号明确比如“收盘价上穿 20 日均线”。出场信号明确比如“收盘价下穿 10 日均线”。止损明确比如“单笔亏损达到本金的 2% 时无条件离场”。仓位明确比如“每笔交易固定使用总资金的 10%”。写下来之后我发现很多模糊想法根本没法执行。比如“感觉趋势要来了”这种描述永远无法编码成程序必须改成“5 日均线大于 20 日均线且 20 日均线向上”这种可验证的表达式。第一个月的产出不是代码而是一份交易规则说明书。这份文档后来成为回测和实盘执行的重要依据。4.2 第二个月回测与过拟合的拉锯第二个月开始写回测代码。最初用的是简单的双均线策略在沪深 300 指数的日线数据上测试。结果看起来很漂亮年化收益率很高。但我很快意识到问题策略参数经过了反复调整是在同一段数据上“试”出来的。这就是典型的过拟合你把参数调得恰好适合过去这段行情但未来行情一旦变化策略可能立刻失效。后来我做了几件事来降低过拟合风险把数据切分成“样本内”和“样本外”两部分样本内用于参数选择样本外用于验证。对参数做敏感性分析观察参数小幅变化是否会导致结果剧烈波动。固定参数后不再随意修改至少观察两到三周的模拟运行。这个月最大的体会是回测曲线好看不代表策略好参数稳健性比“最大收益”更重要。4.3 第三个月小资金模拟验证历史回测阶段结束之后我进入模拟盘阶段。这个阶段不涉及真金白银但非常重要的原因是回测用的是“收盘价”而实际交易中有滑点、有延迟、有盘中波动。模拟阶段的重点是把回测信号转换成“第二天开盘价执行”或“实时行情触发”记录信号产生时间、实际成交价格和回测理论成交价之间的偏差。这一步会暴露出很多回测发现不了的问题。例如当均线金叉信号出现时行情往往已经走了一段实际买入价格可能比回测价格高不少。这个差异如果积累起来会直接吃掉期望值中的“正收益”。模拟阶段持续时间不能太短。一个完整的策略至少需要经历一波行情从启动到结束的周期才能判断信号在实际市场环境下是否有效。4.4 第四个月实盘试运行与记录第四个月我用极小金额进行实盘试运行目的是验证整个流程而不追求盈利。重点观察以下内容订单是否能按规则执行是否因为犹豫而错过入场点。盘中出现浮亏时能否严格按照止损规则离场。收盘后的交易记录是否完整能否还原每天的系统状态。实盘阶段最冲击人的一点是即使你知道回测期望值为正面对单笔亏损时依然会产生“要不要再拿一下”的冲动。这说明心理层面的问题不是靠理论学习解决的必须通过足够多的实盘记录来训练。四个月结束后我得到了一份包含每笔交易的记录表进场时间、出场时间、持仓周期、盈亏金额、是否遵守规则、偏离原因。这些数据比任何盈利都宝贵因为它们是后续优化的基础。5. 一个最小可运行的回测示例下面我提供一个完整的、可复现的双均线策略回测示例。代码使用 pandas 实现不依赖复杂框架。你只需要准备一个包含日线数据的 CSV 文件字段包括date、close。5.1 准备数据假设数据文件为data/stock.csv结构如下date,close 2023-01-03,4120.50 2023-01-04,4135.20 ...读取数据并计算收益率import pandas as pd def load_data(path): df pd.read_csv(path, parse_dates[date], index_coldate) df[returns] df[close].pct_change() return df5.2 生成交易信号双均线策略逻辑当 5 日均线上穿 20 日均线时产生做多信号当 5 日均线下穿 20 日均线时产生平仓信号。def generate_signal(df, fast5, slow20): df df.copy() df[ma_fast] df[close].rolling(fast).mean() df[ma_slow] df[close].rolling(slow).mean() # 1 表示持仓0 表示空仓 df[signal] 0 df.loc[df[ma_fast] df[ma_slow], signal] 1 df.loc[df[ma_fast] df[ma_slow], signal] 0 # 使用 shift(1) 避免未来函数 df[position] df[signal].shift(1).fillna(0) return df这里的关键点是shift(1)。如果不做这一步回测会在当天收盘价出现信号的同时按当天收益计算收益属于“未来函数”会高估策略表现。正确做法是用前一天的信号决定当天的持仓状态。5.3 回测引擎与绩效统计import numpy as np def run_backtest(df, init_cash100000, fee_rate0.0003): df df.copy() # 策略每日收益 持仓状态 × 当日收益率 - 交易手续费 trade df[position].diff().abs().fillna(0) df[strategy_return] df[position] * df[returns] - trade * fee_rate df[equity] init_cash * (1 df[strategy_return].fillna(0)).cumprod() return df def compute_metrics(df): df df.copy() # 过滤实际发生的交易 trades df[df[position].diff().fillna(0) ! 0] total_return df[equity].iloc[-1] / df[equity].iloc[0] - 1 # 最大回撤 cummax df[equity].cummax() drawdown (df[equity] - cummax) / cummax max_drawdown drawdown.min() # 年化收益率按 252 个交易日估算 days len(df) annual_return (1 total_return) ** (252 / days) - 1 # 夏普比率 daily_std df[strategy_return].std() sharpe df[strategy_return].mean() / daily_std * np.sqrt(252) if daily_std ! 0 else 0 metrics { 总收益率: f{total_return * 100:.2f}%, 年化收益率: f{annual_return * 100:.2f}%, 最大回撤: f{max_drawdown * 100:.2f}%, 夏普比率: f{sharpe:.2f}, 交易次数: len(trades), } return metrics手续费率按万三计算实际交易中还需要考虑滑点和印花税。你可以根据自己的交易品种调整。5.4 运行与结果解读df load_data(data/stock.csv) df generate_signal(df) df run_backtest(df) metrics compute_metrics(df) for k, v in metrics.items(): print(f{k}: {v})运行后会输出类似下面的结果总收益率: 8.32% 年化收益率: 6.50% 最大回撤: -5.12% 夏普比率: 0.86 交易次数: 24解读这些指标时不要只看总收益率。最大回撤决定了你能否拿住仓位夏普比率反映了收益的稳定性交易次数则决定了统计样本是否足够。如果一个策略交易次数太少即使收益率高也不具备统计意义上的可靠性。6. 高频踩坑与排查思路在四个月的复盘中我遇到了不少问题。下面整理成表格方便你按图索骥问题现象常见原因解决思路回测收益很高实盘跑不出来回测中使用了未来函数或未考虑滑点检查信号是否使用了当日收盘价计算当日收益必须shift(1)回测结果在不同时间段差异巨大策略过拟合只适应特定行情做样本内外划分做参数敏感性分析交易次数太少统计意义不足信号条件过于严格放宽条件增加样本量或使用更长时间周期数据资金曲线一直小亏偶尔大赚趋势跟踪策略典型特征重点监控盈亏比是否大于 3不必过分在意胜率实盘下单价格与回测价格偏差大未考虑滑点、冲击成本模拟盘阶段统计滑点均值并纳入回测成本盘中总是手动干预信号风控规则不够细把退出条件写成程序自动执行减少人工判断指标全部正常但资金曲线仍在下降仓位管理不匹配单笔亏损过大降低单笔仓位设置总回撤熔断线这里的核心思路是遇到异常结果不要急着修改参数而是先检查代码逻辑是否存在错误再确认成本假设是否合理最后才考虑策略本身是否有效。7. 最佳实践与工程建议7.1 从策略到系统的模块化把交易系统拆成信号、仓位、风控、记录四个模块互相独立。这样每次迭代只修改一个模块不影响其他模块。例如信号模块只需要输出position系列仓位模块根据position和其他条件决定实际下单量风控模块单独监控账户状态。模块之间通过明确的接口通信便于测试和回滚。一个简单的数据流如下行情数据 → 信号生成 → 仓位计算 → 风控检查 → 订单记录 → 绩效统计7.2 风险管理与仓位控制仓位管理是交易系统中容易被低估的部分。固定金额下单看似简单但资金曲线波动会很大按比例下单更灵活但需要处理资金接近归零的问题。推荐的做法是单笔风险不超过总资金的 2%并设置每日亏损熔断线。具体数值可以根据策略的胜率和平均亏损水平调整但一旦确定就不要频繁变更。计算单笔仓位时可以使用下面的公式仓位比例 单笔允许亏损 / (开仓价 - 止损价)举个例子假设总资金 10 万单笔允许亏损 2% 即 2000 元。如果开仓价 100 元止损价 95 元那么最大持仓数量为2000 / (100 - 95) 400 股这种计算方式把“允许亏多少”和“止损距离有多远”结合了起来比简单按百分比下单更科学。7.3 交易日志与生命周期管理每笔交易都应当记录以下内容进场时间、出场时间、价格。进场理由、出场理由。是否严格按规则执行。偏差原因分析。策略参数版本号。建议给策略代码和参数都加上版本号例如ma_cross_v1.2。当你后续修改了任何规则都应该升级版本并保留旧版本的结果用于对比。生命周期管理还包括“策略下线”标准。当一个策略连续多笔交易亏损或者回撤超过预设阈值就应该停止运行并进入复盘流程而不是继续期待下一笔翻本。8. 复盘总结与下一步学习路线四个月时间我最大的收获并不是某个具体策略而是建立了一套“提出假设 → 编码实现 → 历史验证 → 模拟执行 → 实盘试运行 → 统计分析 → 迭代改进”的闭环流程。如果你准备开始搭建自己的交易系统建议按下面的顺序走先写规则把交易想法写成可编程的、可验证的规则。再做回测用历史数据验证规则是否具备正期望值。模拟验证至少运行一个月记录滑点和执行偏差。小额实盘坚持记录每笔交易不断比对实际执行与规则的一致性。持续迭代每次只改一个变量保留实验记录。下一步可以继续学习的内容包括多品种组合回测、因子分析、机器学习特征筛选、动态仓位管理等。但要记住交易系统的核心从来不是某一次判断的胜负而是长期执行下的期望值累积。系统越简单、越稳健、越容易被坚持执行往往比复杂但脆弱的系统更实用。如果你也在搭建自己的交易系统欢迎在评论区分享你的心得或踩坑经历。大家一起讨论往往比自己闷头优化更容易发现问题。