Files
News-ML-Decoder/eval/RESULTS_2026-09-17.md
Harwinder Sekhon 01baef48ff Initial import: XAUUSD news sentiment models, evals, and data tooling
TF-IDF baseline (news_sentiment.py, models/xauusd_sentiment.joblib) and the
two-head DeBERTa-v3-small transformer (transformer/, news_transformer.py)
with its Google News crawler and DeepSeek labeller (data_collect/), the
golden-set and archive evals (eval/), and the 2026-09-22 and 2026-09-27
results and progress notes.

Transformer weights (model.pt, model.onnx) and the crawl database are not
tracked; rebuild with transformer/train.py and transformer/export_onnx.py.
The DeepSeek label cache in data/ is tracked.
2026-09-27 19:22:30 -04:00

8.6 KiB
Raw Permalink Blame History

ML-NEWS scikit model vs DeepSeek — first evaluation, 2026-09-17

Model under test: models/xauusd_sentiment.joblib (TF-IDF word + char, logistic regression, trained 2026-09-17 on 606 DeepSeek-labelled gold headlines from Sep 8–17). Teacher: DeepSeek deepseek-v4-flash through ForexBot2's live prompt (sha1 d32e04c6). Scripts and raw outputs: this folder (results/*_20260917_22*). DeepSeek spend for the whole exercise: 11 calls.

Verdict

The model is a partial copy of DeepSeek, not a replacement. It has learned what gold-specific headline words mean and nothing else: perfect on direct gold news and safe-haven stories, near random on macro doctrine (hawkish/dovish Fed, dollar, yields, risk-on), zero on negation, and it assigns a direction to irrelevant headlines. DeepSeek is still 78/78 on the independent set today.

Test Truth Model DeepSeek Keyword fallback
Golden set, 78 reviewer-labelled headlines, 3-class human 39/78 = 50.0% 78/78 = 100% 24/78 = 30.8%
Golden set, directional rows only (70), right sign human 38/70 = 54.3% 70/70 16/70
Archive, expanding daily folds, pooled OOF (567 rows), 3-class DeepSeek 64.4% (is the label) 44.4%
Archive OOF, sign agreement when both lean DeepSeek 74.8% 86.8% (on 152 rows only)
Live feed, last 24 h, 66 headlines not in training, 3-class DeepSeek 57.6% (66.7% headline-only) 39.4%
Live feed, gold-mentioning and not in training (37) DeepSeek 70.3% (75.7% headline-only) 35.1%
DeepSeek re-scored 100 archived headlines (teacher self-agreement) DeepSeek (earlier) 90.0% 3-class, 98.6% sign

Paired McNemar on the golden set: model vs DeepSeek 0 vs 39 discordant (p < 0.0001); model vs keyword 28 vs 13 (p = 0.028). The model beats the keyword fallback and loses to DeepSeek by a wide margin.

Golden set by category (hits / n): model | DeepSeek | keyword

distractors         1/8  |  8/8  |  8/8     irrelevant news gets a direction
dovish_fed          6/8  |  8/8  |  7/8
gold_direct         8/8  |  8/8  |  4/8     learned
hawkish_fed         3/8  |  8/8  |  2/8     "Hot CPI fuels Fed tightening" -> bullish
inverted_metrics    3/6  |  6/6  |  0/6
negation_traps      0/8  |  8/8  |  2/8     "signals no rate hike" -> bearish 0.85 conf
real_yields         3/6  |  6/6  |  1/6
risk_on             1/4  |  4/4  |  0/4
safe_haven          8/8  |  8/8  |  0/8     learned
strong_usd          2/7  |  7/7  |  0/7     "Greenback rallies" -> bullish
weak_usd            4/7  |  7/7  |  0/7

The model's confidence does not flag its errors on this set: the negation-trap miss above carries 0.85 confidence and the "consumer confidence surges" miss 0.82.

Archive time-split (retrain on days < d, score day d)

day        train test | full acc  F1   | head acc  F1   | keyword
09-10        49   87  |  62.1%  0.626  |  51.7%  0.521  |  47.1%
09-11       136   91  |  52.7%  0.524  |  53.8%  0.525  |  39.6%
09-14       227  130  |  76.9%  0.714  |  72.3%  0.656  |  58.5%
09-15       357   84  |  69.0%  0.656  |  61.9%  0.605  |  44.0%
09-16       441  115  |  55.7%  0.545  |  54.8%  0.531  |  33.0%
09-17       556   60  |  68.3%  0.643  |  75.0%  0.703  |  40.0%
pooled      —    567  |  64.4%  0.636  |  61.4%  0.607  |  44.4%

"full" = headline + description (the shipped recipe), "head" = headline only. Agreement does not rise with more training days yet; the day-to-day swing (53–77%) is larger than any trend. In-sample the shipped bundle scores 97.4%, so the gap to 64% is generalisation, not capacity. The description helps a little on the archive folds and hurts on the live sample (60.5% vs 68.4%); both differences are inside the noise of these sample sizes.

Ceiling: DeepSeek disagrees with itself

Re-sending 100 archived headlines today (same prompt, same model, temperature 0, different batch neighbours): 90% same class, 93% directional, 98.6% same sign, but only 42% the identical number, mean |difference| 0.09. A student cannot exceed about 90% class agreement on this label. Where DeepSeek changed its class (9 rows) the OOF model matched one of its two answers on 7.

Confidence routing (pooled OOF, shipped recipe)

conf >=  kept share  kept agreement  hybrid agreement (rest to DeepSeek)
 0.60      44%          80.9%            91.5%
 0.70      28%          87.4%            96.5%
 0.80      17%          95.8%            99.3%
 0.90       6.5%       100.0%           100.0%

The WORKFLOW doc's 0.70 / 0.20 starting point keeps 28% of headlines local at 87% agreement with DeepSeek. That saves roughly a quarter of the calls, which are 56 ms and a fraction of a cent per headline in batches of 25 anyway. The golden set shows the caveat: high confidence on a macro or negated headline is not a reliable signal there.

Structural findings

  1. Training covers gold-mentioning headlines only. The archive stores DeepSeek's XAU score only when the headline matches a gold keyword; for USD-only headlines only the overall score is kept and DeepSeek's xau is discarded (_apply_llm_sentiment). So the model never saw a Fed, dollar or yields headline that did not also say "gold", which is exactly where it fails. Live, 29 of the 76 headlines were USD-only; the model was at 60% on them versus 72–77% on gold-mentioning ones. Archiving DeepSeek's xau for every headline would fix the training set at no extra cost.
  2. Nine days of data spanning one regime (a Fed hiking cycle in September 2026). The 2026-09-08 rule stands: do not deploy before about 4,000 labelled headlines over three or more weeks.
  3. Negation and doctrine are not learnable from 606 bag-of-words examples; the regex domain markers catch "dollar ... strengthen" but not "greenback rallies" or "dollar index hits high".
  4. Latency: model 1 ms per headline in-process; DeepSeek 56 ms per headline (1.4 s per batch of 25). Latency is not the constraint.

Addendum: backfilling the missing gold labels (same day)

backfill_labels.py sent the 961 archive headlines without a gold label (930 DeepSeek-scored without a gold key, 31 never scored) through the live DeepSeek path: 38 calls, all labelled, kept in results/backfill_labels.jsonl. Macro labels at ±0.05: 350 bearish, 228 bullish, 383 neutral (40% neutral versus 22% on gold headlines).

Training set Gold-headline OOF Macro-headline OOF Golden set
gold rows only (616, shipped data) 64.4% 38.4% 39/78
gold + macro rows (1,577) 61.6% 51.8% 42/78

Adding the macro headlines lifts macro agreement by 13 points but costs 3 points on gold headlines, and the golden-set change is a reshuffle, not a fix: distractors 1→6, hawkish 3→5, strong-USD 2→4, weak-USD 4→6, but dovish 6→3, safe-haven 8→4, risk-on 1→0. On nine days of one regime the linear model cannot hold both worlds at once. The missing-label problem was real, but repairing it on the existing corpus does not produce a usable model; it makes the corpus complete so that weeks of further collection can.

Side finding for the live system: on the 930 macro headlines, the gold reading the platform displays for a non-gold headline (the inverted overall score, _gold_direction_label) matches DeepSeek's own gold score only 61% of the time (sign 77%). Display-only, but the macro persona and the summary read that label.

experiment_recipe.py (same day): stripping the publisher suffix, marking negated words and weighting confident labels moved OOF agreement by less than a point and the golden set to 40–42/78; negation traps went from 0/8 to 2/8. Cleaning is not the bottleneck.

After the archive patch (drafted the same day in ForexBot2)

Once the ForexBot2 patch is live, logs/newsdata.db gains a xau_score column holding DeepSeek's gold score for every LLM-scored row, and headlines naming no tracked currency arrive with currencies_json = []. news_sentiment.py still reads its label from score_by_currency_json["XAU"], so it will keep seeing only the gold-keyword rows until its loader is switched to xau_score (fall back to the JSON key for rows older than the column). Skip rows whose source is a test feed (feed.example, good.example): the ForexBot2 test suite used to write fixture headlines into the real archive; they are keyword-scored, so the score_source = 'llm' filter already excludes them.

How to keep measuring

eval_live.py appends every fresh headline to results/live_shadow.jsonl; run it daily and the out-of-sample sample grows for free (cached DeepSeek answers are never re-bought). Re-run eval_golden.py after any retrain; it is the only independent truth.