Initial commit: Quantum Terminal — Free & Open Source Trading Platform
This commit is contained in:
@@ -0,0 +1,173 @@
|
||||
"""
|
||||
================================================================================
|
||||
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),
|
||||
]
|
||||
Reference in New Issue
Block a user