Files
ForexBot2/backtest/research/news_sentiment_value_2026-09-04.md
T
Harwinder Sekhon 509519866e news: beginner-language summary, 4 h headline window, drop neutral headlines; --news log report
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
2026-09-04 15:14:58 -04:00

130 lines
8.2 KiB
Markdown
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.
# News sentiment vs. price — does the news score carry information? — 2026-09-04
**Source evidence:** `logs/signals.db` 2026-08-13 → 2026-09-03 (14,683 rows, 13,947 with
a price; candle snapshots from 08-18, full-shape headline blobs from 08-19), read with
`mode=ro`. A continuous H1/M15/M5 candle stream was rebuilt from the stored 14-bar
`candles_json` snapshots (H1: 293 bars, 08-18 05:00 → 09-03 22:00). Headlines were
resolved through `news_snapshots` (4,054 distinct headlines, 2,024 gold-tagged).
Outcomes are the repo's own advisory model (`advisory_r_multiple` on the 144 `RESOLVED`
rows, R at $0.65). One-year context from the Oanda H1 parquet
(`backtest/cache/oanda_XAU_USD_1h_20250814_20260814.parquet`). No backtest: the
backtester runs with news off, and no headline history exists beyond these 3 weeks, so a
replay cannot test news. The same check reruns with
`python analyze_signal_log.py --stored-candles --news`.
Question asked by the owner: the log reads bullish most of the time — what did the
indicators and the news say when gold fell, and is news scoring a waste?
## Findings
**The numeric news score is a rear-view mirror.** `news_sentiment` (= mean gold-headline
score − mean dollar-headline score over the last 12 h) tracks what gold did over the
*previous* day and says nothing stable about the next one.
| hourly mean sentiment vs. gold return (n≈293 h) | Pearson |
|---|---|
| past 24 h | **+0.67** |
| past 8 h / 4 h / 1 h | +0.32 / +0.20 / +0.07 |
| next 1 h / 4 h / 8 h / 24 h | +0.05 / +0.10 / +0.21 / +0.14 |
| next 8 h, first half (08-18→25) / second half (08-26→09-03) | **−0.37 / +0.12** |
| next 24 h, first half / second half | −0.12 / −0.09 |
| daily mean vs same-day / previous-day / next-day move | +0.39 / +0.37 / **+0.03** |
| *change* in sentiment vs next 4 h / 8 h / 24 h | −0.09 / −0.03 / −0.01 |
Partial correlation with the next 8 h after removing the past-24 h return is +0.26 pooled
but flips sign between halves. Alternative aggregates (gold-specific score only, dropping
price-report headlines, only headlines < 2 h old) show the same shape: past-24 h
correlation 0.59–0.82, no stable forward correlation. A naive "hold in the direction of
sentiment" rule made +$272 over 279 hours vs +$120 buy-and-hold, but trading the *change*
in sentiment lost −$200: the gain is trend persistence (sentiment ≈ trailing return), not
news.
**Why the log reads bullish.** 62% of rows have sentiment ≥ +0.1, 21% ≤ −0.1. Component
means: gold headlines +0.158, dollar headlines +0.048. The window was a 16% August rally,
so the average headline was bullish because price had been rising. Of the gold-tagged
headlines, 42% merely restate the chart (price-up reports 489, mean +0.37; price-down 366,
mean −0.18) and 18% are long-horizon forecast pieces ("$5,400 target", "$50,000/oz"; 60%
positive, 21% of all positive score mass). The per-headline LLM labels are correct — every
regex-flagged "mis-score" was a correctly scored "yields fall → gold up" headline — which
matches the 78/78 golden set of 2026-08-06. The averaging, the 12 h window and the
headline universe are what destroy the information.
**What news and indicators said on the way down** (top $4,697 on 08-25 00:00 UTC, low
$4,283 on 09-02 03:00; an 8.8% drop in a week):
| moment | price | news sentiment | engine (M15) |
|---|---|---|---|
| top, 08-25 00:00 | 4,697 | +0.30, rising to **+0.44** two hours after the top (window high) | ranging, composite +5.9; 9 BUY setups that day: −7.4R, 11% win |
| 08-26/27 | −$60 | still +0.15…+0.37; first bearish hour 08-26 20:00, **44 h after the top**, price already −$102 | trending_down 134/138 rows, composite −4.6/−7.8; SELL setups 0/3 on 08-27 |
| 08-28 Warsh speech 13:00–16:00 | −$122 in 3 h, −$140 day | +0.24…+0.30 overnight, **0.00 at 13:00**, −0.17 only at 16:00; summary all morning: "a hawkish tone could pressure gold, dovish hints may lift it" | composite −6.2; SELL setups 4/4, +3.3R |
| 09-01 | −$128 day | −0.04…−0.20, "gold under pressure" all day (correct, describing the move) | trending_down 275 rows, composite −12.2, 51 SELL rows; SELL setups 7/7, +3.5R |
| low, 09-02 03:00 | 4,283 | −0.16 at the low, −0.19 six hours later; first bullish hour 09-02 19:00, **16 h after the low**, price already +$108 | SELL setups at the bottom 0/2, −2.05R |
Sentiment peaked at the top and troughed at the bottom. The engine also lagged (it is
trend-following) but flipped regime and composite within hours; its SELL setups won on the
trend days and lost at the turns, the same as its BUYs.
**Outcomes** (144 RESOLVED, +37.8R): BUY 121 / +43.2R / 70% win; SELL 23 / −5.4R / 48%.
| direction × sentiment bucket (≥+0.15 bull, ≤−0.05 bear) | n | R | mean | win |
|---|---|---|---|---|
| BUY, bullish news | 81 | +32.0 | +0.40 | 73% |
| BUY, neutral | 34 | +10.6 | +0.31 | 68% |
| BUY, bearish news | 6 | +0.65 | +0.11 | 50% |
| SELL, bearish news | 14 | +0.13 | +0.01 | 71% |
| SELL, neutral | 5 | −1.46 | −0.29 | 20% |
| SELL, bullish news | 4 | −4.08 | −1.02 | 0% |
The splits are the trend confound again ("BUY with bullish news" = "BUY in an uptrend").
Cleanest counter-example: 08-25, the most bullish news day of the drop period (+0.25),
9 BUYs at the top lost −7.4R. corr(sentiment, R): BUY +0.10, SELL −0.45 (n=23).
**Macro persona** (reads the same headlines plus high-impact events): macro=support 51
BUYs → +32.6R, 90% win; macro=neutral 16 → −3.2R, 38%. Within bullish-sentiment BUYs:
support 42 → +25.2R (88%) vs neutral 7 → −2.9R (29%). On 08-25 all 7 panel rows were
macro=neutral ("awaiting PCE, mixed") while the numeric aggregate said +0.25 bullish.
Confounded with regime (64 of 84 supports were trending_up) and n≈16 on the informative
side. Not enough to promote or demote; the standing rule (re-score at ~300 outcomes)
applies.
**Event risk** cannot be evaluated here: only 4 event windows in the sample
(08-19 17:00, 08-26 12:00, 08-28 13:00, 09-01 13:00), none produced a setup. The standing
evidence remains the 2026-07-11 audit (event gates would cost +4.6…+7.2R/yr).
**One-year context:** daily moves ≤ −$100 happened 17 times in the year to 2026-08-14;
15 of 47 rolling 3-week windows contained a ≥ 8% drawdown. This sample is one ordinary
rally-then-drop regime.
**Simulated 4 h window + neutral drop** (the change shipped with this note, on 11,136
full-shape scans): never leaves a scan empty (median 19 directional headlines, 8
gold-specific; worst hour 02:00 UTC has < 3 gold headlines 28% of the time), the aggregate
correlates 0.78 with the old one and becomes bolder (share |score| ≥ 0.3 rises 17% → 41%),
with the same lack of forward information (next-24 h corr +0.20; next-8 h halves
−0.22 / −0.10).
## Method
- Hourly join convention: a scan in hour H (boundary + 10 s) sees the bar closed at H,
i.e. bar H's open; forward returns run from bar H's open, past returns end there.
- Correlations are Pearson on hourly means (no scipy); halves split at 2026-08-26 00:00.
- Headline classes by regex on the title (price-up / price-down / forecast-style); the
gold-specific score is `score_by_currency.XAU` where present, else `score`.
- Outcome buckets use the representative row's `news_sentiment`; macro stance from
`panel_takes_json`.
- Everything above reproduces from the DB alone; nothing depends on live feeds.
## Decisions
- `sentiment_affects_signals` and `event_risk_caps_signals` stay `False`. No gate is
built in either direction (follow or fade): 3 weeks, one regime.
- The headline labelling is not the problem; the pool and the averaging are. Shipped the
same day: `headline_max_age_seconds` 12 h → 4 h with undated items dropped, and
`get_fundamental_context` drops headlines whose every relevant score is inside ±0.1
(`_is_neutral_headline`). The market-summary prompt now writes for someone who has
never traded, sends headlines newest-first with the platform's own gold direction, and
caps at 400 chars. **`news_sentiment` rows from the deploy date onward are a mean over
directional ≤ 4 h headlines — log comparisons must split at that date.**
- Keep the macro persona running and logging; re-check with `--news` and the persona
scorecard at ~300 outcomes.
- Refuted hypothesis, do not re-propose without new data: "news sentiment leads price".