RustyBT Exception Handling Guide¶
Overview¶
RustyBT implements a comprehensive exception hierarchy that provides context-rich error handling across the entire framework. This guide explains the exception structure, when to use each exception type, and best practices for error handling.
Exception Hierarchy¶
RustyBTError (base)
├── DataError
│ ├── DataNotFoundError
│ ├── DataValidationError
│ ├── LookaheadError
│ └── DataAdapterError
├── OrderError
│ ├── OrderRejectedError
│ ├── OrderNotFoundError
│ ├── InsufficientFundsError
│ └── InvalidOrderError
├── BrokerError
│ ├── BrokerConnectionError
│ ├── BrokerAuthenticationError
│ ├── BrokerRateLimitError
│ └── BrokerResponseError
├── StrategyError
│ ├── StrategyInitializationError
│ ├── StrategyExecutionError
│ └── InvalidSignalError
├── ValidationError
│ ├── ConfigValidationError
│ ├── AssetValidationError
│ └── ParameterValidationError
└── CircuitBreakerError
├── CircuitBreakerTrippedError
└── AlignmentCircuitBreakerError
Base Exception: RustyBTError¶
All RustyBT exceptions inherit from RustyBTError, which provides:
- Context-rich error messages: Attach structured context data to exceptions
- Structured logging: Automatic conversion to log-friendly dictionaries
- Cause tracking: Link exceptions to their underlying causes
Basic Usage¶
from rustybt.exceptions import BrokerConnectionError
# Simple exception
raise BrokerConnectionError("Failed to connect")
# With context
raise BrokerConnectionError(
"Connection timeout",
broker="Binance",
context={"host": "api.binance.com", "timeout": 30}
)
# With cause
try:
# Some operation
pass
except Exception as e:
raise BrokerConnectionError(
"Connection failed",
broker="Binance",
cause=e
)
Context and Logging¶
All exceptions can be converted to structured log fields:
import structlog
from rustybt.exceptions import OrderRejectedError
logger = structlog.get_logger(__name__)
try:
# Submit order
pass
except OrderRejectedError as exc:
# Automatically includes all context
logger.error("order_failed", **exc.to_log_fields())
Exception Categories¶
1. Data Exceptions¶
Used for data acquisition and validation errors.
DataNotFoundError¶
Raised when requested data is not available.
from rustybt.exceptions import DataNotFoundError
# Example usage
raise DataNotFoundError(
"Price data not found",
asset="AAPL",
start="2023-01-01",
end="2023-01-31"
)
When to use: - Asset data not in database - Historical data unavailable for requested timeframe - Missing price information
DataValidationError¶
Raised when data fails validation checks.
from rustybt.exceptions import DataValidationError
# Example usage
raise DataValidationError(
"Invalid OHLCV data: high < low",
invalid_rows=5,
context={"symbol": "AAPL"}
)
When to use: - OHLCV validation failures (high < low, etc.) - Missing required columns - Data type mismatches
LookaheadError¶
Raised when attempting to access future data (lookahead bias).
from rustybt.exceptions import LookaheadError
# Example usage
raise LookaheadError(
"Attempted to access future data",
requested_dt="2023-01-02",
current_dt="2023-01-01"
)
When to use: - Accessing data beyond current simulation time - Preventing lookahead bias in backtests
DataAdapterError¶
Raised when data adapter operations fail.
from rustybt.exceptions import DataAdapterError
# Example usage
raise DataAdapterError(
"API request failed",
adapter="YFinanceAdapter",
attempt=3,
context={"symbol": "AAPL", "error_code": 404}
)
When to use: - API request failures - Network errors in data fetching - Rate limit exceeded
2. Order Exceptions¶
Used for order submission and management errors.
OrderRejectedError¶
Raised when broker rejects an order.
from rustybt.exceptions import OrderRejectedError
# Example usage
raise OrderRejectedError(
"Order rejected by broker",
order_id="ORD123",
asset="AAPL",
broker="Binance",
reason="Insufficient margin"
)
When to use: - Broker rejects order - Margin requirements not met - Invalid order parameters
InsufficientFundsError¶
Raised when account lacks sufficient funds.
from rustybt.exceptions import InsufficientFundsError
# Example usage
raise InsufficientFundsError(
"Insufficient funds for order",
required="10000.00",
available="5000.00",
context={"asset": "BTC"}
)
When to use: - Cash balance too low - Margin insufficient - Position limits exceeded
InvalidOrderError¶
Raised for invalid order parameters.
from rustybt.exceptions import InvalidOrderError
# Example usage
raise InvalidOrderError(
"Invalid limit price",
parameter="limit_price",
value="-150.00",
context={"asset": "AAPL"}
)
When to use: - Negative prices - Invalid order types - Quantity outside allowed range
3. Broker Exceptions¶
Used for broker API and connection errors.
BrokerConnectionError¶
Raised when unable to connect to broker.
from rustybt.exceptions import BrokerConnectionError
# Example usage
raise BrokerConnectionError(
"Connection timeout",
broker="Binance",
context={"host": "api.binance.com", "timeout": 30}
)
When to use: - Network connectivity issues - Broker API unreachable - WebSocket connection failures
BrokerAuthenticationError¶
Raised when authentication fails.
from rustybt.exceptions import BrokerAuthenticationError
# Example usage
raise BrokerAuthenticationError(
"Invalid API credentials",
broker="Kraken"
)
When to use: - Invalid API keys - Expired tokens - Permission denied
BrokerRateLimitError¶
Raised when rate limit is exceeded.
from rustybt.exceptions import BrokerRateLimitError
# Example usage
raise BrokerRateLimitError(
"Rate limit exceeded",
broker="Coinbase",
reset_after=60.0
)
When to use: - Too many requests per second - Daily API quota exceeded - Need to implement backoff
4. Strategy Exceptions¶
Used for strategy execution errors.
StrategyInitializationError¶
Raised when strategy initialization fails.
from rustybt.exceptions import StrategyInitializationError
# Example usage
raise StrategyInitializationError(
"Failed to load strategy config",
context={"config_file": "strategy.yaml"}
)
StrategyExecutionError¶
Raised when strategy execution fails.
from rustybt.exceptions import StrategyExecutionError
# Example usage
raise StrategyExecutionError(
"Strategy crashed during handle_data",
context={"asset": "AAPL", "timestamp": "2023-01-01"}
)
5. Validation Exceptions¶
Used for configuration and parameter validation.
ConfigValidationError¶
Raised for invalid configuration.
from rustybt.exceptions import ConfigValidationError
# Example usage
raise ConfigValidationError(
"Missing required field",
field="api_key",
context={"config_file": "config.yaml"}
)
Error Handling Utilities¶
RustyBT provides utilities for common error handling patterns in rustybt.utils.error_handling.
Retry with Exponential Backoff¶
Automatically retry operations on transient failures:
from rustybt.utils.error_handling import retry_async
from rustybt.exceptions import BrokerConnectionError, BrokerRateLimitError
async def submit_order():
# Your order submission logic
pass
# Retry with exponential backoff
result = await retry_async(
submit_order,
retry_exceptions=(BrokerConnectionError, BrokerRateLimitError),
max_attempts=5,
base_delay=1.0,
max_delay=30.0,
backoff_factor=2.0,
jitter=0.25,
context={"operation": "submit_order", "asset": "AAPL"}
)
Parameters:
- retry_exceptions: Tuple of exceptions that should trigger retry
- max_attempts: Maximum retry attempts (including first try)
- base_delay: Initial delay in seconds
- max_delay: Maximum delay cap
- backoff_factor: Exponential multiplier (default: 2.0)
- jitter: Randomization factor (0.0-1.0)
- context: Additional logging context
User vs. Developer Messages¶
Provide different messages for users and developers:
from rustybt.utils.error_handling import render_user_message, render_developer_context
from rustybt.exceptions import BrokerConnectionError
try:
# Connect to broker
pass
except BrokerConnectionError as exc:
# User sees clean message
user_msg = render_user_message(exc)
print(f"Error: {user_msg}")
# Output: "Failed to connect"
# Developer sees full context
dev_context = render_developer_context(exc)
logger.error("connection_failed", **dev_context)
# Logs: {"error": "BrokerConnectionError", "broker": "Binance", ...}
Structured Exception Logging¶
Log exceptions with full context:
from rustybt.utils.error_handling import log_exception
from rustybt.exceptions import DataNotFoundError
try:
# Fetch data
pass
except DataNotFoundError as exc:
log_exception(
exc,
level="warning",
extra={"user_id": "user123", "session": "abc"}
)
Flatten Multiple Exceptions¶
Combine context from multiple exceptions:
from rustybt.utils.error_handling import flatten_exceptions
errors = [
BrokerConnectionError("Connection failed", broker="Binance"),
DataNotFoundError("Data missing", asset="BTC"),
]
context = flatten_exceptions(errors)
logger.error("multiple_failures", **context)
# Output: {
# "error_1": "BrokerConnectionError",
# "error_1_broker": "Binance",
# "error_2": "DataNotFoundError",
# "error_2_asset": "BTC"
# }
Best Practices¶
1. Always Provide Context¶
# ❌ Bad: No context
raise BrokerConnectionError("Connection failed")
# ✅ Good: Rich context
raise BrokerConnectionError(
"Connection failed",
broker="Binance",
context={"host": "api.binance.com", "error_code": "ETIMEDOUT"}
)
2. Use Specific Exceptions¶
# ❌ Bad: Generic exception
raise BrokerError("Order rejected")
# ✅ Good: Specific exception
raise OrderRejectedError(
"Order rejected",
order_id="ORD123",
reason="Insufficient margin"
)
3. Link Causes¶
# ✅ Good: Preserve exception chain
try:
response = requests.get(url)
except requests.RequestException as e:
raise BrokerConnectionError(
"Failed to connect",
broker="Binance",
cause=e
)
4. Log Before Raising¶
from rustybt.utils.error_handling import log_exception
try:
# Operation
pass
except BrokerConnectionError as exc:
log_exception(exc, level="error")
raise
5. Catch Specific, Raise Specific¶
# ✅ Good: Catch and raise specific exceptions
try:
data = fetch_data(asset)
except BrokerRateLimitError as e:
logger.warning("rate_limited", **e.to_log_fields())
# Wait and retry
await asyncio.sleep(60)
data = fetch_data(asset)
except BrokerConnectionError as e:
logger.error("connection_failed", **e.to_log_fields())
raise
6. Graceful Degradation¶
from rustybt.utils.error_handling import log_exception
try:
# Try primary data source
data = fetch_from_primary(asset)
except DataAdapterError as exc:
log_exception(exc, level="warning")
# Fall back to secondary source
data = fetch_from_fallback(asset)
Testing Exception Handling¶
All exception handling should be thoroughly tested:
import pytest
from rustybt.exceptions import OrderRejectedError
def test_order_rejection():
"""Test order rejection handling."""
with pytest.raises(OrderRejectedError) as exc_info:
submit_invalid_order()
assert exc_info.value.context["order_id"] is not None
assert "rejected" in str(exc_info.value).lower()
Migration from Legacy Exceptions¶
If you have legacy code using standard Python exceptions, migrate gradually:
# Old code
raise ValueError("Invalid order amount")
# New code
from rustybt.exceptions import InvalidOrderError
raise InvalidOrderError(
"Invalid order amount",
parameter="amount",
value=-100
)