Compact hierarchical live signal output
Restructure print_signal_report so decision (signal · grade · score · confidence · MTF) sits at the top, followed by trade setup, context line, MTF breakdown, top supporting/opposing reasons in two columns, warnings, and an optional detail block under --detail. Adds _compact_reason helper that strips known indicator name prefixes via _INDICATOR_PREFIXES so reason lines stay short.
This commit is contained in:
@@ -79,81 +79,108 @@ def print_header(title: str = "USD/JPY SIGNAL INTELLIGENCE"):
|
|||||||
|
|
||||||
# ── Live signal renderer ───────────────────────────────────────────
|
# ── Live signal renderer ───────────────────────────────────────────
|
||||||
|
|
||||||
def print_signal_report(report: SignalReport, detail: bool = True):
|
_INDICATOR_PREFIXES = (
|
||||||
|
"EMA Trend", "RSI", "MACD", "Bollinger Bands", "Stochastic",
|
||||||
|
"ADX", "Ichimoku", "Divergence", "Candlestick", "Pivot Points",
|
||||||
|
"News", "Regime",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _compact_reason(reason: str) -> str:
|
||||||
|
"""Shorten a verbose reason string for the compact view."""
|
||||||
|
s = reason
|
||||||
|
if s.startswith("STRONG: "):
|
||||||
|
s = s[8:]
|
||||||
|
# Strip the leading "<Indicator>: " prefix — it's always an indicator name
|
||||||
|
for prefix in _INDICATOR_PREFIXES:
|
||||||
|
if s.startswith(prefix + ": "):
|
||||||
|
s = s[len(prefix) + 2:]
|
||||||
|
break
|
||||||
|
# Drop trailing detail after first ' | '
|
||||||
|
if " | " in s:
|
||||||
|
s = s.split(" | ", 1)[0]
|
||||||
|
return s.strip()
|
||||||
|
|
||||||
|
|
||||||
|
def print_signal_report(report: SignalReport, detail: bool = False):
|
||||||
sc = signal_color(report.final_signal)
|
sc = signal_color(report.final_signal)
|
||||||
qc = quality_color(report.signal_quality)
|
qc = quality_color(report.signal_quality)
|
||||||
sig_name = report.final_signal.name.replace("_", " ")
|
sig_name = report.final_signal.name.replace("_", " ")
|
||||||
|
|
||||||
print(f" {Colors.BOLD}{Colors.WHITE}{report.display_name}{Colors.RESET}"
|
# ── 1. DECISION ────────────────────────────────────────────────
|
||||||
f" │ Price: {Colors.BOLD}{report.current_price:.3f}{Colors.RESET}"
|
mtf_tag = (f"{Colors.GREEN}MTF✓{Colors.RESET}" if report.mtf_agreement
|
||||||
f" │ Regime: {Colors.CYAN}{report.market_regime}{Colors.RESET}"
|
else f"{Colors.YELLOW}MTF✗{Colors.RESET}")
|
||||||
f" │ Vol: {report.volatility_regime}")
|
print(f" {sc}▶ {sig_name}{Colors.RESET} "
|
||||||
|
f"{qc}{report.signal_quality}{Colors.RESET} · "
|
||||||
|
f"{sc}{report.final_score:+.1f}{Colors.RESET} · "
|
||||||
|
f"{report.final_confidence:.0f}% · {mtf_tag}")
|
||||||
|
print(f" {Colors.GRAY}{report.action_recommendation}{Colors.RESET}")
|
||||||
|
|
||||||
print(f"\n {Colors.BOLD}SIGNAL:{Colors.RESET} {sc}{sig_name}{Colors.RESET}"
|
# ── 2. TRADE SETUP (if signal is actionable) ───────────────────
|
||||||
f" │ Score: {sc}{report.final_score:+.1f}{Colors.RESET}"
|
if report.trade_setup:
|
||||||
f" │ Grade: {qc}{report.signal_quality}{Colors.RESET}"
|
ts = report.trade_setup
|
||||||
f" │ Confidence: {report.final_confidence:.0f}%")
|
dc = Colors.GREEN if ts.direction == "BUY" else Colors.RED
|
||||||
|
print(f"\n {Colors.BOLD}Setup{Colors.RESET} "
|
||||||
|
f"{dc}{ts.direction}{Colors.RESET} @ {Colors.BOLD}{ts.entry_price:.3f}{Colors.RESET} "
|
||||||
|
f"→ SL {Colors.RED}{ts.stop_loss:.3f}{Colors.RESET} "
|
||||||
|
f"({ts.risk_pips:.1f}p) "
|
||||||
|
f"→ TP {Colors.GREEN}{ts.take_profit_1:.3f}{Colors.RESET} "
|
||||||
|
f"({ts.reward_pips:.1f}p) "
|
||||||
|
f"RR 1:{ts.risk_reward:.1f}")
|
||||||
|
|
||||||
print(f" {Colors.BOLD}Action:{Colors.RESET} {report.action_recommendation}")
|
# ── 3. CONTEXT (price · regime · vol · MTF breakdown) ──────────
|
||||||
|
print(f"\n {Colors.WHITE}{report.display_name}{Colors.RESET} "
|
||||||
|
f"{Colors.BOLD}{report.current_price:.3f}{Colors.RESET} · "
|
||||||
|
f"{Colors.CYAN}{report.market_regime}{Colors.RESET} · "
|
||||||
|
f"vol {report.volatility_regime}")
|
||||||
|
|
||||||
if report.mtf_signals:
|
if report.mtf_signals:
|
||||||
mtf_status = (f" {Colors.GREEN}✓ ALIGNED{Colors.RESET}"
|
parts = []
|
||||||
if report.mtf_agreement
|
|
||||||
else f" {Colors.YELLOW}✗ DIVERGENT{Colors.RESET}")
|
|
||||||
print(f"\n {Colors.BOLD}Multi-Timeframe:{Colors.RESET}{mtf_status}")
|
|
||||||
for tf, sig in report.mtf_signals.items():
|
for tf, sig in report.mtf_signals.items():
|
||||||
tf_sc = signal_color(sig)
|
tf_sc = signal_color(sig)
|
||||||
tf_score = report.mtf_scores.get(tf, 0)
|
sc_val = report.mtf_scores.get(tf, 0)
|
||||||
print(f" {tf:>5}: {tf_sc}{sig.name.replace('_',' '):<12}{Colors.RESET}"
|
parts.append(f"{tf} {tf_sc}{sig.name.replace('_',' ')}{Colors.RESET} {sc_val:+.1f}")
|
||||||
f" ({tf_score:+.1f})")
|
print(f" {Colors.GRAY}MTF{Colors.RESET} " + " · ".join(parts))
|
||||||
|
|
||||||
|
# ── 4. WHY — top 3 each side, compact ──────────────────────────
|
||||||
|
bulls = [_compact_reason(r) for r in report.bullish_reasons[:3]]
|
||||||
|
bears = [_compact_reason(r) for r in report.bearish_reasons[:3]]
|
||||||
|
if bulls or bears:
|
||||||
|
print()
|
||||||
|
rows = max(len(bulls), len(bears))
|
||||||
|
for i in range(rows):
|
||||||
|
left = f"{Colors.GREEN}+{Colors.RESET} {bulls[i]}" if i < len(bulls) else ""
|
||||||
|
right = f"{Colors.RED}-{Colors.RESET} {bears[i]}" if i < len(bears) else ""
|
||||||
|
print(f" {left:<60} {right}")
|
||||||
|
|
||||||
|
# ── 5. WARNINGS / NEWS / EVENTS ────────────────────────────────
|
||||||
|
if report.warnings:
|
||||||
|
for w in report.warnings:
|
||||||
|
print(f" {Colors.YELLOW}⚠{Colors.RESET} {w}")
|
||||||
|
if report.event_description and report.event_risk != "clear":
|
||||||
|
print(f" {Colors.YELLOW}📅{Colors.RESET} {report.event_description}")
|
||||||
|
if report.news_description and report.news_description != "Neutral news sentiment":
|
||||||
|
print(f" {Colors.MAGENTA}📰{Colors.RESET} {report.news_description}")
|
||||||
|
|
||||||
|
# ── 6. DETAIL (only with --detail) ─────────────────────────────
|
||||||
if detail and report.primary_analysis:
|
if detail and report.primary_analysis:
|
||||||
print(f"\n {Colors.BOLD}Indicators ({report.primary_analysis.timeframe}):{Colors.RESET}")
|
print(f"\n {Colors.BOLD}Indicators ({report.primary_analysis.timeframe}){Colors.RESET}")
|
||||||
for name, ind in report.primary_analysis.indicators.items():
|
for name, ind in report.primary_analysis.indicators.items():
|
||||||
if name.startswith("_") or ind.weight == 0:
|
if name.startswith("_") or ind.weight == 0:
|
||||||
continue
|
continue
|
||||||
isc = signal_color(ind.signal)
|
isc = signal_color(ind.signal)
|
||||||
print(f" {ind.name:<18} {isc}{ind.signal.name.replace('_',' '):<12}{Colors.RESET}"
|
print(f" {ind.name:<16} {isc}{ind.signal.name.replace('_',' '):<11}{Colors.RESET} "
|
||||||
f" ({ind.confidence:.0f}%) │ {Colors.GRAY}{ind.description}{Colors.RESET}")
|
f"{ind.confidence:>3.0f}% {Colors.GRAY}│ {ind.description}{Colors.RESET}")
|
||||||
|
|
||||||
if report.bullish_reasons:
|
|
||||||
print(f"\n {Colors.GREEN}▲ Bullish Factors:{Colors.RESET}")
|
|
||||||
for r in report.bullish_reasons[:5]:
|
|
||||||
print(f" {Colors.GREEN}+{Colors.RESET} {r}")
|
|
||||||
if report.bearish_reasons:
|
|
||||||
print(f"\n {Colors.RED}▼ Bearish Factors:{Colors.RESET}")
|
|
||||||
for r in report.bearish_reasons[:5]:
|
|
||||||
print(f" {Colors.RED}-{Colors.RESET} {r}")
|
|
||||||
|
|
||||||
if report.warnings:
|
|
||||||
print(f"\n {Colors.YELLOW}⚠ Warnings:{Colors.RESET}")
|
|
||||||
for w in report.warnings:
|
|
||||||
print(f" {Colors.YELLOW}!{Colors.RESET} {w}")
|
|
||||||
|
|
||||||
if report.news_description and report.news_description != "Neutral news sentiment":
|
|
||||||
print(f"\n {Colors.MAGENTA}📰 News:{Colors.RESET} {report.news_description}")
|
|
||||||
if report.event_description and report.event_risk != "clear":
|
|
||||||
print(f" {Colors.YELLOW}📅 Event:{Colors.RESET} {report.event_description}")
|
|
||||||
|
|
||||||
if report.trade_setup:
|
|
||||||
ts = report.trade_setup
|
|
||||||
dir_color = Colors.GREEN if ts.direction == "BUY" else Colors.RED
|
|
||||||
print(f"\n {Colors.BOLD}Trade Setup:{Colors.RESET}")
|
|
||||||
print(f" Direction: {dir_color}{ts.direction}{Colors.RESET}")
|
|
||||||
print(f" Entry: {ts.entry_price:.3f}")
|
|
||||||
print(f" Stop Loss: {Colors.RED}{ts.stop_loss:.3f}{Colors.RESET} ({ts.risk_pips:.1f} pips)")
|
|
||||||
print(f" Take Profit 1: {Colors.GREEN}{ts.take_profit_1:.3f}{Colors.RESET} ({ts.reward_pips:.1f} pips)")
|
|
||||||
print(f" Take Profit 2: {Colors.GREEN}{ts.take_profit_2:.3f}{Colors.RESET}")
|
|
||||||
print(f" Risk:Reward: 1:{ts.risk_reward:.1f}")
|
|
||||||
|
|
||||||
if report.primary_analysis and report.primary_analysis.key_levels:
|
|
||||||
levels = report.primary_analysis.key_levels
|
levels = report.primary_analysis.key_levels
|
||||||
print(f"\n {Colors.BOLD}Key Levels:{Colors.RESET}")
|
if levels:
|
||||||
for lvl in ["r3", "r2", "r1", "pivot", "s1", "s2", "s3"]:
|
shown = [lvl for lvl in ["r3", "r2", "r1", "pivot", "s1", "s2", "s3"] if lvl in levels]
|
||||||
if lvl in levels:
|
if shown:
|
||||||
val = levels[lvl]
|
print(f"\n {Colors.BOLD}Pivot Levels{Colors.RESET}")
|
||||||
color = Colors.RED if val > report.current_price else Colors.GREEN
|
for lvl in shown:
|
||||||
print(f" {lvl.upper():>6}: {color}{val:.3f}{Colors.RESET}")
|
val = levels[lvl]
|
||||||
|
color = Colors.RED if val > report.current_price else Colors.GREEN
|
||||||
|
print(f" {lvl.upper():>6} {color}{val:.3f}{Colors.RESET}")
|
||||||
|
|
||||||
|
|
||||||
def report_to_dict(report: SignalReport) -> dict:
|
def report_to_dict(report: SignalReport) -> dict:
|
||||||
|
|||||||
Reference in New Issue
Block a user