
NautilusTrader量化交易终极指南-第6章第2节-领域模型-精度就是生命线一句话导读交易系统里没有差不多价格点一位、数量错一位钱就没了NautilusTrader 用定点数把精度焊死。本文导航为什么不能用 float 算钱Currency / Money钱的字典与实体Price / Quantity两个带精度的定点数price_precision / size_precision / make_qty 的正确性细节一个价格精度错一位的真实事故小结下节预告1. 浮点数的原罪先做个实验。打开 Python 敲三行0.10.20.30000000000000004这就是 IEEE 754 双精度浮点的老毛病——0.1 和 0.2 在二进制里是无限循环小数凑不齐。平时打印出来看着像 0.3可一进比较或者累加成千上万次误差就滚雪球。对回测研究来说这点误差可能无伤大雅但对拿真金白银去下单的实盘交易这就是定时炸弹。想一想你用 float 算买入金额累加个一千笔差出来一个价位成交了谁来赔我见过最离谱的案例也正是本节的事故部分浮点误差导致的还不是几毛钱的问题而是数量级错误。这点下面细说。总之结论就一条金融计算别用 float 当真相源。2. Currency 与 Money钱的字典和实体NautilusTrader 把这套东西建模得很讲究两张图看全定义被引用amount currency拥有拥有定价依据Currencycode precision iso4217Money一笔真实的钱Instrumentprice_precisionsize_precisionCurrency币种的字典Currency描述一个币种携带code如USD、EUR和precision小数位。一套系统里同一币种只有一份定义所以往往用 dict 去重fromnautilus_trader.modelimportCurrency usdCurrency.from_str(USD)print(usd.precision)# 2Currency是币种是什么它自己不带具体金额。Money带着币种的一笔钱真正算钱要用Money它把金额和币种绑在一起fromnautilus_trader.modelimportMoney costMoney(299.99,USD)print(cost.currency.code)# USDMoney的好处是金额永远跟币种绑定杜绝计量单位错乱。比如 USD 和 EUR 不可能被直接相加因为值不同。这让引擎在跨币种换算、结算时能自动做校验。3. Price 和 Quantity两个带精度的定点数这两个是定点数型的核心。所谓定点就是内部不靠二进制浮点存储而是按最小增量来精确表达。Price价格受price_precision约束。Quantity数量受size_precision约束还要求能被size_increment整除。举外汇对的例子EUR/USD 的price_precision5、size_precision0数量按手为单位整数。你构造价格fromnautilus_trader.modelimportPrice,Quantity pricePrice.from_str(1.08500)# 保留 5 位qtyQuantity.from_str(100000)# 最小变动量对齐一旦你违反精度约束比如给 5 位精度的价格填入 6 位小数引擎会直接抛错而不是悄悄截断。宁可报错不可静默失真——这条原则贯穿整个引擎。4. price_precision / size_precision / make_qty这三个东西的配合是精度体系落地的核心。先看它们怎么来fromnautilus_trader.modelimportInstrumentId,Symbol,Venue,CurrencyPair,Currency,Price,Quantity pairCurrencyPair(instrument_idInstrumentId.from_str(EUR/USD.SIM),raw_symbolSymbol(EUR/USD),base_currencyCurrency.from_str(EUR),quote_currencyCurrency.from_str(USD),price_precision5,# 价格 5 位小数size_precision0,# 数量整数手ts_event0,ts_init0,)引擎在构造合约时会根据刚刚构造出的 price_precision / size_precision 自动对齐并设置内部的最小变动量——你不用手动去设 price_increment / size_increment那是引擎自己算出来的。price_precision决定价格数值能精确到小数点后几位size_precision决定数量能精确到几位。make_qty让数量吐个合法的真正下单前必须把数量规整到能被size_increment整除。make_qty就是干这个的fromnautilus_trader.modelimportQuantity# size_precision0 的合约数量必须是整数qQuantity.make_qty(25.7)# 自动对齐到整数print(q)# 25如果传进来25是合法的但25.7一眼就不合规make_qty会把它对齐到引擎认定的最小变动单位。这条在你处理信号算出的小数数量时尤其重要——信号可能算出 1.733 手实际能成交的只有整数手你就得靠make_qty落地。示意图理清精度链路CurrencyPair 定义price_precision 5 位size_precision 0 位Price 定点 5 位小数Quantity 定点整数价格校验 符合最小跳动make_qty 对齐最小变动量下单价合法下单量合法入库成交一句话总结这条链Instrument 定义精度 → Price/Quantity 严格落地 → make_qty 兜底对齐 → 保证下单合法。一环断交易就失真。5. 一个价格精度错一位的真实事故这是我当年帮排查过的案例至今想起来都脊背发凉。背景某个回测策略是从数据文件里读价格下单的。数据文件里 EUR/USD 价格是1.08504 位小数而合约price_precision设的是5。有个同学图省事直接Price(1.0850)裸构造没走精度校验。问题出在哪在浮点加法上。系统拿1.0850当底层双精度做0.00005的撮合推进跑了几十万个 tick 之后累积的浮点尾差把价格推到了1.085050000000001这种带尾巴的数。打印出来看着没事但一旦拿去算下单金额price1.085000000000001# 脏浮点qty100000# 10 万单位notionalprice*qtyprint(notional)这一乘尾差放大十万倍几厘钱的误差瞬间变成几块、几十块的偏差。更狠的是还有个完全相反的例子有人把price_precision设小了导致下单金额整体缩水/放大一个数量级——原本该下 1 万的系统按错误精度解析成了 1 千或 10 万成交瞬间爆仓风险拉满。这类事故用 float 写出来的代码光靠肉眼 Unit-test 根本查不出来——因为它平时都是看起来对的。教训就一句话凡是参与下单价、下单量、成交金额计算的数值一律走 NautilusTrader 的Price / Quantity / Money并由 Instrument 的price_precision / size_precision背书别自己裸用 float。我已经把这条写进自己项目的project_rules里当红线了。6. 小结float 算钱误差会在高频率累加里滚雪球实盘会出事。Currency定义币种字典Money把金额和币种绑定。Price/Quantity是定点数分别受price_precision/size_precision约束。引擎靠 Instrument 精度自动推导最小变动量下单前用make_qty对齐数量。违反精度宁报错不静默截断“宁可报错不可失真”。真实事故提醒价格精度错一位下单金额可差十倍浮点尾差会乘数量放大。下节预告有了合约和精度接下来该上数据了。下一节进第 7 章数据模型我把引擎最常用的四类数据——QuoteTick、TradeTick、Bar、OrderBook——一次性讲透它们的字段、构造、适用场景对比一张表给你。如果精度这块点醒了你点赞、收藏、关注三连下节数据模型篇见。