-
Notifications
You must be signed in to change notification settings - Fork 724
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add example messaging with actor & signal (#2408)
- Loading branch information
1 parent
3d6a881
commit acc5e0d
Showing
3 changed files
with
231 additions
and
0 deletions.
There are no files selected for viewing
16 changes: 16 additions & 0 deletions
16
examples/backtest/example_11_messaging_with_actor_signals/README.md
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
# Actor-Based Signal Messaging Example | ||
|
||
This example demonstrates the simplest form of messaging in NautilusTrader using Actor-Based Signals. | ||
It shows how to implement lightweight notifications between components using string-based signals. | ||
|
||
## What You'll Learn | ||
|
||
- How to use signals for simple notifications (price extremes in this case) | ||
- How to publish signals with single string values | ||
- How to subscribe to signals and handle them in `on_signal` callback | ||
|
||
## Implementation Highlights | ||
|
||
- Uses `SimpleNamespace` for signal name constants | ||
- Shows both signal publishing and subscription | ||
- Demonstrates signal handling with pattern matching in `on_signal` |
96 changes: 96 additions & 0 deletions
96
examples/backtest/example_11_messaging_with_actor_signals/run_example.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,96 @@ | ||
#!/usr/bin/env python3 | ||
# ------------------------------------------------------------------------------------------------- | ||
# Copyright (C) 2015-2025 Nautech Systems Pty Ltd. All rights reserved. | ||
# https://nautechsystems.io | ||
# | ||
# Licensed under the GNU Lesser General Public License Version 3.0 (the "License"); | ||
# You may not use this file except in compliance with the License. | ||
# You may obtain a copy of the License at https://www.gnu.org/licenses/lgpl-3.0.en.html | ||
# | ||
# Unless required by applicable law or agreed to in writing, software | ||
# distributed under the License is distributed on an "AS IS" BASIS, | ||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
# See the License for the specific language governing permissions and | ||
# limitations under the License. | ||
# ------------------------------------------------------------------------------------------------- | ||
|
||
from decimal import Decimal | ||
|
||
from strategy import DemoStrategy | ||
from strategy import DemoStrategyConfig | ||
|
||
from examples.utils.data_provider import prepare_demo_data_eurusd_futures_1min | ||
from nautilus_trader.backtest.engine import BacktestEngine | ||
from nautilus_trader.config import BacktestEngineConfig | ||
from nautilus_trader.config import LoggingConfig | ||
from nautilus_trader.model import Bar | ||
from nautilus_trader.model import TraderId | ||
from nautilus_trader.model.currencies import USD | ||
from nautilus_trader.model.enums import AccountType | ||
from nautilus_trader.model.enums import OmsType | ||
from nautilus_trader.model.identifiers import Venue | ||
from nautilus_trader.model.instruments.base import Instrument | ||
from nautilus_trader.model.objects import Money | ||
|
||
|
||
if __name__ == "__main__": | ||
|
||
# ---------------------------------------------------------------------------------- | ||
# 1. Configure and create backtest engine | ||
# ---------------------------------------------------------------------------------- | ||
|
||
engine_config = BacktestEngineConfig( | ||
trader_id=TraderId("BACKTEST-SIGNALS-001"), # Unique identifier for this backtest | ||
logging=LoggingConfig(log_level="INFO"), | ||
) | ||
engine = BacktestEngine(config=engine_config) | ||
|
||
# ---------------------------------------------------------------------------------- | ||
# 2. Prepare market data | ||
# ---------------------------------------------------------------------------------- | ||
|
||
prepared_data: dict = prepare_demo_data_eurusd_futures_1min() | ||
venue_name: str = prepared_data["venue_name"] | ||
eurusd_instrument: Instrument = prepared_data["instrument"] | ||
eurusd_1min_bartype = prepared_data["bar_type"] | ||
eurusd_1min_bars: list[Bar] = prepared_data["bars_list"] | ||
|
||
# ---------------------------------------------------------------------------------- | ||
# 3. Configure trading environment | ||
# ---------------------------------------------------------------------------------- | ||
|
||
# Set up the trading venue with a margin account | ||
engine.add_venue( | ||
venue=Venue(venue_name), | ||
oms_type=OmsType.NETTING, # Use a netting order management system | ||
account_type=AccountType.MARGIN, # Use a margin trading account | ||
starting_balances=[Money(1_000_000, USD)], # Set initial capital | ||
base_currency=USD, # Account currency | ||
default_leverage=Decimal(1), # No leverage (1:1) | ||
) | ||
|
||
# Register the trading instrument | ||
engine.add_instrument(eurusd_instrument) | ||
|
||
# Load historical market data | ||
engine.add_data(eurusd_1min_bars) | ||
|
||
# ---------------------------------------------------------------------------------- | ||
# 4. Configure and run strategy | ||
# ---------------------------------------------------------------------------------- | ||
|
||
# Create strategy configuration | ||
strategy_config = DemoStrategyConfig( | ||
instrument=eurusd_instrument, | ||
bar_type=eurusd_1min_bartype, | ||
) | ||
|
||
# Create and register the strategy | ||
strategy = DemoStrategy(config=strategy_config) | ||
engine.add_strategy(strategy) | ||
|
||
# Execute the backtest | ||
engine.run() | ||
|
||
# Clean up resources | ||
engine.dispose() |
119 changes: 119 additions & 0 deletions
119
examples/backtest/example_11_messaging_with_actor_signals/strategy.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,119 @@ | ||
# ------------------------------------------------------------------------------------------------- | ||
# Copyright (C) 2015-2025 Nautech Systems Pty Ltd. All rights reserved. | ||
# https://nautechsystems.io | ||
# | ||
# Licensed under the GNU Lesser General Public License Version 3.0 (the "License"); | ||
# You may not use this file except in compliance with the License. | ||
# You may obtain a copy of the License at https://www.gnu.org/licenses/lgpl-3.0.en.html | ||
# | ||
# Unless required by applicable law or agreed to in writing, software | ||
# distributed under the License is distributed on an "AS IS" BASIS, | ||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
# See the License for the specific language governing permissions and | ||
# limitations under the License. | ||
# ------------------------------------------------------------------------------------------------- | ||
|
||
import types | ||
|
||
from nautilus_trader.common.enums import LogColor | ||
from nautilus_trader.config import StrategyConfig | ||
from nautilus_trader.core.datetime import unix_nanos_to_dt | ||
from nautilus_trader.model.data import Bar | ||
from nautilus_trader.model.data import BarType | ||
from nautilus_trader.model.instruments import Instrument | ||
from nautilus_trader.trading.strategy import Strategy | ||
|
||
|
||
class DemoStrategyConfig(StrategyConfig, frozen=True): | ||
instrument: Instrument | ||
bar_type: BarType | ||
|
||
|
||
# Signal names for price extremes | ||
signals = types.SimpleNamespace() | ||
signals.NEW_HIGHEST_PRICE = "NewHighestPriceReached" | ||
signals.NEW_LOWEST_PRICE = "NewLowestPriceReached" | ||
|
||
|
||
class DemoStrategy(Strategy): | ||
""" | ||
A demonstration strategy showing how to use Actor-Based Signal messaging. | ||
This example demonstrates the simplest messaging approach in NautilusTrader: | ||
- Using signals for lightweight notifications (price extremes in this case) | ||
- Publishing signals with single string values | ||
- Subscribing to signals and handling them in on_signal | ||
Two signals are used: | ||
- NewHighestPriceReached: Published when a new maximum price is detected | ||
- NewLowestPriceReached: Published when a new minimum price is detected | ||
""" | ||
|
||
def __init__(self, config: DemoStrategyConfig): | ||
super().__init__(config) | ||
|
||
# Initialize price tracking | ||
self.highest_price = float("-inf") | ||
self.lowest_price = float("inf") | ||
|
||
def on_start(self): | ||
# Subscribe to market data | ||
self.subscribe_bars(self.config.bar_type) | ||
self.log.info(f"Subscribed to {self.config.bar_type}", color=LogColor.YELLOW) | ||
|
||
# Subscribe to signals - each signal subscription will trigger on_signal when that signal is published | ||
self.subscribe_signal(signals.NEW_HIGHEST_PRICE) | ||
self.subscribe_signal(signals.NEW_LOWEST_PRICE) | ||
self.log.info("Subscribed to price extreme signals", color=LogColor.YELLOW) | ||
|
||
def on_bar(self, bar: Bar): | ||
# Check for new highest price | ||
if bar.close > self.highest_price: | ||
self.highest_price = bar.close | ||
self.log.info(f"New highest price detected: {bar.close}") | ||
# Publish a lightweight signal (can only contain a single value of type str/int/float) | ||
self.publish_signal( | ||
name=signals.NEW_HIGHEST_PRICE, # Signal name | ||
value=signals.NEW_HIGHEST_PRICE, # Signal value | ||
ts_event=bar.ts_event, | ||
) | ||
|
||
# Check for new lowest price | ||
if bar.close < self.lowest_price: | ||
self.lowest_price = bar.close | ||
self.log.info(f"New lowest price detected: {bar.close}") | ||
# Publish a lightweight signal (can only contain a single value of type str/int/float) | ||
self.publish_signal( | ||
name=signals.NEW_LOWEST_PRICE, | ||
value=signals.NEW_LOWEST_PRICE, # Using same string as name for simplicity | ||
ts_event=bar.ts_event, | ||
) | ||
|
||
def on_signal(self, signal): | ||
""" | ||
Handle incoming signals. | ||
This method is automatically called when any signal we're subscribed to is published. | ||
Important: In the signal handler, we can only match against signal.value | ||
(signal.name is not accessible in the handler). | ||
""" | ||
match signal.value: | ||
case signals.NEW_HIGHEST_PRICE: | ||
self.log.info( | ||
f"New highest price was reached. | " | ||
f"Signal value: {signal.value} | " | ||
f"Signal time: {unix_nanos_to_dt(signal.ts_event)}", | ||
color=LogColor.GREEN, | ||
) | ||
case signals.NEW_LOWEST_PRICE: | ||
self.log.info( | ||
f"New lowest price was reached. | " | ||
f"Signal value: {signal.value} | " | ||
f"Signal time: {unix_nanos_to_dt(signal.ts_event)}", | ||
color=LogColor.RED, | ||
) | ||
|
||
def on_stop(self): | ||
self.log.info("Strategy stopped.") |