Fix scoring math; add 10-min auto-scan A-grade alerts with news block

- engine: composite-score denominator now confidence-weighted, so the
  output actually spans -100..+100 (the prior formula put confidence
  in the numerator only, capping the realistic score around ±25-30
  and making STRONG_BUY ≥40 essentially unreachable).
- engine: ATR percentile uses iloc[-50:-1] to exclude the current bar
  from its own comparison (off-by-one in volatility regime).
- signal_aggregator: always blend primary score with MTF average. The
  prior code only blended on agreement, leaving the primary unblended
  on disagreement — backwards. Disagreement is exactly when the score
  should be pulled toward consensus.
- telegram_bot: JobQueue auto-scan every 10 min that pushes A/A+ to
  ALERT_CHAT_ID with a prominent News & Events block (sentiment +
  headline summary + event-risk + alignment hint). Per-bar dedup so
  persistent setups alert once per new candle, not every 10 min.
  Suppresses alerts when event_risk = "danger".
- telegram_bot: /myid returns chat ID for ALERT_CHAT_ID setup. Auto-scan
  silently disabled when ALERT_CHAT_ID is unset.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
Harwinder
2026-05-06 12:50:30 -04:00
parent 3a17471c0e
commit 402d49e094
3 changed files with 235 additions and 17 deletions
+12 -8
View File
@@ -499,7 +499,8 @@ class TechnicalEngine:
# Compare to historical ATR
if len(atr) >= 50:
atr_percentile = (atr.iloc[-1] > atr.iloc[-50:]).mean() * 100
# Exclude current bar so ATR isn't compared against itself
atr_percentile = (atr.iloc[-1] > atr.iloc[-50:-1]).mean() * 100
else:
atr_percentile = 50.0
@@ -844,7 +845,8 @@ class TechnicalEngine:
- Confidence scaling
"""
total_score = 0.0
total_weight = 0.0
score_weight = 0.0 # denominator for score: weight × confidence
weight_sum = 0.0 # denominator for confidence average: weight only
total_confidence = 0.0
regime_weights = {
@@ -863,16 +865,18 @@ class TechnicalEngine:
regime_mult = active_regime_weights.get(name, 1.0)
weight *= regime_mult
conf_factor = ind.confidence / 100.0
signal_score = ind.signal.value # -2 to +2
weighted_score = signal_score * weight * (ind.confidence / 100.0)
total_score += weighted_score
total_weight += weight
total_score += signal_score * weight * conf_factor
score_weight += weight * conf_factor
weight_sum += weight
total_confidence += ind.confidence * weight
if total_weight > 0:
normalized_score = (total_score / total_weight) * 50 # scale to -100..+100
avg_confidence = total_confidence / total_weight
if score_weight > 0:
# Confidence-weighted average of signal_score (∈ [-2,+2]); ×50 → [-100,+100]
normalized_score = (total_score / score_weight) * 50
avg_confidence = total_confidence / weight_sum if weight_sum > 0 else 0
else:
normalized_score = 0
avg_confidence = 0