Skip to content

Logging System

Overview

The Quantstrip logging system provides centralized, thread-safe logging for all application components. It automatically handles log rotation, console output, and separate error tracking for the web interface.

Key Features

  • Automatic daily log rotation with 30-day retention
  • Thread-aware logging (tracks which thread generated each log)
  • Separate error logs for web interface issues
  • Console output for real-time monitoring
  • Simulation console buffer for web IDE
  • Singleton pattern ensures consistent logging across application

Using the Logger

Basic Setup

The logger is automatically initialized when you import it. Simply get a logger instance for your module:

import logging

logger = logging.getLogger(__name__)

# Now you can log messages
logger.info("Strategy initialized")
logger.warning("Low liquidity detected")
logger.error("Order failed")


Log Levels

The logger supports standard Python logging levels:

Level When to Use Example
DEBUG Detailed diagnostic information Variable values, state transitions
INFO General informational messages Trade executions, strategy signals
WARNING Warning messages for unexpected situations Missing data, API rate limits
ERROR Error messages for failures Order rejections, connection failures
CRITICAL Critical failures requiring immediate attention Database corruption, system crashes

Example:

import logging

logger = logging.getLogger(__name__)

# Different log levels
logger.debug(f"Calculating signal with price={price}, volume={volume}")
logger.info(f"Generated BUY signal for {symbol}")
logger.warning(f"Spread wider than expected: {spread:.4f}")
logger.error(f"Failed to place order: {error_msg}")
logger.critical(f"Database connection lost!")


Logging in Trading Clients

Here's how to use logging in your custom trading clients:

import logging
from base_client import BaseClient

logger = logging.getLogger(__name__)

class Client(BaseClient):
    def __init__(self):
        super().__init__()
        logger.info("Client initialized")

    def on_bar(self, symbol, bar):
        logger.debug(f"Processing bar for {symbol}: close={bar.close}")

        # Your strategy logic
        if self.should_buy(bar):
            logger.info(f"BUY signal generated for {symbol} at {bar.close}")
            self.place_order(symbol, 100, "BUY")

    def on_order_filled(self, order):
        logger.info(f"Order filled: {order.symbol} {order.side} "
                   f"{order.quantity}@{order.fill_price}")

    def on_error(self, error):
        logger.error(f"Error occurred: {error}")


Structured Logging

Use f-strings to create clear, structured log messages:

# Good - Clear and structured
logger.info(f"Position opened: symbol={symbol}, qty={qty}, price={price:.2f}")

# Good - Include context
logger.warning(f"Order retry {attempt}/{max_attempts} for {symbol}")

# Bad - Vague message
logger.info("Something happened")

# Bad - Missing context
logger.error("Failed")


Logging Exceptions

Always log the full exception traceback for debugging:

import logging
import traceback

logger = logging.getLogger(__name__)

try:
    result = risky_operation()
except Exception as e:
    # Log with traceback
    logger.error(f"Operation failed: {e}")
    logger.error(traceback.format_exc())

    # Or use exception() method for automatic traceback
    logger.exception(f"Operation failed for {symbol}")


Logger Output Format

Logs are automatically formatted with: - Timestamp (YYYY-MM-DD HH:MM:SS) - Log level (INFO, WARNING, ERROR, etc.) - Module name - Thread name - Message

Example output:

2025-01-17 14:30:45 - INFO - my_strategy - MainThread - Strategy initialized
2025-01-17 14:30:46 - INFO - my_strategy - MainThread - BUY signal for AAPL at 150.25
2025-01-17 14:30:47 - WARNING - my_strategy - MainThread - API rate limit approaching
2025-01-17 14:30:48 - ERROR - my_strategy - MainThread - Order rejected: Insufficient funds


Log Files

Service Log

Main application log with all system messages.

  • Location: Data/logs/service.log
  • Rotation: Daily at midnight
  • Retention: 30 days
  • Contents: All application logs (INFO and above)

Dash Error Log

Separate log for web interface errors.

  • Location: Data/logs/dash_errors.log
  • Rotation: Daily at midnight
  • Retention: 7 days
  • Contents: Dash/Flask/Werkzeug errors only

Tip

If your web interface isn't working, check dash_errors.log first.


Common Patterns

Strategy Logging

import logging

logger = logging.getLogger(__name__)

class Client(BaseClient):
    def __init__(self):
        super().__init__()
        self.strategy_name = "Momentum v2"
        logger.info(f"Initialized {self.strategy_name}")

    def run_client(self):
        logger.info(f"Starting {self.strategy_name}")

        try:
            self.connect()
            self.subscribe_data()
            self.main_loop()
        except Exception as e:
            logger.error(f"{self.strategy_name} crashed: {e}")
            logger.exception("Full traceback:")
        finally:
            logger.info(f"Shutting down {self.strategy_name}")
            self.cleanup()

Trade Execution Logging

def place_order(self, symbol, quantity, side):
    logger.info(f"Placing {side} order: {quantity} {symbol}")

    try:
        order_id = self.broker.submit_order(symbol, quantity, side)
        logger.info(f"Order submitted: ID={order_id}")
        return order_id
    except Exception as e:
        logger.error(f"Order failed for {symbol}: {e}")
        logger.exception("Order failure details:")
        return None

def on_order_filled(self, execution):
    logger.info(
        f"FILL: {execution.side} {execution.shares} {execution.symbol} "
        f"@ ${execution.price:.2f} (Order ID: {execution.order_id})"
    )

Position Management Logging

def update_position(self, symbol, new_position):
    old_position = self.positions.get(symbol, 0)

    if new_position != old_position:
        change = new_position - old_position
        logger.info(
            f"Position change: {symbol} {old_position} -> {new_position} "
            f"({change:+d})"
        )

        self.positions[symbol] = new_position

        # Log position action
        if old_position == 0:
            logger.info(f"OPENED position in {symbol}")
        elif new_position == 0:
            logger.info(f"CLOSED position in {symbol}")
        elif old_position * new_position < 0:
            logger.info(f"REVERSED position in {symbol}")

Data Feed Logging

def on_bar(self, symbol, bar):
    # Debug-level for frequent events
    logger.debug(
        f"Bar: {symbol} O={bar.open:.2f} H={bar.high:.2f} "
        f"L={bar.low:.2f} C={bar.close:.2f} V={bar.volume}"
    )

def on_connection_lost(self):
    logger.warning("Market data connection lost, attempting reconnect...")

def on_connection_restored(self):
    logger.info("Market data connection restored")

Error Recovery Logging

def execute_with_retry(self, operation, max_attempts=3):
    for attempt in range(1, max_attempts + 1):
        try:
            logger.debug(f"Attempt {attempt}/{max_attempts}: {operation}")
            result = operation()
            logger.info(f"Operation succeeded on attempt {attempt}")
            return result
        except Exception as e:
            logger.warning(
                f"Attempt {attempt}/{max_attempts} failed: {e}"
            )

            if attempt == max_attempts:
                logger.error(f"All {max_attempts} attempts failed")
                logger.exception("Final failure details:")
                raise

            time.sleep(2 ** attempt)  # Exponential backoff

Performance Monitoring

import time
import logging

logger = logging.getLogger(__name__)

def monitor_performance(func):
    """Decorator to log function execution time"""
    def wrapper(*args, **kwargs):
        start = time.time()
        result = func(*args, **kwargs)
        elapsed = time.time() - start

        if elapsed > 1.0:  # Warn if slow
            logger.warning(
                f"{func.__name__} took {elapsed:.2f}s (slow!)"
            )
        else:
            logger.debug(
                f"{func.__name__} took {elapsed:.3f}s"
            )

        return result
    return wrapper

@monitor_performance
def calculate_signals(self, data):
    # Your calculation logic
    pass

Best Practices

Do's

Use appropriate log levels

logger.debug("Detailed variable state")  # For debugging only
logger.info("Normal operations")         # Standard events
logger.warning("Unexpected but handled") # Warnings
logger.error("Operation failed")         # Errors

Include context in messages

logger.info(f"Order filled: {symbol} {qty}@{price} (ID: {order_id})")

Log exceptions with traceback

logger.exception("Failed to process order")

Use module-level loggers

logger = logging.getLogger(__name__)  # At module level


Don'ts

Don't log sensitive information

# BAD - Exposes credentials
logger.info(f"Connecting with password: {password}")

# GOOD - Mask sensitive data
logger.info("Connecting to broker")

Don't log excessively in tight loops

# BAD - Logs every iteration
for i in range(1000000):
    logger.info(f"Processing {i}")  # Creates huge logs!

# GOOD - Log summary
for i in range(1000000):
    process(i)
logger.info(f"Processed 1,000,000 items")

Don't use print() instead of logging

# BAD - Won't be captured in log files
print("Order placed")

# GOOD - Use logger
logger.info("Order placed")

Don't create logger in init

# BAD
class Client:
    def __init__(self):
        self.logger = logging.getLogger(__name__)  # Creates new instance

# GOOD
logger = logging.getLogger(__name__)  # Module-level

class Client:
    def __init__(self):
        logger.info("Initialized")


Thread-Safe Logging

The logging system is fully thread-safe. Each log message includes the thread name automatically:

import threading
import logging

logger = logging.getLogger(__name__)

def worker_task(task_id):
    logger.info(f"Task {task_id} started")
    # Do work...
    logger.info(f"Task {task_id} completed")

# Run multiple threads
threads = []
for i in range(5):
    t = threading.Thread(target=worker_task, args=(i,))
    t.start()
    threads.append(t)

for t in threads:
    t.join()

Output shows thread names:

2025-01-17 14:30:45 - INFO - module - Thread-1 - Task 0 started
2025-01-17 14:30:45 - INFO - module - Thread-2 - Task 1 started
2025-01-17 14:30:46 - INFO - module - Thread-1 - Task 0 completed
2025-01-17 14:30:46 - INFO - module - Thread-2 - Task 1 completed


Simulation Console

When running strategies in the web IDE, logs are captured in real-time and displayed in the console.

The simulation console shows: - Logger output from simulation_thread - Print statements from your code - Errors and exceptions

Example client with console output:

import logging

logger = logging.getLogger(__name__)

class Client(BaseClient):
    def run_client(self):
        logger.info("Strategy starting...")  # Appears in console
        print("Loading data...")             # Also appears in console

        try:
            self.execute_strategy()
            logger.info("Strategy completed")
        except Exception as e:
            logger.error(f"Strategy failed: {e}")  # Red in console


Troubleshooting

Logs Not Appearing

Check log level:

import logging

# Ensure logger level is set correctly
logger = logging.getLogger(__name__)
logger.setLevel(logging.DEBUG)  # Will show DEBUG and above

Verify logger name:

# Use __name__ for automatic module naming
logger = logging.getLogger(__name__)

# Or use explicit name
logger = logging.getLogger("my_strategy")


Too Many Logs

Reduce log level:

# Only show warnings and errors
logger.setLevel(logging.WARNING)

Add conditional logging:

# Only log every 100th iteration
if iteration % 100 == 0:
    logger.debug(f"Progress: {iteration} iterations")


Performance Impact

Logging has minimal performance impact, but be cautious in high-frequency scenarios:

# BAD - Logs every tick
def on_tick(self, tick):
    logger.info(f"Tick: {tick.price}")  # Too frequent!

# GOOD - Log aggregates
def on_tick(self, tick):
    self.tick_count += 1
    if self.tick_count % 1000 == 0:
        logger.info(f"Processed {self.tick_count} ticks")


Log File Locations

All log files are stored in the Data/logs/ directory:

Data/
└── logs/
    ├── service.log              # Current main log
    ├── service.log.2025-01-16   # Previous day's log
    ├── service.log.2025-01-15   # 2 days ago
    ├── ...
    ├── dash_errors.log          # Current web errors
    └── dash_errors.log.2025-01-16

Log Retention

  • Main logs are kept for 30 days
  • Dash error logs are kept for 7 days
  • Logs rotate automatically at midnight