diff --git a/scripts/live/telegram_daily.py b/scripts/live/telegram_daily.py index 1413948..ac8238c 100644 --- a/scripts/live/telegram_daily.py +++ b/scripts/live/telegram_daily.py @@ -36,6 +36,31 @@ def _fmt_usd(x: float) -> str: return f"${x:,.2f}" +def _soglia_accensione(segni: list[int], neg_refs: list[float]) -> float | None: + """(#35 C3) Quale riferimento, superato, accende TP01 (mean(segni) > 0)? + + I segni sono sempre in {-1,+1}, mai 0 (trend_state li costruisce con `1 if px>refs[h] + else -1`: nessun pareggio possibile). Con k segni negativi da far diventare positivi, la + somma cresce di 2 per flip: serve il piu' piccolo k tale che sum(segni)+2k > 0. La soglia + e' il k-esimo riferimento negativo piu' basso (k=1 -> il minimo, come prima del fix; k=2 -> + il secondo, ecc.) โ€” non sempre il minimo: con TUTTI i riferimenti negativi, superare solo il + piu' basso lascia la media <= 0 e TP01 a 0 (bug segnalato in #35 C3). + + None se la media e' gia' positiva (nessuna soglia da dare) o se k supera il numero di + riferimenti negativi disponibili (non dovrebbe accadere: con n segni, flippare tutti gli n + negativi porta comunque la somma a +n > 0, quindi k <= len(neg_refs) sempre โ€” resta una + guardia difensiva, non un caso noto).""" + s = sum(segni) + if s > 0 or not neg_refs: + return None + k = 0 + while s + 2 * k <= 0: + k += 1 + if k > len(neg_refs): + return None + return sorted(neg_refs)[k - 1] + + def trend_state() -> list[str]: """Stato TSMOM per asset + quanto manca all'accensione (2 orizzonti su 3 positivi). @@ -59,10 +84,10 @@ def trend_state() -> list[str]: tgt = max(0.0, m) line = f" {a} {px:,.0f} ยท segni " + "/".join(f"{sg[h]:+d}" for h in HORIZONS) + \ f" โ†’ TP01 {tgt:.2f}" - neg = [(h, refs[h]) for h in HORIZONS if sg[h] < 0] - if neg and tgt == 0.0: - h, r = min(neg, key=lambda t: t[1]) - line += f"\n accensione a {r:,.0f} ({(r/px-1)*100:+.1f}%)" + if tgt == 0.0: + r = _soglia_accensione(list(sg.values()), [refs[h] for h in HORIZONS if sg[h] < 0]) + if r is not None: + line += f"\n accensione a {r:,.0f} ({(r/px-1)*100:+.1f}%)" out.append(line) except Exception as e: out.append(f" {a}: stato trend non calcolabile ({type(e).__name__})") diff --git a/tests/test_telegram_daily.py b/tests/test_telegram_daily.py index 135a68d..d11d1b6 100644 --- a/tests/test_telegram_daily.py +++ b/tests/test_telegram_daily.py @@ -52,3 +52,33 @@ def test_convenzione_trend_e_documentata(): """La convenzione media-dei-segni (non frazione) e' la ragione per cui TP01 e' 0 con 1/3 su: se sparisce dal codice, il report mentirebbe sul 'quanto manca'.""" assert "media dei SEGNI" in telegram_daily.trend_state.__doc__ + + +def test_soglia_accensione_2_negativi_1_positivo(): + """2 negativi + 1 positivo: basta 1 flip -> soglia = il riferimento negativo PIU' BASSO.""" + r = telegram_daily._soglia_accensione([-1, -1, 1], [100.0, 200.0]) + assert r == 100.0 + + +def test_soglia_accensione_3_negativi(): + """3 negativi: servono 2 flip -> soglia = il SECONDO riferimento negativo piu' basso, non il + minimo (il bug di #35 C3: superare solo il minimo lascia la media <= 0).""" + r = telegram_daily._soglia_accensione([-1, -1, -1], [100.0, 200.0, 300.0]) + assert r == 200.0 + + +def test_soglia_accensione_1_negativo_2_positivi_gia_acceso(): + """1 negativo + 2 positivi: la media e' gia' positiva (TP01 > 0) -> nessuna soglia da dare.""" + assert telegram_daily._soglia_accensione([-1, 1, 1], [100.0]) is None + + +def test_soglia_accensione_nessun_negativo(): + """Tutti positivi: nessun riferimento negativo, nessuna soglia.""" + assert telegram_daily._soglia_accensione([1, 1, 1], []) is None + + +def test_soglia_accensione_servono_tutti_i_flip(): + """2 negativi su 2 (nessun positivo): servono ENTRAMBI i flip -> k == len(neg_refs), soglia + = il piu' alto dei due (l'ultimo a bastare), non il minimo.""" + r = telegram_daily._soglia_accensione([-1, -1], [200.0, 100.0]) + assert r == 200.0