30 秒看懂
在 CMU 的一场演讲中,Jane Street 的 Ron Minsky 解释了公司作为做市商和套利者的角色,并将技术选择与正确性、敏捷性和性能联系起来。他认为简洁代码和静态检查有助于人们理解交易系统并安全地修改它们。 他用交易指令示例演示代数数据类型,并讨论 OCaml 当时在界面、并发和库方面的局限。在问答环节,他解释公司如何逐步采用这门语言,并区分基于经验的判断与受控证据。
- 1做市与库存做市商通过买卖报价促成交易,并争取赚取价差。但如果库存价格朝不利方向变化,仍会面临风险。
- 2速度提高错误成本自动化系统能快速重复错误决策。循环越快,人发现问题并介入的时间可能越少。
- 3阅读是正确性的一部分关键交易代码逐行接受审查。Minsky 强调,代码既是给机器的指令,也是人与人之间的沟通。
- 4类型支持修改带标签的指令类型和穷尽匹配,让编译器可以指出漏掉新增加的取消并替换分支的代码。
- 5生态缺口在演讲时,Minsky 认为用户界面、并发工具、库和大规模编程方面存在不足。
- 6逐步采用最初的研究工作和后续实验随时间扩大;公司并非一步切换所有系统。
要点建议
- 做市商通过报出买卖价格提供流动性,同时管理库存风险。
- 交易越快,正确性就越重要,因为错误可能在人介入前反复发生。
- 可读代码、更少的样板代码和静态检查可以让审查与修改更安全。
- 代数数据类型和穷尽模式匹配能在系统演进时指出需要修改的位置。
- 语言选择还取决于运营需求、生态局限、招聘和团队现有代码。
按章节阅读整理后的全文
交易公司选择 OCaml
Minsky 介绍 Jane Street 从主流语言转向 OCaml 的历程,并从业务和技术需求出发解释这一决定。
Jane Street 刚成立时使用金融软件中的主流工具:电子表格背后的 Visual Basic,以及 C、Java 和 C++。经过数年,我们将主要开发语言转为 OCaml。我们是一家交易公司,业务并不天然属于静态类型函数式语言最早流行的学术领域。
我们使用 OCaml,是因为认为它适合解决我们面临的问题,尽管这些问题并不是设计该语言时的初衷。为了说明决定,我会先介绍业务,再讲业务对技术的要求,最后讨论 OCaml 的适用之处与不足。
投资者、投机者和做市商
他区分不同的交易动机,解释做市商如何报价、赚取价差并管理库存风险。
交易公司通过买卖证券赚钱,但这个定义并不能充分说明它的工作。区分几种理想化的交易动机有助于理解。真实参与者未必恰好属于其中某一类,这些类别也并不穷尽。
投资者承担资本风险以换取预期回报。普通投资者不一定声称知道哪些证券定价有误;购买多元化指数基金就是获得广泛市场敞口而不试图挑选赢家的例子。
投机者则试图利用自己对证券定价错误的判断。如果证券看起来被低估,投机者就会买入,期待市场最终认识其价值。仅仅判断正确还不够:如果资金要用于其他机会,头寸需要在有用的时间内盈利。
假设你认为 IBM 的价值高于市场判断。无限期持有或许最终能通过股息获得价值,但同时也会占用资金,让你错过其他机会。投机者可以是个人,也可以是对冲基金等专业机构。
做市商是一种以促成交易为业的专业参与者。做市商同时报出买价和较高的卖价,力求反复成交并赚取价差。博彩经营者把类似差额称为 vig,杂货店则称其为加价。

更窄的价差能提供更有吸引力的价格,带来更多交易量。和杂货商一样,做市商主要并非试图预测几个月后的价格;业务依赖大量交易、库存管理和成本控制。
这并不能消除价格风险。如果杂货商买入一批番茄后价格下跌,竞争者可能以更低价格出售,杂货商就会亏损。同样,做市商也会尽量避免积累价格朝不利方向变化的头寸。
套利与 Jane Street 的业务
一个货币兑换例子说明套利及其与做市的关系。他介绍公司当时的自营结构和规模。
套利是另一种专业活动。假设你在一个市场将美元换成英镑,在另一个市场把英镑换成日元,再在第三个市场换回美元。如果完成一圈后美元比起初更多,这些价格就提供了套利机会。
这类机会通常会消失,因为人们会寻找并执行套利。他们的交易推动不一致的价格相互靠近,使市场更有效率。在不同工具的价格相互制约时,做市和套利关系密切。
例如,如果两个货币市场流动性充足,第三个市场交易不频繁,流动市场的价格可以帮助推算不活跃市场的价格。这个估值应影响做市报价,否则信息更充分的对手方可能利用过时价格交易。
Jane Street 从事做市和套利,而非长期投资或依靠预测多年后价格的那种投机。我们可能对未来几秒、几分钟或几小时的情况有判断,但这与预测多年后的走势有很大不同。
在演讲时,我们将自己描述为自营交易公司,没有明确的客户或外部投资者。提供流动性仍然间接服务投资者:当指数基金代表退休储蓄者交易时,更紧密的市场可以改善成交,即使双方都不把对方视作客户。
公司的营运资本来自管理公司的人。这种结构让我们能从第一性原理考虑技术选择,不必由外部客户规定技术。当时我们在几个办公室约有 190 名员工,其中约 35 人花大量时间编写 OCaml。
OCaml 并不局限于小型研究角落:它是系统管理、监控、交易和研究系统的首选语言。我们也使用其他工具,但它的使用范围广泛,因此语言与运营要求是否匹配很重要。
正确性、敏捷性和性能
交易软件必须避免快速重复错误,适应不断变化的市场并高效处理数据。运营复杂性使人的时间成为稀缺资源。
首要要求是正确性。没有什么比在紧密循环中重复错误交易决策更快地烧掉资金。我们的系统直接与市场交互,因此一直担心出错,并投入大量精力预防。
为了说明当时的风险,美国国内股票交易中有 1% 至 2% 的交易一方是 Jane Street,每天有数亿股和数十亿美元名义价值流经系统。这些并非同时持有的全部头寸,但小错误重复许多次仍可能造成灾难。
速度对有效执行很重要,但也会让错误传播得更快。循环越紧,人越难在损失累积之前发现并介入。执行越快,软件质量就越关键。
敏捷性是指随着环境变化快速修改代码的能力。员工的新想法需要软件变更;新规则、证券、交易场所,以及交易所行为的变化也一样。系统周围的市场始终在变化。
市场演变说明了这一问题。Minsky 描述了早年 Nasdaq 手工执行和电话交易的时代,以及随后电子交易场所、路由和新法规的出现。这是他在演讲时对市场历史的描述。
电子市场、新交易场所、暗池和 ETF 等工具改变了系统需要理解的内容。期权和国际股票市场也有类似变化。随着系统变得更加电子化,变化可能来得更快,因此高效更新代码至关重要。
性能是另一项要求。大量证券的行情数据高速到来,我们既要快速响应,也要存储数据用于后续研究、建模和模拟。Minsky 在当时的例子中提到约一毫秒的响应时间和每天约一太字节的原始数据。
按照最大型科学或互联网系统的标准,每天一太字节并不巨大。但硬件只是成本的一部分:软件效率越低,需要运营的机器和基础设施人员就越多。人的注意力是稀缺的,因此单纯增加硬件可能占用本可做其他重要工作的人。
可读代码与类型系统
Minsky 解释公司的代码审查文化,认为简洁和静态类型有助于人们推理程序。样板代码使审查和修改更加困难。
我们保证正确性的方式从阅读代码开始。在我加入之前,公司一些负责人承诺在最关键的交易系统投入生产之前逐行阅读代码。他们担心软件故障,而作出技术决策的人也必须理解这些代码。
代码不仅是让机器做事的方式,也是向其他人表达系统意图的方式。这一观点影响了我们的选择。计划从电子表格支持的 Visual Basic 转向面向对象系统的过程中出现问题,部分原因是生成的代码难以阅读。
提议的代码很冗长,读者发现多层动态分派难以追踪:看到一次调用时,究竟会运行哪个实现?我记得很难清楚地回答。对我们来说,OCaml 有两个有助于阅读的特点:简洁和类型。
在保持清晰的前提下,简洁表达可以让代码更易审查。把每个函数都重命名为两个字符并无帮助。有价值的简洁来自将常见模式表达一次,例如使用高阶函数和函子;经过谨慎约束的宏也可能有帮助。
样板代码经常重复出现,是因为语言难以直接表达反复使用的模式。它的信息密度低,反而出人意料地难以审查:读者会略过看似熟悉的内容,尽管细微差别仍必须正确。
阅读代码时,人们会构建一个非形式化论证,判断它是否按预期工作。机械检查器能确认的任何事情都可以减轻部分负担。OCaml 有很多有用功能,但我认为参数多态和代数数据类型提供了很大一部分实际价值。
交易协议中的代数数据类型
订单、取消以及取消并替换说明了和类型与穷尽匹配。模型变化时,编译器警告可以指出需要更新的位置。
代数数据类型结合了积类型和和类型。积类型把多个值组合在一起,类似记录或 C 结构体;和类型表示不同选择,即这种值或那种值。在普通应用代码中,能直接表示不同选择尤其有用。
模式匹配提供了分析这些选择的便利方式。编译器可以对缺失、重复或不可能的分支发出警告。未处理的分支很重要,因为该分支的值到来时可能导致运行时异常。
考虑一个简化的交易所协议。订单有标识符、价格和数量;取消指令通过标识符引用已有订单。我们可以定义一个带标签的指令类型,其值是订单或取消指令,然后把两者的列表作为一个流处理。

过滤函数可以接收指令列表和订单 ID。它匹配每个项目并比较相应字段:订单比较自己的 ID,取消指令比较所引用的 ID。这是一个小例子,却展示了类型和分支分析如何协作。
假设我们增加取消并替换指令,即原子地移除订单并以新价格和数量替换的请求。将这个选项加入指令类型后,编译器可以警告已有匹配没有处理它。如果移除一个选项,编译器也能警告不可能的分支。

随着类型演进,这些检查有助于在整个代码库中维护不变量。它们还通过指出需要关注的代码来支持重构。但并非每种写法都自动受益:兜底分支会消除警告,新增选项时可能使代码变得脆弱。
人们容易把类型系统视为独立的一层,认为它有时拒绝合理程序,有时捕获错误。要用好 ML,需要把类型视为设计工具,主动编码希望编译器帮助检查的性质。
性能与生态权衡
他讨论敏捷性和正确性的关系,并将性能可预测性与 SQL 优化器进行比较。他也介绍了 OCaml 工具和库当时的缺口。
这些性质同样支持敏捷性。需要修改时,关键问题是我们多快能确信修改后的系统仍然正确。静态检查有助于找到受影响代码,减少样板代码则让同一修改不必在许多地方手动重复。
在性能方面,ML 处于设计空间中一个有用的位置。它足够有表达力,能够支持清晰、较为声明式的风格,同时支持相对直接的编译策略,生成快速代码。更多表达力或更多控制各自都可能带来权衡。
SQL 展示了另一种权衡。它能在很高层次表达某些任务,但由优化器决定执行方式。查询稍作修改或表大小发生变化,就可能导致执行计划改变,性能大幅波动;Minsky 说这种不可预测性给公司带来过实际问题。
OCaml 的代码生成和垃圾回收器对 Jane Street 很有效,包括针对低延迟软实时行为的调优。性能也存在弱点,但 Minsky 的主要观点是:ML 在表达力和工程师可以推理的性能之间提供了有效折中。
演讲时的生态系统也有实际弱点。构建用户界面很痛苦:虽然存在封装,Minsky 认为适合工业界面的优秀 OCaml 原生库很少。公司一些成功的界面使用 curses 风格的文本显示。
并发是另一个问题。他描述的 OCaml 实现可以使用线程与底层库交互,但无法在单个进程中让 OCaml 代码真正并行运行在多个核心上。通过消息通信的独立进程是可行的,但有效使用这种方式的高层工具有限。
小型语言社区可以拥有优秀人才,但库的数量仍少于 Java 或 Perl 社区。Minsky 还指出包分发、命名空间和大规模编程支持方面的缺口。Jane Street 当时已有约一百万行 OCaml,因此这些弱点是实际问题而非纯理论问题。
他认为最大的改进机会在核心语言之外。库、界面和实用工具并不总是学术资助所奖励的方向。Jane Street 的暑期项目资助学生与教师合作,让函数式语言更适用于真实应用。
他说另一个好处是招聘。吸引对 OCaml 感兴趣的优秀程序员是最初决定的一部分,而不只是事后的惊喜。演讲时公司仍在寻找这样的人。
关于实现与大型系统的问答
听众询问 Standard ML、编译策略和团队组织。Minsky 解释共享代码仓库及语言比较的局限。
在问答中,Minsky 区分 OCaml 与仅仅为 ML 添加面向对象扩展的语言。它有对象系统,但他说 Jane Street 不使用它。OCaml 和 Standard ML 的历史与实现不同,但相同之处多于不同之处。
被问到为何选择 OCaml 而不是 Standard ML 时,他讨论了实现上的权衡。MLton 的全程序策略可以降低某些抽象的成本,而 OCaml 原生编译器策略相对简单,并提供有用的 Unix 和 C 集成。他强调历史因素也很重要:他在 Cornell 的同事 Mark Hayden 使用 OCaml,Minsky 因而开始在 Jane Street 的项目中使用它。
他澄清,OCaml 原生编译器会进行一些优化,但并非名字可能暗示的激进优化器。尽管方法简单,它生成的代码性能很好。全程序编译器可以消除抽象成本,但工程师可能需要推理具体优化何时发生。
一位提问者问函数式编程是否改变了团队组织。Minsky 说,在公司当时的规模下,团队把大部分代码保存在一个共享的 Mercurial 仓库中。共同快照、构建和测试让团队更容易一起验证系统,并跨多个组件修改。
能够看到并修改整个系统的代码,赋予团队灵活性。修改不必止步于独立发布、无法访问其实现的接口。不过,他认为随着系统和社区成长,更强的大规模编程工具仍然有价值。
另一位提问者问,早期系统中的重复代码究竟是设计失败,还是与语言有关。Minsky 提醒,语言比较很难证明:不同的人在不同阶段编写系统,团队也在各次实现之间有所学习。他的判断来自经验,而非受控比较。
尽管如此,他认为在函数式代码中更容易抽取共同功能。高阶函数使小型抽象相对轻量。当抽象需要更多工作时,程序员更可能复制模板并稍作修改,让同一种模式散布在代码库中。
Minsky 区分了与面向对象编程相关的几个概念。他重视抽象和接口,认为子类型在某些情况下有用,但默认更喜欢参数多态。例如,列表 map 可以精确表明它将一种类型的值转换成另一种类型的值。
他更批评将继承和动态分派作为默认方式。在他看来,它们可能让推理究竟运行什么代码更加困难,也可能要求理解被继承的实现。他指出,面向对象设计文献本身也经常建议优先使用组合而非继承。
他并不声称 OCaml 可以完美表达每种抽象。语言和类型系统仍有局限,他的比较也是无法通过实验完全证明的判断。但他认为轻量抽象减少了提取和复用简单模式所需的工作。
采用、招聘与替代方案
Minsky 讨论招聘、并行和逐步采用,最后对 F# 和切换成本作出带限定的历史性评价。
被问到使用 OCaml 是否让自己更富有时,Minsky 先拿工作保障开玩笑,然后给出有保留的肯定回答。招聘优秀程序员更容易,他也认为更好的技术帮助公司更快行动并赚更多钱。不过他承认,这些因素很难分开。
关于物理并行,他说公司已经在集群和交易中使用多个处理器。有些工作负载天然适合并行,因此复制简单单元很有效。更好的基础设施会帮助社区和 Jane Street,但他所描述的限制并不意味着无法进行并行计算。
关于大规模编程,Minsky 希望有更好的打包、命名空间和开发工具,尤其是更清晰的错误信息。由于 Jane Street 的许多软件是内部维护的定制系统,缺少外部集成对公司而言可能没有其他地方那么痛苦。
采用过程是逐步进行的,并不是开会决定重写公司系统。Minsky 最初构建了自己的研究环境,包括一个小型集群,而公司当时大体使用 Windows。决定留下后,他招聘小团队,用 OCaml 开展研究和交易策略工作。
现有电子表格系统最初务实可行,但为每种变化复制并修改系统不是可持续的长期方法。团队考虑下一代系统时,OCaml 招聘出人意料地成功:邮件列表上的一次招聘请求带来了优秀候选人,并促成三次聘用。
Minsky 没有一次切换所有系统,而是在三个月内写出一个系统的初版。团队试用它,与现有系统比较、阅读代码,然后逐步扩大使用。他说,他们预期向优秀程序员教授这门语言,而不是要求未来每位员工都已掌握。
被问及 F# 时,Minsky 赞赏它让函数式编程触达更广泛受众的潜力,但说明了 Jane Street 不计划切换的原因。现有百万行 OCaml 代码使用的功能移植成本高昂,他也担心 F# 对公司符号工作负载的分配行为。
他还对与 .NET 库互操作时的 null 值表示担忧,同时明确承认自己对 F# 的某些细节并不确定。他尚未充分研究,无法确定其对象系统能否在自己的场景中取代 OCaml 函子。这些评论反映他当时的判断,而不是对语言现状的比较。
最后,Minsky 说,团队对用于自身系统的 OCaml 和 Unix 更熟悉。他举出 Windows 上精确计时作为高性能交易应用当时关注的历史问题。除了语言功能,这一选择也反映了团队积累的代码、环境和需求。
同频道的其他文章
1:48:29EN → 简体中文
1:36:37EN → 简体中文
1:05:45EN → 简体中文
1:25:00EN → 简体中文
1:16:29EN → 简体中文
27:39EN → 简体中文