"""Calibrate AlphaExitEngineV7 thresholds for synthetic LONG EFSM paths. This script replays BLUE V7 decision journal price paths with side inverted to LONG. It follows the original V7 SHORT calibration pattern: 1. Reconstruct per-trade path from V7 journal rows. 2. Compute the natural end-of-path LONG outcome. 3. Replay AlphaExitEngineV7 on the same path using side=LONG. 4. Sweep configurable threshold surfaces. 5. Compare the first V7 EXIT against the natural end outcome. The output is a calibration proxy for EFSM FLIP_LONG trades, not proof from actual exchange-filled LONG trades. """ from __future__ import annotations import argparse import base64 import csv import json import logging import math import sys import urllib.request from collections import defaultdict from dataclasses import asdict from pathlib import Path from statistics import fmean from typing import Any ROOT = Path("/mnt/dolphinng5_predict") sys.path.insert(0, str(ROOT / "nautilus_dolphin")) sys.path.insert(1, str(ROOT)) from nautilus_dolphin.nautilus.alpha_exit_v7_engine import AlphaExitEngineV7, AlphaExitV7Config # noqa: E402 CH_URL = "http://localhost:8123/?database=dolphin" AUTH = "Basic " + base64.b64encode(b"dolphin:dolphin_ch_2026").decode() FEE_PCT = 0.0004 logging.getLogger("nautilus_dolphin.nautilus.alpha_exit_v7_engine").setLevel(logging.ERROR) def _query(sql: str) -> str: req = urllib.request.Request(CH_URL, data=sql.encode(), headers={"Authorization": AUTH}) return urllib.request.urlopen(req, timeout=60).read().decode() def load_v7_rows(limit_trades: int = 0) -> list[dict[str, Any]]: trade_filter = "" if limit_trades > 0: trade_filter = ( "AND trade_id IN (" "SELECT trade_id FROM (" "SELECT trade_id, max(ts) AS mx FROM v7_decision_events " "WHERE strategy='blue' AND side='SHORT' GROUP BY trade_id ORDER BY mx DESC " f"LIMIT {int(limit_trades)}" "))" ) sql = f""" SELECT ts, trade_id, asset, entry_price, current_price, quantity, leverage, bar_idx, decision_seq, bars_held, ob_imbalance, exf_funding, exf_dvol, exf_fear_greed, exf_taker FROM v7_decision_events WHERE strategy='blue' AND side='SHORT' {trade_filter} ORDER BY trade_id ASC, decision_seq ASC, ts ASC FORMAT CSVWithNames """ text = _query(sql) rows: list[dict[str, Any]] = [] for r in csv.DictReader(text.splitlines()): rows.append({ "ts": r["ts"], "trade_id": r["trade_id"], "asset": r["asset"], "entry_price": float(r["entry_price"] or 0.0), "current_price": float(r["current_price"] or 0.0), "quantity": float(r["quantity"] or 0.0), "leverage": float(r["leverage"] or 0.0), "bar_idx": int(float(r["bar_idx"] or 0)), "decision_seq": int(float(r["decision_seq"] or 0)), "bars_held": int(float(r["bars_held"] or 0)), "ob_imbalance": float(r["ob_imbalance"] or 0.0), "exf_funding": float(r["exf_funding"] or 0.0), "exf_dvol": float(r["exf_dvol"] or 0.0), "exf_fear_greed": float(r["exf_fear_greed"] or 0.0), "exf_taker": float(r["exf_taker"] or 0.0), }) return rows def group_paths(rows: list[dict[str, Any]]) -> list[list[dict[str, Any]]]: grouped: dict[str, list[dict[str, Any]]] = defaultdict(list) for row in rows: grouped[row["trade_id"]].append(row) paths = [] for path in grouped.values(): path.sort(key=lambda r: (r["decision_seq"], r["bar_idx"], r["ts"])) clean = [r for r in path if r["entry_price"] > 0 and r["current_price"] > 0] if len(clean) >= 2: paths.append(clean) paths.sort(key=lambda p: p[-1]["ts"]) return paths def natural_long_return(path: list[dict[str, Any]]) -> float: entry = path[0]["entry_price"] last = path[-1]["current_price"] return (last - entry) / entry - FEE_PCT if entry > 0 else 0.0 def pnl_dollars(path: list[dict[str, Any]], ret: float) -> float: notional = abs(path[0]["entry_price"] * path[0]["quantity"]) return notional * ret def replay_path(path: list[dict[str, Any]], cfg: AlphaExitV7Config) -> dict[str, Any]: engine = AlphaExitEngineV7( bar_duration_sec=11.0, bounce_model_path="/tmp/nonexistent-bounce-model.pkl", config=cfg, ) ctx = engine.make_context(entry_price=path[0]["entry_price"], entry_bar=path[0]["bar_idx"], side=0) first_exit = None decisions = [] for row in path: if hasattr(ctx, "set_exf"): ctx.set_exf( funding=row["exf_funding"], dvol=row["exf_dvol"], fear_greed=row["exf_fear_greed"], taker=row["exf_taker"], ) dec = engine.evaluate( ctx, current_price=row["current_price"], current_bar=row["bar_idx"], ob_imbalance=row["ob_imbalance"], asset=row["asset"], ) decisions.append(dec) if first_exit is None and dec["action"] == "EXIT": first_exit = (row, dec) break nat_ret = natural_long_return(path) if first_exit is None: exit_ret = nat_ret exit_row = path[-1] exit_dec = decisions[-1] exited = False else: exit_row, exit_dec = first_exit exit_ret = (exit_row["current_price"] - path[0]["entry_price"]) / path[0]["entry_price"] - FEE_PCT exited = True return { "trade_id": path[0]["trade_id"], "asset": path[0]["asset"], "n_rows": len(path), "natural_ret": nat_ret, "natural_pnl": pnl_dollars(path, nat_ret), "exit_ret": exit_ret, "exit_pnl": pnl_dollars(path, exit_ret), "delta_pnl": pnl_dollars(path, exit_ret) - pnl_dollars(path, nat_ret), "exited": exited, "exit_action": exit_dec.get("action"), "exit_reason": exit_dec.get("reason") or "", "exit_pressure": float(exit_dec.get("exit_pressure", 0.0) or 0.0), "exit_bars_held": int(exit_dec.get("bars_held", 0) or 0), "exit_mae": float(exit_dec.get("mae", 0.0) or 0.0), "exit_mfe": float(exit_dec.get("mfe", 0.0) or 0.0), "exit_mae_risk": float(exit_dec.get("mae_risk", 0.0) or 0.0), "exit_mfe_risk": float(exit_dec.get("mfe_risk", 0.0) or 0.0), } def equity_stats(vals: list[float]) -> dict[str, float]: eq = 1.0 peak = 1.0 dd = 0.0 for r in vals: eq *= max(0.0, 1.0 + r) peak = max(peak, eq) dd = max(dd, (peak - eq) / peak if peak else 0.0) return { "n": len(vals), "wr": sum(1 for r in vals if r > 0) / len(vals) if vals else 0.0, "mean": fmean(vals) if vals else 0.0, "compound": eq - 1.0, "max_dd": dd, } def summarize(results: list[dict[str, Any]], cfg: AlphaExitV7Config, name: str) -> dict[str, Any]: natural_rets = [r["natural_ret"] for r in results] exit_rets = [r["exit_ret"] for r in results] deltas = [r["delta_pnl"] for r in results] return { "name": name, "config": asdict(cfg), "n": len(results), "exits": sum(1 for r in results if r["exited"]), "exit_rate": sum(1 for r in results if r["exited"]) / len(results) if results else 0.0, "natural": { **equity_stats(natural_rets), "pnl": sum(r["natural_pnl"] for r in results), }, "v7": { **equity_stats(exit_rets), "pnl": sum(r["exit_pnl"] for r in results), }, "delta_pnl": sum(deltas), "positive_delta_trades": sum(1 for d in deltas if d > 0), "negative_delta_trades": sum(1 for d in deltas if d < 0), "avg_exit_pressure": fmean([r["exit_pressure"] for r in results if r["exited"]]) if any(r["exited"] for r in results) else 0.0, "reasons": dict(sorted({ reason: sum(1 for r in results if r["exit_reason"] == reason) for reason in {r["exit_reason"] for r in results} }.items())), } def candidate_configs() -> list[tuple[str, AlphaExitV7Config]]: out = [("short_default", AlphaExitV7Config())] for threshold in [1.4, 1.7, 2.0, 2.35, 2.69, 3.0]: out.append((f"exit_p{threshold}", AlphaExitV7Config(exit_pressure_threshold=threshold))) for tier_scale in [0.5, 0.75, 1.0, 1.25, 1.5]: out.append(( f"mae_scale_{tier_scale}", AlphaExitV7Config( mae_tier1_k=3.5 * tier_scale, mae_tier2_k=7.0 * tier_scale, mae_tier3_k=12.0 * tier_scale, mae_tier1_floor=0.005 * tier_scale, mae_tier2_floor=0.012 * tier_scale, mae_tier3_floor=0.025 * tier_scale, ), )) for mfe_scale in [0.5, 0.75, 1.25, 1.5]: out.append(( f"mfe_risk_scale_{mfe_scale}", AlphaExitV7Config( mfe_convexity_exit_risk=1.5 * mfe_scale, mfe_convexity_soft_risk=0.3 * mfe_scale, mfe_accel_risk=0.2 * mfe_scale, ), )) for late_start in [0.3, 0.45, 0.6, 0.75]: out.append((f"late_start_{late_start}", AlphaExitV7Config(mae_late_start_frac=late_start))) for threshold in [1.7, 2.0, 2.35]: for mae_scale in [0.5, 0.75, 1.25]: out.append(( f"combo_p{threshold}_mae{mae_scale}", AlphaExitV7Config( exit_pressure_threshold=threshold, mae_tier1_k=3.5 * mae_scale, mae_tier2_k=7.0 * mae_scale, mae_tier3_k=12.0 * mae_scale, mae_tier1_floor=0.005 * mae_scale, mae_tier2_floor=0.012 * mae_scale, mae_tier3_floor=0.025 * mae_scale, ), )) return out def main() -> None: parser = argparse.ArgumentParser() parser.add_argument("--limit-trades", type=int, default=0) parser.add_argument("--out", default="/tmp/v7_long_calibration.json") args = parser.parse_args() rows = load_v7_rows(limit_trades=args.limit_trades) paths = group_paths(rows) summaries = [] for name, cfg in candidate_configs(): results = [replay_path(path, cfg) for path in paths] summaries.append(summarize(results, cfg, name)) summaries.sort(key=lambda r: (r["delta_pnl"], r["v7"]["pnl"], -r["exits"]), reverse=True) payload = { "method": "Synthetic LONG replay of BLUE SHORT V7 decision journal paths; bounce model disabled.", "input": { "rows": len(rows), "paths": len(paths), "limit_trades": args.limit_trades, }, "top_by_delta": summaries[:20], "all": summaries, } Path(args.out).write_text(json.dumps(payload, indent=2, sort_keys=True), encoding="utf-8") print(json.dumps({ "input": payload["input"], "top_by_delta": [ { "name": s["name"], "n": s["n"], "exits": s["exits"], "exit_rate": s["exit_rate"], "natural_pnl": s["natural"]["pnl"], "v7_pnl": s["v7"]["pnl"], "delta_pnl": s["delta_pnl"], "natural_compound": s["natural"]["compound"], "v7_compound": s["v7"]["compound"], "v7_dd": s["v7"]["max_dd"], "reasons": s["reasons"], } for s in summaries[:12] ], }, indent=2, sort_keys=True)) if __name__ == "__main__": main()