Phase 4 hardening: status CLI, lock file, backup job, hash enforce, pooling, real bias
Sei interventi mirati sui rischi operativi rilevati nell'audit post-Fase 4. 317 test pass, mypy strict pulito, ruff clean. 1. status CLI: legge SQLite reale e mostra kill_switch, posizioni aperte, environment, config_version, last_health_check, started_at. Sostituisce il placeholder "phase 0 skeleton". 2. Lock file single-instance: runtime/lockfile.py acquisisce data/.lockfile via fcntl.flock al boot di run_forever; un secondo container fallisce subito con LockError. 3. Backup orario nello scheduler: nuovo job APScheduler 0 * * * * chiama scripts.backup.backup_database + prune_backups. 4. config_hash enforce su start: il CLI start verifica l'integrità del file (enforce_hash=True). Mismatch → exit 1 prima di toccare stato. dry-run resta enforce_hash=False per debug. 5. Connection pooling MCP: RuntimeContext espone un httpx.AsyncClient long-lived condiviso da tutti i wrapper (limits 20/10 connections/keepalive). aclose() chiamato in run_forever finale. 6. Bias direzionale reale: deribit.historical_close + deribit.adx_14 popolano TrendContext con spot a 30 giorni e ADX(14) effettivi. Sblocca bull_put e bear_call. Quando i dati storici mancano l'engine emette alert MEDIUM e cade su no_entry in modo deterministico. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
+54
-15
@@ -80,14 +80,46 @@ def main(ctx: click.Context, log_dir: Path, log_level: str) -> None:
|
||||
|
||||
|
||||
@main.command()
|
||||
def status() -> None:
|
||||
@click.option(
|
||||
"--db",
|
||||
type=click.Path(dir_okay=False, path_type=Path),
|
||||
default=_DEFAULT_DB_PATH,
|
||||
show_default=True,
|
||||
)
|
||||
def status(db: Path) -> None:
|
||||
"""Print engine status snapshot."""
|
||||
header = f"[bold cyan]Cerbero Bite[/bold cyan] v{__version__}"
|
||||
if not db.exists():
|
||||
console.print(
|
||||
f"{header}\n"
|
||||
"engine state: [yellow]never started[/yellow] "
|
||||
"(state.sqlite missing)"
|
||||
)
|
||||
return
|
||||
|
||||
conn = connect_state(db)
|
||||
try:
|
||||
run_migrations(conn)
|
||||
repo = Repository()
|
||||
sys_state = repo.get_system_state(conn)
|
||||
open_positions = repo.list_open_positions(conn)
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
if sys_state is None:
|
||||
console.print(f"{header}\nengine state: [yellow]uninitialised[/yellow]")
|
||||
return
|
||||
|
||||
armed = sys_state.kill_switch == 1
|
||||
flag = "[red]ARMED[/red]" if armed else "[green]disarmed[/green]"
|
||||
console.print(
|
||||
f"[bold cyan]Cerbero Bite[/bold cyan] v{__version__}\n"
|
||||
f"engine state: [yellow]idle[/yellow]\n"
|
||||
f"kill_switch: [green]0 (disarmed)[/green]\n"
|
||||
f"open positions: 0\n"
|
||||
f"phase: 0 (skeleton)"
|
||||
f"{header}\n"
|
||||
f"kill_switch: {flag}"
|
||||
f"{' reason=' + (sys_state.kill_reason or '?') if armed else ''}\n"
|
||||
f"open positions: {len(open_positions)}\n"
|
||||
f"config_version: {sys_state.config_version}\n"
|
||||
f"started_at: {sys_state.started_at.isoformat()}\n"
|
||||
f"last_health_check: {sys_state.last_health_check.isoformat()}"
|
||||
)
|
||||
|
||||
|
||||
@@ -145,8 +177,9 @@ def _build_orchestrator(
|
||||
audit: Path,
|
||||
environment: str,
|
||||
eur_to_usd: float,
|
||||
enforce_hash: bool = True,
|
||||
) -> Orchestrator:
|
||||
loaded = load_strategy(strategy_path, enforce_hash=False)
|
||||
loaded = load_strategy(strategy_path, enforce_hash=enforce_hash)
|
||||
token = load_token(path=token_file)
|
||||
return make_orchestrator(
|
||||
cfg=loaded.config,
|
||||
@@ -170,14 +203,19 @@ def start(
|
||||
eur_to_usd: float,
|
||||
) -> None:
|
||||
"""Start the engine main loop (scheduler + monitoring)."""
|
||||
orch = _build_orchestrator(
|
||||
strategy_path=strategy_path,
|
||||
token_file=token_file,
|
||||
db=db,
|
||||
audit=audit,
|
||||
environment=environment,
|
||||
eur_to_usd=eur_to_usd,
|
||||
)
|
||||
try:
|
||||
orch = _build_orchestrator(
|
||||
strategy_path=strategy_path,
|
||||
token_file=token_file,
|
||||
db=db,
|
||||
audit=audit,
|
||||
environment=environment,
|
||||
eur_to_usd=eur_to_usd,
|
||||
enforce_hash=True,
|
||||
)
|
||||
except Exception as exc:
|
||||
console.print(f"[red]boot aborted[/red]: {type(exc).__name__}: {exc}")
|
||||
sys.exit(1)
|
||||
console.print(
|
||||
f"[bold cyan]Cerbero Bite[/bold cyan] starting "
|
||||
f"(env={environment}, db={db}, audit={audit})"
|
||||
@@ -213,6 +251,7 @@ def dry_run(
|
||||
audit=audit,
|
||||
environment=environment,
|
||||
eur_to_usd=eur_to_usd,
|
||||
enforce_hash=False,
|
||||
)
|
||||
|
||||
async def _go() -> None:
|
||||
|
||||
Reference in New Issue
Block a user