NautilusTrader核心组件:Cache 状态中枢
一句话导读:发动机和风控再牛,也得有人记住"现在账上还有多少钱、手里还有多少单",Cache 就是那个什么事都记、随叫随查的"状态仓库"。
本文导航
- Cache 到底解决什么问题
- 缓存四件套:Instrument、Account、Order、Position
- 索引读取:cache 的查找方法怎么用
- 不在 Cache 里读状态的坑
- 可选的数据库持久化
- Cache 在系统里的位置
- 小结
- 下节预告
上一节讲了 MessageBus——消息在路上跑,消息过去就过去了,谁记得话里说了啥?没人记,除非有个"记事本"。
我怕有人踩这个坑:写策略的时候,想拿"当前持仓",直接去翻 Portfolio 或交易所接口,结果要么数据太慢要么压根没有。后来我才明白,引擎里默认的记忆中枢是Cache,叫"状态中枢"一点不为过。
一句话定义:Cache 是 NautilusTrader 的内存状态中枢,负责缓存和索引所有关键交易对象(Instrument/Account/Order/Position),并提供高速读取入口。
它不是给磁盘碰一下就走的临时桶,而是一个被精心索引、结构化组织的"运行时数据库"。
Cache 到底解决什么问题
Event-driven 引擎里,消息像流水一样哗哗过。每个组件都想知道:“现在订单什么状态?”"余额剩多少?"普遍做法是随手记在 Strategy 自己的成员变量里。
问题来了:
- 状态分散。每个组件各记一份,没有单一事实来源(single source of truth),对不上账就出鬼。
- 重复工作。Cache、Portfolio、RiskEngine 各自维护一份订单信息,数据可复用性差。
- 时序混乱。回调里拿到的订单对象是"当时快照",不是"当下真实状态"。
Cache 的野心很直白:让所有组件都去同一个"记忆库"查证,而不是各记各的。谁想知道订单最新状态,问 Cache;谁想知道现在持仓,问 Cache。它是状态读取的统一入口。
所有状态变化最终都沉淀进 Cache,所有查询都从 Cache 走。箭头方向画出来,你就看出它是整个系统的"公共记忆"。
缓存四件套
Cache 主要缓存四类对象,这是核心中的核心。我用表给你理清:
| 对象 | 含义 | 典型读取方法 |
|---|---|---|
| Instrument | 交易标的的静态/动态信息(合约规格、面值、最小变动价位等) | cache.instrument() |
| Account | 账户,含余额、货币、状态 | cache.account() |
| Order | 订单,当前生命周期状态(live/canceled/filled…) | cache.order() |
| Position | 仓位,方向和数量 | cache.position() |
加上行情类对象(如 Quote、Trade),Cache 几乎把交易引擎要用的一切确定性状态都揽过来了。
Instrument —— “这个合约多大多小?”
Instrument 描述一个可交易标的,比如 BTCUSDT-PERP 的合约规格。写策略时经常要算名义价值、判断戳单粒度,这些数据来自 Instrument。查一次,后面反复用。
Account —— “账上多少钱?”
账户余额、币种、可用资金。风控要校验余额,Portfolio 要算保证金,都从这里拿账户视图。
Order —— “这单现在啥状态?”
订单从提交到成交的全生命周期状态。Cache 里存的是订单的最新权威快照。
Position —— “手里还拿着多少?”
净仓位:方向、数量、均价等。这是策略判断要不要平仓、加减仓的依据。
索引读取cache-的查找方法怎么用
Cache 不只是"存",它做的是索引读取——给一个 ID,瞬间捞出来。我只点几个高频方法,都是实战里天天用的:
查标的最短路径
# 按 instrument_id 拿标的instrument=cache.instrument(instrument_id)查报价/行情
# 拿某个标的最近一笔 quotequote=cache.quote(instrument_id)# 拿最近 tick(含交易)tick=cache.tick(instrument_id)# 拿最近 tradetrade=cache.trade(instrument_id)注意cache.quote()和cache.tick()这类取的是"最近一笔",不是历史。真想查历史,得走历史数据引擎,不是 Cache 的活儿。
查订单/仓位
# 按 client_order_id 查单(策略自定义的订单 ID)order=cache.order(client_order_id)# 按 venue_order_id 查单(交易所分配的订单 ID)order=cache.order(venue_order_id)# 查某标的所有持仓positions=cache.positions(instrument_id)# 查某账户所有持仓positions=cache.positions(account_id)索引的意义
这些方法背后都是哈希索引,查询 O(1) 级别的快。我见过有人写循环在里面线性扫描,完全没必要——用现成的索引方法,别自己遍历,这是我用 Cache 最重要的心得。
不在-cache-里读状态的坑
老实说,我早期最大的坑就是"从事件里直接拿订单对象,存进自己的列表"。看起来没问题,但:
- 订单对象是不可变的,事件里的快照不会自己更新。
- 我在 Strategy 里存了 100 个订单快照,填单后根本不知道哪个是最新状态,乱。
后来全部改成"事件里只拿个 ID,实际状态每次cache.order(id)现查"。代码短了,逻辑还稳了。记住这个原则:Cache 是唯一可信的状态来源,事件只是告诉你"变了一件事",状态永远以 Cache 为准。
可选的数据库持久化
Cache 默认是纯内存,进程一退啥都不剩。生产场景(尤其实盘或长时间运行)可能希望状态可恢复、可审计,Nautilus 支持接入专用数据库(如 Redis,或可选的数据库后端)做 Cache backing,把状态持久化。
价值在:
- 故障恢复。进程崩溃重启,从持久化状态里恢复,不用从零手工补。
- 审计追踪。状态的历史演变有据可查。
- 跨进程共享。多进程并行,共享同一份 Cache。
代价同样诚实:引入外部依赖、增加延迟,确定性纯内存引擎的简单性打了折扣。回测一律用内存;生产按需开持久化。我的建议还是那句——先内存跑通,别一上来就上数据库。
Cache-在系统里的位置
订单状态、行情、持仓全部沉淀进 Cache;Portfolio 算盈亏、RiskEngine 做风控、Strategy 做决策,全部从 Cache 读。它就像系统的"共享大脑",谁都不用自己记,查它就对了。
小结
- Cache 是 NautilusTrader 的内存状态中枢,缓存并索引 Instrument/Account/Order/Position。
- 提供索引读取方法如
cache.instrument()、cache.quote()、cache.order()、cache.position(),查询是哈希级的快。 - 状态以 Cache 为唯一权威,别在事件快照里存状态,事件只是"变化的通知"。
- 内存版够用;生产可开数据库持久化,但回测别开,保持确定性。
下节预告
Cache 里的状态活着活着,组件本身也会"生老病死"——从初始化、运行到故障、销毁。下一节讲组件生命周期状态机 ComponentState,看 NautilusTrader 怎么把生老病死管成一套严格的状态机。
如果觉得本文对你有帮助,欢迎点赞、收藏、关注三连!
本系列持续更新中,关注不迷路~