Files
mt5-ai-xauusd-trader/src/core/log_config.py
T
xnessomGitHubxnessomgoogle-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com>
e52b45ab6d ⚙️ Jules02: Performance and runtime analysis — [high-throughput optimizations] (#1214)
* feat: ⚙️ Jules02: Performance and runtime analysis — high-throughput optimizations

This PR implements several key performance improvements across the trading system:

1. **Backtesting Speedup**: Replaced per-bar `profile` context managers in `BacktestEngine` with lightweight `time.perf_counter()` accumulators. This reduces loop overhead by ~40% for typical backtests while providing a more detailed execution breakdown.
2. **Database Efficiency**: Optimized the trade closure sequence in `main.py` and `TradeLogger` to eliminate redundant database queries by passing and returning ORM objects directly.
3. **Live Loop Consolidation**: Redundant technical indicators (ATR, Volatility) in the live trading loop are now extracted from already-computed feature dataframes or regime info, avoiding expensive rolling window calculations.
4. **Log Processing Optimization**: Hardened `SecretMaskingProcessor` with a primitive fast-path and in-place recursion support, significantly reducing overhead for high-frequency logging events.
5. **Hygiene**: Organized performance benchmarks into a dedicated `benchmarks/` directory.

Verified with `pytest` and custom benchmark scripts.
- Backtest (2000 bars): 0.19s -> 0.11s
- Masking primitive fast-path: ~0.3us
- All 15 relevant tests passed.

Co-authored-by: xnessom <255288032+xnessom@users.noreply.github.com>

* feat: ⚙️ Jules02: Performance and runtime analysis — high-throughput optimizations

This PR implements several key performance improvements across the trading system:

1. **Backtesting Speedup**: Replaced per-bar `profile` context managers in `BacktestEngine` with lightweight `time.perf_counter()` accumulators. This reduces loop overhead by ~40% for typical backtests while providing a more detailed execution breakdown.
2. **Database Efficiency**: Optimized the trade closure sequence in `main.py` and `TradeLogger` to eliminate redundant database queries by passing and returning ORM objects directly.
3. **Live Loop Consolidation**: Redundant technical indicators (ATR, Volatility) in the live trading loop are now extracted from already-computed feature dataframes or regime info, avoiding expensive rolling window calculations.
4. **Log Processing Optimization**: Hardened `SecretMaskingProcessor` with a primitive fast-path and correct in-place recursion support, significantly reducing overhead for high-frequency logging events.
5. **Dependency Harmony**: Resolved a version conflict between `python-socketio` and `metaapi-cloud-sdk` by pinning a compatible version.

Verified with `pytest` and custom benchmark scripts.
- Backtest (2000 bars): 0.19s -> 0.11s
- All 15 relevant tests passed.
- Linting and dependency checks pass.

Co-authored-by: xnessom <255288032+xnessom@users.noreply.github.com>

---------

Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com>
Co-authored-by: xnessom <255288032+xnessom@users.noreply.github.com>
2026-05-15 05:31:57 +00:00

167 lines
6.2 KiB
Python

"""
Security-hardened logging configuration for MT5 AI Trading Bot.
src/core/log_config.py
"""
from __future__ import annotations
import contextlib
import logging
from typing import Any
from src.core.config import TradingConfig
class SecretMaskingProcessor(logging.Filter):
"""
Unified security processor that masks sensitive values in both
structlog events and standard logging records.
Dynamically retrieves secrets from TradingConfig to ensure any
SecretStr field is never leaked to logs.
"""
def __init__(self, config: TradingConfig | None = None, mask: str = "[MASKED]") -> None:
super().__init__()
self.mask = mask
self.secrets: set[str] = set()
self.sensitive_patterns = [
"password",
"token",
"secret",
"key",
"auth",
"credential",
"private",
]
if config:
self.update_secrets(config)
def update_secrets(self, config: TradingConfig) -> None:
"""
Extract all SecretStr/SecretBytes values from the config.
Dynamically discovers all secret fields to prevent leaks as the schema evolves.
"""
# Use the class's model_fields to avoid Pydantic instance attribute warnings
if not hasattr(config.__class__, "model_fields"):
return
for field_name in config.__class__.model_fields:
val = getattr(config, field_name, None)
# Extract the raw value if it's a Pydantic Secret type
secret_val = None
if hasattr(val, "get_secret_value"):
secret_val = val.get_secret_value()
elif "Secret" in str(type(val)):
# Defensive check for other secret-like types that might not have get_secret_value
with contextlib.suppress(AttributeError, TypeError):
secret_val = val.get_secret_value()
if secret_val is not None:
if isinstance(secret_val, bytes):
secret_val = secret_val.decode("utf-8", errors="replace")
if isinstance(secret_val, str) and len(secret_val) > 3:
self.secrets.add(secret_val)
# Generic URL credential extraction: protocol://user:password@host
# This protects embedded passwords in DATABASE_URL, REDIS_URL, etc.
for secret in list(self.secrets):
if isinstance(secret, str) and "://" in secret and "@" in secret:
try:
# Extract auth part (user:password) between :// and @
# Using rsplit for @ to handle cases where @ might be in password (escaped)
auth_part = secret.split("://", 1)[1].rsplit("@", 1)[0]
if ":" in auth_part:
# Password is the part after the LAST colon in the auth section
password = auth_part.rsplit(":", 1)[1]
if password and len(password) > 3:
self.secrets.add(password)
except (IndexError, ValueError):
continue
def redact_any(self, data: Any, _in_place: bool = False) -> Any:
"""
Recursively redact secrets and sensitive fields from any data structure.
Args:
data: The data to redact.
_in_place: Whether to modify dicts/lists in-place (internal use).
"""
# 1. Fast-path for non-string primitives (numbers, bools)
if isinstance(data, (int, float, bool)) or data is None:
return data
if isinstance(data, str):
# 2. Mask known secret values in strings
if not self.secrets or len(data) < 4:
return data
result = data
for secret in self.secrets:
if secret in result:
result = result.replace(secret, self.mask)
return result
elif isinstance(data, dict):
# 3. Handle dictionaries (optimized for structlog processors)
target = data if _in_place else {}
for k, v in data.items():
is_sensitive_key = isinstance(k, str) and any(
p in k.lower() for p in self.sensitive_patterns
)
if is_sensitive_key:
if isinstance(v, (str, int, float)) or v is None:
target[k] = self.mask
else:
target[k] = self.redact_any(v, _in_place=_in_place)
else:
redacted_v = self.redact_any(v, _in_place=_in_place)
if not _in_place or redacted_v is not v:
target[k] = redacted_v
return target
elif isinstance(data, (list, tuple)):
# 4. Handle sequences
if _in_place and isinstance(data, list):
for i, item in enumerate(data):
data[i] = self.redact_any(item, _in_place=True)
return data
return type(data)(self.redact_any(v, _in_place=_in_place) for v in data)
elif isinstance(data, set):
return {self.redact_any(v, _in_place=_in_place) for v in data}
return data
def __call__(self, logger: Any, method_name: str, event_dict: dict[str, Any]) -> dict[str, Any]:
"""
Structlog-compatible processor interface.
Uses in-place modification for high-performance log processing.
"""
return self.redact_any(event_dict, _in_place=True)
def filter(self, record: logging.LogRecord) -> bool:
"""
Standard logging Filter interface.
Redacts secrets from the log message and its arguments.
"""
if isinstance(record.msg, str):
record.msg = self.redact_any(record.msg)
if record.args:
if isinstance(record.args, dict):
record.args = self.redact_any(record.args)
elif isinstance(record.args, (list, tuple)):
record.args = tuple(self.redact_any(arg) for arg in record.args)
return True
_masking_processor = SecretMaskingProcessor()
def get_masking_processor() -> SecretMaskingProcessor:
"""Retrieve the global masking processor instance."""
return _masking_processor