un flag sconosciuto non e' l'azione di default: guardia su 18 script, indice USDE orario, pulizia
MISURATO oggi durante la revisione: `trades_db.py --help` non stampava l'uso, cadeva in sync() e riscriveva meta.ultimo_sync. Nessuno dei 18 script di scripts/live/ usava argparse: un flag sbagliato era il ramo else. Su journal.py avrebbe scritto pagina e riga di DB, su analista.py avrebbe speso una chiamata al modello e mandato un Telegram. - src/live/cli.valida: prima istruzione di ogni __main__, prima di connect()/sync/rete. --help -> 0 con l'uso; flag ignoto, valore mancante o posizionale -> 2 con l'elenco dei previsti (P4). NIENTE argparse: cambierebbe messaggi, codici d'uscita e --help di script che il cron gia' chiama. - 18 script cablati (i 3 che scrivono + 14 + cc01), flag invariati. - tests/test_cli_flag.py (30): elenco DERIVATO dalla cartella (P1), valida come prima istruzione, uso che documenta i flag, e i flag che il CRON usa davvero restano accettati (P15/P16); end-to-end su --help e flag ignoto con trades.db non toccato (M15). Verificato a mano: monitor_health --quiet, trades_db --sync --quiet, book_execute dry-run. Debito §5.15, primo passo: balance_watch (orario) registra `usde_usdc` a ogni campione — None con la ragione se illeggibile, mai 1,0. Il cablaggio nel bound quando la serie ha storia. Pulizia dalla revisione: tests/helpers.carica_script al posto della 15a copia del loader importlib (5 file del libro live); il fill di prova via upsert_fills invece di un INSERT che lasciava verified NULL; asserzioni non ancorate al padding; movimenti_capitale accetta le righe gia' lette (una SELECT invece di due ai due lati di una scrittura del cron). Test 910 verdi (+52). Diario 2026-09-02c. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XDJsH3iDSaBns3ccpPBwiu
This commit is contained in:
@@ -372,9 +372,24 @@ d'ancora in modo diverso (nella differenza si cancella in parte, nel livello per
|
|||||||
−1,5% (indice 0,95) resta sotto soglia e nel P&L; una rottura piena (−31%) supera 2× un mercato
|
−1,5% (indice 0,95) resta sotto soglia e nel P&L; una rottura piena (−31%) supera 2× un mercato
|
||||||
"fermo" e diventerebbe un **«movimento» scorporato come prelievo**, nel giornale e nel report —
|
"fermo" e diventerebbe un **«movimento» scorporato come prelievo**, nel giornale e nel report —
|
||||||
l'invenzione silenziosa che P12 vieta. Il bound corretto somma `quota_usde × |Δ indice usde_usdc|`,
|
l'invenzione silenziosa che P12 vieta. Il bound corretto somma `quota_usde × |Δ indice usde_usdc|`,
|
||||||
ma l'indice e' registrato **una volta al giorno** (`usde_watch`, 12:35Z): serve una serie oraria
|
ma l'indice era registrato **una volta al giorno** (`usde_watch`, 12:35Z). ◐ **Primo passo fatto
|
||||||
(`balance_watch` la potrebbe portare) prima di chiuderlo. Intanto: chi legge un «movimento»
|
il 02/09:** `balance_watch` (orario, sola lettura) registra ora `usde_usdc` a ogni campione —
|
||||||
negativo senza bonifico in uscita **lo verifichi contro `usde_watch.jsonl`** prima di crederci.
|
None con la ragione se l'indice non e' leggibile, mai 1,0. **Manca il cablaggio nel bound**, che
|
||||||
|
si fa quando la serie ha storia. Intanto: chi legge un «movimento» negativo senza bonifico in
|
||||||
|
uscita **lo verifichi contro `balance_watch.jsonl`** prima di crederci.
|
||||||
|
|
||||||
|
16. ✅ **RIPARATO (2026-09-02).** Nessuno dei **18** script di `scripts/live/` usava argparse: un
|
||||||
|
flag sconosciuto non era un errore, era **il ramo `else`**. Costo misurato lo stesso giorno
|
||||||
|
durante una revisione: `trades_db.py --help` non stampava l'uso, cadeva in `sync()` e riscriveva
|
||||||
|
`meta.ultimo_sync`; sugli altri due sarebbe stato peggio — `journal.py --help` avrebbe scritto
|
||||||
|
pagina e riga di DB, `analista.py --help` avrebbe speso una chiamata al modello e mandato un
|
||||||
|
Telegram. Ora `src/live/cli.valida` e' la **prima istruzione** di ogni `__main__`: `--help`
|
||||||
|
stampa l'uso ed esce **0**, un flag ignoto esce **2** con l'elenco di quelli previsti (P4).
|
||||||
|
Niente argparse: cambierebbe messaggi, codici d'uscita e `--help` di script che il cron gia'
|
||||||
|
chiama — qui si lascia intatto cio' che gira. **`tests/test_cli_flag.py` (30)**: l'elenco degli
|
||||||
|
script si **deriva dalla cartella** (uno nuovo senza guardia fa fallire il test), verifica che
|
||||||
|
`valida` sia la prima istruzione, che l'uso documenti i flag accettati, e che **i flag che il
|
||||||
|
cron usa davvero restino accettati** (P15/P16). Diario `2026-09-02c-flag-e-pulizia.md`.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -589,10 +604,11 @@ uv run python scripts/analysis/certify_feed.py [--local] # certifica i f
|
|||||||
uv run python scripts/portfolio/run_portfolio.py # report del portafoglio (ricerca)
|
uv run python scripts/portfolio/run_portfolio.py # report del portafoglio (ricerca)
|
||||||
uv run python scripts/live/paper_portfolio.py # avanza il paper (forward-only)
|
uv run python scripts/live/paper_portfolio.py # avanza il paper (forward-only)
|
||||||
uv run python scripts/live/trades_db.py --report # stato libro di bordo + P&L (TWR, non e1/e0)
|
uv run python scripts/live/trades_db.py --report # stato libro di bordo + P&L (TWR, non e1/e0)
|
||||||
|
uv run python scripts/live/<script>.py --help # uso e flag (dal 02/09: un flag ignoto esce 2)
|
||||||
uv run python scripts/live/trades_db.py --reconcile # incrocio delle 3 fonti sui fill
|
uv run python scripts/live/trades_db.py --reconcile # incrocio delle 3 fonti sui fill
|
||||||
uv run python scripts/live/journal.py # voce del giorno (numeri + lettura)
|
uv run python scripts/live/journal.py # voce del giorno (numeri + lettura)
|
||||||
uv run python scripts/live/analista.py --secco # analisi del giorno, senza salvare
|
uv run python scripts/live/analista.py --secco # analisi del giorno, senza salvare
|
||||||
uv run pytest # test (858, tutti verdi al 02/09)
|
uv run pytest # test (910, tutti verdi al 02/09)
|
||||||
```
|
```
|
||||||
|
|
||||||
```python
|
```python
|
||||||
|
|||||||
@@ -0,0 +1,77 @@
|
|||||||
|
# 2026-09-02 — Un flag sconosciuto non è l'azione di default
|
||||||
|
|
||||||
|
*Scritto fra le 15:45Z e le 16:05Z. Il difetto è stato misurato, non ipotizzato: la revisione del
|
||||||
|
codice di questa sessione ha eseguito `trades_db.py --help` come sonda di tempi e lo ha visto
|
||||||
|
sincronizzare il DB.*
|
||||||
|
|
||||||
|
## 1. Il difetto
|
||||||
|
|
||||||
|
Nessuno dei 18 script di `scripts/live/` usava argparse. Leggevano gli argomenti così:
|
||||||
|
|
||||||
|
```python
|
||||||
|
if "--reconcile" in args: ...
|
||||||
|
elif "--report" in args: ...
|
||||||
|
else: sync() # <- qui finiva QUALUNQUE cosa non riconosciuta
|
||||||
|
```
|
||||||
|
|
||||||
|
Un flag sbagliato non era un errore: era il ramo finale. Costo osservato e costi possibili:
|
||||||
|
|
||||||
|
| script | `--help` faceva | gravità |
|
||||||
|
|---|---|---|
|
||||||
|
| `trades_db.py` | `sync()`: riscriveva `meta.ultimo_sync` | osservato oggi, danno nullo |
|
||||||
|
| `journal.py` | scriveva la pagina di oggi e una riga nel DB | non osservato, ma scrive |
|
||||||
|
| `analista.py` | spendeva una chiamata al modello e mandava un Telegram | non osservato, ma spende |
|
||||||
|
|
||||||
|
## 2. La riparazione, e perché non è argparse
|
||||||
|
|
||||||
|
`src/live/cli.valida(nome, uso, flag=..., con_valore=...)`, chiamata come **prima istruzione** di
|
||||||
|
ogni blocco `__main__` — prima di `connect()`, prima del sync, prima della rete.
|
||||||
|
|
||||||
|
- `--help` / `-h` → stampa l'uso, esce **0**;
|
||||||
|
- flag non dichiarato, o valore mancante, o posizionale → stderr con **cosa** e **cosa si poteva**
|
||||||
|
(P4), esce **2** (errore d'uso, distinto dall'1 con cui gli script segnalano un esito negativo);
|
||||||
|
- tutto il resto invariato.
|
||||||
|
|
||||||
|
**Argparse no.** Cambierebbe messaggi d'errore, codici d'uscita e il comportamento di `--help` su
|
||||||
|
18 script che il cron già invoca: romperebbe ciò che gira per riparare ciò che non gira mai. Qui
|
||||||
|
serviva l'opposto — lasciare intatti i flag esistenti e rifiutare solo l'ignoto.
|
||||||
|
|
||||||
|
## 3. Il test guarda entrambe le metà del contratto
|
||||||
|
|
||||||
|
`tests/test_cli_flag.py`, 30 test:
|
||||||
|
|
||||||
|
- **l'elenco degli script si deriva dalla cartella** (P1): uno script nuovo che legge `sys.argv` a
|
||||||
|
mano e non chiama `valida` fa fallire il test, non passa inosservato perché nessuno ha aggiornato
|
||||||
|
una lista;
|
||||||
|
- `valida` dev'essere la **prima istruzione** di `__main__` (uscire dopo l'effetto non è uscire);
|
||||||
|
- l'uso deve **nominare** i flag che lo script accetta;
|
||||||
|
- **i flag che il cron usa davvero devono restare accettati** (P15/P16, letti dai `cron_*.sh`): una
|
||||||
|
guardia che ferma il libro di bordo la notte stessa sarebbe peggio del difetto;
|
||||||
|
- end-to-end sui tre script che scrivono: `--help` esce 0 e **non tocca `trades.db`**; un flag
|
||||||
|
ignoto esce 2 e non lo tocca (M15: il caso che ha causato il difetto, non un caso vicino).
|
||||||
|
|
||||||
|
Verificato a mano anche il contrario: `monitor_health.py --quiet`, `trades_db.py --sync --quiet`
|
||||||
|
e `book_execute.py` in dry-run escono 0 come prima.
|
||||||
|
|
||||||
|
## 4. Il resto della sessione
|
||||||
|
|
||||||
|
**Indice USDE orario** (debito §5.15, primo passo). Il classificatore dei movimenti di capitale
|
||||||
|
confronta due letture di equity a un'ora di distanza e il suo bound guarda solo BTC/ETH, mentre il
|
||||||
|
31% dell'equity è USDE: un depeg sarebbe scorporato come un prelievo. `usde_watch` registra
|
||||||
|
l'indice una volta al giorno — troppo rado. Ora `balance_watch` (orario, sola lettura) lo registra
|
||||||
|
a ogni campione, `None` con la ragione se non leggibile, mai 1,0. Il cablaggio nel bound viene
|
||||||
|
quando la serie ha storia: prima serve il dato, poi la regola.
|
||||||
|
|
||||||
|
**Pulizia lasciata dalla revisione.** `tests/helpers.carica_script` sostituisce la dodicesima copia
|
||||||
|
del caricatore `importlib` (15 in giro, con nomi di modulo diversi per lo stesso file e differenze
|
||||||
|
silenziose su `sys.modules`); il fill di prova si scrive con `upsert_fills` invece che con un
|
||||||
|
`INSERT` a mano che lasciava `verified` NULL, una forma che la produzione non produce; le
|
||||||
|
asserzioni sul testo del report non sono più ancorate al padding; `movimenti_capitale` accetta le
|
||||||
|
righe già lette, così il report non fa due SELECT sulla stessa tabella a un'ora del cron — due
|
||||||
|
letture ai due lati di una scrittura descriverebbero due istanti diversi.
|
||||||
|
|
||||||
|
## 5. Cosa NON ho fatto
|
||||||
|
|
||||||
|
- I loader `importlib` dei test sulla **ricerca** (moduli in `scripts/research/`) restano come
|
||||||
|
sono: sono a livello di modulo, con radici diverse, e toccarli non ripara niente.
|
||||||
|
- Il bound USDE nel classificatore: manca la storia, non il codice.
|
||||||
@@ -939,6 +939,22 @@ parser accetta solo espressioni regolari e rifiuta il resto. Vincolo del minuto
|
|||||||
un commento che dichiara una cadenza diversa da quella che gira si paga quando qualcuno lo "corregge"
|
un commento che dichiara una cadenza diversa da quella che gira si paga quando qualcuno lo "corregge"
|
||||||
nel verso sbagliato.**
|
nel verso sbagliato.**
|
||||||
|
|
||||||
|
### Un flag sconosciuto non è l'azione di default (2026-09-02)
|
||||||
|
|
||||||
|
Nessuno dei 18 script di `scripts/live/` usava argparse: leggevano `sys.argv` con `in` e `index`,
|
||||||
|
quindi un flag sbagliato non era un errore — era il ramo `else`. Il costo si è misurato lo stesso
|
||||||
|
giorno, dentro una revisione: `trades_db.py --help` è caduto in `sync()` e ha riscritto
|
||||||
|
`meta.ultimo_sync`. Sui due script che scrivono davvero sarebbe stato peggio: la pagina di giornale
|
||||||
|
e una riga di DB, oppure una chiamata al modello più un Telegram. **La riparazione non è argparse** —
|
||||||
|
cambierebbe messaggi d'errore, codici d'uscita e `--help` di script che il cron già chiama, cioè
|
||||||
|
romperebbe ciò che gira per riparare ciò che non gira mai. È una funzione di venti righe
|
||||||
|
(`src/live/cli.valida`) che rifiuta ciò che non è dichiarato ed esce **prima di qualunque effetto**:
|
||||||
|
`--help` → 0, flag ignoto → 2 con l'elenco dei previsti (P4). Il test deriva l'elenco degli script
|
||||||
|
dalla cartella (P1: un file nuovo senza guardia fallisce, non passa inosservato), controlla che
|
||||||
|
`valida` sia la prima istruzione di `__main__` (uscire dopo `connect()` sarebbe uscire dopo
|
||||||
|
l'effetto) e — l'altra metà del contratto, P15/P16 — che **i flag che il cron usa davvero restino
|
||||||
|
accettati**: una guardia che ferma il libro di bordo la notte stessa sarebbe peggio del difetto.
|
||||||
|
|
||||||
### Cosa resta scoperto
|
### Cosa resta scoperto
|
||||||
|
|
||||||
1. **La sonda dice di chi è il guasto, non lo aggira.** Col gateway giù il libro continua ad
|
1. **La sonda dice di chi è il guasto, non lo aggira.** Col gateway giù il libro continua ad
|
||||||
|
|||||||
@@ -115,7 +115,21 @@ def scrivi(con, g: date, modello: str, secco: bool = False, verbose: bool = True
|
|||||||
return stato
|
return stato
|
||||||
|
|
||||||
|
|
||||||
|
USO = """uso: analista.py [--giorno AAAA-MM-GG] [--modello NOME] [--secco] [--no-telegram] [--quiet]
|
||||||
|
|
||||||
|
(nessun flag) scrive l'analisi di OGGI nel campo `analisi` e manda la notifica
|
||||||
|
--giorno G analizza il giorno G
|
||||||
|
--modello M modello da usare (default: src.live.analista.MODELLO_DEFAULT)
|
||||||
|
--secco stampa e NON salva, niente Telegram
|
||||||
|
--no-telegram salva ma non manda la notifica
|
||||||
|
--quiet non stampa
|
||||||
|
|
||||||
|
⚠️ SPENDE una chiamata al modello e MANDA un messaggio Telegram, salvo --secco/--no-telegram."""
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
from src.live.cli import valida
|
||||||
|
valida("analista.py", USO, flag=("--secco", "--no-telegram", "--quiet"),
|
||||||
|
con_valore=("--giorno", "--modello"))
|
||||||
con = T.connect()
|
con = T.connect()
|
||||||
g = date.fromisoformat(_arg("--giorno", datetime.now(timezone.utc).date().isoformat()))
|
g = date.fromisoformat(_arg("--giorno", datetime.now(timezone.utc).date().isoformat()))
|
||||||
st = scrivi(con, g, modello=_arg("--modello", A.MODELLO_DEFAULT),
|
st = scrivi(con, g, modello=_arg("--modello", A.MODELLO_DEFAULT),
|
||||||
|
|||||||
@@ -85,6 +85,17 @@ def campiona(c: DeribitRead) -> dict:
|
|||||||
rec["n_posizioni"] = sum(1 for p in pos if abs(float(p.get("size") or 0)) > 1)
|
rec["n_posizioni"] = sum(1 for p in pos if abs(float(p.get("size") or 0)) > 1)
|
||||||
except Exception:
|
except Exception:
|
||||||
rec["nozionale_lordo"], rec["n_posizioni"] = None, None
|
rec["nozionale_lordo"], rec["n_posizioni"] = None, None
|
||||||
|
# INDICE usde_usdc, ORARIO (aggiunto 2026-09-02, debito §5.15). `usde_watch` lo registra una
|
||||||
|
# volta al giorno (12:35Z): troppo rado per il classificatore dei movimenti di capitale, che
|
||||||
|
# confronta due letture di equity a un'ora di distanza e oggi guarda solo BTC/ETH — con il
|
||||||
|
# 31% dell'equity in USDE, un depeg sarebbe scorporato come un PRELIEVO. Questa e' la serie
|
||||||
|
# che manca; il cablaggio nel bound viene dopo, quando ci sara' storia. None se non leggibile
|
||||||
|
# (P5: "non vedo" non e' "il peg tiene").
|
||||||
|
try:
|
||||||
|
from src.live.usde import prezzo_indice
|
||||||
|
rec["usde_usdc"] = prezzo_indice()
|
||||||
|
except Exception as e:
|
||||||
|
rec["usde_usdc"], rec["usde_usdc_errore"] = None, f"{type(e).__name__}"
|
||||||
return rec
|
return rec
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -355,5 +355,14 @@ def main():
|
|||||||
raise
|
raise
|
||||||
|
|
||||||
|
|
||||||
|
USO = """uso: book_execute.py [--execute]
|
||||||
|
|
||||||
|
uv run python scripts/live/book_execute.py # DRY-RUN (piano, nessun ordine)
|
||||||
|
uv run python scripts/live/book_execute.py --execute # esegue SOLO se execution_enabled=true
|
||||||
|
|
||||||
|
Dettaglio nel docstring in testa al file."""
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
from src.live.cli import valida
|
||||||
|
valida("book_execute.py", USO, flag=("--execute",), con_valore=())
|
||||||
main()
|
main()
|
||||||
|
|||||||
@@ -93,5 +93,13 @@ def main() -> None:
|
|||||||
print(" regime QUIET: carry non raccoglibile (coerente con luglio 2026).")
|
print(" regime QUIET: carry non raccoglibile (coerente con luglio 2026).")
|
||||||
|
|
||||||
|
|
||||||
|
USO = """uso: cc01_regime_watch.py
|
||||||
|
|
||||||
|
(nessun flag) sorveglia il regime di CC01 e stampa lo stato.
|
||||||
|
|
||||||
|
Dettaglio nel docstring in testa al file."""
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
from src.live.cli import valida
|
||||||
|
valida("cc01_regime_watch.py", USO)
|
||||||
main()
|
main()
|
||||||
|
|||||||
@@ -144,5 +144,15 @@ def main() -> int:
|
|||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
USO = """uso: edge_watch.py [--quiet]
|
||||||
|
|
||||||
|
edge_watch.py — sorveglianza dell'EDGE del book live. Riporta e allerta; non tocca nulla.
|
||||||
|
uv run python scripts/live/edge_watch.py # report
|
||||||
|
uv run python scripts/live/edge_watch.py --quiet # solo se qualcosa scatta (cron)
|
||||||
|
|
||||||
|
Dettaglio nel docstring in testa al file."""
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
from src.live.cli import valida
|
||||||
|
valida("edge_watch.py", USO, flag=("--quiet",), con_valore=())
|
||||||
raise SystemExit(main())
|
raise SystemExit(main())
|
||||||
|
|||||||
@@ -252,5 +252,15 @@ def main() -> int:
|
|||||||
return 2 if r["level"] == "AZIONE" else 0
|
return 2 if r["level"] == "AZIONE" else 0
|
||||||
|
|
||||||
|
|
||||||
|
USO = """uso: fee_watch.py [--quiet]
|
||||||
|
|
||||||
|
fee_watch.py — sorveglia lo schema fee di Deribit e applica la regola DECISA IN ANTICIPO.
|
||||||
|
uv run python scripts/live/fee_watch.py # report
|
||||||
|
uv run python scripts/live/fee_watch.py --quiet # stampa/allerta solo se qualcosa cambia
|
||||||
|
|
||||||
|
Dettaglio nel docstring in testa al file."""
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
from src.live.cli import valida
|
||||||
|
valida("fee_watch.py", USO, flag=("--quiet",), con_valore=())
|
||||||
raise SystemExit(main())
|
raise SystemExit(main())
|
||||||
|
|||||||
@@ -41,7 +41,19 @@ def una(con, g: date, nota: str | None = None, verbose: bool = True) -> Path:
|
|||||||
return f
|
return f
|
||||||
|
|
||||||
|
|
||||||
|
USO = """uso: journal.py [--giorno AAAA-MM-GG] [--backfill N] [--nota "testo"] [--quiet]
|
||||||
|
|
||||||
|
(nessun flag) scrive la voce di OGGI (pagina marcata PARZIALE: il giorno non e' chiuso)
|
||||||
|
--giorno G scrive la voce del giorno G
|
||||||
|
--backfill N ricostruisce gli ultimi N giorni, dal piu' vecchio
|
||||||
|
--nota "..." annota il giorno (campo dell'operatore, mai riscritto da un ricalcolo)
|
||||||
|
--quiet non stampa la riga di riepilogo
|
||||||
|
|
||||||
|
⚠️ SCRIVE: una riga nel DB e una pagina in docs/journal/. Sola lettura su feed, log e venue."""
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
from src.live.cli import valida
|
||||||
|
valida("journal.py", USO, flag=("--quiet",), con_valore=("--giorno", "--backfill", "--nota"))
|
||||||
# sincronizzare PRIMA: altrimenti la sezione 'Libro' (letta dal log) e la sezione 'P&L'
|
# sincronizzare PRIMA: altrimenti la sezione 'Libro' (letta dal log) e la sezione 'P&L'
|
||||||
# (letta dal DB) descrivono due istanti diversi e la pagina si contraddice da sola.
|
# (letta dal DB) descrivono due istanti diversi e la pagina si contraddice da sola.
|
||||||
sys.path.insert(0, str(ROOT / "scripts" / "live"))
|
sys.path.insert(0, str(ROOT / "scripts" / "live"))
|
||||||
|
|||||||
@@ -137,5 +137,14 @@ def main():
|
|||||||
raise
|
raise
|
||||||
|
|
||||||
|
|
||||||
|
USO = """uso: live_execute.py [--execute]
|
||||||
|
|
||||||
|
uv run python scripts/live/live_execute.py # DRY-RUN (piano, nessun ordine)
|
||||||
|
uv run python scripts/live/live_execute.py --execute # esegue SOLO se execution_enabled=true
|
||||||
|
|
||||||
|
Dettaglio nel docstring in testa al file."""
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
from src.live.cli import valida
|
||||||
|
valida("live_execute.py", USO, flag=("--execute",), con_valore=())
|
||||||
main()
|
main()
|
||||||
|
|||||||
@@ -74,5 +74,15 @@ def main():
|
|||||||
(f"{len(r['orders'])} ordine/i costruito/i sopra." if r["orders"] else "Target flat: 0 ordini."))
|
(f"{len(r['orders'])} ordine/i costruito/i sopra." if r["orders"] else "Target flat: 0 ordini."))
|
||||||
|
|
||||||
|
|
||||||
|
USO = """uso: live_trend.py [--equity V] [--no-net]
|
||||||
|
|
||||||
|
uv run python scripts/live/live_trend.py # shadow su mainnet reale
|
||||||
|
uv run python scripts/live/live_trend.py --equity 2000 # forza la base di sizing
|
||||||
|
uv run python scripts/live/live_trend.py --no-net # offline: solo matematica + parita'
|
||||||
|
|
||||||
|
Dettaglio nel docstring in testa al file."""
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
from src.live.cli import valida
|
||||||
|
valida("live_trend.py", USO, flag=("--no-net",), con_valore=("--equity",))
|
||||||
main()
|
main()
|
||||||
|
|||||||
@@ -88,5 +88,14 @@ def main():
|
|||||||
print(" Validato: invio ordine reale, fill, fee reali, reconciliation, ritorno a flat.")
|
print(" Validato: invio ordine reale, fill, fee reali, reconciliation, ritorno a flat.")
|
||||||
|
|
||||||
|
|
||||||
|
USO = """uso: microtest.py [--live]
|
||||||
|
|
||||||
|
uv run python scripts/live/microtest.py # DRY-RUN: nessun ordine inviato
|
||||||
|
uv run python scripts/live/microtest.py --live # invia il round-trip REALE
|
||||||
|
|
||||||
|
Dettaglio nel docstring in testa al file."""
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
from src.live.cli import valida
|
||||||
|
valida("microtest.py", USO, flag=("--live",), con_valore=())
|
||||||
main()
|
main()
|
||||||
|
|||||||
@@ -55,5 +55,15 @@ def main() -> int:
|
|||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
USO = """uso: monitor_health.py [--quiet]
|
||||||
|
|
||||||
|
monitor_health.py — i forward-monitor stanno registrando? Riporta e allerta, non tocca nulla.
|
||||||
|
uv run python scripts/live/monitor_health.py # report
|
||||||
|
uv run python scripts/live/monitor_health.py --quiet # stampa/allerta solo se scatta
|
||||||
|
|
||||||
|
Dettaglio nel docstring in testa al file."""
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
from src.live.cli import valida
|
||||||
|
valida("monitor_health.py", USO, flag=("--quiet",), con_valore=())
|
||||||
raise SystemExit(main())
|
raise SystemExit(main())
|
||||||
|
|||||||
@@ -118,5 +118,13 @@ def main():
|
|||||||
print(f" GTAA (IB, asof {asof}): " + ", ".join(f"{k} {v:.0%}" for k, v in gw.items() if v) + f" | cash {cash:.0%}")
|
print(f" GTAA (IB, asof {asof}): " + ", ".join(f"{k} {v:.0%}" for k, v in gw.items() if v) + f" | cash {cash:.0%}")
|
||||||
|
|
||||||
|
|
||||||
|
USO = """uso: paper_combo.py [--reset] [--status]
|
||||||
|
|
||||||
|
uv run python scripts/live/paper_combo.py [--status|--reset]
|
||||||
|
|
||||||
|
Dettaglio nel docstring in testa al file."""
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
from src.live.cli import valida
|
||||||
|
valida("paper_combo.py", USO, flag=("--status", "--reset"), con_valore=())
|
||||||
main()
|
main()
|
||||||
|
|||||||
@@ -87,5 +87,15 @@ def main():
|
|||||||
print(f" posizioni correnti: {pf.current_positions()}")
|
print(f" posizioni correnti: {pf.current_positions()}")
|
||||||
|
|
||||||
|
|
||||||
|
USO = """uso: paper_portfolio.py [--reset] [--status]
|
||||||
|
|
||||||
|
uv run python scripts/live/paper_portfolio.py # avanza (init al 1o run)
|
||||||
|
uv run python scripts/live/paper_portfolio.py --status # solo stato
|
||||||
|
uv run python scripts/live/paper_portfolio.py --reset # azzera (riparte da ora)
|
||||||
|
|
||||||
|
Dettaglio nel docstring in testa al file."""
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
from src.live.cli import valida
|
||||||
|
valida("paper_portfolio.py", USO, flag=("--status", "--reset"), con_valore=())
|
||||||
main()
|
main()
|
||||||
|
|||||||
@@ -224,5 +224,15 @@ def main() -> None:
|
|||||||
print("\n fatto. I file *.%s.* sono l'evidenza del difetto e restano nel backup." % SUFFISSO)
|
print("\n fatto. I file *.%s.* sono l'evidenza del difetto e restano nel backup." % SUFFISSO)
|
||||||
|
|
||||||
|
|
||||||
|
USO = """uso: paper_regen.py [--solo V]
|
||||||
|
|
||||||
|
paper_regen.py — RIGENERA le serie dei forward-monitor rotti da `advance()` (§5.1).
|
||||||
|
uv run python scripts/live/paper_regen.py # tutti e 4
|
||||||
|
uv run python scripts/live/paper_regen.py --solo paper_statarb
|
||||||
|
|
||||||
|
Dettaglio nel docstring in testa al file."""
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
from src.live.cli import valida
|
||||||
|
valida("paper_regen.py", USO, flag=(), con_valore=("--solo",))
|
||||||
main()
|
main()
|
||||||
|
|||||||
@@ -58,5 +58,15 @@ def main() -> None:
|
|||||||
print(f"\n invio: {rep['invio']}")
|
print(f"\n invio: {rep['invio']}")
|
||||||
|
|
||||||
|
|
||||||
|
USO = """uso: scale_watch.py [--quiet] [--secco]
|
||||||
|
|
||||||
|
uv run python scripts/live/scale_watch.py # osserva e stampa, invia se ALLARME
|
||||||
|
uv run python scripts/live/scale_watch.py --secco # osserva e stampa, NON invia nulla
|
||||||
|
uv run python scripts/live/scale_watch.py --quiet # una riga sola (per il cron)
|
||||||
|
|
||||||
|
Dettaglio nel docstring in testa al file."""
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
from src.live.cli import valida
|
||||||
|
valida("scale_watch.py", USO, flag=("--quiet", "--secco"), con_valore=())
|
||||||
main()
|
main()
|
||||||
|
|||||||
@@ -146,5 +146,14 @@ def main() -> None:
|
|||||||
print("inviato" if send(txt) else "NON inviato (config Telegram assente o rete KO)")
|
print("inviato" if send(txt) else "NON inviato (config Telegram assente o rete KO)")
|
||||||
|
|
||||||
|
|
||||||
|
USO = """uso: telegram_daily.py [--dry-run]
|
||||||
|
|
||||||
|
uv run python scripts/live/telegram_daily.py # calcola e invia
|
||||||
|
uv run python scripts/live/telegram_daily.py --dry-run # stampa e basta, non invia
|
||||||
|
|
||||||
|
Dettaglio nel docstring in testa al file."""
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
from src.live.cli import valida
|
||||||
|
valida("telegram_daily.py", USO, flag=("--dry-run",), con_valore=())
|
||||||
main()
|
main()
|
||||||
|
|||||||
@@ -125,8 +125,16 @@ def report() -> None:
|
|||||||
con.close()
|
con.close()
|
||||||
|
|
||||||
|
|
||||||
|
USO = """uso: trades_db.py [--sync] [--report] [--reconcile] [--quiet]
|
||||||
|
|
||||||
|
--sync (default) legge log e jsonl e aggiorna data/live/trades.db — idempotente
|
||||||
|
--report stato del libro di bordo + P&L (TWR, non e1/e0)
|
||||||
|
--reconcile incrocio delle tre fonti sui fill (log x jsonl x venue)
|
||||||
|
--quiet con --sync: non stampa il riepilogo"""
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
args = sys.argv[1:]
|
from src.live.cli import valida
|
||||||
|
args = valida("trades_db.py", USO, flag=("--sync", "--report", "--reconcile", "--quiet"))
|
||||||
if "--reconcile" in args:
|
if "--reconcile" in args:
|
||||||
reconcile()
|
reconcile()
|
||||||
elif "--report" in args:
|
elif "--report" in args:
|
||||||
|
|||||||
@@ -330,5 +330,15 @@ def main() -> int:
|
|||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
USO = """uso: usde_watch.py [--quiet]
|
||||||
|
|
||||||
|
usde_watch.py — sorveglianza giornaliera del collaterale USDE: reward, depeg, quota. SOLA LETTURA.
|
||||||
|
uv run python scripts/live/usde_watch.py # report completo
|
||||||
|
uv run python scripts/live/usde_watch.py --quiet # stampa/allerta solo transizioni
|
||||||
|
|
||||||
|
Dettaglio nel docstring in testa al file."""
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
from src.live.cli import valida
|
||||||
|
valida("usde_watch.py", USO, flag=("--quiet",), con_valore=())
|
||||||
sys.exit(main())
|
sys.exit(main())
|
||||||
|
|||||||
@@ -98,5 +98,15 @@ def main() -> int:
|
|||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
USO = """uso: venue_watch.py [--quiet]
|
||||||
|
|
||||||
|
venue_watch.py — runner ORARIO del tripwire di venue. Allerta su Telegram, NON blocca nulla.
|
||||||
|
uv run python scripts/live/venue_watch.py # un giro, stampa il report
|
||||||
|
uv run python scripts/live/venue_watch.py --quiet # solo in caso di allarme (per il cron)
|
||||||
|
|
||||||
|
Dettaglio nel docstring in testa al file."""
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
from src.live.cli import valida
|
||||||
|
valida("venue_watch.py", USO, flag=("--quiet",), con_valore=())
|
||||||
raise SystemExit(main())
|
raise SystemExit(main())
|
||||||
|
|||||||
@@ -0,0 +1,66 @@
|
|||||||
|
"""Argomenti da riga di comando degli script operativi: un flag ignoto NON e' l'azione di default.
|
||||||
|
|
||||||
|
PERCHE' ESISTE (2026-09-02). Nessuno degli script di `scripts/live/` usava argparse: leggevano
|
||||||
|
`sys.argv` con `in` e `index`, quindi un flag sbagliato non era un errore — era il ramo `else`.
|
||||||
|
Costo misurato lo stesso giorno, durante una revisione: `trades_db.py --help` non ha stampato
|
||||||
|
l'uso, e' caduto in `sync()` e ha riscritto `meta.ultimo_sync`. Gli altri due sono peggio:
|
||||||
|
`journal.py --help` avrebbe scritto la pagina di oggi e una riga nel DB, `analista.py --help`
|
||||||
|
avrebbe speso una chiamata al modello e mandato un messaggio Telegram.
|
||||||
|
|
||||||
|
TRE REGOLE, e nient'altro:
|
||||||
|
1. **Nessun argparse.** Gli script hanno flag posizionali-per-nome (`--report`, `--secco`) e
|
||||||
|
flag con valore (`--giorno 2026-08-21`); argparse cambierebbe i messaggi d'errore, il
|
||||||
|
codice d'uscita e il comportamento di `--help` di 18 script che il cron gia' chiama. Qui
|
||||||
|
serve il contrario: **lasciare intatto cio' che gira** e rifiutare cio' che non e' previsto.
|
||||||
|
2. **Il cron non deve cambiare.** I flag dichiarati sono quelli che gli script gia' accettano:
|
||||||
|
`valida()` non ne aggiunge e non ne toglie (P15: una regola si prova contro il codice che
|
||||||
|
dovrebbe eseguirla). L'unica novita' e' `--help`, che prima non esisteva.
|
||||||
|
3. **Si esce PRIMA di qualunque effetto.** `valida()` va chiamata come prima istruzione del
|
||||||
|
blocco `__main__`, prima di `connect()`, prima del sync, prima della rete.
|
||||||
|
|
||||||
|
Codici d'uscita: 0 per `--help` (l'utente ha chiesto l'uso), 2 per un flag ignoto (errore d'uso,
|
||||||
|
distinto dal 1 con cui gli script segnalano un esito negativo del loro lavoro).
|
||||||
|
"""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import sys
|
||||||
|
|
||||||
|
|
||||||
|
class UsoSbagliato(Exception):
|
||||||
|
"""Flag non previsto da questo script. Portato con l'elenco di cio' che e' previsto (P4)."""
|
||||||
|
|
||||||
|
|
||||||
|
def valida(nome: str, uso: str, *, flag: tuple[str, ...] = (), con_valore: tuple[str, ...] = (),
|
||||||
|
argv: list[str] | None = None, esci: bool = True) -> list[str]:
|
||||||
|
"""Controlla `argv` contro i flag dichiarati. Ritorna gli argomenti; solleva o esce se ignoti.
|
||||||
|
|
||||||
|
`flag` sono i booleani (`--quiet`), `con_valore` quelli che consumano l'argomento seguente
|
||||||
|
(`--giorno 2026-08-21`). `--help`/`-h` stampano `uso` ed escono 0. Con `esci=False` non
|
||||||
|
tocca `sys.exit` e solleva `UsoSbagliato`: e' la forma che i test usano.
|
||||||
|
"""
|
||||||
|
args = list(sys.argv[1:] if argv is None else argv)
|
||||||
|
if "--help" in args or "-h" in args:
|
||||||
|
if not esci:
|
||||||
|
return args
|
||||||
|
print(uso.strip())
|
||||||
|
raise SystemExit(0)
|
||||||
|
noti, ignoti, attesa_valore = set(flag) | set(con_valore), [], False
|
||||||
|
for a in args:
|
||||||
|
if attesa_valore: # e' il valore di un flag noto, non un flag
|
||||||
|
attesa_valore = False
|
||||||
|
continue
|
||||||
|
if not a.startswith("-"):
|
||||||
|
ignoti.append(a); continue # posizionale: nessuno di questi script ne ha
|
||||||
|
if a not in noti:
|
||||||
|
ignoti.append(a); continue
|
||||||
|
attesa_valore = a in con_valore
|
||||||
|
if attesa_valore:
|
||||||
|
ignoti.append(f"{args[-1]} (manca il valore)")
|
||||||
|
if ignoti:
|
||||||
|
msg = (f"{nome}: argomento non previsto: {', '.join(ignoti)}\n"
|
||||||
|
f" previsti: {' '.join(sorted(noti)) or '(nessuno)'}\n\n{uso.strip()}")
|
||||||
|
if not esci:
|
||||||
|
raise UsoSbagliato(msg)
|
||||||
|
print(msg, file=sys.stderr)
|
||||||
|
raise SystemExit(2)
|
||||||
|
return args
|
||||||
+7
-4
@@ -118,7 +118,7 @@ def _px_1h(asset: str) -> pd.Series | None:
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
def movimenti_capitale(con, fino_ts: str) -> dict:
|
def movimenti_capitale(con, fino_ts: str, rows=None) -> dict:
|
||||||
"""Salti di equity fra letture consecutive: versamenti e prelievi, da scorporare dal P&L.
|
"""Salti di equity fra letture consecutive: versamenti e prelievi, da scorporare dal P&L.
|
||||||
|
|
||||||
PERCHE' ESISTE. Il P&L di giornale e' un delta di equity, quindi un versamento ci finisce
|
PERCHE' ESISTE. Il P&L di giornale e' un delta di equity, quindi un versamento ci finisce
|
||||||
@@ -139,8 +139,11 @@ def movimenti_capitale(con, fino_ts: str) -> dict:
|
|||||||
non si distingue dal mercato e RESTA nel P&L. E' lo stesso limite del rilevatore live.
|
non si distingue dal mercato e RESTA nel P&L. E' lo stesso limite del rilevatore live.
|
||||||
"""
|
"""
|
||||||
from src.live import book as _book # lazy: prende la soglia viva, anche monkeypatchata
|
from src.live import book as _book # lazy: prende la soglia viva, anche monkeypatchata
|
||||||
rows = con.execute("SELECT ts_utc, equity FROM equity WHERE ts_utc <= ? ORDER BY ts_utc",
|
# `rows` si puo' passare gia' letto: due SELECT sulla stessa tabella a un'ora del cron
|
||||||
(fino_ts,)).fetchall()
|
# possono cadere ai due lati di una scrittura e descrivere due istanti diversi.
|
||||||
|
if rows is None:
|
||||||
|
rows = con.execute("SELECT ts_utc, equity FROM equity WHERE ts_utc <= ? ORDER BY ts_utc",
|
||||||
|
(fino_ts,)).fetchall()
|
||||||
soglia = float(_book.EQUITY_JUMP_ALERT)
|
soglia = float(_book.EQUITY_JUMP_ALERT)
|
||||||
candidati = [(a, b) for a, b in zip(rows, rows[1:])
|
candidati = [(a, b) for a, b in zip(rows, rows[1:])
|
||||||
if a["equity"] and abs(b["equity"] - a["equity"]) / a["equity"] >= soglia]
|
if a["equity"] and abs(b["equity"] - a["equity"]) / a["equity"] >= soglia]
|
||||||
@@ -231,7 +234,7 @@ def rendimento_twr(con, fino_ts: str) -> dict:
|
|||||||
if not rows:
|
if not rows:
|
||||||
return dict(twr=None, motivo="nessuna lettura di equity", segmenti=[], eventi=[],
|
return dict(twr=None, motivo="nessuna lettura di equity", segmenti=[], eventi=[],
|
||||||
certi=0.0, ambigui=0.0, delta_equity=None, trading=None, letture=0)
|
certi=0.0, ambigui=0.0, delta_equity=None, trading=None, letture=0)
|
||||||
mv = movimenti_capitale(con, fino_ts)
|
mv = movimenti_capitale(con, fino_ts, rows=rows) # stessa lettura, un solo istante
|
||||||
e0, e1 = float(rows[0]["equity"]), float(rows[-1]["equity"])
|
e0, e1 = float(rows[0]["equity"]), float(rows[-1]["equity"])
|
||||||
if e0 <= 0:
|
if e0 <= 0:
|
||||||
return dict(twr=None, trading=None,
|
return dict(twr=None, trading=None,
|
||||||
|
|||||||
@@ -0,0 +1,34 @@
|
|||||||
|
"""Utilita' condivise dei test. `tests/` e' su sys.path (c'e' un conftest), quindi si importa
|
||||||
|
con `from helpers import carica_script`.
|
||||||
|
|
||||||
|
PERCHE' ESISTE (2026-09-02). `scripts/` non e' un package importabile per nome, quindi ogni test
|
||||||
|
che tocca uno script si caricava il modulo a mano con `importlib.util.spec_from_file_location`:
|
||||||
|
**15 copie della stessa dozzina di righe**, con nomi di modulo diversi per lo stesso file e
|
||||||
|
differenze silenziose (chi registra in `sys.modules` e chi no — e chi non lo fa rompe `pickle`,
|
||||||
|
`dataclasses` e i decoratori che risolvono per nome). Una funzione sola, con la registrazione
|
||||||
|
fatta sempre.
|
||||||
|
"""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import importlib.util
|
||||||
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
ROOT = Path(__file__).resolve().parents[1]
|
||||||
|
|
||||||
|
|
||||||
|
def carica_script(rel: str, nome: str | None = None):
|
||||||
|
"""Carica per PERCORSO uno script del repo (es. "scripts/live/trades_db.py").
|
||||||
|
|
||||||
|
`nome` e' il nome sotto cui il modulo viene registrato in `sys.modules`; il default e' il
|
||||||
|
nome del file. Registrare PRIMA di eseguire e' voluto: un modulo che si auto-importa o usa
|
||||||
|
`dataclasses` durante l'esecuzione deve trovarsi.
|
||||||
|
"""
|
||||||
|
p = (ROOT / rel).resolve()
|
||||||
|
assert p.exists(), f"script inesistente: {rel}"
|
||||||
|
nome = nome or p.stem
|
||||||
|
spec = importlib.util.spec_from_file_location(nome, p)
|
||||||
|
mod = importlib.util.module_from_spec(spec)
|
||||||
|
sys.modules[nome] = mod
|
||||||
|
spec.loader.exec_module(mod)
|
||||||
|
return mod
|
||||||
+9
-15
@@ -192,12 +192,9 @@ def test_gate_requires_both_switches():
|
|||||||
|
|
||||||
def test_config_default_disarmed(tmp_path, monkeypatch):
|
def test_config_default_disarmed(tmp_path, monkeypatch):
|
||||||
"""load_config di book_execute mette execution_enabled=False di default (fail-safe)."""
|
"""load_config di book_execute mette execution_enabled=False di default (fail-safe)."""
|
||||||
import importlib
|
# `scripts/` non e' un package importabile per nome: si carica via percorso
|
||||||
be = importlib.import_module("scripts.live.book_execute") if False else None
|
from helpers import carica_script
|
||||||
# carica il modulo via path (scripts/ non e' un package importabile per nome)
|
mod = carica_script("scripts/live/book_execute.py")
|
||||||
import importlib.util
|
|
||||||
spec = importlib.util.spec_from_file_location("book_execute", PROJECT_ROOT / "scripts/live/book_execute.py")
|
|
||||||
mod = importlib.util.module_from_spec(spec); spec.loader.exec_module(mod)
|
|
||||||
monkeypatch.setattr(mod, "CONFIG", tmp_path / "nope.json") # config assente
|
monkeypatch.setattr(mod, "CONFIG", tmp_path / "nope.json") # config assente
|
||||||
assert mod.load_config()["execution_enabled"] is False
|
assert mod.load_config()["execution_enabled"] is False
|
||||||
|
|
||||||
@@ -419,9 +416,8 @@ def test_book_report_flags_skh_feed_error(monkeypatch):
|
|||||||
|
|
||||||
def test_book_execute_surfaces_skh_error(monkeypatch, capsys):
|
def test_book_execute_surfaces_skh_error(monkeypatch, capsys):
|
||||||
"""Se il report porta skh_error, _run() lo stampa E chiama notify (niente flat silenzioso)."""
|
"""Se il report porta skh_error, _run() lo stampa E chiama notify (niente flat silenzioso)."""
|
||||||
import importlib.util
|
from helpers import carica_script
|
||||||
spec = importlib.util.spec_from_file_location("book_execute", PROJECT_ROOT / "scripts/live/book_execute.py")
|
mod = carica_script("scripts/live/book_execute.py")
|
||||||
mod = importlib.util.module_from_spec(spec); spec.loader.exec_module(mod)
|
|
||||||
|
|
||||||
canned = dict(
|
canned = dict(
|
||||||
last_data=_fresh_bar(), online=True, real_equity=600.0, equity=600.0, eq_basis="test",
|
last_data=_fresh_bar(), online=True, real_equity=600.0, equity=600.0, eq_basis="test",
|
||||||
@@ -481,9 +477,8 @@ def test_book_report_propagates_pos_error(monkeypatch):
|
|||||||
|
|
||||||
def test_book_execute_halts_on_unreadable_position(monkeypatch, capsys):
|
def test_book_execute_halts_on_unreadable_position(monkeypatch, capsys):
|
||||||
"""ARMATO + --execute + ordine presente: il gate DEVE fermarsi PRIMA di costruire il trader."""
|
"""ARMATO + --execute + ordine presente: il gate DEVE fermarsi PRIMA di costruire il trader."""
|
||||||
import importlib.util
|
from helpers import carica_script
|
||||||
spec = importlib.util.spec_from_file_location("book_execute", PROJECT_ROOT / "scripts/live/book_execute.py")
|
mod = carica_script("scripts/live/book_execute.py")
|
||||||
mod = importlib.util.module_from_spec(spec); spec.loader.exec_module(mod)
|
|
||||||
|
|
||||||
canned = dict(
|
canned = dict(
|
||||||
last_data=_fresh_bar(), online=True, real_equity=598.0, equity=598.0, eq_basis="mainnet USDC",
|
last_data=_fresh_bar(), online=True, real_equity=598.0, equity=598.0, eq_basis="mainnet USDC",
|
||||||
@@ -542,9 +537,8 @@ def test_book_report_propagates_eq_fallback(monkeypatch):
|
|||||||
|
|
||||||
def test_book_execute_eq_fallback_warns_but_proceeds(monkeypatch, capsys):
|
def test_book_execute_eq_fallback_warns_but_proceeds(monkeypatch, capsys):
|
||||||
"""eq_fallback: avvisa + notify MA PROSEGUE (non e' un halt, a differenza di pos_error)."""
|
"""eq_fallback: avvisa + notify MA PROSEGUE (non e' un halt, a differenza di pos_error)."""
|
||||||
import importlib.util
|
from helpers import carica_script
|
||||||
spec = importlib.util.spec_from_file_location("book_execute", PROJECT_ROOT / "scripts/live/book_execute.py")
|
mod = carica_script("scripts/live/book_execute.py")
|
||||||
mod = importlib.util.module_from_spec(spec); spec.loader.exec_module(mod)
|
|
||||||
|
|
||||||
canned = dict(
|
canned = dict(
|
||||||
last_data=_fresh_bar(), online=True, real_equity=None, equity=2000.0,
|
last_data=_fresh_bar(), online=True, real_equity=None, equity=2000.0,
|
||||||
|
|||||||
@@ -116,11 +116,8 @@ def test_posizione_flat_non_puo_diventare_naked():
|
|||||||
# B. Isolamento per asset dentro book_execute._run().
|
# B. Isolamento per asset dentro book_execute._run().
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
def _carica_book_execute():
|
def _carica_book_execute():
|
||||||
spec = importlib.util.spec_from_file_location(
|
from helpers import carica_script
|
||||||
"book_execute", PROJECT_ROOT / "scripts/live/book_execute.py")
|
return carica_script("scripts/live/book_execute.py")
|
||||||
mod = importlib.util.module_from_spec(spec)
|
|
||||||
spec.loader.exec_module(mod)
|
|
||||||
return mod
|
|
||||||
|
|
||||||
|
|
||||||
def _report_due_asset():
|
def _report_due_asset():
|
||||||
|
|||||||
@@ -58,11 +58,8 @@ def test_funzionale_feed_stantio_blocca_lesecuzione_e_allerta(monkeypatch, capsy
|
|||||||
"""Il test che conta: conto ONLINE, posizione LEGGIBILE, ordine PRESENTE — cioe' la situazione
|
"""Il test che conta: conto ONLINE, posizione LEGGIBILE, ordine PRESENTE — cioe' la situazione
|
||||||
esatta del 2026-07-14 — ma feed vecchio. Non deve partire nessun ordine e deve arrivare l'alert.
|
esatta del 2026-07-14 — ma feed vecchio. Non deve partire nessun ordine e deve arrivare l'alert.
|
||||||
"""
|
"""
|
||||||
import importlib.util
|
from helpers import carica_script
|
||||||
spec = importlib.util.spec_from_file_location(
|
mod = carica_script("scripts/live/book_execute.py", "book_execute_iso")
|
||||||
"book_execute_iso", ROOT / "scripts" / "live" / "book_execute.py")
|
|
||||||
mod = importlib.util.module_from_spec(spec)
|
|
||||||
spec.loader.exec_module(mod)
|
|
||||||
|
|
||||||
canned = dict(
|
canned = dict(
|
||||||
last_data="2026-07-08", online=True, real_equity=598.0, equity=598.0,
|
last_data="2026-07-08", online=True, real_equity=598.0, equity=598.0,
|
||||||
@@ -94,11 +91,8 @@ def test_funzionale_feed_stantio_blocca_lesecuzione_e_allerta(monkeypatch, capsy
|
|||||||
def test_config_senza_la_chiave_non_esplode(monkeypatch, capsys):
|
def test_config_senza_la_chiave_non_esplode(monkeypatch, capsys):
|
||||||
"""Una config priva di max_data_age_days deve ricadere sul default, non sollevare KeyError
|
"""Una config priva di max_data_age_days deve ricadere sul default, non sollevare KeyError
|
||||||
dentro il percorso con soldi veri."""
|
dentro il percorso con soldi veri."""
|
||||||
import importlib.util
|
from helpers import carica_script
|
||||||
spec = importlib.util.spec_from_file_location(
|
mod = carica_script("scripts/live/book_execute.py", "book_execute_iso2")
|
||||||
"book_execute_iso2", ROOT / "scripts" / "live" / "book_execute.py")
|
|
||||||
mod = importlib.util.module_from_spec(spec)
|
|
||||||
spec.loader.exec_module(mod)
|
|
||||||
canned = dict(last_data=str(pd.Timestamp.now(tz="UTC").normalize().date()), online=True,
|
canned = dict(last_data=str(pd.Timestamp.now(tz="UTC").normalize().date()), online=True,
|
||||||
real_equity=598.0, equity=598.0, eq_basis="t", cap_per_asset=300.0,
|
real_equity=598.0, equity=598.0, eq_basis="t", cap_per_asset=300.0,
|
||||||
skh_error=None, pos_error=None, assets=[], orders=[])
|
skh_error=None, pos_error=None, assets=[], orders=[])
|
||||||
|
|||||||
@@ -0,0 +1,168 @@
|
|||||||
|
"""Un flag ignoto non e' l'azione di default (2026-09-02).
|
||||||
|
|
||||||
|
Costo misurato durante una revisione dello stesso giorno: `trades_db.py --help` non stampava
|
||||||
|
l'uso, cadeva in `sync()` e riscriveva `meta.ultimo_sync`. Su `journal.py` lo stesso errore
|
||||||
|
avrebbe scritto una pagina e una riga di DB; su `analista.py` avrebbe speso una chiamata al
|
||||||
|
modello e mandato un Telegram.
|
||||||
|
|
||||||
|
Il test guarda anche l'altra meta' del contratto (P15): i flag che il CRON usa devono restare
|
||||||
|
accettati — una guardia che rompe il cron e' peggio del difetto che ripara.
|
||||||
|
"""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import ast
|
||||||
|
import re
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
ROOT = Path(__file__).resolve().parents[1]
|
||||||
|
sys.path.insert(0, str(ROOT))
|
||||||
|
|
||||||
|
from src.live.cli import UsoSbagliato, valida # noqa: E402
|
||||||
|
|
||||||
|
# Tutti gli script di scripts/live/ che NON usano argparse: la guardia deve coprirli tutti, e
|
||||||
|
# l'elenco si DERIVA dalla cartella (P1) — un file nuovo senza guardia fa fallire il test, non
|
||||||
|
# passa inosservato perche' nessuno ha aggiornato una lista.
|
||||||
|
LIVE = Path(__file__).resolve().parents[1] / "scripts" / "live"
|
||||||
|
|
||||||
|
|
||||||
|
def _senza_argparse() -> list[str]:
|
||||||
|
fuori = []
|
||||||
|
for f in sorted(LIVE.glob("*.py")):
|
||||||
|
s = f.read_text()
|
||||||
|
if f.name == "__init__.py" or "argparse" in s or "if __name__" not in s:
|
||||||
|
continue
|
||||||
|
fuori.append(f.name)
|
||||||
|
return fuori
|
||||||
|
|
||||||
|
|
||||||
|
SCRIPT_GUARDATI = tuple(_senza_argparse())
|
||||||
|
assert SCRIPT_GUARDATI, "nessuno script da guardare: il modulo non sta controllando niente"
|
||||||
|
SCRIVONO = ("trades_db.py", "journal.py", "analista.py") # i tre che hanno pagato il difetto
|
||||||
|
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------- la funzione
|
||||||
|
|
||||||
|
def _v(argv, **kw):
|
||||||
|
return valida("x.py", "uso: x.py", argv=argv, esci=False, **kw)
|
||||||
|
|
||||||
|
|
||||||
|
def test_un_flag_noto_passa_e_torna_gli_argomenti():
|
||||||
|
assert _v(["--report"], flag=("--report",)) == ["--report"]
|
||||||
|
|
||||||
|
|
||||||
|
def test_un_flag_ignoto_e_un_errore_non_il_default():
|
||||||
|
with pytest.raises(UsoSbagliato) as e:
|
||||||
|
_v(["--help-me"], flag=("--report",))
|
||||||
|
assert "--help-me" in str(e.value) and "--report" in str(e.value) # P4: cosa, e cosa si puo'
|
||||||
|
|
||||||
|
|
||||||
|
def test_il_valore_di_un_flag_non_e_scambiato_per_un_flag():
|
||||||
|
assert _v(["--giorno", "2026-08-21"], con_valore=("--giorno",)) == ["--giorno", "2026-08-21"]
|
||||||
|
with pytest.raises(UsoSbagliato):
|
||||||
|
_v(["--giorno"], con_valore=("--giorno",)) # manca il valore
|
||||||
|
|
||||||
|
|
||||||
|
def test_un_valore_che_sembra_un_flag_resta_un_valore():
|
||||||
|
assert _v(["--nota", "--quiet"], con_valore=("--nota",), flag=("--quiet",))
|
||||||
|
|
||||||
|
|
||||||
|
def test_un_posizionale_e_ignoto():
|
||||||
|
with pytest.raises(UsoSbagliato):
|
||||||
|
_v(["report"], flag=("--report",))
|
||||||
|
|
||||||
|
|
||||||
|
def test_senza_argomenti_non_solleva():
|
||||||
|
assert _v([], flag=("--report",)) == []
|
||||||
|
|
||||||
|
|
||||||
|
def test_help_non_e_un_errore():
|
||||||
|
assert _v(["--help"], flag=("--report",)) == ["--help"]
|
||||||
|
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------- gli script veri
|
||||||
|
|
||||||
|
def _uso(nome: str) -> str:
|
||||||
|
"""La costante USO dello script, letta dal sorgente senza importarlo."""
|
||||||
|
src = (ROOT / "scripts" / "live" / nome).read_text()
|
||||||
|
for n in ast.parse(src).body:
|
||||||
|
if isinstance(n, ast.Assign) and getattr(n.targets[0], "id", "") == "USO":
|
||||||
|
return n.value.value
|
||||||
|
raise AssertionError(f"{nome} non dichiara USO")
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("nome", SCRIPT_GUARDATI)
|
||||||
|
def test_lo_script_chiama_valida_prima_di_ogni_effetto(nome):
|
||||||
|
"""`valida` dev'essere la PRIMA istruzione eseguibile del blocco __main__: uscire dopo aver
|
||||||
|
aperto il DB o sincronizzato sarebbe uscire dopo l'effetto."""
|
||||||
|
src = (ROOT / "scripts" / "live" / nome).read_text()
|
||||||
|
main = [n for n in ast.parse(src).body
|
||||||
|
if isinstance(n, ast.If) and "__main__" in ast.dump(n.test)]
|
||||||
|
assert main, f"{nome}: nessun blocco __main__"
|
||||||
|
corpo = [n for n in main[0].body if not isinstance(n, ast.ImportFrom | ast.Import)]
|
||||||
|
chiamate = [n for n in ast.walk(corpo[0]) if isinstance(n, ast.Call)]
|
||||||
|
assert any(getattr(c.func, "id", "") == "valida" for c in chiamate), \
|
||||||
|
f"{nome}: la prima istruzione di __main__ non e' valida()"
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("nome", SCRIPT_GUARDATI)
|
||||||
|
def test_l_uso_elenca_esattamente_i_flag_dichiarati(nome):
|
||||||
|
"""P4 e P1: il testo d'aiuto non e' prosa libera — deve nominare i flag che lo script accetta."""
|
||||||
|
src = (ROOT / "scripts" / "live" / nome).read_text()
|
||||||
|
dichiarati = set(re.findall(r'["\'](--[a-z-]+)["\']', src.split("if __name__")[1]))
|
||||||
|
uso = _uso(nome)
|
||||||
|
mancanti = {f for f in dichiarati if f not in uso}
|
||||||
|
assert not mancanti, f"{nome}: flag accettati ma non documentati in USO: {mancanti}"
|
||||||
|
|
||||||
|
|
||||||
|
# I flag che il cron usa DAVVERO, letti dagli script di cron: se la guardia ne rifiutasse uno,
|
||||||
|
# il libro di bordo si fermerebbe la notte stessa (P16: il cron gira dal working tree).
|
||||||
|
def test_i_flag_del_cron_sono_tutti_accettati():
|
||||||
|
usati: dict[str, set[str]] = {}
|
||||||
|
for sh in (ROOT / "scripts").glob("cron_*.sh"):
|
||||||
|
for riga in sh.read_text().splitlines():
|
||||||
|
for nome in SCRIPT_GUARDATI:
|
||||||
|
if f"scripts/live/{nome}" in riga and not riga.lstrip().startswith("#"):
|
||||||
|
usati.setdefault(nome, set()).update(re.findall(r"(--[a-z-]+)", riga))
|
||||||
|
assert usati, "nessun cron chiama gli script guardati: il test non sta controllando niente"
|
||||||
|
for nome, flag in usati.items():
|
||||||
|
src = (ROOT / "scripts" / "live" / nome).read_text()
|
||||||
|
noti = set(re.findall(r'["\'](--[a-z-]+)["\']', src.split("if __name__")[1]))
|
||||||
|
assert flag <= noti, f"{nome}: il cron usa {flag - noti}, che la guardia rifiuterebbe"
|
||||||
|
|
||||||
|
# controllo positivo (M15): il confronto sa dire di no. Se il cron usasse un flag non
|
||||||
|
# dichiarato, questo test lo direbbe — qui lo si verifica su un caso finto.
|
||||||
|
assert not {"--inventato"} <= {"--quiet"}
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("nome", SCRIVONO)
|
||||||
|
def test_help_esce_zero_e_non_scrive(nome):
|
||||||
|
"""Controllo end-to-end (M15): il caso che ha causato il difetto, sui tre script veri."""
|
||||||
|
prima = (ROOT / "data" / "live" / "trades.db").stat().st_mtime
|
||||||
|
r = subprocess.run([sys.executable, str(ROOT / "scripts" / "live" / nome), "--help"],
|
||||||
|
capture_output=True, text=True, cwd=ROOT, timeout=120)
|
||||||
|
assert r.returncode == 0, r.stderr[-500:]
|
||||||
|
assert "uso:" in r.stdout
|
||||||
|
assert (ROOT / "data" / "live" / "trades.db").stat().st_mtime == prima
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("nome", SCRIVONO)
|
||||||
|
def test_un_flag_ignoto_esce_due_e_non_scrive(nome):
|
||||||
|
prima = (ROOT / "data" / "live" / "trades.db").stat().st_mtime
|
||||||
|
r = subprocess.run([sys.executable, str(ROOT / "scripts" / "live" / nome), "--nonesiste"],
|
||||||
|
capture_output=True, text=True, cwd=ROOT, timeout=120)
|
||||||
|
assert r.returncode == 2, r.stdout[-300:]
|
||||||
|
assert "non previsto" in r.stderr
|
||||||
|
assert (ROOT / "data" / "live" / "trades.db").stat().st_mtime == prima
|
||||||
|
|
||||||
|
|
||||||
|
def test_ogni_script_senza_argparse_ha_la_guardia():
|
||||||
|
"""L'elenco e' derivato dalla cartella: uno script nuovo che legge sys.argv a mano e non
|
||||||
|
chiama `valida` fa fallire questo test invece di ripetere il difetto in silenzio."""
|
||||||
|
senza = [n for n in SCRIPT_GUARDATI
|
||||||
|
if "valida(" not in (LIVE / n).read_text().split("if __name__")[1]]
|
||||||
|
assert not senza, f"script senza guardia sui flag: {senza}"
|
||||||
|
assert len(SCRIPT_GUARDATI) >= 17, f"troppo pochi script guardati: {SCRIPT_GUARDATI}"
|
||||||
@@ -659,3 +659,24 @@ def test_la_leva_del_classificatore_include_la_scala_e_il_tetto_di_codice(db, mo
|
|||||||
_serie_equity(db, [("2026-08-25T10:47:01+00:00", 600.0), ("2026-08-25T11:47:01+00:00", 2000.0)])
|
_serie_equity(db, [("2026-08-25T10:47:01+00:00", 600.0), ("2026-08-25T11:47:01+00:00", 2000.0)])
|
||||||
mv = J.movimenti_capitale(db, "2026-08-25T23:59:59+00:00")
|
mv = J.movimenti_capitale(db, "2026-08-25T23:59:59+00:00")
|
||||||
assert mv["leva_tetto"] == pytest.approx(min(0.5 * 2 * 1.25, B.LEVA_LORDA_MAX))
|
assert mv["leva_tetto"] == pytest.approx(min(0.5 * 2 * 1.25, B.LEVA_LORDA_MAX))
|
||||||
|
|
||||||
|
|
||||||
|
def test_balance_watch_registra_anche_l_indice_usde(monkeypatch):
|
||||||
|
"""Debito §5.15, primo passo (02/09): serve una serie ORARIA dell'indice usde_usdc — quella
|
||||||
|
di `usde_watch` e' giornaliera, e il classificatore confronta letture a un'ora di distanza.
|
||||||
|
Il campione la porta, e un indice non leggibile e' None con la ragione, mai 1.0 (P5)."""
|
||||||
|
from helpers import carica_script
|
||||||
|
bw = carica_script("scripts/live/balance_watch.py")
|
||||||
|
|
||||||
|
class _C:
|
||||||
|
def account_summary(self, cur): return {"balance": 1.0, "equity": 1.0}
|
||||||
|
def positions(self, cur): return []
|
||||||
|
|
||||||
|
from src.live import usde as U
|
||||||
|
monkeypatch.setattr(U, "prezzo_indice", lambda *a, **k: 0.9997)
|
||||||
|
assert bw.campiona(_C())["usde_usdc"] == 0.9997
|
||||||
|
|
||||||
|
def _rotto(*a, **k): raise RuntimeError("giu'")
|
||||||
|
monkeypatch.setattr(U, "prezzo_indice", _rotto)
|
||||||
|
rec = bw.campiona(_C())
|
||||||
|
assert rec["usde_usdc"] is None and rec["usde_usdc_errore"] == "RuntimeError"
|
||||||
|
|||||||
+15
-20
@@ -6,25 +6,21 @@ finisce in tmp grazie alla fixture autouse di conftest: qui non si tocca il libr
|
|||||||
"""
|
"""
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import importlib.util
|
|
||||||
import sys
|
import sys
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
import pandas as pd
|
import pandas as pd
|
||||||
import pytest
|
|
||||||
|
|
||||||
ROOT = Path(__file__).resolve().parents[1]
|
ROOT = Path(__file__).resolve().parents[1]
|
||||||
sys.path.insert(0, str(ROOT))
|
sys.path.insert(0, str(ROOT))
|
||||||
|
|
||||||
|
from helpers import carica_script # noqa: E402
|
||||||
from src.live import journal as J # noqa: E402
|
from src.live import journal as J # noqa: E402
|
||||||
from src.live import tradesdb as T # noqa: E402
|
from src.live import tradesdb as T # noqa: E402
|
||||||
|
|
||||||
|
|
||||||
def _carica_script():
|
def _carica_script():
|
||||||
spec = importlib.util.spec_from_file_location("trades_db_script", ROOT / "scripts/live/trades_db.py")
|
return carica_script("scripts/live/trades_db.py", "trades_db_script")
|
||||||
mod = importlib.util.module_from_spec(spec)
|
|
||||||
spec.loader.exec_module(mod)
|
|
||||||
return mod
|
|
||||||
|
|
||||||
|
|
||||||
def _px_piatto(livello=100.0):
|
def _px_piatto(livello=100.0):
|
||||||
@@ -33,11 +29,11 @@ def _px_piatto(livello=100.0):
|
|||||||
|
|
||||||
|
|
||||||
def _prepara(con, equity):
|
def _prepara(con, equity):
|
||||||
con.execute("INSERT INTO fills (fill_id, ts_utc, ts_source, asset, side, qty, price, fee) "
|
# il fill si scrive con `upsert_fills`, non con un INSERT a mano: una riga scritta a mano
|
||||||
"VALUES ('f1','2026-08-24T10:47:15+00:00','test','BTC','buy',0.001,60000.0,0.01)")
|
# aveva `verified` NULL, una forma che la produzione non produce mai
|
||||||
for ts, eq in equity:
|
T.upsert_fills(con, [T.Fill(ts_utc="2026-08-24T10:47:15+00:00", ts_source="test",
|
||||||
con.execute("INSERT INTO equity (ts_utc, equity, src) VALUES (?,?,?)", (ts, eq, "test"))
|
asset="BTC", side="buy", qty=0.001, price=60000.0, fee=0.01)])
|
||||||
con.commit()
|
T.upsert_equity(con, [(ts, eq, "test") for ts, eq in equity])
|
||||||
|
|
||||||
|
|
||||||
def test_il_report_stampa_il_TWR_e_non_il_grezzo(monkeypatch, capsys):
|
def test_il_report_stampa_il_TWR_e_non_il_grezzo(monkeypatch, capsys):
|
||||||
@@ -51,10 +47,10 @@ def test_il_report_stampa_il_TWR_e_non_il_grezzo(monkeypatch, capsys):
|
|||||||
con.close()
|
con.close()
|
||||||
_carica_script().report()
|
_carica_script().report()
|
||||||
out = capsys.readouterr().out
|
out = capsys.readouterr().out
|
||||||
assert "TWR : +3.95%" in out
|
assert "TWR" in out and "+3.95%" in out
|
||||||
assert "+234.95%" not in out and "+235" not in out # il grezzo non compare piu'
|
assert "+234.95%" not in out # il grezzo non compare piu'
|
||||||
assert "movimenti capitale : +1,400.00 certi" in out
|
assert "+1,400.00 certi" in out
|
||||||
assert "trading da arming : +9.70" in out
|
assert "+9.70" in out
|
||||||
assert "movimenti di capitale INCLUSI" in out # il delta $ resta, etichettato
|
assert "movimenti di capitale INCLUSI" in out # il delta $ resta, etichettato
|
||||||
|
|
||||||
|
|
||||||
@@ -65,7 +61,7 @@ def test_il_report_senza_movimenti_dice_lo_stesso_numero_di_prima(monkeypatch, c
|
|||||||
con.close()
|
con.close()
|
||||||
_carica_script().report()
|
_carica_script().report()
|
||||||
out = capsys.readouterr().out
|
out = capsys.readouterr().out
|
||||||
assert "TWR : +5.00%" in out and "movimenti capitale" not in out
|
assert "+5.00%" in out and "movimenti capitale" not in out
|
||||||
|
|
||||||
|
|
||||||
def test_il_report_a_base_zero_dice_n_d_e_non_l_intero_conto(monkeypatch, capsys):
|
def test_il_report_a_base_zero_dice_n_d_e_non_l_intero_conto(monkeypatch, capsys):
|
||||||
@@ -77,9 +73,8 @@ def test_il_report_a_base_zero_dice_n_d_e_non_l_intero_conto(monkeypatch, capsys
|
|||||||
con.close()
|
con.close()
|
||||||
_carica_script().report()
|
_carica_script().report()
|
||||||
out = capsys.readouterr().out
|
out = capsys.readouterr().out
|
||||||
assert "trading da arming : n/d" in out and "TWR : n/d" in out
|
assert out.count("n/d") >= 2 and "TWR" in out
|
||||||
assert "trading da arming : +610.00" not in out # l'intero conto NON e' "trading"
|
assert "+610.00 di equity" in out and "arming : +610.00" not in out
|
||||||
assert "(+610.00 di equity, movimenti di capitale INCLUSI)" in out # il fatto resta, etichettato
|
|
||||||
|
|
||||||
|
|
||||||
def test_il_report_con_il_feed_assente_non_stampa_il_grezzo_come_TWR(monkeypatch, capsys):
|
def test_il_report_con_il_feed_assente_non_stampa_il_grezzo_come_TWR(monkeypatch, capsys):
|
||||||
@@ -93,6 +88,6 @@ def test_il_report_con_il_feed_assente_non_stampa_il_grezzo_come_TWR(monkeypatch
|
|||||||
con.close()
|
con.close()
|
||||||
_carica_script().report()
|
_carica_script().report()
|
||||||
out = capsys.readouterr().out
|
out = capsys.readouterr().out
|
||||||
assert "TWR : n/d" in out and "trading da arming : n/d" in out
|
assert out.count("n/d") >= 2 and "TWR" in out
|
||||||
assert "% =" not in out and "+236" not in out
|
assert "% =" not in out and "+236" not in out
|
||||||
assert "[ambiguo: mercato non misurabile" in out
|
assert "[ambiguo: mercato non misurabile" in out
|
||||||
|
|||||||
Reference in New Issue
Block a user