How to Debug Strategy Logic and Inspect Internal State in Nautilus Trader
Enable DEBUG logging via strategy._log.setLevel("DEBUG") and inspect self.cache and self.portfolio attributes to trace real-time strategy state without modifying library code.
Nautilus Trader is a high-performance algorithmic trading platform that isolates strategy execution within a Strategy instance. When you need to debug strategy logic and inspect internal state, the framework provides built-in logging, cache access, and portfolio inspection capabilities that allow deep visibility without altering the core library.
Enable Detailed Logging for Strategy Debugging
Every Strategy instance creates its own logger accessible via self._log. This logger captures all framework interactions, including order submissions, position changes, and market data events.
Configure Logging via StrategyConfig
Set logging flags when defining your strategy configuration in nautilus_trader/trading/config.py:
from nautilus_trader.trading.config import StrategyConfig
class MyStrategyConfig(StrategyConfig, frozen=True):
log_events: bool = True # Log all events received by the strategy
log_commands: bool = True # Log all commands sent by the strategy
These flags ensure that the strategy's internal message processing is recorded in the logs.
Runtime Log Level Adjustment
For existing strategy instances, change the log level dynamically to capture DEBUG output:
strategy._log.setLevel("DEBUG")
This is useful when you need verbose output only during specific market conditions or debugging sessions.
File Output for Post-Mortem Analysis
Redirect logs to a file for offline analysis without modifying the core library:
import logging
strategy._log.addHandler(logging.FileHandler("strategy_debug.log"))
All subsequent log entries write to strategy_debug.log, preserving a record of strategy logic execution.
Inspect Core Components at Runtime
The Strategy class in nautilus_trader/trading/strategy.pyx maintains references to core components after registration with the Trader. These attributes provide read-only access to the platform's internal state.
Access the Cache for Market Data
The self.cache attribute (type Cache) stores market data, order-book snapshots, and instrument definitions:
def on_bar(self, bar: Bar) -> None:
# Query the latest quote for the instrument
quote = self.cache.quote_tick(bar.instrument_id)
self._log.debug(f"Latest quote: {quote}")
# Access historical bars if cached
bars = self.cache.bars(bar.instrument_id)
self._log.debug(f"Cached bars count: {len(bars)}")
Query Portfolio State and Positions
The self.portfolio attribute (type PortfolioFacade) exposes open positions, cash balances, and P&L calculations:
def on_trade(self, trade: TradeTick) -> None:
# List all open positions
positions = self.portfolio.positions()
self._log.debug(f"Open positions: {list(positions)}")
# Check exposure for specific instrument
exposure = self.portfolio.exposure(trade.instrument_id)
self._log.debug(f"Current exposure: {exposure}")
# Access unrealized P&L
pnl = self.portfolio.unrealized_pnl(trade.instrument_id)
self._log.debug(f"Unrealized P&L: {pnl}")
Both components reflect the live state maintained by the Trader and update synchronously with market events.
Use Python Inspection Utilities
Nautilus Trader includes utilities in nautilus_trader/core/inspect.py to differentiate platform objects from user-defined types.
Verify Method Signatures
When debugging command submissions, verify expected parameters using Python's standard inspect module:
import inspect
# Check submit_order signature before calling
sig = inspect.signature(self.submit_order)
self._log.debug(f"submit_order signature: {sig}")
This prevents errors from incorrect argument passing when debugging complex order logic.
Identify Nautilus Object Types
Use is_nautilus_class to filter collections containing mixed object types:
from nautilus_trader.core.inspect import is_nautilus_class
def debug_cache_contents(self):
for obj in self.cache.objects():
if is_nautilus_class(type(obj)):
self._log.debug(f"Nautilus object: {obj}")
else:
self._log.debug(f"User object: {obj}")
This distinction helps when serializing objects or diagnosing type-related bugs.
Interactive Debugging Techniques
Breakpoint Debugging with pdb
Since the strategy runs in the same Python process as the Trader, you can insert standard breakpoints:
def on_trade(self, trade: TradeTick) -> None:
import pdb; pdb.set_trace()
# When execution pauses, inspect:
# (pdb) self.cache.quote_tick(trade.instrument_id)
# (pdb) self.portfolio.positions()
# (pdb) self._log.handlers
At the breakpoint, you have direct access to self, self.cache, self.portfolio, and all strategy attributes.
External Trader Inspection
When running interactive sessions (Jupyter or REPL) with a Trader reference, query loaded strategies externally:
# trader is an instance of nautilus_trader.trading.trader.Trader
for strat in trader.strategies():
print(f"Strategy ID: {strat.id}")
print(f"State: {strat.state}")
print(f"Position count: {len(strat.portfolio.positions())}")
The Trader also exposes actor_ids(), strategy_ids(), and component clocks for system-wide diagnostics.
Running Tests with Diagnostic Logging
The repository contains unit tests that exercise the strategy API in tests/unit_tests/trading/test_strategy_pyo3.py. Run these with debug logging enabled to observe internal behavior:
export NAUTILUS_LOG_LEVEL=DEBUG
pytest -vv tests/unit_tests/trading/test_strategy_pyo3.py -k test_strategy
This outputs the same structured logs you will see in production, allowing you to verify strategy behavior before deployment.
Summary
- Enable DEBUG logging via
strategy._log.setLevel("DEBUG")orStrategyConfigflags to capture all events and commands. - Inspect internal state through
self.cache(market data) andself.portfolio(positions, P&L) attributes available in every strategy. - Use Python inspection tools like
inspect.signature()andis_nautilus_class()to verify API calls and filter object types. - Attach breakpoints using
pdb.set_trace()for interactive debugging with full access to strategy components. - Query the Trader externally via
trader.strategies()to monitor loaded strategies in Jupyter or REPL sessions.
Frequently Asked Questions
How do I enable DEBUG logging in Nautilus Trader?
Set the log level on the strategy instance after creation: strategy._log.setLevel("DEBUG"). Alternatively, configure log_events=True and log_commands=True in your StrategyConfig subclass. For file output, add a logging.FileHandler to strategy._log.
Can I modify strategy state during a pdb debugging session?
Yes. When execution pauses at a pdb.set_trace() breakpoint inside a strategy method, you have full read-write access to self and all its attributes including self.cache, self.portfolio, and private variables. You can call methods and modify state interactively.
What is the difference between self.cache and self.portfolio?
self.cache (type Cache) provides access to market data such as quote ticks, trade ticks, bars, and order-book snapshots. self.portfolio (type PortfolioFacade) exposes trading account state including open positions, cash balances, exposure, and unrealized P&L. Both are read-only from the strategy perspective.
How do I inspect all loaded strategies from the Trader instance?
Access the strategies() method on a Trader instance: for strat in trader.strategies(): print(strat.id, strat.state). The Trader class in nautilus_trader/trading/trader.py also provides strategy_ids() and actor_ids() methods for component enumeration.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →