|
| 1 | +# Messaging styles |
| 2 | + |
| 3 | +NautilusTrader is an **event-driven** framework where components communicate by sending and receiving messages. |
| 4 | +Understanding the different messaging styles is crucial for building effective trading systems. |
| 5 | + |
| 6 | +This guide explains the three primary messaging patterns available in NautilusTrader: |
| 7 | + |
| 8 | +| **Messaging Style** | **Purpose** | **Best For** | |
| 9 | +|:---|:---|:---| |
| 10 | +| **MessageBus - Publish/Subscribe to topics** | Low-level, direct access to the message bus | Custom events, system-level communication | |
| 11 | +| **Actor-Based - Publish/Subscribe Data** | Structured trading data exchange | Trading metrics, indicators, data needing persistence | |
| 12 | +| **Actor-Based - Publish/Subscribe Signal** | Lightweight notifications | Simple alerts, flags, status updates | |
| 13 | + |
| 14 | +Each approach serves different purposes and offers unique advantages. This guide will help you decide which messaging |
| 15 | +pattern to use in your NautilusTrader applications. |
| 16 | + |
| 17 | +## MessageBus publish/subscribe to topics |
| 18 | + |
| 19 | +### Concept |
| 20 | + |
| 21 | +The `MessageBus` is the central hub for all messages in NautilusTrader. It enables a **publish/subscribe** pattern |
| 22 | +where components can publish events to **named topics**, and other components can subscribe to receive those messages. |
| 23 | +This decouples components, allowing them to interact indirectly via the message bus. |
| 24 | + |
| 25 | +### Key benefits and use cases |
| 26 | + |
| 27 | +The message bus approach is ideal when you need: |
| 28 | +- **Cross-component communication** within the system. |
| 29 | +- **Flexibility** to define any topic and send any type of payload (any Python object). |
| 30 | +- **Decoupling** between publishers and subscribers who don't need to know about each other. |
| 31 | +- **Global Reach** where messages can be received by multiple subscribers. |
| 32 | +- Working with events that don't fit within the predefined `Actor` model. |
| 33 | +- Advanced scenarios requiring full control over messaging. |
| 34 | + |
| 35 | +### Considerations |
| 36 | + |
| 37 | +- You must track topic names manually (typos could result in missed messages). |
| 38 | +- You must define handlers manually. |
| 39 | + |
| 40 | +### Quick overview code |
| 41 | + |
| 42 | +```python |
| 43 | +from nautilus_trader.core.message import Event |
| 44 | + |
| 45 | +# Define a custom event |
| 46 | +class Each10thBarEvent(Event): |
| 47 | + TOPIC = "each_10th_bar" # Topic name |
| 48 | + def __init__(self, bar): |
| 49 | + self.bar = bar |
| 50 | + |
| 51 | +# Subscribe in a component (in Strategy) |
| 52 | +self.msgbus.subscribe(Each10thBarEvent.TOPIC, self.on_each_10th_bar) |
| 53 | + |
| 54 | +# Publish an event (in Strategy) |
| 55 | +event = Each10thBarEvent(bar) |
| 56 | +self.msgbus.publish(Each10thBarEvent.TOPIC, event) |
| 57 | + |
| 58 | +# Handler (in Strategy) |
| 59 | +def on_each_10th_bar(self, event: Each10thBarEvent): |
| 60 | + self.log.info(f"Received 10th bar: {event.bar}") |
| 61 | +``` |
| 62 | + |
| 63 | +### Full example |
| 64 | + |
| 65 | +[MessageBus Example](https://github.com/nautechsystems/nautilus_trader/tree/develop/examples/backtest/example_09_custom_event_with_msgbus) |
| 66 | + |
| 67 | +## Actor-based publish/subscribe data |
| 68 | + |
| 69 | +### Concept |
| 70 | + |
| 71 | +This approach provides a way to exchange trading specific data between `Actor`s in the system. |
| 72 | +(note: each `Strategy` inherits from `Actor`). It inherits from `Data`, which ensures proper timestamping |
| 73 | +and ordering of events - crucial for correct backtest processing. |
| 74 | + |
| 75 | +### Key Benefits and Use Cases |
| 76 | + |
| 77 | +The Data publish/subscribe approach excels when you need: |
| 78 | +- **Exchange of structured trading data** like market data, indicators, custom metrics, or option greeks. |
| 79 | +- **Proper event ordering** via built-in timestamps (`ts_event`, `ts_init`) crucial for backtest accuracy. |
| 80 | +- **Data persistence and serialization** through the `@customdataclass` decorator, integrating seamlessly with NautilusTrader's data catalog system. |
| 81 | +- **Standardized trading data exchange** between system components. |
| 82 | + |
| 83 | +### Considerations |
| 84 | + |
| 85 | +- Requires defining a class that inherits from `Data` or uses `@customdataclass`. |
| 86 | + |
| 87 | +### Inheriting from `Data` vs. using `@customdataclass` |
| 88 | + |
| 89 | +**Inheriting from `Data` class:** |
| 90 | +- Adds the required `ts_event` and `ts_init` attributes and their getters. These ensure proper data ordering in backtests based on timestamps. |
| 91 | + |
| 92 | +**The `@customdataclass` decorator:** |
| 93 | +- Adds `ts_event` and `ts_init` attributes if they are not already present. |
| 94 | +- Provides serialization functions: `to_dict()`, `from_dict()`, `to_bytes()`, `to_arrow()`, etc. |
| 95 | +- Enables data persistence and external communication. |
| 96 | + |
| 97 | +### Quick overview code |
| 98 | + |
| 99 | +```python |
| 100 | +from nautilus_trader.core.data import Data |
| 101 | +from nautilus_trader.model.custom import customdataclass |
| 102 | + |
| 103 | +@customdataclass |
| 104 | +class GreeksData(Data): |
| 105 | + delta: float |
| 106 | + gamma: float |
| 107 | + |
| 108 | +# Publish data (in Actor / Strategy) |
| 109 | +data = GreeksData(delta=0.75, gamma=0.1, ts_event=1630000000, ts_init=1630000000) |
| 110 | +self.publish_data(GreeksData, data) |
| 111 | + |
| 112 | +# Subscribe to receiving data (in Actor / Strategy) |
| 113 | +self.subscribe_data(GreeksData) |
| 114 | + |
| 115 | +# Handler (this is static callback function with fixed name) |
| 116 | +def on_data(self, data: Data): |
| 117 | + if isinstance(data, GreeksData): |
| 118 | + self.log.info(f"Delta: {data.delta}, Gamma: {data.gamma}") |
| 119 | +``` |
| 120 | + |
| 121 | +### Full example |
| 122 | + |
| 123 | +[Actor-Based Data Example](https://github.com/nautechsystems/nautilus_trader/tree/develop/examples/backtest/example_10_messaging_with_actor_data) |
| 124 | + |
| 125 | +## Actor-based publish/subscribe signal |
| 126 | + |
| 127 | +### Concept |
| 128 | + |
| 129 | +**Signals** are a lightweight way to publish and subscribe to simple notifications within the actor framework. |
| 130 | +This is the simplest messaging approach, requiring no custom class definitions. |
| 131 | + |
| 132 | +### Key Benefits and Use Cases |
| 133 | + |
| 134 | +The Signal messaging approach shines when you need: |
| 135 | +- **Simple, lightweight notifications/alerts** like "RiskThresholdExceeded" or "TrendUp". |
| 136 | +- **Quick, on-the-fly messaging** without defining custom classes. |
| 137 | +- **Broadcasting alerts or flags** as primitive data (`int`, `float`, or `str`). |
| 138 | +- **Easy API integration** with straightforward methods (`publish_signal`, `subscribe_signal`). |
| 139 | +- **Multiple subscriber communication** where all subscribers receive signals when published. |
| 140 | +- **Minimal setup overhead** with no class definitions required. |
| 141 | + |
| 142 | +### Considerations |
| 143 | + |
| 144 | +- Each signal can contain only **single value** of type: `int`, `float`, and `str`. That means no support for complex data structures or other Python types. |
| 145 | +- In the `on_signal` handler, you can only differentiate between signals using `signal.value`, as the signal name is not accessible in the handler. |
| 146 | + |
| 147 | +### Quick overview code |
| 148 | + |
| 149 | +```python |
| 150 | +# Define signal constants for better organization (optional but recommended) |
| 151 | +import types |
| 152 | +signals = types.SimpleNamespace() |
| 153 | +signals.NEW_HIGHEST_PRICE = "NewHighestPriceReached" |
| 154 | +signals.NEW_LOWEST_PRICE = "NewLowestPriceReached" |
| 155 | + |
| 156 | +# Subscribe to signals (in Actor/Strategy) |
| 157 | +self.subscribe_signal(signals.NEW_HIGHEST_PRICE) |
| 158 | +self.subscribe_signal(signals.NEW_LOWEST_PRICE) |
| 159 | + |
| 160 | +# Publish a signal (in Actor/Strategy) |
| 161 | +self.publish_signal( |
| 162 | + name=signals.NEW_HIGHEST_PRICE, |
| 163 | + value=signals.NEW_HIGHEST_PRICE, # value can be the same as name for simplicity |
| 164 | + ts_event=bar.ts_event, # timestamp from triggering event |
| 165 | +) |
| 166 | + |
| 167 | +# Handler (this is static callback function with fixed name) |
| 168 | +def on_signal(self, signal): |
| 169 | + # IMPORTANT: We match against signal.value, not signal.name |
| 170 | + match signal.value: |
| 171 | + case signals.NEW_HIGHEST_PRICE: |
| 172 | + self.log.info( |
| 173 | + f"New highest price was reached. | " |
| 174 | + f"Signal value: {signal.value} | " |
| 175 | + f"Signal time: {unix_nanos_to_dt(signal.ts_event)}", |
| 176 | + color=LogColor.GREEN |
| 177 | + ) |
| 178 | + case signals.NEW_LOWEST_PRICE: |
| 179 | + self.log.info( |
| 180 | + f"New lowest price was reached. | " |
| 181 | + f"Signal value: {signal.value} | " |
| 182 | + f"Signal time: {unix_nanos_to_dt(signal.ts_event)}", |
| 183 | + color=LogColor.RED |
| 184 | + ) |
| 185 | +``` |
| 186 | + |
| 187 | +### Full example |
| 188 | + |
| 189 | +[Actor-Based Signal Example](https://github.com/nautechsystems/nautilus_trader/tree/develop/examples/backtest/example_11_messaging_with_actor_signals) |
| 190 | + |
| 191 | +## Summary and decision guide |
| 192 | + |
| 193 | +Here's a quick reference to help you decide which messaging style to use: |
| 194 | + |
| 195 | +### Decision guide: Which style to choose? |
| 196 | + |
| 197 | +| **Use Case** | **Recommended Approach** | **Setup required** | |
| 198 | +|:---|:---|:---| |
| 199 | +| Custom events or system-level communication | `MessageBus` + Pub/Sub to topic | Topic + Handler management | |
| 200 | +| Structured trading data | `Actor` + Pub/Sub Data + optional `@customdataclass` if serialization is needed | New class definition inheriting from `Data` (handler `on_data` is predefined) | |
| 201 | +| Simple alerts/notifications | `Actor` + Pub/Sub Signal | Just signal name | |
0 commit comments