diff --git a/AGENTS.md b/AGENTS.md
index b0f02e7..c51e59e 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -131,6 +131,10 @@ tail -100 ~/mt5-docker/config/.wine/drive_c/Program\ Files/MetaTrader\ 5/MQL5/Lo
- **Largest Win:** $8,091
- **Largest Loss:** -$805
+## DevOps / Infrastructure
+
+Server infrastructure details saved in: `/home/garfield/devops/INFRASTRUCTURE.md`
+
## Conversation History
Before working on this codebase, read:
@@ -138,6 +142,7 @@ Before working on this codebase, read:
conversation-history/2026-03-21-mql-trading-bots.md
conversation-history/2026-03-29-session-save.md
conversation-history/2026-03-30-weekend-gap-short-signal-fix.md
+conversation-history/2026-03-30-dns-whitelist.md
```
## Notes for AI Agents
diff --git a/n8n-workflow-http.json b/n8n-workflow-http.json
new file mode 100644
index 0000000..ea07fb6
--- /dev/null
+++ b/n8n-workflow-http.json
@@ -0,0 +1,89 @@
+{
+ "name": "MQL Settings Monitor - HTTP Version",
+ "nodes": [
+ {
+ "parameters": {
+ "rule": {
+ "interval": [
+ {
+ "field": "hours",
+ "hoursInterval": 6
+ }
+ ]
+ }
+ },
+ "name": "Every 6 Hours",
+ "type": "n8n-nodes-base.scheduleTrigger",
+ "typeVersion": 1,
+ "position": [250, 300]
+ },
+ {
+ "parameters": {
+ "requestMethod": "POST",
+ "url": "http://localhost:8080/validate",
+ "options": {
+ "timeout": 30000
+ }
+ },
+ "name": "Call Validation API",
+ "type": "n8n-nodes-base.httpRequest",
+ "typeVersion": 1,
+ "position": [450, 300]
+ },
+ {
+ "parameters": {
+ "conditions": {
+ "string": [
+ {
+ "value1": "={{ $json.status }}",
+ "operation": "equal",
+ "value2": "error"
+ }
+ ]
+ }
+ },
+ "name": "Has Issues?",
+ "type": "n8n-nodes-base.if",
+ "typeVersion": 1,
+ "position": [650, 300]
+ },
+ {
+ "parameters": {
+ "chatId": "={{ $env.TELEGRAM_CHAT_ID }}",
+ "text": "=šØ MQL Settings Alert:\n\n{{ $json.message }}\n\nIssues: {{ $json.issues }}\n\nā° {{ new Date().toISOString() }}"
+ },
+ "name": "Send Telegram",
+ "type": "n8n-nodes-base.telegram",
+ "typeVersion": 1,
+ "position": [850, 200]
+ },
+ {
+ "parameters": {},
+ "name": "No Action",
+ "type": "n8n-nodes-base.noOp",
+ "typeVersion": 1,
+ "position": [850, 400]
+ }
+ ],
+ "connections": {
+ "Every 6 Hours": {
+ "main": [
+ [{ "node": "Call Validation API", "type": "main", "index": 0 }]
+ ]
+ },
+ "Call Validation API": {
+ "main": [
+ [{ "node": "Has Issues?", "type": "main", "index": 0 }]
+ ]
+ },
+ "Has Issues?": {
+ "main": [
+ [{ "node": "Send Telegram", "type": "main", "index": 0 }],
+ [{ "node": "No Action", "type": "main", "index": 0 }]
+ ]
+ }
+ },
+ "settings": {},
+ "staticData": null,
+ "tags": []
+}
diff --git a/n8n-workflow-ssh.json b/n8n-workflow-ssh.json
new file mode 100644
index 0000000..92f98ad
--- /dev/null
+++ b/n8n-workflow-ssh.json
@@ -0,0 +1,98 @@
+{
+ "name": "MQL Settings Monitor - SSH Version",
+ "nodes": [
+ {
+ "parameters": {
+ "rule": {
+ "interval": [
+ {
+ "field": "hours",
+ "hoursInterval": 6
+ }
+ ]
+ }
+ },
+ "name": "Every 6 Hours",
+ "type": "n8n-nodes-base.scheduleTrigger",
+ "typeVersion": 1,
+ "position": [250, 300]
+ },
+ {
+ "parameters": {
+ "command": "/home/garfield/mql-trading-bots/scripts/validate-settings.sh",
+ "cwd": "/home/garfield/mql-trading-bots"
+ },
+ "name": "Run Validation Script",
+ "type": "n8n-nodes-base.ssh",
+ "typeVersion": 1,
+ "position": [450, 300],
+ "credentials": {
+ "sshPassword": {
+ "id": "local-ssh",
+ "name": "Local SSH"
+ }
+ }
+ },
+ {
+ "parameters": {
+ "conditions": {
+ "number": [
+ {
+ "value1": "={{ $json.code }}",
+ "operation": "notEqual",
+ "value2": 0
+ }
+ ]
+ }
+ },
+ "name": "Issues Found?",
+ "type": "n8n-nodes-base.if",
+ "typeVersion": 1,
+ "position": [650, 300]
+ },
+ {
+ "parameters": {
+ "chatId": "={{ $env.TELEGRAM_CHAT_ID }}",
+ "text": "=šØ MQL Settings Issues:\n\n{{ $json.stdout }}\n\nā° {{ new Date().toISOString() }}"
+ },
+ "name": "Send Telegram Alert",
+ "type": "n8n-nodes-base.telegram",
+ "typeVersion": 1,
+ "position": [850, 200],
+ "credentials": {
+ "telegramApi": {
+ "id": "telegram-bot",
+ "name": "Telegram Bot"
+ }
+ }
+ },
+ {
+ "parameters": {},
+ "name": "No Issues",
+ "type": "n8n-nodes-base.noOp",
+ "typeVersion": 1,
+ "position": [850, 400]
+ }
+ ],
+ "connections": {
+ "Every 6 Hours": {
+ "main": [
+ [{ "node": "Run Validation Script", "type": "main", "index": 0 }]
+ ]
+ },
+ "Run Validation Script": {
+ "main": [
+ [{ "node": "Issues Found?", "type": "main", "index": 0 }]
+ ]
+ },
+ "Issues Found?": {
+ "main": [
+ [{ "node": "Send Telegram Alert", "type": "main", "index": 0 }],
+ [{ "node": "No Issues", "type": "main", "index": 0 }]
+ ]
+ }
+ },
+ "settings": {},
+ "staticData": null,
+ "tags": []
+}
diff --git a/scripts/setup-cron.sh b/scripts/setup-cron.sh
new file mode 100755
index 0000000..10ddcd9
--- /dev/null
+++ b/scripts/setup-cron.sh
@@ -0,0 +1,27 @@
+#!/bin/bash
+# Setup cron job for MQL settings monitoring
+
+echo "Setting up cron job for MQL settings monitoring..."
+
+# Check if python3 is available
+if ! command -v python3 &> /dev/null; then
+ echo "ERROR: python3 not found"
+ exit 1
+fi
+
+# Create cron entry
+CRON_CMD="0 */6 * * * cd /home/garfield/mql-trading-bots && python3 scripts/validate-and-notify.py >> /tmp/mql-validate.log 2>&1"
+
+# Add to crontab
+(crontab -l 2>/dev/null; echo "$CRON_CMD") | crontab -
+
+echo "Cron job added: Runs every 6 hours"
+echo "Logs: /tmp/mql-validate.log"
+echo ""
+echo "To configure Telegram notifications, set these environment variables:"
+echo " export TELEGRAM_BOT_TOKEN='your_bot_token'"
+echo " export TELEGRAM_CHAT_ID='your_chat_id'"
+echo ""
+echo "Add these to your ~/.bashrc to persist:"
+echo " echo 'export TELEGRAM_BOT_TOKEN=\"your_token\"' >> ~/.bashrc"
+echo " echo 'export TELEGRAM_CHAT_ID=\"your_id\"' >> ~/.bashrc"
diff --git a/scripts/validate-and-notify.py b/scripts/validate-and-notify.py
new file mode 100755
index 0000000..a288251
--- /dev/null
+++ b/scripts/validate-and-notify.py
@@ -0,0 +1,157 @@
+#!/usr/bin/env python3
+"""
+MQL Settings Validator with Telegram Notifications
+Run via cron: python3 validate-and-notify.py
+"""
+
+import os
+import re
+import subprocess
+import sys
+from datetime import datetime
+
+try:
+ import requests
+except ImportError:
+ print("Installing requests...")
+ subprocess.run([sys.executable, "-m", "pip", "install", "requests", "-q"])
+ import requests
+
+# Configuration
+TELEGRAM_BOT_TOKEN = os.environ.get("TELEGRAM_BOT_TOKEN", "")
+TELEGRAM_CHAT_ID = os.environ.get("TELEGRAM_CHAT_ID", "")
+REPO_DIR = "/home/garfield/mql-trading-bots"
+
+def send_telegram(message):
+ """Send message to Telegram"""
+ if not TELEGRAM_BOT_TOKEN or not TELEGRAM_CHAT_ID:
+ print("Telegram not configured. Message:")
+ print(message)
+ return False
+
+ url = f"https://api.telegram.org/bot{TELEGRAM_BOT_TOKEN}/sendMessage"
+ payload = {
+ "chat_id": TELEGRAM_CHAT_ID,
+ "text": message,
+ "parse_mode": "HTML"
+ }
+
+ try:
+ response = requests.post(url, json=payload, timeout=10)
+ return response.status_code == 200
+ except Exception as e:
+ print(f"Failed to send Telegram: {e}")
+ return False
+
+def check_set_files():
+ """Validate all .set files"""
+ issues = []
+ warnings = []
+
+ os.chdir(REPO_DIR)
+
+ # Check confluence files
+ confluence_files = [f for f in os.listdir(".") if f.startswith("confluence-") and f.endswith(".set")]
+ grid_files = [f for f in os.listdir(".") if f.startswith("grid-") and f.endswith(".set")]
+
+ print(f"Found {len(confluence_files)} confluence files, {len(grid_files)} grid files")
+
+ # Check for invalid variable names
+ invalid_pattern = re.compile(r'InpMinConfluenceStrength|MinConfluenceStrength', re.IGNORECASE)
+ for f in confluence_files:
+ with open(f, 'r') as file:
+ content = file.read()
+ if invalid_pattern.search(content):
+ issues.append(f"ā Invalid variable in {f}: InpMinConfluenceStrength (should be InpMinStrength)")
+
+ # Check for missing critical parameters
+ critical_params_conf = ['InpMinConfluence', 'InpMinStrength', 'InpMagicNumber', 'InpCloseBeforeWeekend']
+ critical_params_grid = ['InpCloseBeforeWeekend', 'InpMaxDailyDrawdown']
+
+ for f in confluence_files:
+ with open(f, 'r') as file:
+ content = file.read()
+ for param in critical_params_conf:
+ if param not in content:
+ issues.append(f"ā Missing {param} in {f}")
+
+ for f in grid_files:
+ with open(f, 'r') as file:
+ content = file.read()
+ for param in critical_params_grid:
+ if param not in content:
+ issues.append(f"ā Missing {param} in {f}")
+
+ # Check for high strength thresholds (warnings)
+ for f in confluence_files:
+ with open(f, 'r') as file:
+ content = file.read()
+ match = re.search(r'InpMinStrength=(\d+\.?\d*)', content)
+ if match:
+ strength = float(match.group(1))
+ if strength >= 0.80:
+ warnings.append(f"ā ļø High threshold in {f}: {strength} (may block trades)")
+
+ # Check debug mode
+ debug_off = 0
+ for f in confluence_files + grid_files:
+ with open(f, 'r') as file:
+ content = file.read()
+ if 'InpDebugMode=false' in content:
+ debug_off += 1
+
+ if debug_off > 5:
+ warnings.append(f"ā ļø Debug disabled in {debug_off} files - you won't see trade decisions")
+
+ return issues, warnings, len(confluence_files), len(grid_files)
+
+def main():
+ """Main function"""
+ print("=" * 50)
+ print("MQL Settings Validator")
+ print(f"Time: {datetime.now().isoformat()}")
+ print("=" * 50)
+
+ issues, warnings, conf_count, grid_count = check_set_files()
+
+ # Build report
+ report_lines = [
+ "š MQL Settings Check",
+ f"Confluence files: {conf_count}",
+ f"Grid files: {grid_count}",
+ ""
+ ]
+
+ if issues:
+ report_lines.append("ā Issues Found:")
+ report_lines.extend(issues[:10]) # Limit to 10 issues
+ if len(issues) > 10:
+ report_lines.append(f"... and {len(issues) - 10} more issues")
+ report_lines.append("")
+
+ if warnings:
+ report_lines.append("ā ļø Warnings:")
+ report_lines.extend(warnings[:5])
+ report_lines.append("")
+
+ if not issues and not warnings:
+ report_lines.append("ā
All checks passed!")
+ report_lines.append("No issues found.")
+
+ report_lines.append(f"\nā° {datetime.now().strftime('%Y-%m-%d %H:%M')}")
+
+ report = "\n".join(report_lines)
+ print("\n" + report.replace('', '').replace('', ''))
+
+ # Send notification if issues found or if it's a scheduled check
+ if issues or warnings:
+ send_telegram(report)
+ return 1
+ else:
+ # Only send success message on first run of the day
+ if datetime.now().hour == 9:
+ send_telegram(report)
+ return 0
+
+if __name__ == "__main__":
+ sys.exit(main())