跳转至

系统设计的工程哲学

一开始只是写了个小插件,用来识别笔、段、中枢这些基本结构。但用着用着就发现,这点功能根本不够用——真正想要的是一套完整的系统:能自动选股、自动识别技术形态、计算各种指标信号,还能跑历史回测来验证策略。

这一做就是许多年。说不上轻松,但好处是:这套工具终于能跑通从信号识别到策略验证的完整闭环。本章不讲某个具体模块怎么实现,讲的是这个「小系统长成大系统」的过程中,沉淀下来的几条工程哲学。它们不是从书上学来的,是从一次次拒绝需求、修 bug、改参数、重读自己代码里长出来的。

单元功能与整体系统:拒绝的底气

开发系统的过程中,经常被问到:为什么不做这个?为什么不做那个?这个能不能那样改一下?

通常我的回答是拒绝,或者「暂时不做、以后再说」。原因有两类:有些功能我自己确实用不到;有些不是现阶段该考虑的内容。我心里按自己的路线图在推进。

如果非要给这种「固执」一个理论解释,那就是单元功能与整体系统的关系:

  • 量化交易系统由多个模块组成,每个模块负责特定功能,模块的独立性与相互协作直接决定系统的性能和稳定性;
  • 整体架构初步定型之后,很长一段时间仍处在单元功能开发的中期——完整系统要靠非常多的单元功能组合协调来完成;
  • 单元功能先以实验性质存在,经过使用检验,能保留下来的最终合并进生产系统,强化生产系统的能力和效率。

这个视角给独立开发者两重保护。对外,它给「说不」以底气:不做的功能不是被否定了,是不在当前的主线上;功能请求永远无穷无尽,路线图只有一条。对内,它给「慢」以合法性:整体架构定型不代表系统成型,中间隔着漫长的单元功能沉淀期,这个过程急不来。

路线图怎么排?当时的两条主线是一个好例子:一条是多开发形态模型,再设计统一的评估机制做横向对比——先有货架,再有评比;另一条是开发自选股管理功能——第一条线一旦可用,马上要能集成进去。两条线一虚一实,一条生产弹药,一条修路架桥,互为承接。

模块自治:独立可用,组合更强

单元功能怎么设计才算好?第一原则是模块自治:每个功能做成独立模块,可以单独用,也可以组合起来用。

以形态识别为例:MACD 背离检测(面积背离与黄白线背离两种方法)、价格回拉、V 形反转——每种形态都是独立模块。代码自动识别形态、算出参考价位,用户要哪个取哪个。自治的好处在小系统阶段不明显,在系统长大后是救命的:任何一个模块出问题,火不会烧到别的模块;任何一个模块要重写,别的模块不动。

模块自治还解决了「哪个模型好」这个大问题。给每个筛选模型配上模拟交易模块,对筛出来的标的进行模拟建仓和跟踪,统计胜率、盈亏比、平均持仓周期——数据摆出来,模型之间的横向对比一目了然。不需要争论哪种技术分析更高级,让模拟盘说话。

参数设计上也有一个反直觉的教训:盈亏比不是越高越好。 回测不同盈亏比参数后发现,盈亏比太高时止盈位太远,很多单子还没到止盈就先被止损出局了。参数没有「越极端越好」,只有「与整个系统的出场逻辑配套才好」。这也是模块自治的另一面:参数属于系统,不属于孤立的指标。

技术指标有滞后性,回测结果不代表未来收益——把这两句免责声明刻在设计里,比写在文章结尾有用得多。

模块自治的纪律由此可以总结成三条:每个模块有独立的价值(不依赖其他模块也能用)、有独立的度量(自己的模拟盘、自己的胜率盈亏比)、有独立的生死(表现差就下架,不拖累系统)。做不到这三条,模块迟早会长成甩不掉的赘肉。

防御性编程:一行 None 炸掉整条链

讲工程哲学,最生动的教材永远是事故。一次典型的故障:K 线页面突然白屏,接口返回 500,而起因不过是一个再正常不过的查询请求——只是那只股票停牌了。

停牌意味着没有真实交易、没有价格波动、没有成交量。上游的前复权转换在处理停牌数据时返回了 None,而代码没有在第一时间拦截,一行 None 就这样炸掉了整条调用链:数据获取层对 None 调方法崩溃,业务编排层继续对 None 取下标,接口层把崩溃包装成 500 送到用户面前。

空值就像多米诺骨牌的第一张——越晚拦截,波及面越大。最理想的拦截点是数据的产出端,而非消费端。

修复分三层,恰好构成防御性编程的完整分层:

防御层 动作 原则
数据层 空值检查移到操作之前,先判空后使用 先检查,后使用
业务层 请求无数据时,自动查找该标的最后真实交易日,用那个日期重试 好的错误处理不只是「不崩溃」,而是「在能退让的地方,给用户一个合理的结果」
接口层 兜底也失败时,返回清晰的错误信息 每一层做自己该做的事,None 就不会穿透到用户面前

这个事故里还藏着一个更深的坑:兜底查询最初用「成交量大于 0」过滤停牌数据,结果返回的日期还是停牌日。查数据库才发现,行情数据源在停牌期间推送的成交量不是 0,而是一个约 1e-39 量级的浮点噪声——数学上确实大于 0。最终方案简单粗暴:阈值改成大于 1,因为真实交易的成交量至少以手计。

不要假设数据源的边界值。停牌的成交量不是 0,退市的代码不会消失,盘前数据的时间戳可能不在交易时段内。用业务语义定义阈值,而不是数学上的零。

防御性编程不是「到处加 if」,而是一套分层策略:数据层拦截异常,业务层降级兜底,接口层优雅响应。这和交易系统的风控分层是同构的——止损线、总回撤线、黑天鹅预案,本质上都是「让错误在最早、最便宜的地方被拦截」。

这类故障也沉淀出一张标准化的排查速查表,遇到「接口突然 500」按序执行:

步骤 操作 目的
1 确认服务状态(容器是否在运行) 区分「挂了」和「错了」
2 查看错误日志尾部 拿到第一现场
3 定位崩溃行号(TypeError / KeyError 的 traceback) 找到炸点
4 从崩溃点向上找第一个 return None 找到空值源头
5 直查数据库验证数据存在性 确认是数据问题还是代码问题
6 检查边界值(停牌成交量不为 0、退市代码不消失) 排除想当然

排查流程本身也值得一条哲学注解:修 bug 的产出不只是修复,还有流程。把一次性排障经验固化成速查表,下次故障的成本就降了一个量级——这是小系统长成大系统时最划算的投资。

参数演进案例:从单一金额到首仓加仓分离

系统的参数不是设计出来的,是演进出来的。一个代表性案例是买入金额的决策链改造。

故事的起点是一次代码审计:需求只是「让用户能分别配置首次买入金额和每次买入金额」,听上去是个半天的小功能。但顺着数据流去查「这两个字段到底在哪里被消费」时,发现一个反直觉的事实——必选池里的首次买入金额字段,全后端零消费点。它在数据库里安安静静地躺着,前端表单辛辛苦苦地写入,却没有任何一行代码在读它。

配置字段若无人消费,就是技术债的隐形形态。它装修好了,却从来没住进去人。

更深的问题在概念层:整条买入链路只有一个单一金额概念。但首次建仓(要打出底仓,金额大)和持仓后加仓(顺着趋势补一笔,金额小)在业务上差别很大——用同一个数,要么首次买太少,要么加仓买太多。

改造方案有两个关键设计。

其一,四级决策链 + 软回退。 金额查找从三级扩展到四级,优先级从具体到通用:

优先级 数据源 定位
① 最高 单标的策略覆盖 对这只票的专门安排,最具体
② 必选池 「明确要买」的标的,承诺最强
③ 监控池 「观察候选」的标的,后接入决策链的一级
④ 兜底 全局默认参数 只认每次金额,保底不保优

每一级都有「首次」和「每次」两个字段,取值遵循软回退:首次建仓优先取首次金额,没配就回退到每次金额,再没配才漏到下一级——绝不报错,绝不下零单。用户在某一级配了每次金额却没配首次金额,说明他对这只标的有金额意图,首次建仓就用他配的数,而不是粗暴跳过或报错。

让缺省安全落地,比强制用户填满所有字段更接近工程上的善意。好的决策链不是「要求完备」,而是「来什么都能用」。

其二,用一个辅助函数消除分支重复。 「取首次还是取每次」的取舍收敛成一个小函数,首次时先瞄一眼首次金额、没有就接着看每次金额;非首次直接跳到每次金额——没有重复的 if/else。顺带一提的是首/加仓的判定:下单函数的作用域里早就有现成的持仓代码集合,不在持仓中 = 首次,一次判定,零额外查询。

复用作用域内已有的信号,比新加一次查询更优雅。首/加仓的判定信号一直都在,只是没人想到用它。

这个案例浓缩了参数演进的三条规律:概念失真要先于实现修(单一金额是概念问题,不是代码问题);缺省要安全(软回退优于硬失败);分支要收敛(一个辅助函数好过四处 if/else)。

AI 时代独立开发者的位置

这套系统近年的迭代速度明显加快,一个不可忽视的变量是 AI。用 Claude Code 重构整个系统的强度可以达到一周消耗数亿 token——它不是 demo,是跑在真实市场里的工具:数据采集、缠论分析、回测引擎、信号生成、实盘对接,每个模块都由真实业务场景驱动。

对这个时代的独立开发者,我的体会可以收敛成三句话。

AI 放大认知,不替你思考。 AI 能写代码、调 bug、做架构决策,但有些事它做不了:判断一个信号逻辑在极端行情下会不会失效,要靠交易经验;决定风控参数怎么设才不会在正常波动中被止损出局、真出黑天鹅时又保得住本金,要靠对风险的敬畏;选择数据源、设计回测框架、定义策略边界,要靠领域知识。

强者用 AI,十倍百倍地变强。弱者用 AI,原形毕露。

AI 必须附着于真实的生产环境才有价值。 没有真实业务场景,AI 只是个高级玩具。量化交易恰好是最硬核的真实场景之一——代码写得对不对,市场会直接给答案;回测曲线不会陪你演戏,回撤数据不会因为你努力就变小。这也是为什么「先学完再动手」在 AI 时代失效了:在做的过程中学,错了市场惩罚你,对了市场奖励你。

落到人身上,能在 AI 时代活得好的是三种画像,恰好也是独立开发者的自我要求:

画像 含义 在量化开发中的对应
能找到真实生产环境的人 不是学完再练,是在做的过程中学 系统跑在真实市场里,错误有真实代价
把工作当创造的人 搭系统不是完成任务,是构建自己的工具体系 从数据到信号到风控到执行,每一环都在创造
不被旧经验困住的人 从「我觉得」到「数据说」,主动放下旧的自己 盘感让位于概率,拍脑袋让位于回测

旧经验可能是新围墙。 手动交易积累的「盘感」「直觉」,在系统化交易面前很多是不靠谱的:你以为的「感觉要涨」,系统会告诉你过去几百次类似形态中上涨概率只有 47%;你以为的「这次不一样」,回测数据会说每次你都这么说。不是经验没用,而是经验需要被验证,而不是被迷信。

在 AI 时代,经验在旧系统里是护城河,在新系统里很可能是围墙。

还有一个准确的比喻:用 AI 搭系统的感觉像玩经营游戏。你不是在「学编程」,你是在搭建一个有输入有输出的系统——数据源是原材料,信号引擎是加工车间,风控模块是质检流程,实盘交易是物流配送,回测框架是实验室。每个模块都可以优化、迭代、替换,系统就像一座在不断升级的工厂。这个上瘾不是刷短视频的多巴胺,是看到信号逻辑跑出合理曲线的成就感,是风控参数调优后最大回撤下降的踏实感。

AI 时代对「有能力」的重新定义:不是你说自己多厉害,是你能交付什么。

结语:一步一步来

把本章的哲学收拢成一句话:小系统长成大系统,靠的不是一次伟大的设计,而是无数次小的、方向一致的修正。

  • 整体架构定型后安心做单元功能,留下的合并进生产系统——这是纵向的生长,这个过程急不得,注定漫长:能保留下来的单元功能,才会成为整个系统的一部分;
  • 模块自治、独立可用、组合更强——这是横向的分工;
  • 防御分层、业务语义阈值——这是对意外的敬畏;
  • 软回退、缺省安全、分支收敛——这是对用户的善意;
  • AI 放大认知、经验接受验证——这是时代给独立开发者的杠杆。

这些哲学没有一条是先验设计出来的,全部是事后从实践里反推出来的——这本身就是最后一条、也是最重要的一条工程哲学。

方向明确,剩下的就一步一步来。

知识来源

  • 量化交易系统设计中单元功能和整体系统的关系(2024-12-17)
  • FreshQuant量化(一行 None 炸掉整个接口 量化系统的防御性编程)(2026-05-28)
  • FreshQuant量化(从单一金额到首仓加仓分离)(2026-07-06)
  • Fq股票量化框架正在一点点进步和优化 | 总结和前瞻(2025-12-16)
  • 李想说 AI 是照妖镜,我用它搭了套量化交易系统(2026-05-14)