news corpus for a local scorer + agent panel v2 + dashboard read-only (2026-09-17); books byte-identical

Agent panel v2: personas return p_win anchored to the book base rates with a
code-computed decision context; verdict = equal-weight mean (verdict_source
"mean"); only the macro persona runs live (agent_panel.personas ["macro"]);
prompt version and exact payloads logged; offline replay harness
scripts/panel_replay.py; base-rate table agent_panel_base_rates.json
(scripts/build_panel_base_rates.py). Backtester `strategy` column now carries
the generator's own stamp (label-only; five reference books re-pinned, all
62 other columns identical).

News corpus (after evaluating the owner's ML-NEWS scikit model against
DeepSeek: golden set 39/78 vs 78/78, out-of-sample agreement 64% against a
90% self-agreement ceiling; verdict: fallback only, not a replacement):
- NewsSentiment.xau_score + archive column xau_score keep DeepSeek's gold
  score for every headline (930 of the first 1,546 DeepSeek rows had none);
  an existing newsdata.db gains the column in place on first open
- headlines naming no tracked currency are scored and archived
  (currencies_json []) but never returned to the platform
- _apply_llm_sentiment sends every new headline in its cycle, 25 per call;
  a failed call ends the cycle's sending

Dashboard publisher never writes a database: main() calls
app_config.set_db_writes_for_process(False), honoured by signal_log_enabled()
and news_archive_enabled() (owner decision: the bot is the single writer).

Test suite: the hermetic app config now points both writers at a
suite-private temp dir — the RSS fixture tests were writing fixture
headlines into the real logs/newsdata.db — with a Module 32 test pinning it.
529 tests.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
Harwinder Sekhon
2026-09-17 19:49:29 -04:00
parent 552199857a
commit 1f4741b557
21 changed files with 3803 additions and 259 deletions
+21 -1
View File
@@ -168,7 +168,23 @@ def print_signal_report(report: SignalReport, detail: bool = False):
print(f" {Colors.MAGENTA}📰{Colors.RESET} {report.news_description}")
if report.news_summary:
print(f" {Colors.MAGENTA}🧠{Colors.RESET} {report.news_summary}")
if report.panel_verdict:
if report.panel_verdict and report.panel_verdict_source == "mean":
base = (report.panel_context or {}).get("base_rate") or {}
usual = base.get("win_rate")
usual_txt = (f" (usual for this kind of setup: {usual:.0f}%)"
if usual is not None else "")
print(f" {Colors.CYAN}🤖{Colors.RESET} AI estimate: about a "
f"{Colors.BOLD}{report.panel_probability:.0f}%{Colors.RESET} "
f"chance this trade pays off{usual_txt}. "
f"{report.panel_rationale} Still being tested.")
for t in report.panel_takes:
p_win = t.get("p_win")
p_txt = f"{p_win:3.0f}%" if p_win is not None else " - "
label = t['persona'] if len(report.panel_takes) > 1 else "reasons"
print(f" {Colors.GRAY}{label:<11} {p_txt} "
f"for: {t.get('reason_for') or '-'} | against: "
f"{t.get('reason_against') or '-'}{Colors.RESET}")
elif report.panel_verdict:
panel_label = ("AI macro view"
if report.panel_verdict_source == "macro" else "AI Panel")
print(f" {Colors.CYAN}🤖{Colors.RESET} {panel_label}: "
@@ -230,6 +246,10 @@ def report_to_dict(report: SignalReport) -> dict:
"panel_created_at": report.panel_created_at,
"panel_cache_status": report.panel_cache_status,
"panel_payload_hash": report.panel_payload_hash,
"panel_verdict_source": report.panel_verdict_source,
"panel_prompt_version": report.panel_prompt_version,
"panel_probability": round(report.panel_probability, 1),
"panel_context": report.panel_context,
"watch_line": report.watch_line,
"bullish_reasons": report.bullish_reasons,
"bearish_reasons": report.bearish_reasons,