Files
momentum-system/config_monitoring/alert_snapshot_interval.py
T

188 lines
7.2 KiB
Python

#!/usr/bin/env python3
"""
Config Monitoring Script: Alerts on suboptimal quote snapshot intervals
Purpose: Detect overtrading risk when QUOTE_SNAPSHOT_INTERVAL_SECONDS < 30s
Author: momentum-system team
"""
import os
import yaml
import re
from datetime import datetime, timezone
from pathlib import Path
# Discord webhook/notification endpoint (configure as needed)
DISCORD_WEBHOOK_URL = os.getenv("DISCORD_WEBHOOK_URL", "")
PROJECT_CHANNEL_ID = os.getenv("MOMENTUM_PROJECT_CHANNEL", "1537839209954541658")
# Optimal ranges
MIN_SNAPSHOT_INTERVAL_SECONDS = 30 # Below this triggers alert
MAX_SNAPSHOT_INTERVAL_SECONDS = 120 # Above this may miss signals
def parse_env_file(env_path: str) -> dict:
"""Parse .env or Docker Compose environment section."""
config = {}
if not os.path.exists(env_path):
return config
with open(env_path, 'r') as f:
for line in f:
line = line.strip()
if line and not line.startswith('#'):
if '=' in line:
key, _, value = line.partition('=')
config[key.strip()] = value.strip()
return config
def load_docker_compose(compose_path: str) -> dict:
"""Load Docker Compose file and extract environment variables."""
if not os.path.exists(compose_path):
return {}
with open(compose_path, 'r') as f:
try:
return yaml.safe_load(f)
except yaml.YAMLError:
return {}
def check_snapshot_interval(env_config: dict) -> tuple[bool, float]:
"""
Check if snapshot interval is within optimal range.
Returns: (is_valid, current_value)
"""
interval_str = env_config.get("QUOTE_SNAPSHOT_INTERVAL_SECONDS", "60")
try:
interval = int(interval_str)
is_valid = MIN_SNAPSHOT_INTERVAL_SECONDS <= interval <= MAX_SNAPSHOT_INTERVAL_SECONDS
return is_valid, interval
except (ValueError, TypeError):
return False, float('nan')
def main():
"""Main monitoring logic."""
# Check multiple possible config locations
config_paths = [
"/opt/data/workspace/momentum-system/.env",
"/mnt/Volume/Storage/Apps/momentum/compose.yaml",
"/etc/docker-compose.yml",
]
env_config = {}
found_valid_config = False
for config_path in config_paths:
if os.path.exists(config_path):
found_valid_config = True
print(f"Checking config at: {config_path}")
try:
# Check both .env file format and Docker Compose YAML
env_config = parse_env_file(config_path)
if "QUOTE_SNAPSHOT_INTERVAL_SECONDS" not in env_config:
# Try to extract from yaml content if it's a compose file
compose_data = load_docker_compose(config_path)
if compose_data and 'services' in compose_data:
for service_name in ["dashboard", "loop"]:
service = compose_data['services'].get(service_name, {})
service_env = service.get('environment', {})
# Extract value from environment string like 'VAR=value'
interval_value = None
for env_var in service_env:
if env_var.startswith("QUOTE_SNAPSHOT_INTERVAL_SECONDS="):
parts = env_var.split('=', 1)
if len(parts) == 2:
interval_value = parts[1].strip().strip("'\"")
break
if interval_value:
env_config["QUOTE_SNAPSHOT_INTERVAL_SECONDS"] = interval_value
except Exception as e:
print(f"Error parsing config: {e}")
continue
if not found_valid_config:
print("No configuration file found at expected paths")
# Check via Docker compose volumes
try:
import subprocess
result = subprocess.run(
["docker", "compose", "config"],
capture_output=True,
text=True,
cwd="/mnt/Volume/Storage/Apps/momentum"
)
if result.returncode == 0:
env_config = {
line.strip().split('=', 1): val.strip()
for line in result.stdout.split('\n')
if '=' in line and not line.startswith('#')
}
except Exception as e:
print(f"Could not load via docker compose config: {e}")
env_config = {}
# Check snapshot interval
if not env_config or "QUOTE_SNAPSHOT_INTERVAL_SECONDS" not in env_config:
print("QUOTE_SNAPSHOT_INTERVAL_SECONDS not found in configuration")
return
is_valid, interval = check_snapshot_interval(env_config)
current_time = datetime.now(timezone.utc).strftime("%Y-%m-%d %H:%M:%S UTC")
if is_valid:
print(f"✅ OK ({current_time}): QUOTE_SNAPSHOT_INTERVAL_SECONDS={interval}s")
print(f" Current interval is within optimal range [{MIN_SNAPSHOT_INTERVAL_SECONDS}-{MAX_SNAPSHOT_INTERVAL_SECONDS}s]")
# Log to audit file
log_path = Path("/opt/data/workspace/momentum-system/logs/config_audit.log")
log_path.parent.mkdir(parents=True, exist_ok=True)
with open(log_path, 'a') as f:
f.write(f"{current_time}|STATUS=OK|VALUE={interval}s\n")
else:
print(f"🚨 ALERT ({current_time}): QUOTE_SNAPSHOT_INTERVAL_SECONDS={interval}s")
print(f" Current interval is below minimum of {MIN_SNAPSHOT_INTERVAL_SECONDS}s")
print(f" Risk: OVERTRADING during volatile opens!")
# Log to audit file
log_path = Path("/opt/data/workspace/momentum-system/logs/config_audit.log")
log_path.parent.mkdir(parents=True, exist_ok=True)
with open(log_path, 'a') as f:
f.write(f"{current_time}|STATUS=ALERT|VALUE={interval}s|MIN={MIN_SNAPSHOT_INTERVAL_SECONDS}s|MAX={MAX_SNAPSHOT_INTERVAL_SECONDS}s\n")
# Send Discord notification if webhook configured
if DISCORD_WEBHOOK_URL and interval < MIN_SNAPSHOT_INTERVAL_SECONDS:
try:
import requests
message = (
f"🚨 **Momentum Bot Config Alert**\n\n"
f"⏱️ Snapshot Interval Too Fast\n\n"
f":warning: Current: `{interval}s`\n"
f":white_check_mark: Required: `>= {MIN_SNAPSHOT_INTERVAL_SECONDS}s`\n\n"
f"🔗 View Config:\n`/mnt/Volume/Storage/Apps/momentum/compose.yaml`\n\n"
f"💡 Impact:\n- Overtrading during volatile opens\n- Phantom signals from noise\n- Poor entry timing\n"
)
requests.post(DISCORD_WEBHOOK_URL, json={"content": message})
print(f"\n📢 Discord notification sent")
except Exception as e:
print(f"Could not send Discord notification: {e}")
if __name__ == "__main__":
main()