Files
ForexBot2/deploy/README-dashboard.md
T
Harwinder Sekhon 3afc62c847 alerts: require a trade setup, log every send, drop stale spread-gate vetoes, daily log rotation
Delivery-side fixes from the 2026-09-08 three-day signal-log + telegram-log
review. Nothing in the signal core changes: the 3-year H1 and M15 books at
$0.65 were regenerated before/after and are identical on every trade column
(H1 253 / +70.02R / PF 1.98, M15 1092 / +151.90R / PF 1.38); only the
exported `warnings` text differs, on 50/253 H1 and 358/1092 M15 trades.

- signal_aggregator: step 7 moves into `_attach_core_setup`; a final pass
  after the PB/CP archetypes (`_drop_stale_spread_gate_vetoes`) moves a core
  generator's "SPREAD GATE: ... costs would eat the edge" veto to
  `cleaned_warnings` whenever a later generator shipped a setup. The
  2026-09-08 07:45 UTC A-grade alert carried that line beside a $26.49 TP1;
  a fifth of H1 and a third of M15 A-grade alerts had been doing the same.
  The prefix is now the `_SPREAD_GATE_PREFIX` constant shared by both
  emitters and both cleanups.
- telegram_bot: alerts require a trade setup (`_is_alert_candidate`, shared
  by the scan and breach paths; no-setup candidates log
  `alert skipped: ... (gate reason)`). The 07:50 UTC B-grade SELL with no
  setup had gone out under the same warning.
- telegram_bot: every delivered alert / breach alert / violent-move heads-up
  logs an `... sent:` INFO line with the signal-log row id (only failures
  were logged before, so the review had to reconstruct sends from panel
  rows). Send fan-out is one `_broadcast` helper for both paths.
- telegram_bot: `telegram.log` rotates daily x 30 instead of 3-hourly x 240
  (old `_HH` files on the live box need one manual delete).
- signal_log / CLAUDE.md: `cleaned_warnings_json` is no longer a CP-only
  marker; docs say so.
- test_all: 468 tests (+2): stale-veto cleanup incl. the PB-shaped case and
  the both-vetoed case, and the alert-candidate rule with its skip line; the
  rotation test asserts MIDNIGHT / interval 86400 / 30 backups.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018xY4NPPrx6NJoG6QWHzwpd
2026-09-08 11:13:52 -04:00

6.7 KiB

Deploying the web dashboard (live LAMP server)

The dashboard is a static publisher: dashboard_publisher.py runs as its own daemon, scans XAU/USD at candle closes, and atomically writes dashboard.html + news_summary.php + snapshot.json + news.json into a folder Apache already serves. Apache does all web serving — no new port, no Python web server, and the telegram bot process is completely unaffected by anything that happens here (see the module docstring for the independence rules). The one dynamic piece is news_summary.php (§6): an on-demand DeepSeek summary for a clicked headline, whitelist-gated against news.json.

All steps below run on the live server (/home/Forex/ForexBot2).

1. Get the code

cd /home/Forex/ForexBot2
git pull
venv/bin/python test_all.py        # optional sanity: suite must be green

No new Python dependencies are required.

2. Make the web folder writable

The dashboard publishes straight into the portal docroot /var/www/trade/ (owner decision 2026-08-03) — it adds exactly six files there: dashboard.html, dashboard.css, dashboard.js, news_summary.php, snapshot.json, news.json (plus the apache-owned ai/ cache dir from §6). The daemon user must be able to write them:

# if the portal files can be owned by the daemon user:
sudo chown Forex: /var/www/trade
# otherwise grant write without changing ownership:
sudo setfacl -m u:Forex:rwx /var/www/trade

(A different path is one edit away: the unit's Environment=DASHBOARD_OUT_DIR= line, or --out for one-off runs.)

SELinux (RHEL/Alma/CentOS enforcing): directories under /var/www normally already carry httpd_sys_content_t, and new files inherit the parent label, so no action is usually needed. If Apache 403s despite correct permissions:

sudo restorecon -R /var/www/trade

3. Smoke-test once

venv/bin/python dashboard_publisher.py --once
ls /var/www/trade    # dashboard.html/.css/.js news_summary.php snapshot.json news.json added next to the portal

Then open http://<server>/dashboard.html (path relative to however the vhost maps /var/www/trade) — all four timeframe cards, the calendar, and the newswire should populate.

4. Install the daemon

sudo cp deploy/gold-dashboard.service /etc/systemd/system/
# edit User= if the repo owner is not "Forex"
sudo systemctl daemon-reload
sudo systemctl enable --now gold-dashboard
systemctl status gold-dashboard
tail -f logs/dashboard.log

The daemon scans all four timeframes at boot, then wakes at every M5 boundary (+10 s) and rescans whichever timeframes just closed. Logs rotate every 3 h (240 backups); the bot's own telegram.log rotates daily (30 backups) since 2026-09-08.

5. Optional Apache polish

The page polls its JSON with conditional GETs (cache: "no-cache" → Apache answers unchanged files with 304), so no Apache config is strictly needed. To be explicit anyway:

<Directory "/var/www/trade">
    <FilesMatch "\.json$">
        Header set Cache-Control "no-cache"
    </FilesMatch>
</Directory>

The page is deliberately public (owner decision 2026-08-03). To restrict it later, add standard Apache basic auth (htpasswd + Require valid-user) on that directory — no code changes needed.

6. AI summary endpoint (PHP) — one-time setup

Clicking a newswire headline opens a detail dialog that fetches an on-demand DeepSeek gold-impact summary from news_summary.php (installed next to the page by the daemon). Owner decision 2026-08-07: a real on-demand endpoint on the public page, accepted with these guards baked into the PHP — the id must match a headline in the published news.json (arbitrary text can never reach the LLM), each headline's summary is cached forever in ai/ (≤1 API call per headline, ever), and a file-based daily cap fails closed on I/O errors. Since 2026-09-04 the engine publishes only headlines up to 4 h old whose score leans one way (neutral items are dropped), so a headline leaves the whitelist sooner: a dialog left open past that point returns unknown_id ("This headline has left the live feed"); summaries already cached in ai/ stay reachable.

Requirements: php + the curl extension (php -m | grep curl).

# 1. SELinux: allow httpd outbound HTTPS — WITHOUT this every summary call
#    dies as a curl timeout even with perfect perms (RHEL-family default).
sudo setsebool -P httpd_can_network_connect 1

# 2. API key OUTSIDE the docroot, readable by Apache only:
sudo install -d -o root -g root -m 755 /etc/gold-dashboard
sudo sh -c 'umask 137; echo "sk-..." > /etc/gold-dashboard/deepseek.key'
sudo chown root:apache /etc/gold-dashboard/deepseek.key   # mode 640

# 3. apache-owned cache dir (the daemon runs as Forex and can't create it):
sudo install -d -o apache -g apache -m 755 /var/www/trade/ai
sudo semanage fcontext -a -t httpd_sys_rw_content_t '/var/www/trade/ai(/.*)?'
sudo restorecon -Rv /var/www/trade/ai

Knobs (Apache SetEnv in the vhost, both optional): AI_SUMMARY_DAILY_CAP (default 150), DEEPSEEK_KEY_FILE (default /etc/gold-dashboard/deepseek.key).

Verify from the shell — expect 400 / 404 / 200 / 200-from-cache:

curl -s "http://<server>/news_summary.php?id=zzz"                 # {"error":"bad_id"}
curl -s "http://<server>/news_summary.php?id=$(printf 'a%.0s' {1..40})"  # unknown_id
ID=$(python3 -c "import json;print(json.load(open('/var/www/trade/news.json'))['headlines'][0]['id'])")
curl -s "http://<server>/news_summary.php?id=$ID"   # real summary (2-12 s)
curl -s "http://<server>/news_summary.php?id=$ID"   # instant, from ai/<id>.json

Operational notes

  • Updating: git pull + systemctl restart gold-dashboard. The daemon re-copies static/dashboard.html, dashboard.css, dashboard.js and news_summary.php into the web folder at startup.
  • If the daemon dies, systemd restarts it in 30 s; worst case the page shows stale data with its own "updated N min ago" evidence. Apache and the telegram bot are unaffected.
  • DeepSeek: the publisher process has its own daily request cap (deepseek.daily_request_cap, default 500) separate from the bot's — expect roughly up to ~430 extra calls/day worst case, V4-Flash pricing.
  • Two independent DeepSeek meters hit the same API key: the publisher's in-process cap above, and the PHP endpoint's file cap (ai/.ht_cap.json, default 150/day). They don't communicate — worst-case spend is their sum; DeepSeek-side spend alerting is the true backstop. Kept separate on purpose (independence rule: the PHP never imports/reads bot code or config.json).
  • Credentials: uses the same env/.env/config.json resolution as the rest of the platform (Oanda candles feed; news needs no key; DeepSeek optional).