Verdict from the 3-week signal log (backtest/research/news_sentiment_value_2026-09-04.md): news_sentiment is a trailing-24h echo of price (r +0.67 with the past day, +0.03 with the next; most bullish 2 h after the 08-25 top, bearish 44 h later; most bearish at the 09-02 low, bullish 16 h later). Per-headline LLM labels are correct; the 12 h pool and the averaging are what destroy the information. Both gating flags stay False. - news_engine: _LLM_SUMMARY_SYSTEM rewritten for someone who has never traded (no acronyms, two worked examples); summary payload newest-first with the platform's own gold direction; reply cap 300 -> 400 chars. - config: headline_max_age_seconds 43200 -> 14400; RSS items without a parsable pubDate are dropped (they used to be stamped "now" and never aged out). - news_engine.get_fundamental_context drops headlines whose every relevant score is inside +-0.1 (_is_neutral_headline) before the per-currency mean; news_sentiment from this date is a mean over directional <=4 h headlines (log comparisons split here). - static: dashboard copy for the 4 h pool; news_summary.php prompt gets the same style rule. - analyze_signal_log: --news (under --stored-candles) prints sentiment lead/lag with split halves, sentiment at the window high/low with flip lags, and outcome buckets by direction x news x macro stance; LogRow gains news_sentiment/event_risk; _row_index shared. - tests: 406 -> 414 (age window, undated drop, neutral filter, summary prompt/payload/cap, --news helpers, load_rows round-trip); RSS fixtures now carry a pubDate. - docs: user/beginner guides, CLAUDE.md, deploy README. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0169BgFtWjp1SEAa5392Q9jH
6.8 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 four files there:
dashboard.html, 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 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), same scheme as the bot.
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-copiesstatic/dashboard.htmlandstatic/news_summary.phpinto 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). - Restart window: for the first minutes after a daemon restart the new page can meet a pre-upgrade news.json without headline ids — those rows simply render non-clickable until the first news publish.
- Credentials: uses the same env/.env/config.json resolution as the rest of the platform (Oanda candles feed; news needs no key; DeepSeek optional).