Fintech Engineering Handbook
AI 深度解读
Fintech Engineering Handbook:构建金钱系统的工程模式深度解读
背景
在金融科技(Fintech)领域,软件工程不仅仅是代码的堆砌,更是对信任、精确性和一致性的极致追求。《Fintech Engineering Handbook》(金融科技工程手册)是一份旨在描述在“金钱”作为系统核心要素时,软件工程中最重要的设计模式的资源文档。
该文档由 Hacker News 社区分享,其定位是一份“活文档”,欢迎持续贡献。它的目标受众非常广泛:
- 新入行者:帮助其快速熟悉金融科技领域的业务逻辑及构建可信金钱系统的关键模式。
- 现有从业者:作为解决特定问题时的参考手册,以及团队内部统一技术词汇的工具。
- 外部人员:理解构建金融系统与构建普通软件系统的本质区别及其背后的原因。
这份手册的核心价值在于它不仅仅罗列技术栈,而是从底层原则出发,阐述了如何确保金钱数据的完整性、准确性和不可篡改性。
核心内容
三大核心原则
所有后续的工程实践均围绕以下三个不可妥协的原则展开:
- 无虚构数据(No invented data): 金钱不能凭空产生。系统绝不能容忍数据重复或任意余额更新。这一原则通过幂等性(Idempotency)、去重(Deduplication)和对账(Reconciliation)机制来强制执行。
- 无数据丢失(No lost data): 所有涉及金钱的操作都必须被追踪并持久化。这一原则通过全精度计算、至少一次投递(At-least-once delivery)、事件溯源(Event Sourcing)、审计轨迹(Audit trails)和数据不可变性(Immutability)来保护。
- 无信任(No trust): 不信任外部提供商、内部组件乃至外部世界。这一原则通过验证 Webhook、跨源数据交叉检查以及在假设破裂时大声报错(Fail loudly)来维护。
金钱的表示(Representing Money)
在移动或记录金钱之前,首先必须决定如何表示它。这涉及货币值的建模、存储、计算和转换。错误的表示会导致上层所有逻辑继承错误。
精度处理(Precision Handling)
金钱表示是金融系统中最基本的决策之一,主要有四种方式:
- 浮点数(Floating-point):
使用内置的
float或double类型。虽然速度最快且内存效率最高,无需额外库,但它会产生不可预测的精度损失,几乎永远不是一个好主意。 - 任意精度(Arbitrary precision):
如 Java 的
BigDecimal。允许精确控制计算精度,代码可预测,开发者可以决定舍入发生的位置和方式。适合中间工作,如外汇(FX)或定价数学计算,其中涉及多个操作的链式调用。 - 最小单位精度(Minor-units precision):
对于大多数法定货币,保持与中央银行系统相同的固定精度是可行的。位数由 ISO 4217 标准描述(注意:并非总是 2 位小数)。实践中,这意味着以最小单位存储为整数(例如 €12.34 存储为
1234)。- 加密货币的特殊性:同样使用整数-最小单位思想(如 BTC 的 satoshis,ETH 的 wei),但有两个变体:精度因资产而异(由代币定义,如 ERC-20 的
decimals,通常为 18 位),且 resulting magnitudes 会溢出 64 位整数,因此需要任意宽度整数(BigInt)来存储。
- 加密货币的特殊性:同样使用整数-最小单位思想(如 BTC 的 satoshis,ETH 的 wei),但有两个变体:精度因资产而异(由代币定义,如 ERC-20 的
- 有理数(Rational numbers): 当完全不可接受精度损失时使用。这是最强大的方法,但也有缺点:速度较慢;转换为其他格式时可能丢失精度;通常需要自定义数据类型或库。
选择建议:
- 严禁使用浮点数。
- 存储与计算可以是分离的。例如,使用整数存储金额,但在中间计算时使用
BigDecimal。 - 序列化注意:JSON 中的裸数字通常被解析为 IEEE-754 双精度浮点数。因此,在序列化金钱时,要么作为字符串(
"12.34"),要么作为最小单位的整数发送,以避免在边缘层重新引入浮点问题。
舍入策略(Rounding Strategies)
舍入是不可避免的,必须显式处理:
- 业务决策:不同的舍入策略有不同的影响。有时需要保守(如向下舍入以确保不超支),有时关注统计效应(如四舍六入五成双)。谁获得小数部分可能涉及法律或税务后果。
- 尽量减少舍入:保持全精度的时间越长,在正确上下文中做出正确决策的选项就越多。舍入通常应发生在边界上(如持久化前或展示给用户前)。
- 舍入破坏总和:如果数字被拆分并应用舍入,各部分之和可能不再等于原数字。这需要显式处理,例如设立一个专门的“舍入账户”。
货币处理(Currency Handling)
金钱不能仅用数字表示,必须与货币配对:
- 打包金额与货币:使用
Money新类型(Newtype,如结构体、类、记录等)以最小化错误机会。 - 禁止跨货币算术:系统应禁止直接相加不同货币的金额。转换必须显式进行,并使用严格控制的汇率。
- 使用受控货币集:通过自定义配置、数据库或专用服务管理货币。绝不在系统边界外接受任意货币代码,必须在边界处验证。
- 代码仅标识法币:货币代码仅对法币唯一且适合作为标识符。对于加密货币,需使用更复杂的方法,如
(网络, 合约地址)。 - 货币携带元数据:符号、精度、名称等通常用于显示,而非业务逻辑。
- 挂钩不等于底层:挂钩、桥接和包装的加密货币不等于其底层资产。
外汇汇率(FX Rates)
外汇汇率允许在不同货币间转换金钱:
- 汇率具有方向性:EUR/USD 汇率不等于反转的 USD/EUR 汇率。在交易所,买入和卖出是两个不同价格的不同订单(买卖价差),因此两个方向不能简单反转。
- 汇率时间至关重要:
- 当前时间汇率:用于计算当前持仓或假设交易立即发生的价值。
- 价值日期汇率:用于计算价值变化或税额。
- 两种关键汇率:
- 交易汇率:实际发生转换时的汇率。不直接存储,而是由原始金额和结果金额推导得出。
- 参考汇率(中间市场或央行汇率):用于估值和等价性(当前持仓价值或价值日期的税基),而非任何人实际交易的价格。
- 没有标准汇率:汇率来自市场,因场所或计算方法而异。最接近标准的是央行汇率,但即使如此,其他来源也可能同样有效。
关键要点
- 严禁浮点数:在金融系统中,浮点数(float/double)会导致不可逆的精度丢失,应彻底避免。
- 整数化存储:法币通常以最小单位(如分)的整数形式存储;加密货币需考虑溢出问题,使用 BigInt。
- 序列化陷阱:JSON 数字解析会引入浮点误差,金额序列化必须使用字符串或整数。
- 显式舍入:舍入是业务逻辑的一部分,必须在边界处显式处理,并解决舍入导致的总和偏差问题(如设立舍入账户)。
- 类型安全:使用强类型(如
Money对象)封装金额和货币,禁止不同货币间的直接算术运算。 - 汇率方向性:买入价和卖出价不同,汇率转换必须考虑方向性和时间点(当前时间 vs 价值日期)。
- 零信任架构:不信任任何外部输入,所有数据在边界处必须经过验证,内部组件间需通过幂等性和对账机制保证一致性。
- 审计与溯源:所有金钱变动必须通过事件溯源和不可变日志进行追踪,确保数据可审计
查看原文 →w.pitula.me
