fix(macro): dead placeholder indicators no longer compress composite score

scoring.py docstring promises: 'Missing indicators are excluded and the
remaining weights renormalised — so a single dead API doesn't drag the whole
score toward zero.' But _stablecoin_supply_signal and _open_interest_signal
returned 0.0 (not None) as placeholders — so they stayed in the alive set,
kept their combined 0.10 weight in the denominator, and contributed 0,
systematically compressing the composite toward zero by ~10%.

Example: ahr999 deeply cheap (+1) alone should score +100, but the two 0.0
placeholders dragged it to 80. Now they return None → excluded + renormalised,
matching the module's own stated design. Verified: same input now scores 100.

Re-activate either by returning a real [-1,+1] signal once a trend lookup is
wired in.

72 tests pass.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
k
2026-05-30 03:09:16 +08:00
parent 041f010a30
commit 213bb911e3
+14 -8
View File
@@ -74,11 +74,16 @@ def _eth_btc_signal(v: Optional[float]) -> Optional[float]:
def _stablecoin_supply_signal(v: Optional[float]) -> Optional[float]: def _stablecoin_supply_signal(v: Optional[float]) -> Optional[float]:
"""Absolute supply tells us little day-over-day; we need the delta. Since """Absolute supply tells us little day-over-day; we need the delta, which
this scorer sees only the snapshot, we treat presence as 0 and let the this snapshot-only scorer doesn't have yet.
visual chart show the trend. Returns 0 if we have any value at all."""
if v is None: return None Return None (NOT 0.0) so this indicator is EXCLUDED from the weighted sum
return 0.0 # contribution = 0 until we wire in a trend lookup and its weight is renormalised away — exactly the "a dead indicator must
not drag the score toward zero" rule stated in the module docstring.
Returning 0.0 would keep its 0.05 weight in the denominator and silently
compress every other indicator's contribution. Wire in a trend lookup to
re-activate it."""
return None
def _etf_flow_signal(v: Optional[float]) -> Optional[float]: def _etf_flow_signal(v: Optional[float]) -> Optional[float]:
@@ -95,9 +100,10 @@ def _etf_flow_signal(v: Optional[float]) -> Optional[float]:
def _open_interest_signal(v: Optional[float]) -> Optional[float]: def _open_interest_signal(v: Optional[float]) -> Optional[float]:
"""OI in isolation doesn't tell us direction — we'd need OI vs price """OI in isolation doesn't tell us direction — we'd need OI vs price
correlation. Until we have a trend window, contribute 0.""" correlation. Return None (NOT 0.0) until we have a trend window, so this
if v is None: return None indicator is excluded + its weight renormalised away rather than diluting
return 0.0 every other indicator. (Same reasoning as _stablecoin_supply_signal.)"""
return None
# Weights (sum to 1.0 across all). When an indicator is missing, we drop its # Weights (sum to 1.0 across all). When an indicator is missing, we drop its