509519866e
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
130 lines
8.2 KiB
Markdown
130 lines
8.2 KiB
Markdown
# 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".
|