9f55652d99
Su autorizzazione dell'operatore («usa piu' USDE», «cerca % massima raggiungibile»): - usde_convert al 31,8% (774 USDE), poi sonda a saldo crescente r0906_usde_tetto_sonda.py (passi 100->20->4->1, doppio rifiuto prima di scendere, conferma a saldo neutro): da 31,8% a 68,7% in 39 ordini @ 1,0005, ZERO rifiuti. Fermata dal cuscino di regolamento (70%, derivato da config), non dal venue. «Il 70% non e' raggiungibile ne' ora ne' mai» (31/08) e' falsificato: il tetto e' sparito, non scalato. Non spiegato, puo' tornare. - config: venue_cap_frac -> null (misura in venue_cap_misurato), quota_max_frac 0,50 -> 0,85 (il valore scelto dall'operatore il 30/08 per questo scenario). - debito §5.17: nessuno sorveglia il cuscino USDC (slack $58; una perdita del libro lo consuma da sola). Versamento di prova di EUR 25 il 25/08 08:00Z (dichiarato dall'operatore): +3,3% su $647, sotto la soglia del 10% del rilevatore, contato per 12 giorni come trading. - data/live/movimenti_dichiarati.jsonl (append-only, nel backup) letto da journal.movimenti_dichiarati; movimenti_capitale lo fonde coi rilevati (fonte dichiarato, importo dell'operatore, dopo = prima + importo; dichiarato+rilevato se coincide; fuori letture o riga rotta -> avvisi). Report e nota di giornale lo dicono. - trading da arming +87,35 -> +62,45; TWR +11,98% -> +7,83%. CLAUDE.md §2 aggiornato. - tests: +6 in test_journal (conftest isola il file). Suite 916 verdi. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KSnordG9FT4q8M4MVm85GQ
151 lines
8.4 KiB
Python
151 lines
8.4 KiB
Python
"""Isolamento della suite dai FILE OPERATIVI VIVI.
|
|
|
|
⚠️ PERCHE' ESISTE (2026-08-25, difetto trovato addosso). Lanciando `uv run pytest` la suite ha
|
|
scritto **$5.000 dentro `data/live/equity_seen.json`**, il watermark dell'equity reale del conto.
|
|
Al primo giro successivo del book (09:47 UTC) l'esecutore ha confrontato l'equity vera contro quel
|
|
valore e ha mandato un allarme Telegram:
|
|
|
|
💰 USCITA DI FONDI: $5,000.00 -> $667.68 (-86.6%)
|
|
|
|
**Falso, e generato da un test.** Il colpevole e'
|
|
`test_book_live.py::test_la_formula_del_report_ha_potenza_anche_a_libro_flat`: prende solo
|
|
`monkeypatch` (niente `tmp_path`), sostituisce `shadow_report` con uno che dichiara
|
|
`real_equity=5000.0` e poi chiama `book.book_report()` — che come effetto collaterale scrive il
|
|
watermark. L'helper `_write_cfg` esisteva gia' proprio per questo (difetto gemello del 2026-07-26),
|
|
ma quel test, aggiunto il 21/08, non lo usa.
|
|
|
|
**NON e' un fastidio cosmetico.** Il watermark alimenta il cap di fallback:
|
|
|
|
cap_fallback = min(cap_fisso_di_config, watermark * frac)
|
|
|
|
Con `watermark = $5.000` il cap diventa `min($3.000, $2.500) = $2.500` per asset invece di `$334`:
|
|
su un conto da $668 sono fino a **$5.000 di nozionale lordo, ~7,5x di leva**. Ed e' raggiungibile:
|
|
il ramo `eq_fallback` di `book_execute` **allerta e NON blocca** per scelta dichiarata. Cioe'
|
|
lanciare la suite di test poteva **armare esattamente il pericolo che il watermark esiste per
|
|
impedire** (vedi `src/live/book.py`, nota del 2026-07-26 sul cap fisso e la leva 3,35x).
|
|
|
|
**LA RIPARAZIONE E' STRUTTURALE, NON PER-TEST.** Chiedere a ogni autore di ricordarsi il
|
|
monkeypatch e' la stessa scommessa che ha gia' perso due volte (26/07 e 21/08). Qui il
|
|
reindirizzamento e' **autouse**: vale per ogni test, anche per quelli che verranno scritti domani
|
|
da chi non ha letto questo file. Un test che vuole davvero pilotare il watermark continua a
|
|
funzionare — il suo monkeypatch esplicito vince su questo, che gira prima.
|
|
"""
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
|
|
PROJECT_ROOT = Path(__file__).resolve().parents[1]
|
|
sys.path.insert(0, str(PROJECT_ROOT))
|
|
|
|
|
|
# =============================================================================================
|
|
# IL CANALE DI ALLARME VERO
|
|
# =============================================================================================
|
|
# ⚠️ 2026-08-28, difetto trovato addosso di nuovo. Lanciando `uv run pytest` la suite ha mandato
|
|
# messaggi Telegram VERI sul canale di allarme dell'operatore: cinque per giro, col testo delle
|
|
# fixture — «Analisi di ieri.», «rete KO», «Testo dell'agente.» — sotto l'intestazione di una voce
|
|
# di giornale del 2026-08-21. Colpevoli: i test di `analista.CLI.scrivi()`, che non passavano
|
|
# `telegram=False` e quindi percorrevano `invia()` fino a `notifier.send()`. `_cfg()` legge il
|
|
# token da `.env.mainnet`/`.env`, quindi il fatto che il token non fosse nell'ambiente non
|
|
# proteggeva niente.
|
|
#
|
|
# NON E' COSMETICO. E' P9 al contrario: allarmi finti spesi su un canale che deve restare
|
|
# credibile il giorno che l'allarme e' vero. Chi riceve cinque messaggi identici a ogni giro di
|
|
# test impara a non aprirli — ed e' l'unico canale da cui passano il disaster-SL, l'uscita di
|
|
# fondi e il venue giu'.
|
|
#
|
|
# LA RIPARAZIONE E' STRUTTURALE (stessa lezione del watermark): si blocca la RETE, non si chiede
|
|
# a ogni autore di ricordarsi un parametro. Un test che vuole davvero esercitare `send()` continua
|
|
# a funzionare: patcha `urlopen` da se' nel corpo, e il suo monkeypatch vince su questo.
|
|
_INVII_TENTATI: list[str] = []
|
|
|
|
|
|
@pytest.fixture(autouse=True)
|
|
def _blocca_il_canale_di_allarme_vero(monkeypatch):
|
|
"""Nessun test puo' raggiungere Telegram. Il tentativo si REGISTRA (P3: l'errore si scrive
|
|
nel punto in cui lo si ingoia — `send()` cattura tutto, quindi senza questo elenco un invio
|
|
tentato sparirebbe senza lasciare traccia)."""
|
|
from src.live import notifier
|
|
|
|
def _vietato(url, *a, **k):
|
|
# ⚠️ NON si registra l'URL: contiene il BOT TOKEN in chiaro, e questa lista finisce
|
|
# dentro il messaggio di un assert — cioe' nell'output della suite, nei log di CI e
|
|
# nello scrollback di chiunque lanci i test. Il difetto e' stato fatto e corretto il
|
|
# 2026-08-28: la prima versione stampava `https://api.telegram.org/bot<TOKEN>/sendM…`.
|
|
# Qui serve sapere CHE si e' tentato, non VERSO DOVE.
|
|
_INVII_TENTATI.append("telegram sendMessage")
|
|
raise RuntimeError("invio Telegram VERO bloccato dalla suite (tests/conftest.py)")
|
|
|
|
monkeypatch.setattr(notifier.urllib.request, "urlopen", _vietato)
|
|
|
|
|
|
@pytest.fixture(autouse=True, scope="session")
|
|
def _guardia_nessun_invio_vero():
|
|
"""Bloccare non basta: se un test PROVA a mandare, va trovato e reso esplicito. Un blocco
|
|
silenzioso lascerebbe il tentativo in giro pronto a tornare vero il giorno che qualcuno
|
|
cambia il boundary."""
|
|
yield
|
|
assert not _INVII_TENTATI, (
|
|
f"{len(_INVII_TENTATI)} test hanno PROVATO a mandare un messaggio Telegram vero "
|
|
"(bloccati, ma il tentativo non deve esistere): passare `telegram=False` o iniettare "
|
|
"un sender finto. Vedi la nota in testa a questo file.")
|
|
|
|
|
|
@pytest.fixture(autouse=True)
|
|
def _isola_file_operativi_vivi(tmp_path, monkeypatch):
|
|
"""Ogni test scrive il watermark in una cartella temporanea, mai in `data/live/`.
|
|
|
|
Il valore iniziale NON viene precaricato: un test che si aspetta un watermark deve
|
|
dichiararlo, e la prima lettura in assoluto non produce allarmi per costruzione
|
|
(`write_equity_watermark` ritorna None quando non c'e' un "prima").
|
|
"""
|
|
import src.live.book as book
|
|
monkeypatch.setattr(book, "EQUITY_WATERMARK", tmp_path / "equity_seen.json")
|
|
|
|
# --- trades.db (2026-08-26, estensione §5.12) --------------------------------------------
|
|
# ⚠️ `connect(path=DB_PATH)` cattura il default ALLA DEFINIZIONE: patchare la costante
|
|
# `DB_PATH` non cambierebbe niente. Si avvolge la funzione: un test che passa il path
|
|
# esplicito (tutti quelli esistenti usano `T.connect(tmp_path / ...)`) non cambia
|
|
# comportamento; un test che chiamasse `connect()` nudo finisce in tmp, non sul libro
|
|
# di bordo vivo.
|
|
import src.live.tradesdb as tradesdb
|
|
_connect_vero = tradesdb.connect
|
|
|
|
def _connect_isolato(path=None):
|
|
return _connect_vero(tmp_path / "trades.db" if path is None else path)
|
|
|
|
monkeypatch.setattr(tradesdb, "connect", _connect_isolato)
|
|
|
|
# --- docs/journal/ -----------------------------------------------------------------------
|
|
# `journal.scrivi_file` scrive pagine markdown TRACCIATE DA GIT: un test che lo chiama
|
|
# non deve poter sporcare il working tree (P16: il cron gira dal working tree).
|
|
import src.live.journal as journal
|
|
monkeypatch.setattr(journal, "JOURNAL_DIR", tmp_path / "journal")
|
|
# --- data/live/movimenti_dichiarati.jsonl (2026-09-06) ------------------------------------
|
|
# `journal.movimenti_capitale` LEGGE i movimenti dichiarati dall'operatore: se un test
|
|
# vedesse quelli veri, un versamento reale entrerebbe in una serie di fixture e il test
|
|
# passerebbe o fallirebbe per un motivo che non e' suo. Qui il file non esiste.
|
|
monkeypatch.setattr(journal, "MOVIMENTI_DICHIARATI", tmp_path / "movimenti_dichiarati.jsonl")
|
|
yield
|
|
|
|
|
|
@pytest.fixture(autouse=True, scope="session")
|
|
def _guardia_log_esecuzioni_vivo():
|
|
"""`book_executions.jsonl` e' scritto da `book_execute.py`, che i test caricano ad-hoc con
|
|
importlib: nessun monkeypatch di conftest puo' raggiungerlo PRIMA. Qui si fa l'altra meta'
|
|
del lavoro: impronta a inizio suite, verifica a fine suite. Se questo assert scatta, un
|
|
test ha scritto nel registro VIVO delle esecuzioni — trovarlo e dargli un path suo.
|
|
|
|
Falso positivo possibile ma raro: un FILL REALE del cron :47 mentre la suite gira
|
|
(30 fill in 43 giorni). In quel caso l'orario dell'ultima riga del file coincide col
|
|
giro del cron, non con la suite: verificarlo prima di dare la colpa a un test."""
|
|
f = PROJECT_ROOT / "data" / "live" / "book_executions.jsonl"
|
|
prima = (f.stat().st_mtime_ns, f.stat().st_size) if f.exists() else None
|
|
yield
|
|
dopo = (f.stat().st_mtime_ns, f.stat().st_size) if f.exists() else None
|
|
assert prima == dopo, (
|
|
"un test ha scritto in data/live/book_executions.jsonl (registro VIVO): "
|
|
f"{prima} -> {dopo}. Se l'ultima riga del file e' delle :47 puo' essere un fill "
|
|
"reale del cron; altrimenti trovare il test e isolarlo.")
|