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:
@@ -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):
|
||||
"""load_config di book_execute mette execution_enabled=False di default (fail-safe)."""
|
||||
import importlib
|
||||
be = importlib.import_module("scripts.live.book_execute") if False else None
|
||||
# carica il modulo via path (scripts/ non e' un package importabile per nome)
|
||||
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)
|
||||
# `scripts/` non e' un package importabile per nome: si carica via percorso
|
||||
from helpers import carica_script
|
||||
mod = carica_script("scripts/live/book_execute.py")
|
||||
monkeypatch.setattr(mod, "CONFIG", tmp_path / "nope.json") # config assente
|
||||
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):
|
||||
"""Se il report porta skh_error, _run() lo stampa E chiama notify (niente flat silenzioso)."""
|
||||
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)
|
||||
from helpers import carica_script
|
||||
mod = carica_script("scripts/live/book_execute.py")
|
||||
|
||||
canned = dict(
|
||||
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):
|
||||
"""ARMATO + --execute + ordine presente: il gate DEVE fermarsi PRIMA di costruire il trader."""
|
||||
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)
|
||||
from helpers import carica_script
|
||||
mod = carica_script("scripts/live/book_execute.py")
|
||||
|
||||
canned = dict(
|
||||
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):
|
||||
"""eq_fallback: avvisa + notify MA PROSEGUE (non e' un halt, a differenza di pos_error)."""
|
||||
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)
|
||||
from helpers import carica_script
|
||||
mod = carica_script("scripts/live/book_execute.py")
|
||||
|
||||
canned = dict(
|
||||
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().
|
||||
# ---------------------------------------------------------------------------
|
||||
def _carica_book_execute():
|
||||
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)
|
||||
return mod
|
||||
from helpers import carica_script
|
||||
return carica_script("scripts/live/book_execute.py")
|
||||
|
||||
|
||||
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
|
||||
esatta del 2026-07-14 — ma feed vecchio. Non deve partire nessun ordine e deve arrivare l'alert.
|
||||
"""
|
||||
import importlib.util
|
||||
spec = importlib.util.spec_from_file_location(
|
||||
"book_execute_iso", ROOT / "scripts" / "live" / "book_execute.py")
|
||||
mod = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(mod)
|
||||
from helpers import carica_script
|
||||
mod = carica_script("scripts/live/book_execute.py", "book_execute_iso")
|
||||
|
||||
canned = dict(
|
||||
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):
|
||||
"""Una config priva di max_data_age_days deve ricadere sul default, non sollevare KeyError
|
||||
dentro il percorso con soldi veri."""
|
||||
import importlib.util
|
||||
spec = importlib.util.spec_from_file_location(
|
||||
"book_execute_iso2", ROOT / "scripts" / "live" / "book_execute.py")
|
||||
mod = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(mod)
|
||||
from helpers import carica_script
|
||||
mod = carica_script("scripts/live/book_execute.py", "book_execute_iso2")
|
||||
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,
|
||||
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)])
|
||||
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))
|
||||
|
||||
|
||||
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
|
||||
|
||||
import importlib.util
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
import pandas as pd
|
||||
import pytest
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
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 tradesdb as T # noqa: E402
|
||||
|
||||
|
||||
def _carica_script():
|
||||
spec = importlib.util.spec_from_file_location("trades_db_script", ROOT / "scripts/live/trades_db.py")
|
||||
mod = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(mod)
|
||||
return mod
|
||||
return carica_script("scripts/live/trades_db.py", "trades_db_script")
|
||||
|
||||
|
||||
def _px_piatto(livello=100.0):
|
||||
@@ -33,11 +29,11 @@ def _px_piatto(livello=100.0):
|
||||
|
||||
|
||||
def _prepara(con, equity):
|
||||
con.execute("INSERT INTO fills (fill_id, ts_utc, ts_source, asset, side, qty, price, fee) "
|
||||
"VALUES ('f1','2026-08-24T10:47:15+00:00','test','BTC','buy',0.001,60000.0,0.01)")
|
||||
for ts, eq in equity:
|
||||
con.execute("INSERT INTO equity (ts_utc, equity, src) VALUES (?,?,?)", (ts, eq, "test"))
|
||||
con.commit()
|
||||
# il fill si scrive con `upsert_fills`, non con un INSERT a mano: una riga scritta a mano
|
||||
# aveva `verified` NULL, una forma che la produzione non produce mai
|
||||
T.upsert_fills(con, [T.Fill(ts_utc="2026-08-24T10:47:15+00:00", ts_source="test",
|
||||
asset="BTC", side="buy", qty=0.001, price=60000.0, fee=0.01)])
|
||||
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):
|
||||
@@ -51,10 +47,10 @@ def test_il_report_stampa_il_TWR_e_non_il_grezzo(monkeypatch, capsys):
|
||||
con.close()
|
||||
_carica_script().report()
|
||||
out = capsys.readouterr().out
|
||||
assert "TWR : +3.95%" in out
|
||||
assert "+234.95%" not in out and "+235" not in out # il grezzo non compare piu'
|
||||
assert "movimenti capitale : +1,400.00 certi" in out
|
||||
assert "trading da arming : +9.70" in out
|
||||
assert "TWR" in out and "+3.95%" in out
|
||||
assert "+234.95%" not in out # il grezzo non compare piu'
|
||||
assert "+1,400.00 certi" in out
|
||||
assert "+9.70" in out
|
||||
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()
|
||||
_carica_script().report()
|
||||
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):
|
||||
@@ -77,9 +73,8 @@ def test_il_report_a_base_zero_dice_n_d_e_non_l_intero_conto(monkeypatch, capsys
|
||||
con.close()
|
||||
_carica_script().report()
|
||||
out = capsys.readouterr().out
|
||||
assert "trading da arming : n/d" in out and "TWR : n/d" in out
|
||||
assert "trading da arming : +610.00" not in out # l'intero conto NON e' "trading"
|
||||
assert "(+610.00 di equity, movimenti di capitale INCLUSI)" in out # il fatto resta, etichettato
|
||||
assert out.count("n/d") >= 2 and "TWR" in out
|
||||
assert "+610.00 di equity" in out and "arming : +610.00" not in out
|
||||
|
||||
|
||||
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()
|
||||
_carica_script().report()
|
||||
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 "[ambiguo: mercato non misurabile" in out
|
||||
|
||||
Reference in New Issue
Block a user