NautilusTrader 值类型全解析:Price、Quantity、Money 的定点数设计与实战用法
【免费下载链接】nautilus_traderProduction-grade Rust-native trading engine with deterministic event-driven architecture项目地址: https://gitcode.com/GitHub_Trending/na/nautilus_trader
NautilusTrader 是采用确定性事件驱动架构的生产级交易引擎,其核心模型层通过Price、Quantity、Money三种专用值类型承载价格、数量与金额等交易概念。本文以 docs/concepts/value_types.md 为骨架,结合 crates/model/src/types 下的 Rust 源码与测试,系统讲解这三种类型的不变性设计、定点数存储原理、算术运算的类型规则、精度处理机制与典型实战模式,帮助你在回测与实盘策略中写出类型安全、结果可复现的交易代码。
三种值类型总览
| 类型 | 用途 | 是否允许负数 | 是否带货币 |
|---|---|---|---|
Quantity | 交易数量、订单数量、持仓数量 | 否 | 否 |
Price | 市场价格、买卖报价、价格档位 | 是 | 否 |
Money | 货币金额、盈亏(P&L)、账户余额 | 是 | 是 |
从 Rust 源码看,三者分别定义于 price.rs、quantity.rs 与 money.rs。模块文档明确给出了各自的领域约束:
Price可以取负值,适用于价差(spread)、基差交易或部分衍生品场景(见 price.rs 模块注释);Quantity强制非负,用于交易数量、订单量与持仓量(见 quantity.rs 模块注释);Money同时支持正负值(用于扣款、亏损等),并在算术运算中强制货币一致性(见 money.rs 模块注释)。
不可变性设计
所有值类型都是**不可变(immutable)**的:一旦构造完成便无法修改,所有运算都返回新实例,而不是原地改动。
from nautilus_trader.model import Quantity qty1 = Quantity(100, precision=0) qty2 = Quantity(50, precision=0) # 这创建了一个新的 Quantity;qty1 与 qty2 保持不变 result = qty1 + qty2 print(qty1) # 100 print(qty2) # 50 print(result) # 150这一设计带来三方面收益:
- 线程安全:不可变值可被多个线程安全共享,无需同步原语。这对 NautilusTrader 的事件驱动、多 Actor 架构尤为关键——同一价格对象可被策略、风控与执行组件并发引用;
- 可预测性:值永远不会意外改变,调试时无需追踪"谁改了这个价格";
- 可哈希性:不可变类型可作为字典键与集合元素。Rust 侧
Hash、Eq、Ord等 trait 的实现让这些值可以直接进入HashSet、BTreeSet等容器,fixed_scale.rs 中的测试即验证了跨精度下的哈希与排序一致性。
Rust 侧同样遵循该约定,三个模块的文档均声明"All arithmetic operations return new instances"(见 price.rs)。
算术运算与返回类型规则
值类型支持标准算术运算符(+、-、*、/、%、//)以及一元运算符(-、+、abs)。返回类型取决于运算符与操作数类型——这一设计是值类型的核心灵魂,必须理解清楚。
同类型二元运算:加减保持类型,乘除返回 Decimal
同类型值的加减法保持原类型,保留领域含义(价格加价格仍是价格):
| 运算 | 结果 |
|---|---|
Quantity + Quantity | Quantity |
Quantity - Quantity | Quantity |
Price + Price | Price |
Price - Price | Price |
Money + Money | Money |
Money - Money | Money |
from nautilus_trader.model import Price price1 = Price(100.50, precision=2) price2 = Price(0.25, precision=2) result = price1 + price2 # 返回 Price(100.75, precision=2) print(type(result)) # <class 'nautilus_trader.model.Price'>而同类型值之间的乘法、除法、整除与取模则返回Decimal:
| 运算 | 结果 |
|---|---|
Price * Price | Decimal |
Price / Price | Decimal |
Price // Price | Decimal |
Price % Price | Decimal |
Quantity与Money遵循同样的模式。
为什么乘除不返回原类型?因为结果的量纲(dimension)已经改变:价格乘价格得到"价格平方"而非价格;数量除以数量得到无量纲的比率而非数量。返回Decimal让量纲变化显式化,防止把结果误当作带原单位的数值继续参与领域运算。这一点在 price.rs 的算术行为表中也有完整对应(Price * Price的 Rust 侧乘法实现同样遵循量纲语义)。
一元运算
一元运算符在结果对原类型合法时保持值类型:
| 运算 | Price | Quantity | Money |
|---|---|---|---|
-x(取负) | Price | Decimal | Money |
+x(取正) | Price | Quantity | Money |
abs(x) | Price | Quantity | Money |
int(x) | int | int | int |
float(x) | float | float | float |
round(x) | Decimal | Decimal | Decimal |
特别地,Quantity.__neg__返回Decimal而非Quantity,因为Quantity是无符号类型,无法表示负值——负的数量在领域上没有意义。
from nautilus_trader.model import Currency, Money, Price, Quantity USD = Currency.from_str("USD") price = Price(100.50, precision=2) print(-price) # -100.50 print(type(-price)) # <class 'nautilus_trader.model.Price'> money = Money(-50.00, USD) print(abs(money)) # 50.00 USD print(type(abs(money))) # <class 'nautilus_trader.model.Money'> qty = Quantity(10, precision=0) print(+qty) # 10 print(type(+qty)) # <class 'nautilus_trader.model.Quantity'>混合类型运算:遵循 Python 数值塔
与int、float、Decimal等数值类型混合运算时,返回类型遵循 Python 的数值塔约定:运算向更一般的类型拓宽——float运算返回float,而int与Decimal运算返回Decimal以保留精度。
该规则适用于全部六种二元运算符(+、-、*、/、//、%),且两个方向(值 op 标量与标量 op 值)都成立:
| 左操作数 | 右操作数 | 结果类型 |
|---|---|---|
| 值类型 | int | Decimal |
| 值类型 | float | float |
| 值类型 | Decimal | Decimal |
int | 值类型 | Decimal |
float | 值类型 | float |
Decimal | 值类型 | Decimal |
from decimal import Decimal from nautilus_trader.model import Quantity qty = Quantity(100, precision=0) # Quantity + int -> Decimal result1 = qty + 50 print(type(result1)) # <class 'decimal.Decimal'> # Quantity + float -> float result2 = qty + 50.5 print(type(result2)) # <class 'float'> # Quantity + Decimal -> Decimal result3 = qty + Decimal("50") print(type(result3)) # <class 'decimal.Decimal'>Rust 侧的行为与此严格对应:price.rs 的算术表列出了Price + Decimal -> Decimal、Price + f64 -> f64等组合,Quantity与Money亦然,保证 Rust 与 Python 双端结果一致。
精度处理机制
每个值类型都携带一个精度(precision),表示小数位数:Price与Quantity显式存储precision字段,而Money使用其货币的精度。精度在构造时确定且不可变,不存在"未指定精度"的状态。
定点数表示:底层是整数,不是浮点
值类型内部以整数形式存储,按全局固定精度缩放(high-precision 模式下为 10^16,标准模式下为 10^9),而不是浮点数。精度字段只记录构造时使用的小数位数,控制显示格式化与序列化,但底层原始值始终使用全局标度。
对应的常量定义在 fixed.rs:标准模式(未启用high-precisionfeature)下FIXED_PRECISION = 9、FIXED_SCALAR = 10^9、底层用 64 位整数(PriceRaw = i64、QuantityRaw = u64、MoneyRaw = i64);启用high-precisionfeature 后FIXED_PRECISION = 16、FIXED_SCALAR = 10^16,底层切换到 128 位整数(i128/u128),并在 Arrow 中以FixedSizeBinary(16)表示(见 fixed.rs)。
from nautilus_trader.model import Price p1 = Price(1.23, precision=2) # 显示为 "1.23" p2 = Price(1.230, precision=3) # 显示为 "1.230" p1 == p2 # True:底层数值相同 str(p1) # "1.23" str(p2) # "1.230"精度控制显示,而非身份。两个十进制数值相同但精度不同的价格相等。precision字段决定字符串格式化与显示的小数位数,但相等性基于底层数值。这是文档强调的核心结论,也是 fixed_scale.rs 中大量跨精度比较测试(如#[case(1, 1, Ordering::Equal)]等)所验证的行为:比较与哈希会考虑标度差异而不做四舍五入。
行情序列化会携带精度元数据。当行情数据类型(quotes、trades、order book deltas)写入 Parquet 或 Arrow 格式时,精度会存入文件元数据,以便正确解码数值。同一文件内的所有行情值必须共享同一精度。
:::note 如果某个交易所更改了合约的 tick size(从而改变精度),变更前后写入的数据文件将具有不同的精度元数据,不应合并到同一个文件中。 :::
关于合约级精度如何约束合法价格与数量,参见 Instruments 指南的 Precision 章节:price_precision与size_precision规定了订单价格、触发价格、成交价与订单数量、成交数量的最大小数位数,且价格增量(tick size)的精度必须与price_precision匹配、数量增量的精度必须与size_precision匹配(见 instruments/index.md)。
值得一提的实现细节:为兼容 2025 年 12 月 16 日之前 V2 wrangler 写入的旧目录数据(其使用int(value * FIXED_SCALAR)引入了浮点误差),Arrow 解码路径会通过correct_raw_i64/correct_raw_i128等修正函数把原始值就近舍入到合法的标度倍数(见 fixed.rs 模块文档),这保证了旧数据的向后兼容。
算术精度:取操作数的最大精度
不同精度的值做算术运算时,结果采用操作数中的最大精度。
from nautilus_trader.model import Price price1 = Price(100.5, precision=1) # 1 位小数 price2 = Price(0.125, precision=3) # 3 位小数 result = price1 + price2 print(result) # 100.625 print(result.precision) # 3(取 1 和 3 的最大值)这也与 Rust 侧实现一致:price.rs 的算术表注明Price + Price的精度取两个操作数的最大值。
类型专属约束
Quantity:非负约束
Quantity表示非负数量。尝试构造负数量,或用较大的数量减去较小的数量(结果为负)都会报错:
from nautilus_trader.model import Quantity # 触发 ValueError:Quantity 不能为负 qty = Quantity(-100, precision=0) # 同样触发 ValueError qty1 = Quantity(50, precision=0) qty2 = Quantity(100, precision=0) result = qty1 - qty2 # 结果为 -50,非法Rust 侧对应实现位于 quantity.rs,其中Quantity - Quantity在结果可能为负时会触发 panic(见 quantity.rs 的算术行为表)。
Money:货币一致性约束
Money值携带货币。Money之间的加减运算要求货币匹配:
from nautilus_trader.model import Currency, Money USD = Currency.from_str("USD") EUR = Currency.from_str("EUR") usd_amount = Money(100.00, USD) eur_amount = Money(50.00, EUR) # 合法——同货币 result = usd_amount + Money(25.00, USD) # 触发 ValueError——货币不匹配 result = usd_amount + eur_amount从 money.rs 的文档可知,Rust 侧货币不匹配的加减会直接 panic,且Money的排序行为是:Rust 中先按货币代码字典序、再按标度调整后的金额排序(不做跨币种换算);Python 侧对不同货币代码的比较会直接拒绝。这一点在跨币种账户与组合管理的排序场景中需要特别注意。
实战常用模式
累加值:不可变类型的标准写法
由于值类型不可变,累加通过重新赋值实现:
from nautilus_trader.model import Currency, Money USD = Currency.from_str("USD") total = Money(0.00, USD) amounts = [Money(100.00, USD), Money(50.00, USD), Money(25.00, USD)] for amount in amounts: total = total + amount # 重新绑定到新的 Money 实例 print(total) # 175.00 USD转换为其他类型
值类型提供多种转换方法:
from nautilus_trader.model import Price price = Price(123.456, precision=3) # 转换为 Decimal(保留精度) decimal_value = price.as_decimal() # 转换为 float float_value = price.as_double() # 转换为字符串 string_value = str(price) # "123.456"这些方法在 Rust 侧均有对应实现:as_decimal()定义于 price.rs,底层通过scaled_raw_to_decimal将固定标度原始值还原为Decimal;from_raw/from_raw_checked(见 price.rs)则用于从原始整数构造值——注意 fixed.rs 明确要求from_raw的原始值必须是该精度下标度因子的合法倍数(典型来源是既有值的.raw字段或 Nautilus 产生的 Arrow 数据),否则 debug 构建下会 panic,release 构建下可能得到错误值。
从字符串构造
支持从字符串表示解析值类型:
from nautilus_trader.model import Money, Price, Quantity qty = Quantity.from_str("100.5") price = Price.from_str("99.95") money = Money.from_str("1000.00 USD")Rust 侧通过FromStrtrait 实现(impl FromStr for Price位于 price.rs),Python 绑定与之一致,保证双端解析行为统一。
小结
NautilusTrader 的Price、Quantity、Money值类型是引擎"确定性计算"承诺的基石:不可变语义带来线程安全与可预测性;定点数整数存储配合high-precisionfeature(9 位/16 位精度切换)消除了浮点误差,让跨平台回测结果严格可复现;量纲感知的算术类型规则从类型系统层面杜绝了"价格平方当价格用"这类隐患;精度元数据随行情数据写入 Parquet/Arrow,保障了数据管道往返无损。
对策略开发者而言,掌握以下要点即可写出正确高效的代码:加减同类型值返回原类型,乘除返回Decimal;混用标量时让Decimal/int走Decimal、float走float;精度不同取最大者;Quantity不可为负、Money运算需同币种;数据落盘时保证同文件内精度一致。这些规则在 Rust 与 Python 两端行为完全对齐,是 NautilusTrader 跨语言一致性的典型体现。
【免费下载链接】nautilus_traderProduction-grade Rust-native trading engine with deterministic event-driven architecture项目地址: https://gitcode.com/GitHub_Trending/na/nautilus_trader
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考