gcloud-lab/apps/vwap-monitor/app/scanner.py
Hermes Agent 6183159974 feat: add Prometheus, Grafana, and Tailscale monitoring stack
- Install Prometheus + Grafana via kube-prometheus-stack (ClusterIP only, no public ingress)
- Deploy Tailscale Operator for secure VPN access to internal services
- Add CNPG/PostgreSQL monitoring dashboards
- Add vLLM inference monitoring dashboards (tokens, latency, GPU)
- Add Cilium networking dashboards (policy, traffic, drops)
- Update infra-controllers staging kustomization to include all controllers
- Add monitoring-configs Flux sync for dashboard deployment
- Update README with monitoring architecture and access instructions
- Remove broken stale monitoring files (Azure Key Vault refs, wrong domains)

Access: kubectl port-forward or Tailscale VPN (replace auth key before deploy)
2026-04-26 02:31:53 +00:00

205 lines
7.3 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env python3
"""
VWAP Wave Breach Scanner
========================
Continuous monitoring daemon. Scans instruments on a schedule,
detects VWAP wave breaches, and pushes alerts via Telegram.
Environment variables:
TELEGRAM_BOT_TOKEN — Telegram Bot API token (required)
TELEGRAM_CHAT_ID — Chat ID to send alerts to (required)
SCAN_INTERVAL_SEC — Seconds between scans (default: 120)
BREACH_THRESHOLD — Sigma threshold for alerts (default: 2.0)
No LLM overhead — pure Python, ~20MB RAM.
"""
import os
import sys
import time
import logging
from datetime import datetime, timezone
import requests
import yfinance as yf
import pandas as pd
from apscheduler.schedulers.background import BlockingScheduler
# ── Config ──────────────────────────────────────────────────────────────────
TELEGRAM_BOT_TOKEN = os.environ["TELEGRAM_BOT_TOKEN"]
TELEGRAM_CHAT_ID = os.environ["TELEGRAM_CHAT_ID"]
SCAN_INTERVAL_SEC = int(os.environ.get("SCAN_INTERVAL_SEC", "120"))
BREACH_THRESHOLD = float(os.environ.get("BREACH_THRESHOLD", "2.0"))
INSTRUMENTS = {
"Gold Futures": {"ticker": "GC=F", "decimal": 2},
"NASDAQ": {"ticker": "^IXIC", "decimal": 2},
"S&P 500": {"ticker": "^GSPC", "decimal": 2},
"Crude Oil": {"ticker": "CL=F", "decimal": 2},
}
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s %(levelname)-5s %(message)s",
datefmt="%Y-%m-%d %H:%M:%S",
)
log = logging.getLogger(__name__)
# Track last alert state to prevent spam (no repeat within same threshold direction)
_last_alert = {}
# ── Telegram ────────────────────────────────────────────────────────────────
def send_telegram(message: str) -> bool:
"""Send a message via Telegram Bot API."""
url = f"https://api.telegram.org/bot{TELEGRAM_BOT_TOKEN}/sendMessage"
payload = {
"chat_id": TELEGRAM_CHAT_ID,
"text": message,
"parse_mode": "Markdown",
"disable_web_page_preview": True,
}
try:
resp = requests.post(url, json=payload, timeout=10)
resp.raise_for_status()
log.info("Telegram alert sent: %s", message[:80])
return True
except Exception as e:
log.error("Telegram send failed: %s", e)
return False
# ── VWAP Calculation ────────────────────────────────────────────────────────
def compute_vwap(data: pd.DataFrame) -> dict | None:
"""Compute cumulative VWAP, σ, and deviation."""
df = data.copy()
df["typical"] = (df["High"] + df["Low"] + df["Close"]) / 3.0
df["tp_vol"] = df["typical"] * df["Volume"]
cum_tp_vol = df["tp_vol"].cumsum()
cum_vol = df["Volume"].cumsum().replace(0, 1)
df["cum_vwap"] = cum_tp_vol / cum_vol
df["deviation"] = df["typical"] - df["cum_vwap"]
df["cum_var"] = (df["deviation"] ** 2).cumsum() / cum_vol
df["sigma"] = df["cum_var"] ** 0.5
last = df.iloc[-1]
if last["sigma"] <= 0:
return None
return {
"price": last["Close"],
"vwap": last["cum_vwap"],
"sigma": last["sigma"],
"dev_sigmas": (last["Close"] - last["cum_vwap"]) / last["sigma"],
}
def fetch_data(ticker: str) -> pd.DataFrame:
"""Fetch recent intraday data via yfinance."""
try:
data = yf.Ticker(ticker).history(period="1d", interval="1m", auto_adjust=True)
except Exception:
data = pd.DataFrame()
if len(data) < 30:
data = yf.Ticker(ticker).history(period="5d", interval="1m", auto_adjust=True)
cutoff = pd.Timestamp.now(tz=data.index.tz) - pd.Timedelta(hours=24)
data = data[data.index >= cutoff]
return data
# ── Scanner ─────────────────────────────────────────────────────────────────
def run_scan() -> None:
"""Execute a full scan cycle and push any breaches."""
ts = datetime.now(timezone.utc).strftime("%H:%M:%S UTC")
log.info("── Scan %s ──", ts)
breaches = []
for name, cfg in INSTRUMENTS.items():
try:
data = fetch_data(cfg["ticker"])
if data.empty or len(data) < 20:
log.warning("SKIP %s — insufficient data (%d bars)", name, len(data))
continue
info = compute_vwap(data)
if info is None:
log.warning("SKIP %s — zero sigma", name)
continue
d = cfg["decimal"]
dev = info["dev_sigmas"]
log.info(" %-14s $%10.2f | VWAP $%10.2f | %+.2fσ", name, info["price"], info["vwap"], dev)
if abs(dev) >= BREACH_THRESHOLD:
# Prevent repeat spam: only alert if state changed
alert_key = f"{name}:{dev > 0}"
if _last_alert.get(alert_key) == "breach":
log.info("%s already in breach, skipping repeat", name)
continue
breaches.append((name, cfg["decimal"], dev, info["price"], info["vwap"], info["sigma"]))
_last_alert[alert_key] = "breach"
else:
_last_alert[f"{name}:True"] = "clean"
_last_alert[f"{name}:False"] = "clean"
except Exception as e:
log.error("ERROR %s: %s", name, e)
# Push alerts
for name, d, dev, price, vwap, sigma in breaches:
direction = "⬆️ UP" if dev > 0 else "⬇️ DOWN"
band_label = f"±{int(abs(dev))}σ"
severity = "🚨 **EXTREME**" if abs(dev) >= 3.0 else "⚡ **BREACH**"
msg = (
f"{severity} — VWAP Wave Alert\n\n"
f"**{name}** broke through **{band_label}** band\n"
f"Deviation: **{dev:+.2f}σ**\n"
f"Price: **${price:.{d}f}** | VWAP: **${vwap:.{d}f}**\n"
f"σ: ${sigma:.{d}f} | {direction}\n\n"
f"_at {ts}_"
)
send_telegram(msg)
# ── Main ────────────────────────────────────────────────────────────────────
def main() -> None:
log.info("=" * 60)
log.info(" VWAP Wave Breach Scanner")
log.info(" Interval: %d sec | Threshold: ±%.1fσ", SCAN_INTERVAL_SEC, BREACH_THRESHOLD)
log.info(" Telegram: @chat_id=%s", TELEGRAM_CHAT_ID)
log.info("=" * 60)
# Validate Telegram connectivity
send_telegram(
"🟢 *VWAP Breach Scanner* is online.\n"
f"Scanning every **{SCAN_INTERVAL_SEC}s** — threshold ±**{BREACH_THRESHOLD:.1f}σ**\n"
f"Monitoring: Gold, NASDAQ, S&P 500, Crude Oil"
)
scheduler = BlockingScheduler()
scheduler.add_job(run_scan, "interval", seconds=SCAN_INTERVAL_SEC, id="scan")
# Run immediately on start
run_scan()
log.info("Scanner running. Press Ctrl+C to stop.")
try:
scheduler.start()
except KeyboardInterrupt:
log.info("Shutting down...")
scheduler.shutdown()
if __name__ == "__main__":
main()