Fix scoring/recommendation correctness and calibration
Deploy / lint (push) Successful in 5s
Deploy / test (push) Successful in 32s
Deploy / deploy (push) Successful in 22s

Triggered by CNC showing "LONG (High Confidence)" with SHORT reasoning
and no long setup.

- A: recommendation action + reasoning are ticker-level and identical
  on both setups; reasoning always matches the shown action
- B: recommended_action only picks a direction with a tradeable setup;
  strong bias with no setup (e.g. price at ATH) → NEUTRAL with an
  explanatory reason instead of a fake LONG_HIGH
- C: confidence is a directional-agreement model — opposing signals push
  it below 50 (SHORT on a 92-technical/99-momentum stock ~0%, not 55%)
- D: fundamental score requires >=2 real metrics (market-cap-only no
  longer yields a high score)
- E: RSI score peaks at healthy momentum (~60) and penalizes
  overbought/oversold extremes instead of treating RSI 90 as maximal
- F: fundamentals chain merges fields across providers (FMP market cap
  + Finnhub P/E) instead of stopping at the first with any field
- NEUTRAL label: "No Clear Setup" (covers untradeable-bias case)

Scores recompute on next scan/scoring run; C and E shift score
distributions intentionally.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-06-13 15:34:36 +02:00
parent ffb609d38f
commit d3eb8a2b97
9 changed files with 269 additions and 108 deletions
+11
View File
@@ -89,6 +89,17 @@ class TestComputeRSI:
with pytest.raises(ValidationError, match="RSI requires"):
compute_rsi([100.0] * 5)
def test_overbought_rsi_is_penalized_not_maximal(self):
"""RSI 100 (extreme overbought) must NOT score near 100."""
from app.services.indicator_service import _rsi_to_score
assert _rsi_to_score(100.0) < 40.0 # overbought penalized
assert _rsi_to_score(90.0) < _rsi_to_score(60.0) # extreme < healthy
assert _rsi_to_score(60.0) > 80.0 # healthy momentum rewarded
# All gains → RSI 100 → low score, not 100
result = compute_rsi(_rising_closes(20, step=1))
assert result["score"] < 40.0
# ---------------------------------------------------------------------------
# ATR