cli+dashboard: --panel flag, panel dict keys, TF-dialog panel section
main.py: --panel runs the agent panel on demand (force bypasses enabled/surface/quality toggles; daily budget + LLM cap still apply), prints the verdict + persona takes, and report_to_dict carries the four panel keys — which auto-propagates them into the dashboard's snapshot.json via report_to_webapp_dict. dashboard_publisher attaches in scan_tf gated by agent_panel.dashboard (default off); dashboard.html renders an "AI panel (advisory)" section in the TF detail dialog. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SisdfkxnXNL9iZsXxN3T15
This commit is contained in:
@@ -49,6 +49,7 @@ from datetime import datetime, timezone
|
|||||||
from logging.handlers import TimedRotatingFileHandler
|
from logging.handlers import TimedRotatingFileHandler
|
||||||
from typing import Dict, List, Optional
|
from typing import Dict, List, Optional
|
||||||
|
|
||||||
|
from agent_panel import attach_panel
|
||||||
from config import PlatformConfig, Timeframe, XAUUSD
|
from config import PlatformConfig, Timeframe, XAUUSD
|
||||||
from main import report_to_dict
|
from main import report_to_dict
|
||||||
from news_engine import NewsEngine
|
from news_engine import NewsEngine
|
||||||
@@ -357,6 +358,13 @@ class DashboardPublisher:
|
|||||||
try:
|
try:
|
||||||
report = produce_signal(XAUUSD, tf, self._provider(),
|
report = produce_signal(XAUUSD, tf, self._provider(),
|
||||||
CONFIRMATIONS[tf])
|
CONFIRMATIONS[tf])
|
||||||
|
# Advisory AI panel, post-hoc and display-only. Off unless
|
||||||
|
# config.json agent_panel.dashboard is true. The reuse-TTL
|
||||||
|
# cache + max_panels_per_day budget bound the spend against
|
||||||
|
# this process's own DeepSeek cap (a persisting M5 signal
|
||||||
|
# re-serves its cached verdict instead of re-running every
|
||||||
|
# bar); a fresh panel adds ≤~25 s before this TF publishes.
|
||||||
|
attach_panel(report, surface="dashboard")
|
||||||
self.snapshots[tf.value] = {
|
self.snapshots[tf.value] = {
|
||||||
"status": "ok",
|
"status": "ok",
|
||||||
"scanned_at": datetime.now(timezone.utc).isoformat(),
|
"scanned_at": datetime.now(timezone.utc).isoformat(),
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ if hasattr(sys.stdout, "reconfigure"):
|
|||||||
if hasattr(sys.stderr, "reconfigure"):
|
if hasattr(sys.stderr, "reconfigure"):
|
||||||
sys.stderr.reconfigure(encoding="utf-8")
|
sys.stderr.reconfigure(encoding="utf-8")
|
||||||
|
|
||||||
|
from agent_panel import attach_panel
|
||||||
from config import PlatformConfig, Timeframe, SignalStrength, XAUUSD
|
from config import PlatformConfig, Timeframe, SignalStrength, XAUUSD
|
||||||
from pair_signals import PAIR_PRODUCERS
|
from pair_signals import PAIR_PRODUCERS
|
||||||
from signal_aggregator import SignalAggregator, SignalReport
|
from signal_aggregator import SignalAggregator, SignalReport
|
||||||
@@ -163,6 +164,13 @@ def print_signal_report(report: SignalReport, detail: bool = False):
|
|||||||
print(f" {Colors.MAGENTA}📰{Colors.RESET} {report.news_description}")
|
print(f" {Colors.MAGENTA}📰{Colors.RESET} {report.news_description}")
|
||||||
if report.news_summary:
|
if report.news_summary:
|
||||||
print(f" {Colors.MAGENTA}🧠{Colors.RESET} {report.news_summary}")
|
print(f" {Colors.MAGENTA}🧠{Colors.RESET} {report.news_summary}")
|
||||||
|
if report.panel_verdict:
|
||||||
|
print(f" {Colors.CYAN}🤖{Colors.RESET} AI Panel: "
|
||||||
|
f"{Colors.BOLD}{report.panel_verdict}{Colors.RESET} "
|
||||||
|
f"({report.panel_confidence:.0f}%) — {report.panel_rationale}")
|
||||||
|
for t in report.panel_takes:
|
||||||
|
print(f" {Colors.GRAY}{t['persona']:<11} {t['stance']:<8} "
|
||||||
|
f"{t['note']}{Colors.RESET}")
|
||||||
|
|
||||||
# ── 6. DETAIL (only with --detail) ─────────────────────────────
|
# ── 6. DETAIL (only with --detail) ─────────────────────────────
|
||||||
if detail and report.primary_analysis:
|
if detail and report.primary_analysis:
|
||||||
@@ -209,6 +217,10 @@ def report_to_dict(report: SignalReport) -> dict:
|
|||||||
"news_sentiment": round(report.news_sentiment, 3),
|
"news_sentiment": round(report.news_sentiment, 3),
|
||||||
"news_description": report.news_description,
|
"news_description": report.news_description,
|
||||||
"news_summary": report.news_summary,
|
"news_summary": report.news_summary,
|
||||||
|
"panel_verdict": report.panel_verdict,
|
||||||
|
"panel_confidence": round(report.panel_confidence, 1),
|
||||||
|
"panel_rationale": report.panel_rationale,
|
||||||
|
"panel_takes": report.panel_takes,
|
||||||
"bullish_reasons": report.bullish_reasons,
|
"bullish_reasons": report.bullish_reasons,
|
||||||
"bearish_reasons": report.bearish_reasons,
|
"bearish_reasons": report.bearish_reasons,
|
||||||
"warnings": report.warnings,
|
"warnings": report.warnings,
|
||||||
@@ -268,6 +280,9 @@ Examples:
|
|||||||
help="Primary analysis timeframe (default: H1)")
|
help="Primary analysis timeframe (default: H1)")
|
||||||
parser.add_argument("--detail", action="store_true",
|
parser.add_argument("--detail", action="store_true",
|
||||||
help="Show full indicator breakdown")
|
help="Show full indicator breakdown")
|
||||||
|
parser.add_argument("--panel", action="store_true",
|
||||||
|
help="Run the LLM agent panel on the result "
|
||||||
|
"(advisory only; needs a DeepSeek key)")
|
||||||
parser.add_argument("--json", action="store_true",
|
parser.add_argument("--json", action="store_true",
|
||||||
help="Output as JSON")
|
help="Output as JSON")
|
||||||
parser.add_argument("--verbose", "-v", action="store_true",
|
parser.add_argument("--verbose", "-v", action="store_true",
|
||||||
@@ -323,6 +338,11 @@ def run_live(args, config: PlatformConfig):
|
|||||||
aggregator = SignalAggregator(config)
|
aggregator = SignalAggregator(config)
|
||||||
report = aggregator.analyze_pair(symbol=config.symbol)
|
report = aggregator.analyze_pair(symbol=config.symbol)
|
||||||
|
|
||||||
|
# Explicit user intent: force bypasses the enabled/surface/quality
|
||||||
|
# toggles, but the daily panel budget and LLM cap still apply.
|
||||||
|
if getattr(args, "panel", False):
|
||||||
|
attach_panel(report, surface="cli", force=True)
|
||||||
|
|
||||||
if args.json:
|
if args.json:
|
||||||
print(json.dumps(report_to_dict(report), indent=2))
|
print(json.dumps(report_to_dict(report), indent=2))
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -981,6 +981,22 @@ function detailHTML(tf) {
|
|||||||
${esc(r.news_summary || "")}</div>`);
|
${esc(r.news_summary || "")}</div>`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* AI agent panel (advisory) */
|
||||||
|
if (r.panel_verdict) {
|
||||||
|
const vcls = r.panel_verdict === "AGREE" ? "buy"
|
||||||
|
: r.panel_verdict === "OPPOSE" ? "sell" : "neutral";
|
||||||
|
const takes = (r.panel_takes || []).map(t =>
|
||||||
|
`<tr><td class="ind-k">${esc(t.persona)}</td>
|
||||||
|
<td>${esc(t.stance)}</td>
|
||||||
|
<td class="num">${t.confidence != null ? Math.round(t.confidence) : "—"}</td>
|
||||||
|
<td>${esc(t.note || "")}</td></tr>`).join("");
|
||||||
|
out += sec("AI panel (advisory)", `<div class="d-note" style="margin-bottom:8px">
|
||||||
|
<b class="sig-txt ${vcls}">${esc(r.panel_verdict)}</b> ·
|
||||||
|
conf ${Math.round(r.panel_confidence || 0)}%<br>
|
||||||
|
${esc(r.panel_rationale || "")}</div>
|
||||||
|
${takes ? `<table class="d-table"><tbody>${takes}</tbody></table>` : ""}`);
|
||||||
|
}
|
||||||
|
|
||||||
/* provenance */
|
/* provenance */
|
||||||
out += sec("Scan detail", `<table class="d-table"><tbody>
|
out += sec("Scan detail", `<table class="d-table"><tbody>
|
||||||
<tr><td class="ind-k">Instrument</td><td colspan="5">${esc(r.display_name || r.symbol || "—")}</td></tr>
|
<tr><td class="ind-k">Instrument</td><td colspan="5">${esc(r.display_name || r.symbol || "—")}</td></tr>
|
||||||
|
|||||||
+19
@@ -7282,6 +7282,25 @@ def _():
|
|||||||
ap.reset_panel_state()
|
ap.reset_panel_state()
|
||||||
|
|
||||||
|
|
||||||
|
@test("report_to_dict carries panel keys → dashboard snapshot propagation")
|
||||||
|
def _():
|
||||||
|
from main import report_to_dict as _rtd
|
||||||
|
from dashboard_publisher import report_to_webapp_dict as _rtw
|
||||||
|
report = _panel_report()
|
||||||
|
d = _rtd(report)
|
||||||
|
assert d["panel_verdict"] == "" and d["panel_takes"] == []
|
||||||
|
report.panel_verdict = "AGREE"
|
||||||
|
report.panel_confidence = 77.5
|
||||||
|
report.panel_rationale = "solid confluence"
|
||||||
|
report.panel_takes = [{"persona": "risk", "stance": "support",
|
||||||
|
"confidence": 66.0, "note": "rr fine"}]
|
||||||
|
wd = _rtw(report)
|
||||||
|
assert wd["panel_verdict"] == "AGREE"
|
||||||
|
assert wd["panel_confidence"] == 77.5
|
||||||
|
assert wd["panel_takes"][0]["persona"] == "risk"
|
||||||
|
json.dumps(wd, allow_nan=False)
|
||||||
|
|
||||||
|
|
||||||
@test("agent_panel: attach never raises (LLM explosion, garbage settings)")
|
@test("agent_panel: attach never raises (LLM explosion, garbage settings)")
|
||||||
def _():
|
def _():
|
||||||
class _Bomb:
|
class _Bomb:
|
||||||
|
|||||||
Reference in New Issue
Block a user