Skip to content

Commit aa79703

Browse files
authored
Add docs for messaging styles (#2410)
1 parent 5bad213 commit aa79703

File tree

1 file changed

+201
-0
lines changed

1 file changed

+201
-0
lines changed

docs/concepts/messaging_styles.md

Lines changed: 201 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,201 @@
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

Comments
 (0)