Files

173 lines
6.0 KiB
Python
Raw Permalink Normal View History

"""
================================================================================
Quantum Terminal — Data Server Configuration
================================================================================
Central config for the FastAPI data server (Phase 3C).
All paths are relative to PROJECT_ROOT for portability (Rule 3).
MT5 terminal path is read from local_config.ini (gitignored, Rule 3).
Universe must match the orchestrator's ASSET_UNIVERSE.
Usage:
from server_config import ServerConfig, PROJECT_ROOT
config = ServerConfig()
================================================================================
"""
import configparser
from pathlib import Path
from dataclasses import dataclass, field
from typing import List, Optional, Dict
# ── Portable project root (Rule 3) ──
PROJECT_ROOT = Path(__file__).resolve().parent
# ── Load local_config.ini for machine-specific values (Rule 3) ──
def _load_mt5_path() -> Optional[str]:
"""
Read MT5 terminal path from local_config.ini.
Falls back to None (MT5 auto-detect) if file or key is missing.
Expected format in local_config.ini:
[mt5]
terminal_path = C:/Program Files/MetaTrader 5/terminal64.exe
"""
ini_path = PROJECT_ROOT / "local_config.ini"
if not ini_path.exists():
return None
cp = configparser.ConfigParser()
cp.read(str(ini_path))
return cp.get("mt5", "terminal_path", fallback=None)
MT5_TERMINAL_PATH = _load_mt5_path()
# ── Asset universe — must stay in sync with orchestrator.py ──
ASSET_UNIVERSE = [
# Forex
"EURUSD", "GBPUSD", "USDJPY", "AUDUSD", "USDCHF", "USDCAD",
# Indices
"US500", "USTEC", "GER40", "UK100",
# Commodities
"XAUUSD", "XAGUSD", "XTIUSD",
]
# ── Symbol alias map ──
# Canonical name → list of possible broker names to try (in order).
# The adapter tries each until one resolves in the connected MT5 terminal.
# Only needed for symbols whose canonical name doesn't match the broker.
# Forex pairs (EURUSD etc.) are universal and don't need aliases.
SYMBOL_ALIASES: Dict[str, List[str]] = {
"US500": ["US500", "US500.cash", "SPX500", "SP500", "US500Cash", "USA500"],
"USTEC": ["USTEC", "USTEC.cash", "NAS100", "NSDQ100", "USTECCash", "USTECH"],
"GER40": ["GER40", "GER40.cash", "DAX40", "DE40", "GER40Cash", "GDAXI"],
"UK100": ["UK100", "UK100.cash", "FTSE100", "UK100Cash"],
"XTIUSD": ["XTIUSD", "USOIL", "WTI", "XTIUSD.cash", "USOIL.cash", "CL"],
"XBRUSD": ["XBRUSD", "UKOIL", "BRENT", "XBRUSD.cash", "UKOIL.cash"],
"XAUUSD": ["XAUUSD", "GOLD", "XAUUSD.cash"],
"XAGUSD": ["XAGUSD", "SILVER", "XAGUSD.cash"],
}
# ── MT5 timeframe mapping (string → MT5 constant) ──
# These are the actual MetaTrader5 Python API constants.
# M1-M30 happen to equal their minute value, but H1+ use encoded values.
MT5_TIMEFRAMES = {
"M1": 1, # mt5.TIMEFRAME_M1
"M5": 5, # mt5.TIMEFRAME_M5
"M15": 15, # mt5.TIMEFRAME_M15
"M30": 30, # mt5.TIMEFRAME_M30
"H1": 16385, # mt5.TIMEFRAME_H1
"H4": 16388, # mt5.TIMEFRAME_H4
"D1": 16408, # mt5.TIMEFRAME_D1
"W1": 32769, # mt5.TIMEFRAME_W1
}
# ── Decimal precision per asset class ──
ASSET_DECIMALS = {
# Forex — 5 decimal places (pipettes)
"EURUSD": 5, "GBPUSD": 5, "AUDUSD": 5, "USDCHF": 5, "USDCAD": 5,
"USDJPY": 3,
# Indices — 2 decimal places
"US500": 2, "USTEC": 2, "GER40": 2, "UK100": 2,
# Commodities
"XAUUSD": 2, "XAGUSD": 3, "XTIUSD": 2,
}
# ── Asset class classification ──
ASSET_CLASSES = {
"EURUSD": "FX", "GBPUSD": "FX", "USDJPY": "FX",
"AUDUSD": "FX", "USDCHF": "FX", "USDCAD": "FX",
"US500": "INDEX", "USTEC": "INDEX", "GER40": "INDEX", "UK100": "INDEX",
"XAUUSD": "COMMODITY", "XAGUSD": "COMMODITY",
"XTIUSD": "COMMODITY",
# Futures (Rithmic)
"ES": "FUTURES", "NQ": "FUTURES", "YM": "FUTURES",
"GC": "FUTURES", "SI": "FUTURES", "CL": "FUTURES", "BZ": "FUTURES",
}
@dataclass
class ServerConfig:
"""
Master configuration for the data server.
All file paths are relative to PROJECT_ROOT.
MT5 terminal path comes from local_config.ini (Rule 3).
"""
# ── Network ──
host: str = "127.0.0.1"
port: int = 8501
# ── MT5 ──
mt5_terminal_path: Optional[str] = field(default_factory=lambda: MT5_TERMINAL_PATH)
# ── Universe ──
universe: List[str] = field(default_factory=lambda: list(ASSET_UNIVERSE))
# ── MT5 polling intervals (seconds) ──
tick_interval: float = 1.0 # How often to poll latest ticks
bar_check_interval: float = 5.0 # How often to check for new closed bars
# ── State file paths (relative to PROJECT_ROOT) ──
terminal_payload: str = "terminal_payload.json"
daily_signals: str = "daily_signals.json"
signal_lifecycle: str = "signal_lifecycle.json"
weekly_state: str = "weekly_state.json"
bands_data_dir: str = "bands_data"
# ── Display defaults ──
default_timeframe: str = "M15"
default_lookback_bars: int = 200
# ── Frontend CORS ──
cors_origins: List[str] = field(default_factory=lambda: [
"http://localhost:3000",
"http://127.0.0.1:3000",
])
# ── Reconnection ──
mt5_reconnect_delay: float = 5.0 # Seconds between reconnect attempts
mt5_max_reconnect_attempts: int = 10 # Then log error, keep trying
# ── File watcher ──
file_watch_debounce: float = 1.0 # Seconds to debounce rapid file changes
def resolve_path(self, relative: str) -> Path:
"""Resolve a config path relative to PROJECT_ROOT."""
return PROJECT_ROOT / relative
@property
def watched_files(self) -> List[Path]:
"""All state files that the file watcher should monitor."""
return [
self.resolve_path(self.terminal_payload),
self.resolve_path(self.daily_signals),
self.resolve_path(self.signal_lifecycle),
self.resolve_path(self.weekly_state),
]