analista di bordo: un modello scrive la prosa del giorno, in un campo suo
Aggiunto il quarto livello della pagina, tenuto separato dagli altri tre: numeri -> misurati dal feed e dal DB Lettura -> regole deterministiche, ognuna col suo id Analisi -> questo: prosa di un modello, che puo' sbagliare Nota -> l'operatore NON scrive dentro `nota`, che era la richiesta letterale: quel campo e' dell'operatore, ed e' cio' che a rileggere il giornale fra sei mesi permette di sapere chi ha scritto cosa. L'agente ha `analisi`, marcato col modello, con l'ora e con l'esito del controllo sui numeri. Gira via `claude -p` (verificato con env -i che risponda nell'ambiente nudo di cron), una chiamata al giorno sul giorno CHIUSO, tolte le tool. Tre guardie, una per ogni modo in cui una prosa generata rovina un registro: - NUMERO INVENTATO: numeri_non_supportati() estrae ogni cifra dall'analisi e verifica che compaia in cio' che il modello ha ricevuto. Oltre tre numeri liberi l'analisi e' RIFIUTATA e la pagina resta senza. E' un controllo debole per costruzione, e lo dichiara: prende l'invenzione, non il ragionamento sbagliato. - COMMENTO DI SE': senza_analisi() toglie dalla pagina la sezione dell'agente prima di dargliela. Al primo giro reale il modello aveva letto la propria uscita precedente e prodotto un paragrafo sull'avviso che si era preso il giorno prima — un ciclo di retroazione che in poche settimane avrebbe riempito il giornale di meta-commento, e che nessun controllo automatico puo' distinguere da prosa valida. - ANALISI DI IERI SPACCIATA PER OGGI: se il modello non risponde, la pagina resta VUOTA e il perche' viene registrato (stato + motivo). Il silenzio non diventa continuita'. Corretto anche un falso positivo mio: il tripwire validava sulla sola pagina mentre il prompt include anche il blocco storico, quindi bocciava un'equity vera. Una guardia piu' stretta del contratto produce allarmi che si impara a ignorare. Ogni guardia ha un test in entrambe le direzioni. 695 test passano. Strategia, pesi, config INVARIATI. Nessun ordine. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,99 @@
|
||||
"""L'analista di bordo: fa scrivere a un modello l'analisi del giorno nel campo `analisi`.
|
||||
|
||||
uv run python scripts/live/analista.py # oggi
|
||||
uv run python scripts/live/analista.py --giorno 2026-08-21
|
||||
uv run python scripts/live/analista.py --modello claude-opus-5
|
||||
uv run python scripts/live/analista.py --secco # stampa e non salva
|
||||
|
||||
NON tocca `nota` (dell'operatore) e non tocca nessuna sezione misurata: scrive SOLO in `analisi`.
|
||||
Se il modello non risponde, l'analisi di ieri NON diventa quella di oggi: si registra l'errore
|
||||
e la sezione resta vuota. Sola lettura su tutto il resto; nessun ordine.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import sys
|
||||
from datetime import date, datetime, timedelta, timezone
|
||||
from pathlib import Path
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[2]
|
||||
sys.path.insert(0, str(ROOT))
|
||||
|
||||
from src.live import analista as A # noqa: E402
|
||||
from src.live import journal as J # noqa: E402
|
||||
from src.live import tradesdb as T # noqa: E402
|
||||
|
||||
GIORNI_STORICO = 10
|
||||
|
||||
|
||||
def _arg(flag: str, default=None):
|
||||
a = sys.argv[1:]
|
||||
return a[a.index(flag) + 1] if flag in a and a.index(flag) + 1 < len(a) else default
|
||||
|
||||
|
||||
def storico(con, g: date, n: int = GIORNI_STORICO) -> str:
|
||||
"""Le righe essenziali dei giorni precedenti: serve per vedere COSA E' CAMBIATO."""
|
||||
righe = ["giorno equity delta fill rt posizioni"]
|
||||
for i in range(n, 0, -1):
|
||||
d = (g - timedelta(days=i)).isoformat()
|
||||
r = con.execute("SELECT pnl, libro FROM journal WHERE giorno=?", (d,)).fetchone()
|
||||
if not r:
|
||||
continue
|
||||
import json as _j
|
||||
p, lb = _j.loads(r["pnl"] or "{}"), _j.loads(r["libro"] or "{}")
|
||||
pos = " ".join(f"{a} ${v['posizione']:+,.0f}" for a, v in sorted((lb.get("asset") or {}).items()))
|
||||
dq = p.get("delta_equity")
|
||||
righe.append(f"{d} {p.get('equity_fine') or float('nan'):8,.2f} "
|
||||
f"{' n/d' if dq is None else f'{dq:+8.2f}'} "
|
||||
f"{p.get('fill', 0):>4} {p.get('roundtrip_chiusi', 0):>2} {pos or 'flat'}")
|
||||
return "\n".join(righe)
|
||||
|
||||
|
||||
def scrivi(con, g: date, modello: str, secco: bool = False, verbose: bool = True) -> str:
|
||||
voce = J.costruisci(con, g)
|
||||
J.salva(con, voce) # le sezioni misurate prima, cosi' il modello le legge
|
||||
pagina = J.rendi_markdown(con, voce)
|
||||
st = storico(con, g)
|
||||
prompt = A.costruisci_prompt(pagina, st)
|
||||
# La guardia si applica a TUTTO cio' che il modello ha ricevuto, non alla sola pagina:
|
||||
# i numeri del blocco storico gli sono stati dati, citarli non e' inventare. (Primo giro:
|
||||
# validando sulla sola pagina il tripwire bocciava un'equity vera presa dallo storico —
|
||||
# una guardia piu' stretta del contratto produce allarmi che si imparera' a ignorare.)
|
||||
fonte = A.senza_analisi(pagina) + "\n" + st
|
||||
testo, errore = A.interroga(prompt, modello=modello)
|
||||
|
||||
if errore:
|
||||
stato, motivi = "errore", [errore]
|
||||
testo = ""
|
||||
else:
|
||||
stato, motivi = A.valida(testo, fonte)
|
||||
if stato == "rifiutata":
|
||||
testo = ""
|
||||
|
||||
if verbose:
|
||||
print(f" {g} modello {modello} -> {stato}"
|
||||
+ (f" ({'; '.join(motivi)})" if motivi else ""))
|
||||
if secco:
|
||||
print("\n" + (testo or "(niente da salvare)"))
|
||||
return stato
|
||||
|
||||
if testo:
|
||||
con.execute("""UPDATE journal SET analisi=?, analisi_modello=?, analisi_ts=?,
|
||||
analisi_stato=?, analisi_motivi=? WHERE giorno=?""",
|
||||
(testo, modello, T.ora(), stato, "; ".join(motivi) or None, g.isoformat()))
|
||||
else:
|
||||
# niente analisi: si REGISTRA il perche' e NON si lascia in pagina quella di ieri
|
||||
con.execute("""UPDATE journal SET analisi='', analisi_modello=?, analisi_ts=?,
|
||||
analisi_stato=?, analisi_motivi=? WHERE giorno=?""",
|
||||
(modello, T.ora(), stato, "; ".join(motivi) or None, g.isoformat()))
|
||||
con.commit()
|
||||
J.scrivi_file(con, voce)
|
||||
return stato
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
con = T.connect()
|
||||
g = date.fromisoformat(_arg("--giorno", datetime.now(timezone.utc).date().isoformat()))
|
||||
st = scrivi(con, g, modello=_arg("--modello", A.MODELLO_DEFAULT),
|
||||
secco="--secco" in sys.argv[1:], verbose="--quiet" not in sys.argv[1:])
|
||||
con.close()
|
||||
sys.exit(0 if st in ("ok", "sospetta") else 1)
|
||||
Reference in New Issue
Block a user