Initial commit: Oil Trading Bot (MT5, WTI)

Headless FastAPI-Backend (server.py + core/engine.py) mit Mobile-PWA (web/),
Strategie-/Backtest-Suite und Doku. Secrets, DB, Logs und Laufzeit-State sind
via .gitignore ausgeschlossen; Config-Vorlage: oil_widget_config.ini.example.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Axel Hocks
2026-07-24 08:29:23 +02:00
co-authored by Claude Opus 4.8
commit 75d28827e8
104 changed files with 21059 additions and 0 deletions
+88
View File
@@ -0,0 +1,88 @@
"""
core/logger.py — Zentralisiertes Logging
==========================================
Schreibt sowohl in Konsole als auch in eine rotierende Logdatei.
Wird von allen anderen Modulen über `from core.logger import log` genutzt.
Verwendung:
log.info("Trade BUY 0.5L @ 74.30")
log.warning("Slow API response")
log.error("Order fehlgeschlagen", exc_info=True)
log.debug("Detail-Info") # nur bei DEBUG-Level sichtbar
Vorteile gegenüber print():
- Persistente Datei-Logs (überleben Crashes / pythonw)
- Log-Rotation (max 5 MB pro Datei, 5 Backups)
- Filterbar nach Komponente (logger.getChild('trade'))
- Zeit + Level pro Eintrag
"""
from __future__ import annotations
import logging
import sys
from logging.handlers import RotatingFileHandler
from pathlib import Path
# ── Konfiguration ──────────────────────────────
LOG_FILE = Path(__file__).parent.parent / "oil_widget.log"
LOG_MAX_BYTES = 5_000_000
LOG_BACKUP_COUNT = 5
# Format mit Komponente, Level, Zeit
_FILE_FORMAT = "%(asctime)s [%(levelname)-7s] %(name)-14s | %(message)s"
_CONSOLE_FORMAT = "[%(name)s] %(message)s"
def _setup_logger(level: int = logging.INFO) -> logging.Logger:
"""Erstellt den Root-Logger 'oil' mit Datei- und Konsolen-Handler."""
logger = logging.getLogger("oil")
logger.setLevel(logging.DEBUG) # Detail-Filter erfolgt pro Handler
logger.propagate = False
# Doppelte Handler vermeiden (z.B. bei Reload)
if logger.handlers:
return logger
# Datei-Handler komplettes Detail
try:
fh = RotatingFileHandler(
LOG_FILE, maxBytes=LOG_MAX_BYTES,
backupCount=LOG_BACKUP_COUNT, encoding="utf-8")
fh.setLevel(logging.DEBUG)
fh.setFormatter(logging.Formatter(_FILE_FORMAT,
datefmt="%Y-%m-%d %H:%M:%S"))
logger.addHandler(fh)
except Exception as e:
# Fallback: kein File-Log möglich (z.B. Read-Only-FS)
sys.stderr.write(f"[Logger] Datei-Logging fehlgeschlagen: {e}\n")
# Konsolen-Handler kompakt
ch = logging.StreamHandler(sys.stdout)
ch.setLevel(level)
ch.setFormatter(logging.Formatter(_CONSOLE_FORMAT))
logger.addHandler(ch)
return logger
# Globaler Root-Logger einmalig instanziiert
log = _setup_logger()
def get_logger(component: str) -> logging.Logger:
"""
Liefert einen Sub-Logger pro Komponente, z.B. 'trade', 'mt5', 'ai'.
Erscheint im Log dann als 'oil.trade'.
"""
return log.getChild(component)
def set_console_level(level: str | int):
"""Erlaubt Runtime-Änderung des Konsolen-Log-Levels."""
if isinstance(level, str):
level = getattr(logging, level.upper(), logging.INFO)
for h in log.handlers:
if isinstance(h, logging.StreamHandler) and not isinstance(
h, RotatingFileHandler):
h.setLevel(level)