ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

本地量化Agent架构:Trading-as-Git实现可审计风控与版本回滚

本地量化Agent架构:Trading-as-Git实现可审计风控与版本回滚 其实最开始想清楚要给自己做一个实盘能睡着的量化系统这个目标时我脑子里蹦出来的不是策略、不是胜率而是三个字别再爆仓了。过去两年我见过太多次这样的场景——策略在回测里曲线丝滑得像瀑布一上实盘就变成钝角行情软件里某个参数填错几分钟内把半年的利润全还回去半夜被一条止损短信炸醒才发现风控规则根本没生效。后来我花了大半年时间围绕 OpenAlice 的思路重新搭了一套本地量化 Agent 架构核心就一句话把交易系统当成 Git 仓库来管理让风控不是挂在策略后面的装饰品而是嵌在整个执行链路里的硬约束。这篇文章就把这套架构的完整拆解、落地细节和坑位都摊开讲适合正在从手工跟单/盲目实盘转向系统化本地量化阶段的朋友。先说结论OpenAlice 不是我发明的某个开源项目代号而是我给自己这套架构起的内部名字——Open代表策略和风控规则全部本地可审计、可改写Alice则代表那套负责调度、决策、执行的 Agent 主程序。整个设计的锚点是 Trading-as-Git意思是策略、参数、风控限额、数据版本全都像代码一样进版本库可以提交、对比、回滚、分支实验。它解决的从来不是怎么赚更多而是怎么在亏的时候亏得明白、亏得可控。1. 为什么实盘总是失控以及 Trading-as-Git 到底在治什么病1.1 一次没有后悔药的夜间回撤先说一个让我下决心重构系统的真实经历。那晚 BTC 在半小时内跌了 4%我的网格策略按计划应该在第二档触发止损。结果等我早上醒来发现账户净值已经回撤了 11%。排查了大半天原因非常荒唐策略配置里止损价格写死了但我前两天调整参数时改了网格间距没有同步修改止损触发线更离谱的是风控模块里有一个最大回撤熔断开关默认是关着的我在初始化配置时漏看了这一项。这种事故不是技术难题而是系统结构问题——策略、参数、风控规则散落在不同的脚本和 Excel 里彼此没有版本关联。你改了策略逻辑没人提醒你风控参数需要联动你把某个开关设成默认关闭没有代码审查能拦住你。Trading-as-Git 要治的就是这种状态不可追溯、变更不可回滚、责任不可定位的病。1.2 传统量化开发的三个结构性缺陷我复盘了大量类似事故发现多数人包括当年的我的量化系统都有三个共同的坑第一策略是活的但版本是死的。很多人用strategy_final_v2_really_final.py这种方式管理策略文件改一版就另存一份。时间一长你根本说不清当前跑的参数是哪一版更不可能快速回退到三天前那个表现还行的状态。第二风控和策略耦合太深。最常见的写法是策略函数内部自己写止损、自己算仓位、自己判断要不要下单。听起来灵活实际上一旦策略逻辑改出边界条件风控也跟着失效。风控不应该作为策略的可选分支存在它应该是独立于策略的强制层策略没资格关闭它。第三数据、参数、代码没有统一的时间轴。行情数据每天在变参数每周在调策略逻辑每月在迭代。一旦实盘出现问题你想知道这套参数在这段行情下表现如何却发现历史数据没有留档、当时的参数没有记录、策略代码已经改得面目全非。归因分析根本无从下手。1.3 Trading-as-Git 想解决的三个问题所以我的架构目标非常明确对应用 Git 的三个核心能力可追溯Commit每一次策略调整、参数修改、风控规则变更都必须形成一条带消息的提交记录。几天后回头看你能清楚知道为什么当时要这么改。可回滚Revert/Checkout如果今天的策略跑出来的表现明显不如上周一键切回旧版本而不是凭记忆把参数一个个改回去。可实验Branch新想法先在分支上跑模拟盘和主分支做对比跑通了再合并。主分支永远只放经过验证的、风控完备的版本。这个理念不新鲜但放到本地量化 Agent 的场景里落地细节远比想象中复杂——状态怎么对齐回滚会不会造成仓位不一致Agent 的角色在这个体系里到底是什么这些就是下面要展开的内容。2. 本地量化 Agent 的分层骨架数据、决策、执行、风控2.1 为什么坚持本地部署而不是云端这个选择可能和很多人想的相反。市面上大量量化平台都提供云端的策略托管、回测、实盘一条龙服务为什么还要自己折腾本地我的理由有三个。第一是延迟和稳定性可控。云端平台的网络抖动、维护窗口、接口限流都不受你控制而高频一点的策略对执行链路的每一毫秒都很敏感。本地部署至少可以保证从信号产生到下单指令发出的整条链路不依赖公网质量。第二是数据和策略的私域性。你的策略逻辑、持仓数据、资金曲线如果放在云端等于把底牌交给了第三方。本地部署意味着所有数据都在自己手里安全边界清晰得多。第三是审计和实验的自由度。本地环境里你想改什么就改什么想接哪个数据源就接哪个数据源想对历史数据做多激进的清洗都可以不受平台规则限制。当然本地部署也有代价——硬件维护、网络稳定性、交易所接口的本地代理都要自己扛。后面我会讲我在这上面踩的坑。2.2 四层 Agent 架构与模块职责我把整个系统分成四层每一层都是一个独立的 Agent 模块它们之间通过事件总线通信而不是互相调函数。这样做的好处是职责边界非常清晰任何一个模块挂掉其他模块仍然可以独立运行和报警。层模块代号核心职责关键关注点L0Collector行情订阅、账户资金查询、成交回报接收数据缺失、延迟、断线重连L1Decision读取策略信号、生成目标仓位和价格建议信号质量、过拟合、逻辑正确性L2Executor下单、撤单、拆单、跟踪委托状态滑点控制、接口限频、异常单处理L3Risk独立于策略的强制校验限额、熔断、净持仓检查极端行情、系统故障、人为误操作四层之间不是简单的上下级关系我更愿意把它理解为流水线加安全护栏的组合。Collector 采集到行情后把数据推给 DecisionDecision 跑完策略逻辑产出一个意图我称之为OrderIntent这个意图必须经过 Risk 层校验校验通过才会交给 Executor 执行。Executor 的任何成交回报和异常信息又会回流到 Risk 层和 Journal 日志。有一个容易忽略的点Risk 层不能只存在于 L2 和 L3 之间。我自己的设计里Risk 层有两条通道——一条是实时阻断通道在每笔委托前做前置校验另一条是异步监控通道持续监测全账户的持仓市值、浮亏比例、资金曲线回撤一旦触发阈值可以直接绕过 Executor 向所有策略广播强制平仓/停止开仓指令。这有点类似 Redis 里的 sentinel 机制风控既是每个命令的拦截器也是整个集群的哨兵。2.3 消息流转与 Agent 状态机设计四层之间的事件流我用一个简单的例子说明。假设你跑的是一个 5 分钟级别的 ETH 动量策略Collector 每 5 秒收到一次 tick聚合成 1 分钟 K 线后推送到决策模块。Decision 模块加载当前分支的策略代码计算出当前信号为多头目标仓位是合约账户净值的 30%。Decision 生成一个OrderIntent{action: open_long, symbol: ETH/USDT, size: 0.3 * equity, order_type: market}。Risk 层收到意图后做四项检查当前净持仓是否超过单标的限额、全账户风险敞口是否超过阈值、系统是否处于熔断状态、该策略是否在白名单里。全部通过后Intent 被交给 ExecutorExecutor 负责拆单、下单、监听成交。成交回报回到 Decision 和 Journal同时更新 Risk 层的持仓快照。这个流程里最容易出问题的环节是状态一致性。举个具体例子收到成交回报之后如果网络中断Executor 以为没成交但交易所实际已经成交了。这时候 Agent 内部状态和真实账户状态就产生了偏差。我的解决方案是引入一个对账 Agent每隔 30 秒从交易所拉一次真实持仓和成交记录和本地状态做哈希比对发现不一致立即触发风险告警并暂停该策略的开仓操作。Agent 的状态机设计同样很关键。每个模块我都定义了四个状态Running、Paused、Degraded、Stopped。模块自身故障时不允许悄悄降级而是必须显式推向Degraded或Stopped同时向监控中心上报。为什么要这么严格因为 Agent 系统最危险的状态就是我以为它在跑其实它早就停了。任何人值守的系统都不如状态机可靠状态机不会因为困了就忘记检查心跳。2.4 Agent 的会话记忆如何落地大语言模型领域的 Agent 都有记忆的概念量化 Agent 也需要。但这里的记忆不是自然语言对话历史而是三类关键信息的持久化一是策略运行上下文。当前跑在哪个分支、加载的是哪份配置、正在监听哪些数据源、当前有哪些未完成的委托单。这些信息必须在系统重启后可恢复否则进程一重启Agent 就失忆了容易做出和之前状态冲突的决策。二是决策日志。每一条 OrderIntent 从生成到风控校验再到执行的完整生命周期都要落库。出现问题时能回答这个单子是谁在什么条件下决定下的。三是参数血缘记录。某个策略参数在某个时间段内生效过这个参数是从哪次提交引入的关联的风控配置是什么。这个血缘关系是 Trading-as-Git 落地的基础后面会细说。我的做法是用 SQLite 做本地状态库配合 Git 仓库做代码和配置的版本管理。SQLite 负责高频状态读写Git 负责低频但需要严格审计的变更记录。两者通过change_id字段关联每个状态的变更都能对应到一个 Git commit。3. 策略像 Git 一样管理仓库结构、提交规范与回滚机制3.1 策略仓库的文件组织现在进入 OpenAlice 这套架构最核心的部分——Trading-as-Git 的落地。第一步是把策略资产组织成一个规范的 Git 仓库。我的目录结构大致是这样的strategy-repo/ ├── strategies/ # 每个策略一个子目录 │ ├── eth_momentum_v1/ │ │ ├── config.yaml # 策略参数周期、阈值、目标仓位 │ │ ├── risk_profile.yaml # 该策略专属风控偏好止损、限额 │ │ ├── strategy.py # 信号生成逻辑 │ │ ├── tests/ # 该策略的单元/回测测试 │ │ └── README.md # 策略思路说明 │ ├── btc_grid_v3/ │ └── ... ├── agents/ # Agent 模块代码 │ ├── collector/ │ ├── decision/ │ ├── executor/ │ └── risk/ ├── portfolios/ │ └── main_portfolio.yaml # 组合级风控参数全局限额 ├── data_snapshots/ # 关键时点的行情数据快照描述 ├── reports/ │ └── daily.md # 每日自动生成的运行报告 └── .alice/ ├── state.db # SQLite 状态库 └── journal/ # 交易日志与复盘记录这里要特别强调一点策略代码和风控配置必须放在同一个提交里。也就是说当你修改eth_momentum_v1/config.yaml里的杠杆倍数时risk_profile.yaml里的止损距离、单笔最大亏损额度也必须一并修改并提交。我通过一个 Git pre-commit hook 做强制检查任何一个策略配置文件被修改但同目录下的risk_profile.yaml没有同步变更就拒绝提交。这个约束看起来很死板但它能挡住绝大多数改参数忘了改风控的事故。3.2 一次策略迭代的完整 Git 流程一套比较健康的策略迭代流程大概长这样从main分支切出一个experiment/eth_momentum_vol_filter分支。在该分支上修改策略逻辑加入波动率过滤条件。同步调整risk_profile.yaml——因为过滤后开仓频率降低可以把单笔仓位略微上调同时把止损距离收窄。本地跑回测记录回测报告提交到分支。不急着合并。先让这个分支在模拟盘中跑三到五个交易日每天由系统自动把模拟盘收益曲线和main分支的实盘曲线做对比生成对比报告。对比报告表现稳定且优于主分支再合入main。合并时用git merge --no-ff保留分支历史方便将来追溯为什么当初要加这个过滤条件。合入后打一个 tagv2025.06.15-eth-momentum-v2这个 tag 对应的就是一套完整可回滚的策略版本。这个流程里最容易被忽视的是第二步到第四步之间的回测基准对齐问题。很多人回测用的历史数据是三个月前下载的和当前数据有差异导致回测结果失真。我的做法是回测前必须把历史数据快照的版本记录进提交信息里回测报告里也要注明数据区间和数据源版本。宁可慢一点也不要让回测结果变成一个无法复现的幽灵数据。3.3 回滚不是 git revert 那么简单状态对齐问题Trading-as-Git 最有迷惑性的地方在于你以为回滚策略就像git checkout v2025.06.15一样简单但实际上有一个致命陷阱策略代码可以回滚但已经发生的持仓无法自动跟随回滚。举个例子。假设当前main分支跑的是 v2 策略它在半小时前开了一个 20% 仓位的 ETH 多单。现在你决定回滚到 v1 策略但 v1 的策略逻辑认为当前 ETH 应该空仓。如果你只是把代码切回 v1那么账户里那 20% 的多单就成了不属于任何策略逻辑的孤儿持仓。这比策略亏损更危险因为没有任何代码会为这个持仓负责风控规则也不会覆盖它。我的解决方案是引入一个策略回滚协调器回滚动作分三步执行第一步取消该策略在当前 Agent 决策列表中的注册停止生成新信号。 第二步根据 v1 和目标版本的持仓计算差异生成一组状态迁移指令由 Executor 逐步把持仓调整到目标版本的期望状态。 第三步等待持仓状态对齐、所有未完成委托单全部取消或成交后再切换代码到目标版本恢复决策注册。整个过程在 Journal 里会记录一条迁移日志。如果没有这个协调机制单纯回滚代码大概率会在实盘里制造更大的混乱。3.4 配置即代码参数也要进版本库很多人的 Git 仓库里只放策略代码参数配置写在独立的 JSON 文件里甚至存在数据库里。这在我看来是个巨大的隐患。参数和代码一样也是策略行为的一部分它们必须一起版本化。我在实现里做了一个小设计策略启动时从 Git 仓库的某个 tag 读取config.yaml和risk_profile.yaml并把对应的 commit hash 写入状态库。这样任何时候你去查正在运行的 Agent 实例都能立刻说出它跑的是哪个提交、哪份配置、哪套风控参数。审核和排错的时候这个信息能省下大量时间。我甚至建议把交易所的合约参数也纳入配置比如合约面值、价格精度、最小下单量、手续费等级。这些参数直接影响仓位计算如果交易所调整了规则而系统还在用旧值轻则下单失败重则仓位计算错误。纳入版本库后每次更新就是一次普通的代码提交可以在合并前被 review。4. 风控闭环从监控到熔断再到复盘的完整链路4.1 风控层的四道防线风控闭环是我整套架构里最引以为傲的部分因为它真正做到了独立、强制、可审计。我把它拆成四道防线每一道防线都能独立触发不存在一损俱损的单点结构。第一道防线前置校验。每笔委托在下单前必须通过 Risk 层的参数校验包括价格偏离保护防止市价单在极端行情下以离谱价格成交、单笔最大名义价值、单标的累计持仓上限。校验不通过直接拒绝委托并记录原因。第二道防线实时监控。Risk 层持续监控全账户的资金曲线、浮亏比例、各策略维度的盈亏情况。这里的重点是全账户视角而不是单策略视角。单策略止损了但其他策略可能还在反向加仓账户总体风险仍然可能失控。第三道防线熔断机制。一旦全账户回撤超过预设阈值系统进入熔断状态停止所有策略的开仓操作只允许减仓或平仓。这个状态必须由人工确认才能解除Agent 无权自动恢复。第四道防线事后复盘。每天收盘后系统自动生成当天的策略表现报告、风控触发日志、异常事件清单。复盘不是走形式而是要把每一次风控触发都转化为下一轮的策略或参数调整。四道防线的核心设计理念是分层设防、逐级失效。前面防线被突破后面防线仍然兜底任何一道防线都不依赖策略代码的正确性。这也是为什么 Risk 层必须独立于策略进程——哪怕策略代码完全跑飞Risk 层还能阻断开仓指令。4.2 熔断器设计与动态参数熔断器的实现看似简单实际上有很多细节。我的熔断器分为两个层级策略级和账户级。策略级熔断某个策略在最近 N 笔交易中的亏损超过阈值则暂停该策略的新开仓等待人工诊断或冷却时间结束后再恢复。这类似 Git 里的一种保护分支机制——分支代码质量不达标就不允许合入主分支。账户级熔断全账户净值从近期高点回撤超过设定比例立即停止所有策略开仓。这个比例我通常设在 8%~12% 之间具体的值取决于策略组合的波动特征。账户级熔断一旦触发Agent 会发送告警到手机和 Telegram这里走的是系统自带的通知不依赖任何第三方代理同时把当日所有策略的运行状态、持仓快照、决策日志打包成一份报告方便你冷静下来排查。熔断器的参数不是写死的常量而是动态计算的。举个例子我可以把账户级熔断阈值设置为circuit_breaker_level base_drawdown k * current_volatility其中base_drawdown是基础回撤容忍度current_volatility是最近 20 个交易日的年化波动率k是一个调节系数。这样在低波动环境里阈值收紧防止慢刀子割肉在高波动环境里阈值放宽避免被正常的市场噪音频繁触发熔断。这个动态参数本身也要写进risk_profile.yaml走版本管理。有一个坑值得单独提醒熔断器千万不要在代码里做成触发后自动恢复。我见过一些系统熔断时间到了就自动恢复结果行情还在继续恶化恢复即追加亏损。我的设计是熔断触发后只能人工恢复且恢复前必须填写一段诊断说明提交到 Journal说明为什么认为是安全的、依据是什么。这个流程看似繁琐但它逼着你在恢复前至少冷静思考一轮。4.3 仓位与杠杆计算为什么要有公式而不是拍脑袋仓位管理是风控闭环里最容易被艺术化的部分但真正可靠的系统必须把仓位决策变成一个可计算、可验证的公式。我的基础仓位计算用的是改进版的凯利公式加波动率约束position_size (edge * win_rate - (1 - win_rate)) / edge * equity * risk_factor其中edge是单笔交易的平均盈利与平均亏损比win_rate是历史胜率risk_factor是一个保守系数我通常取 0.2 到 0.3。这个公式算出来的是理论最优仓位的一个折扣值目的是在保留收益潜力的同时避免因单次参数偏移导致过大回撤。真正的仓位计算不能只看期望值还要看波动率。所以在仓位上限上我加了一层约束max_position_notional equity * max_single_position_pctmax_single_position_pct通常在 20%~40% 之间。此外所有策略的合计名义敞口不能超过账户净值的某个倍数这个倍数就是组合层面的杠杆上限。我奉行的原则是单策略的期望再好也不能突破组合层面的风险预算。这就像 Git 分支再激进合入主分支前也必须通过 CI 检查。4.4 复盘是闭环的最后一环把失败变成策略风控闭环最后一步也是最容易偷懒的一步是复盘。很多人把复盘理解为看一眼今天的 PnL写两句感想。这远远不够。我的复盘流程是结构化的每天收盘后自动生成一份报告包含五个部分资金曲线与回撤统计今天净值变化、当前回撤距离前高的百分比。风控事件清单今天触发过哪些风控规则、每一条的触发时间和结果。委托执行质量下单延迟、滑点统计、成交率、撤单原因。策略归因分析每个策略的今日贡献和累计贡献哪些策略在拖后腿。异常与待办今天出现的任何警告、错误、状态漂移以及对应的处理建议。这些报告不会自动治愈系统但它们是下一轮策略迭代的输入。比如如果连续三天在固定时间段出现滑点偏大复盘报告会暴露出这个问题你再回去看当时的市场流动性和委托方式很可能发现是拆单算法在某个时段表现不佳。这个闭环的终点是每一个问题都会被追踪到一次代码提交或参数变更而不是停留在今天运气不好。5. 从零搭建一个最小 OpenAlice选型与关键代码5.1 技术选型落到实操层面如果你想自建一套最小版本的 OpenAlice 架构我的选型建议是这样的核心语言Python。优势是量化生态完善、开发效率高适合快速迭代策略逻辑。性能瓶颈可以通过 Cython 或异步 IO 解决真正的极致低延迟场景再考虑 Rust 或 Go但大多数本地量化场景用 Python 足够。事件总线内部直接用asyncio.Queue实现简单的事件传递跨进程用 Redis Stream。我试过 Kafka对于单机个人系统来说太重了运维成本不划算。状态存储SQLite简单可靠单文件备份方便。关键表加上 WAL 模式读写并发性能足够。数据源CCXT 统一对接交易所 REST/WebSocket行情落库用 Parquet 文件按天存储。策略仓库就是标准 Git配合 GitLab 或 Gitea 做远程仓库。本地一定要开git reflog回滚救命的工具。调度器APScheduler 或简单的 asyncio 任务循环别过度设计。5.2 风控模块的核心骨架代码下面这段是我风控模块里最核心的一段代码简化版但结构是完整的。class RiskEngine: def __init__(self, risk_config: dict, state_store): self.config risk_config self.state state_store self.circuit_breaker_triggered False async def check_intent(self, intent: OrderIntent) - RiskVerdict: if self.circuit_breaker_triggered: return RiskVerdict.reject(account_circuit_breaker_active) # 前置校验 1价格偏离保护 if intent.order_type market: mark_price await self._get_mark_price(intent.symbol) deviation abs(intent.price - mark_price) / mark_price if deviation self.config[max_price_deviation]: return RiskVerdict.reject(price_deviation_too_large) # 前置校验 2单标的净持仓上限 current_pos await self._get_current_position(intent.symbol) proposed_notional intent.size * intent.price if current_pos.notional proposed_notional self.config[max_symbol_notional]: return RiskVerdict.reject(symbol_notional_limit_exceeded) # 前置校验 3全账户名义敞口 total_exposure await self._get_total_exposure() if total_exposure proposed_notional self.config[max_total_notional]: return RiskVerdict.reject(account_notional_limit_exceeded) return RiskVerdict.approve() async def check_account_health(self): equity await self._get_account_equity() peak self.state.get(equity_peak) or equity if equity peak: self.state.set(equity_peak, equity) return drawdown (peak - equity) / peak if drawdown self._current_breaker_level(): await self._trigger_circuit_breaker(drawdown) def _trigger_circuit_breaker(self, drawdown): self.circuit_breaker_triggered True self.state.set(breaker_drawdown, drawdown) # 通知所有策略 Agent只允许平仓不允许开仓 self.broadcast(FULL_STOP_OPENING)这段代码看着不长但几个设计点非常关键。check_intent是被动拦截check_account_health是主动扫描两者互不依赖。_trigger_circuit_breaker里广播的是停止开仓而非停止所有操作因为平仓是解除风险的手段绝对不能一并熔断。我最初犯的错误就是熔断时把所有交易全部停掉结果在极端行情里没有持仓等待被清算。5.3 Git pre-commit hook 的完整实现思路前面提到我用 Git hook 强制策略参数和风控配置联动修改。这个 hook 的核心逻辑并不复杂#!/bin/sh # .git/hooks/pre-commit changed_files$(git diff --cached --name-only) for file in $changed_files; do # 如果修改的是策略配置文件 case $file in strategies/*/config.yaml) strategy_dir$(dirname $file) if ! echo $changed_files | grep -q $strategy_dir/risk_profile.yaml; then echo ERROR: $strategy_dir/config.yaml 被修改必须同步修改 risk_profile.yaml 2 exit 1 fi ;; strategies/*/strategy.py) strategy_dir$(dirname $file) if ! echo $changed_files | grep -q $strategy_dir/risk_profile.yaml; then echo ERROR: $strategy_dir/strategy.py 被修改请确认是否需要同步调整 risk_profile.yaml 2 exit 1 fi ;; esac done exit 0这个 hook 不聪明它只是把改策略必须同步考虑风控这件事变成了一个不可绕过的步骤。你仍然可以在risk_profile.yaml里瞎写一个更激进的值但至少修改行为本身会被记录、被 tracing。有时候工程化的目的不是防止所有错误而是让错误发生得更显眼、更容易定位。5.4 跑通一个极简日内动量策略的完整流程新手建议按这个流程走一遍最小闭环体验 Trading-as-Git 的完整链路初始化仓库创建strategies/demo_momentum/目录写一份最简策略RSI 低于 30 买入高于 70 卖出。写对应的config.yaml和risk_profile.yaml风控参数先按最保守的设置单笔仓位 5%账户最大回撤熔断 5%。提交到main分支打 tagv0.1-demo。模拟盘运行三天。这期间不做任何修改每天看自动生成的日报。三天后切分支修改策略参数比如把 RSI 阈值从 30 改成 25同步调整风控提交再跑三天模拟盘。用git diff对比两次策略的差异结合模拟盘的绩效报告判断哪一版更符合你的风险偏好。选定的版本合入主分支打新 tag。以后随时可以用git checkout回到任何一版。这个流程走一遍你对 Trading-as-Git 的理解就不再是概念层面的而是肌肉层面的。我见过太多人上来就写复杂策略结果连基本的版本管理都没建立出问题只能干瞪眼。6. 实盘运营半年后我想对这套架构做的修正6.1 信息过载比信息不足更致命这套系统上线三个月后我遇到一个完全没想到的问题告警太多了。Collector 断线重连要告警、价格偏离要告警、风控规则触达预警线要告警、Executor 委托超时也要告警。结果就是我在手机上每天收到几十条消息真正重要的告警反而被淹没在噪音里最后我干脆把通知全部关掉了。这不是系统的问题是我的告警分级设计不成熟。后来我重新调整了告警策略把信息分成INFO、WARNING、CRITICAL三档只有CRITICAL才推送手机通知比如熔断触发、账户状态不一致、策略进程崩溃。WARNING级别的告警统一进日报批量查看。这个调整之后信息的信噪比才回归正常。风控系统要的不是事事报告而是异常分级、关键必达。6.2 单一数据源的高可用陷阱另一个让我差点翻车的问题出现在数据源上。我最初只接了一家交易所的 WebSocket 行情某天这家交易所的行情服务连续抖动导致 Collector 一直在断线重连。重连期间策略拿不到新数据明明市场在剧烈波动系统却因为没有数据而表现得像什么都没发生。最麻烦的是断线重连后如果补数据逻辑有缺陷很容易产生重复的 K 线信号计算直接乱套。后来我加了一个数据冗余层同一行情同时订阅两家交易所的 WebSocket做交叉验证。主数据源出问题时自动切换备源切换动作本身触发告警。同时给 K 线生成器增加了数据新鲜度检查超过 3 秒没有新 tick 就标记数据降级策略层可以选择暂停开仓而不是基于陈旧数据硬跑。这个设计的核心思想是数据缺失时必须让系统知道数据缺失而不是带着缺口继续运行。6.3 对 Agent 的信任边界要清晰划分随着系统里 Agent 越来越多我开始思考一个问题到底应该让 Agent 拥有多大的自主权我的结论是信息处理和数据整理可以全自动但涉及资金状态变更的决策必须保留人工审核位。具体来说Collector 自主决定数据源切换、Decision 自主决定信号计算、Executor 在风控额度内自主执行委托——这些都可以。但是账户级的杠杆调整、熔断阈值修改、策略风险预算变更这些操作必须经过人工确认Agent 只能给出建议。这不是技术问题是责任边界问题。出了事故总要有人负责而这个人不应该是藏在代码后面的 Agent。把关键决策留给人工既是对资金的尊重也是对自己系统的敬畏。6.4 风控参数是活的需要定期压力测试最后一点体会也是我现在最重视的风控参数不能设定完就再也不动。市场环境会变策略组合会变账户规模也会变当初设定参数时的假设可能已经不再成立。我现在每季度会做一次完整的风控压力测试用真实历史行情里最极端的几天比如单日暴跌 20% 的情形回放整个系统观察四道防线分别会在什么时候触发、账户最大回撤是否能被限制在可接受范围。测试结果会形成一份报告提交到 Git 仓库。如果压力测试显示某个防线过于迟钝我会调整参数并走完整的版本管理流程。这个季度测试的习惯让我的系统在经历了两次中等强度的行情波动后依然保持了账户回撤在预设范围内。说实话它比任何策略优化带来的安心感都更强。如果你现在还在手工下单 Excel 记账 凭感觉调参的阶段我的建议很直接先把 Git 仓库建起来把风控 hook 挂上跑一段时间的纸面交易感受一下每次改动都有记录、每次事故都能追溯到底是什么体验。等你适应了这种确定性再回头看那些盲目实盘的日子你会觉得那不是在交易是在赌博。
返回列表