journal: i versamenti non sono piu' P&L — movimenti di capitale scorporati dal trading
Il P&L di giornale era un delta di equity: la voce del 25/08 dichiarava +$1.414,57 di "giorno" quando $1.399,39 erano il deposito USDC, e il cumulato dall'arming avrebbe mentito per sempre. Nuova `movimenti_capitale()`: - soglia IMPORTATA da book.EQUITY_JUMP_ALERT, non ridichiarata (P1); - "movimento" solo se il salto supera di >2x il massimo che il mercato MISURATO (feed certificato, tetto di leva da config) poteva produrre fra le due letture; - altrimenti AMBIGUO: dichiarato con attenzione e NON scorporato (P12 -- un crash vero a tutta leva non deve diventare "prelievo"); idem a feed illeggibile (P5). Scansione dell'intera storia: 1 evento, esattamente il deposito (+209,6% contro mercato max 0,22%), zero falsi positivi. `concentrazione` ora lavora sul cumulato di TRADING; le regole sui movimenti stanno sopra l'early-return "nessun giro". Limite dichiarato (D5): un movimento sotto il 10% dell'equity resta nel P&L. 7 prove nuove (31 -> 37), incluso il controllo M15 al contrario. Voce del 25/08 rigenerata: giorno -> trading +$15,18; cumulato -> trading +$59,14. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01B9UyJLHzR7EJzxR3iQ3RN1
This commit is contained in:
@@ -358,3 +358,112 @@ def test_il_cron_non_apre_piu_la_voce_del_giorno_in_corso():
|
||||
assert chiamate, "il giornale non gira piu' nel cron: e' un'altra rottura, non questa"
|
||||
for r in chiamate:
|
||||
assert "--giorno" in r, f"apre un giorno implicito (= oggi, parziale): {r}"
|
||||
|
||||
|
||||
# ------------------------------------------------- movimenti di capitale nel P&L
|
||||
# Il difetto (2026-08-25): la voce dichiarava «giorno: +$1.414,57» quando $1.399 erano un
|
||||
# deposito USDC — il P&L era un delta di equity e ogni versamento ci finiva dentro come
|
||||
# profitto, per sempre (il cumulato dall'arming non si sarebbe mai piu' corretto).
|
||||
|
||||
def _px_piatto(livello=100.0):
|
||||
ix = pd.date_range("2026-08-20", "2026-08-27", freq="1h", tz="UTC")
|
||||
return pd.Series(livello, index=ix)
|
||||
|
||||
|
||||
def _serie_equity(con, punti):
|
||||
for ts, eq in punti:
|
||||
con.execute("INSERT INTO equity (ts_utc, equity, src) VALUES (?,?,?)", (ts, eq, "test"))
|
||||
con.commit()
|
||||
|
||||
|
||||
def test_un_versamento_viene_rilevato_e_scorporato(db, monkeypatch):
|
||||
monkeypatch.setattr(J, "_px_1h", lambda a: _px_piatto())
|
||||
_serie_equity(db, [("2026-08-25T09:47:01+00:00", 667.0),
|
||||
("2026-08-25T10:47:01+00:00", 667.5),
|
||||
("2026-08-25T11:47:01+00:00", 2066.5),
|
||||
("2026-08-25T12:47:01+00:00", 2064.0)])
|
||||
p = J.pnl_giorno(db, date(2026, 8, 25))
|
||||
ev = p["movimenti_giorno"]
|
||||
assert len(ev) == 1 and ev[0]["classe"] == "movimento"
|
||||
assert p["mov_giorno"] == pytest.approx(1399.0)
|
||||
# il delta resta il FATTO, il trading e' il netto
|
||||
assert p["delta_equity"] == pytest.approx(2064.0 - 667.0)
|
||||
assert p["trading_giorno"] == pytest.approx(p["delta_equity"] - 1399.0)
|
||||
assert p["trading_da_arming"] == pytest.approx(p["cumulato_da_arming"] - 1399.0)
|
||||
|
||||
|
||||
def test_il_markdown_scorpora_e_lo_dice(db, monkeypatch):
|
||||
monkeypatch.setattr(J, "_px_1h", lambda a: _px_piatto())
|
||||
_serie_equity(db, [("2026-08-25T10:47:01+00:00", 667.0),
|
||||
("2026-08-25T11:47:01+00:00", 2066.0)])
|
||||
voce = _voce(pnl=J.pnl_giorno(db, date(2026, 8, 25)), giorno="2026-08-25")
|
||||
md = J.rendi_markdown(db, voce)
|
||||
assert "movimenti di capitale" in md and "trading" in md
|
||||
righe = J.lettura(db, voce)
|
||||
assert "movimento" in _ids(righe)
|
||||
|
||||
|
||||
def test_un_crash_a_tutta_leva_resta_AMBIGUO_e_non_diventa_prelievo(db, monkeypatch):
|
||||
# controllo positivo al contrario (M15): il mercato fa -15% nell'ora, il libro al tetto
|
||||
# di leva (1.0x) puo' spiegare il -14% di equity -> NON e' un prelievo, e' dichiarato.
|
||||
ix = pd.date_range("2026-08-20", "2026-08-27", freq="1h", tz="UTC")
|
||||
px = pd.Series(100.0, index=ix)
|
||||
px[ix >= "2026-08-25T11:00:00+00:00"] = 85.0
|
||||
monkeypatch.setattr(J, "_px_1h", lambda a: px)
|
||||
_serie_equity(db, [("2026-08-25T10:47:01+00:00", 600.0),
|
||||
("2026-08-25T11:47:01+00:00", 516.0)])
|
||||
p = J.pnl_giorno(db, date(2026, 8, 25))
|
||||
assert p["movimenti_giorno"][0]["classe"] == "ambiguo"
|
||||
assert p["mov_giorno"] == 0.0 # niente scorporo
|
||||
assert p["trading_giorno"] == pytest.approx(p["delta_equity"])
|
||||
righe = J.lettura(db, _voce(pnl=p, giorno="2026-08-25"))
|
||||
amb = [r for r in righe if r["id"] == "movimento_ambiguo"]
|
||||
assert amb and amb[0]["livello"] == "attenzione"
|
||||
|
||||
|
||||
def test_sotto_soglia_niente_eventi_e_trading_uguale_al_delta(db, monkeypatch):
|
||||
monkeypatch.setattr(J, "_px_1h", lambda a: _px_piatto())
|
||||
_serie_equity(db, [("2026-08-25T10:47:01+00:00", 600.0),
|
||||
("2026-08-25T11:47:01+00:00", 612.0)]) # +2%: sotto EQUITY_JUMP_ALERT
|
||||
p = J.pnl_giorno(db, date(2026, 8, 25))
|
||||
assert p["movimenti_giorno"] == [] and p["mov_giorno"] == 0.0
|
||||
assert p["trading_giorno"] == pytest.approx(p["delta_equity"])
|
||||
md = J.rendi_markdown(db, _voce(pnl=p, giorno="2026-08-25"))
|
||||
assert "movimenti di capitale" not in md
|
||||
|
||||
|
||||
def test_la_soglia_e_DERIVATA_dal_book_non_riscritta(db, monkeypatch):
|
||||
# P1: se il rilevatore del book cambia soglia, il giornale la segue senza essere toccato.
|
||||
from src.live import book as B
|
||||
monkeypatch.setattr(B, "EQUITY_JUMP_ALERT", 0.50)
|
||||
monkeypatch.setattr(J, "_px_1h", lambda a: _px_piatto())
|
||||
_serie_equity(db, [("2026-08-25T10:47:01+00:00", 600.0),
|
||||
("2026-08-25T11:47:01+00:00", 780.0)]) # +30%: sotto la soglia alzata
|
||||
p = J.pnl_giorno(db, date(2026, 8, 25))
|
||||
assert p["movimenti_giorno"] == []
|
||||
|
||||
|
||||
def test_mercato_non_misurabile_e_ambiguo_dichiarato_non_movimento(db, monkeypatch):
|
||||
# P5: "non vedo" non e' "va tutto bene" — senza feed il salto NON si attribuisce.
|
||||
monkeypatch.setattr(J, "_px_1h", lambda a: None)
|
||||
_serie_equity(db, [("2026-08-25T10:47:01+00:00", 667.0),
|
||||
("2026-08-25T11:47:01+00:00", 2066.0)])
|
||||
p = J.pnl_giorno(db, date(2026, 8, 25))
|
||||
e = p["movimenti_giorno"][0]
|
||||
assert e["classe"] == "ambiguo" and e["mercato_max_pct"] is None
|
||||
righe = J.lettura(db, _voce(pnl=p, giorno="2026-08-25"))
|
||||
assert any(r["id"] == "movimento_ambiguo" and "non e' misurabile" in r["testo"] for r in righe)
|
||||
|
||||
|
||||
def test_la_concentrazione_non_conta_i_versamenti_come_pnl(db, monkeypatch):
|
||||
# 30 giorni piatti a 600, poi un versamento da 1400: senza scorporo la regola direbbe
|
||||
# "tutto il P&L viene dagli ultimi 7 giorni" su un P&L che non esiste.
|
||||
monkeypatch.setattr(J, "_px_1h", lambda a: _px_piatto())
|
||||
punti = [(f"2026-07-{g:02d}T12:00:00+00:00", 600.0) for g in range(1, 32)]
|
||||
punti += [(f"2026-08-{g:02d}T12:00:00+00:00", 600.0) for g in range(1, 25)]
|
||||
punti += [("2026-08-25T11:47:01+00:00", 2000.0)]
|
||||
_serie_equity(db, punti)
|
||||
p = J.pnl_giorno(db, date(2026, 8, 25))
|
||||
righe = J.lettura(db, _voce(pnl=p, giorno="2026-08-25"))
|
||||
conc = [r for r in righe if r["id"] == "concentrazione"]
|
||||
assert conc == [] # il cumulato di TRADING e' 0: la regola deve tacere
|
||||
|
||||
Reference in New Issue
Block a user