Skip to content

Commit

Permalink
Add example messaging with actor & signal (#2408)
Browse files Browse the repository at this point in the history
  • Loading branch information
stefansimik authored Mar 5, 2025
1 parent 3d6a881 commit acc5e0d
Show file tree
Hide file tree
Showing 3 changed files with 231 additions and 0 deletions.
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`
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 examples/backtest/example_11_messaging_with_actor_signals/strategy.py
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.")

0 comments on commit acc5e0d

Please sign in to comment.