initial: import DOLPHIN baseline 2026-04-21 from dolphinng5_predict working tree

Includes core prod + GREEN/BLUE subsystems:
- prod/ (BLUE harness, configs, scripts, docs)
- nautilus_dolphin/ (GREEN Nautilus-native impl + dvae/ preserved)
- adaptive_exit/ (AEM engine + models/bucket_assignments.pkl)
- Observability/ (EsoF advisor, TUI, dashboards)
- external_factors/ (EsoF producer)
- mc_forewarning_qlabs_fork/ (MC regime/envelope)

Excludes runtime caches, logs, backups, and reproducible artifacts per .gitignore.
This commit is contained in:
hjnormey
2026-04-21 16:58:38 +02:00
commit 01c19662cb
643 changed files with 260241 additions and 0 deletions

View File

@@ -0,0 +1,229 @@
# proxy_B — Research Filing
**Date:** 2026-03-14
**Status:** Closed for direct exploitation; open as modulator candidate
**Gold baseline:** ROI=+88.55%, PF=1.215, DD=15.05%, Sharpe=4.38, Trades=2155
---
## 1. Signal Definition
```
proxy_B = instability_50 - v750_lambda_max_velocity
```
- `instability_50`: short-window (50-bar) eigenvalue instability in correlation matrix
- `v750_lambda_max_velocity`: long-window (750-bar) max-eigenvalue velocity
- Intuition: **short-term stress MINUS long-term momentum**. When this is high, the
eigenspace is rapidly destabilising relative to its recent trend.
- Available in 5s scan parquets. Computed in `ShadowLoggingEngine.process_day()`.
---
## 2. Discovery & Measurement
**Experiment:** Precursor sweep (`e2e_precursor_auc.py`, `flint_precursor_sweep.py`)
**Metric:** AUC for predicting eigenspace stress events at K=5 bars forward
| Window / Signal | AUC (K=5) |
|-------------------|-----------|
| proxy_B (inst50 v750_vel) | **0.715** |
| instability_50 alone | ~0.65 |
| v750_lambda_max_velocity alone | ~0.61 |
| FlintHDVAE latent z (β=0.1) | 0.6918 |
| vel_div (entry signal) | baseline |
proxy_B leads stress events by ~25 seconds (5-bar horizon on 5s data).
It is NOT the entry signal — it measures a different aspect of the eigenspace.
---
## 3. Orthogonality to System Signals
**Test:** Exp 4 — shadow run, 48/2155 trades had valid aligned pb_entry+vd_entry
(entry_bar alignment bug: only ~2% of trades yield correctly-matched bar-level values;
see Section 6 Technical Note).
| Pair | Pearson r | p-value | Spearman rho | Verdict |
|------|-----------|---------|--------------|---------|
| proxy_B_entry ↔ vel_div_entry | 0.031 | 0.837 | 0.463 | **Orthogonal (ns)** |
| proxy_B_entry ↔ pnl_frac | +0.166 | 0.260 | +0.158 | Not predictive of outcome (ns) |
| **proxy_B_entry ↔ MAE** | **+0.420** | **0.003 \*\*** | +0.149 | **Predicts intraday adversity** |
| proxy_B_entry ↔ hold_bars | 0.054 | 0.717 | 0.171 | Orthogonal (ns) |
| proxy_B_max ↔ pnl_frac | +0.066 | 0.655 | 0.379 | ns |
| proxy_B_max ↔ MAE | +0.047 | 0.750 | 0.280 | ns |
Mann-Whitney (worst-10% pnl vs rest): pb_entry worst=-5.40, rest=+0.27, p=0.183 ns
Mann-Whitney (worst-10% MAE vs rest): pb_entry worst=-5.41, rest=+0.27, p=0.115 ns
**Critical finding:**
- proxy_B IS orthogonal to vel_div (the entry signal) — r≈0, ns ✓
- proxy_B does NOT predict final trade PnL — r=+0.17, ns ✓ (confirms prior findings)
- **proxy_B DOES predict intraday adversity (MAE): r=+0.42, p=0.003** ← KEY
**Mechanistic interpretation:** When proxy_B is high at entry, the trade experiences
a worse intraday adverse excursion (deeper MAE). But final PnL is unaffected because
the engine's exit logic (TP/max_hold/direction-confirm) successfully navigates through
the stress period. This is the complete explanation for why:
1. Gating on proxy_B removes trades that are temporarily stressed but then RECOVER → hurts
2. A proxy-coupled stop would cut those recoveries short → reduces DD but also reduces ROI
3. The signal has genuine information content (AUC=0.715, MAE correlation p=0.003)
but the system is ALREADY correctly managing the trades it tags as stressed
---
## 4. Experiments Performed
### Exp 1 — proxy_B Position Sizing (`exp1_proxy_sizing.py`)
Tests `bet_sizer.base_fraction * scale(proxy_B_at_entry)`.
| Config | ROI% | PF | DD% | Sharpe | scale_mean |
|--------|------|----|-----|--------|------------|
| GOLD | 88.55 | 1.215 | 15.05 | 4.38 | — |
| Baseline (no sizing) | 88.55 | 1.2147 | 15.05 | 4.378 | — |
| S1 [0.5x1.5x] w500 | 91.48 | 1.1782 | 16.93 | 3.528 | 1.004 |
| S2 [0.25x2.0x] w500 | 105.51 | 1.1537 | 20.30 | 2.956 | **1.133** |
| S3 [0.5x1.5x] w1000 | 89.49 | 1.1763 | 16.69 | 3.514 | 1.000 |
| S4 [0.5x1.5x] clip | 87.13 | 1.1628 | 18.03 | 3.184 | 1.019 |
**Finding:** scale_mean > 1.0 in all configs → proxy_B is more often LOW during trading
activity, meaning the engine sizes UP on average. Higher ROI (S2: +17pp) is a leverage
effect, not signal quality — PF drops and Sharpe collapses. **The signal is anti-correlated
with trade quality per unit capital.**
### Exp 2 — proxy_B Shadow Exit (`exp2_proxy_exit.py`)
Post-hoc test: would exiting when proxy_B < threshold have helped?
| Threshold | Trigger rate | AvgDelta% | Early better | Est. ROI |
|-----------|-------------|-----------|--------------|---------|
| p10 | ~60% of trades (at 1 bar) | 0.15% | 37% | 0.96pp |
| p25 | ~69% | +0.04% | 43% | +0.26pp |
| p50 | ~85% | +0.02% | 43% | +0.15pp |
**Note:** High trigger rates are mathematically expected a 120-bar hold has
~100% chance of *any* bar crossing the p50 level. The signal fires constantly
during holds; using it as an exit trigger is noise, not information.
**Verdict:** Holding to natural exit is better. Early exit is weakly beneficial
in only 37-43% of cases.
### Exp 3 — Longer Window Proxies (`exp3_longer_proxies.py`)
All 5 proxy variants × 3 modes (gate/size/exit) × 3 thresholds. AE validation
of top 10 fast-sweep configs.
| Config | AE ROI% | PF | DD% | Note |
|--------|---------|-----|-----|------|
| GOLD | 88.55 | 1.215 | 15.05 | |
| V50/gate/p50 | **21.58** | 0.822 | 31.94 | Catastrophic |
| V150/gate/p50 | **24.34** | 0.909 | 31.97 | Catastrophic |
| B150/gate/p10 | 17.37 | 0.941 | 29.00 | Catastrophic |
| B150/gate/p25 | 1.26 | 0.996 | 28.25 | Marginal hurt |
| Exit modes | 88.55 (=base) | | | 0 early exits |
**Why velocity gates are catastrophic:** V50 = instability_50 v750_velocity and
V150 = instability_150 v750_velocity. The velocity divergence short-minus-long is
highly *noisy* at short windows. Gating on it suppresses large fractions of trades
(compound-leverage paradox: each suppressed trade costs more than it saves due to
capital compounding).
**Exit mode 0-triggers in AE:** `_try_entry` is the wrong hook for exits. The AE
exit path goes through `exit_manager.evaluate()`. Fast-sweep exit approximations
are valid; AE validation of exit modes requires `exit_manager` override.
### Exp 5 — Two-pass β DVAE (`exp5_dvae_twopass.py`)
Does high-β first pass low-β second pass improve latent space?
| Variant | AUC | vs baseline | Active dims |
|---------|-----|-------------|-------------|
| A: single-pass β=0.1 | **0.6918** | | 8/8 |
| B: β=4→β=0.1 | <0.6918 | 0.006 | collapsed |
| C: β=2→β=0.1 | <0.6918 | negative | partial |
| D: dual concat β=4‖β=0.1 | <0.6918 | negative | mixed |
**Root cause:** High-β collapses z_var to 0.0080.019 (from 0.1+ single-pass) by
epoch 10 via KL domination. The collapsed posterior is a *worse* initialiser than
random. β=12 was not tested (β=6 already gave 0/20 active dims).
### Exp 4 — Coupling Sweep (`exp4_proxy_coupling.py`) — COMPLETE
155 configs tested in 0.11s (retroactive). Shadow run confirmed: ROI=88.55%, Trades=2155.
**DD < 15.05% AND ROI ≥ 84.1% candidates (19 found):**
| Config | ROI% | DD% | ΔROI | ΔDD | Note |
|--------|------|----|------|-----|------|
| B/pb_entry/thr0.35/a1.0 | 86.93 | **14.89** | 1.62 | 0.15 | scale_boost, smean=1.061 |
| **E/stop_0.003** | **89.90** | **14.91** | **+1.36** | **0.14** | **pure_stop, 18 triggers** |
| B/pb_entry/thr0.35/a0.5 | 87.74 | 14.97 | 0.81 | 0.08 | scale_boost |
| E/stop_0.005 | 89.29 | 14.97 | +0.74 | 0.07 | pure_stop, 11 triggers |
| E/stop_0.015 | 89.27 | 14.97 | +0.72 | 0.07 | pure_stop, 2 triggers |
| F/stop_0.005/gate_p0.5 | 88.68 | 15.03 | +0.14 | 0.01 | gated_stop, 4 triggers |
Best per mode: scale_suppress DD worsens; hold_limit DD worsens; rising_exit DD worsens;
pure_stop best legitimate DD reducer; gated_stop marginal (few triggers).
**IMPORTANT CAVEAT:** entry_bar alignment bug caused 2107/2155 pb_entry to be NaN
(entry_bar appears to store global_bar_idx not per-day ri). The proxy-coupling modes
(A, B, F) used median fill-in for 98% of trades effectively a null test. Only Mode E
(pure_stop) is fully valid because it uses MAE computed from shadow hold prices.
**Valid conclusion from Exp 4:**
- A 0.3% retroactive stop (`E/stop_0.003`) improves BOTH ROI (+1.36pp) and DD (0.14pp)
- Only 18 trades triggered the improvement is modest but directionally sound
- The proxy-coupled stop (Mode F) needs proper entry_bar alignment to test meaningfully
- **Next step**: implement stop_pct parameter in exit_manager for real engine test
---
## 5. Core Findings
### 5.1 The Compound-Leverage Paradox
With dynamic leverage, gating ANY subset of trades (even below-average quality ones)
costs ROI because capital that would have compounded is left idle. The break-even
requires gated trades to have strongly negative expected value but the 50.5% win
rate means most trades are net-positive.
### 5.2 Why proxy_B Gating Specifically Hurts
scale_mean > 1.0 in position sizing tests = proxy_B is LOWER during most trading
time windows than the neutral baseline. The system naturally avoids high-proxy
periods (or avoids entering during them) already. Gating explicitly on high-proxy
removes the REMAINING high-proxy trades, which happen to be positive on average.
### 5.3 The Unresolved Question: MAE vs Final PnL
proxy_B has AUC=0.715 for eigenspace stress prediction. The signal IS predictive of
something real. The hypothesis (untested until Exp 4): **proxy_B predicts intraday
adversity (MAE) but NOT final trade outcome**, because the engine's exit logic
successfully recovers from intraday stress. If confirmed:
- proxy_B fires during the rough patch mid-trade
- The trade then recovers to its natural TP/exit
- Gating removes trades that look scary but ultimately recover
- **A tighter retroactive stop ONLY during high-proxy periods might reduce DD
without proportionally reducing ROI** — if the recovery is systematic
---
## 6. Open Research Directions
| Priority | Direction | Rationale |
|----------|-----------|-----------|
| HIGH | Exp 4 coupling results | Does gated stop reduce DD without ROI cost? |
| MED | Exit hook override | Implement `exit_manager` proxy gate for proper AE test |
| MED | 5s crossover test | Does vel_div crossover on 5s data escape fee pressure? |
| LOW | Longer proxy windows | B300, B500 (instability_300 not in data) |
| LOW | Combined proxy | B50 × B150 product for sharper stress signal |
---
## 7. Files
| File | Description |
|------|-------------|
| `exp1_proxy_sizing.py` | Position scaling by proxy_B |
| `exp2_proxy_exit.py` | Shadow exit analysis (corrected) |
| `exp3_longer_proxies.py` | All 5 proxies × all 3 modes × 3 thresholds |
| `exp4_proxy_coupling.py` | Coupling sweep + orthogonality test |
| `exp5_dvae_twopass.py` | Two-pass β DVAE test |
| `exp1_proxy_sizing_results.json` | Logged results |
| `exp2_proxy_exit_results.json` | Logged results |
| `exp3_fast_sweep_results.json` | Fast numpy sweep |
| `exp3_alpha_engine_results.json` | AE validation |
| `exp4_proxy_coupling_results.json` | Coupling sweep output |
| `exp5_dvae_twopass_results.json` | Two-pass DVAE output |
| `flint_hd_vae.py` | FlintHDVAE implementation |
| `e2e_precursor_auc.py` | AUC measurement infrastructure |

View File

@@ -0,0 +1,3 @@
"""DOLPHIN Hierarchical Disentangled VAE — multi-generation corpus training."""
from .hierarchical_dvae import HierarchicalDVAE
from .corpus_builder import DolphinCorpus

View File

@@ -0,0 +1,171 @@
"""
FlintGatedEngine — TEST FORK. Does NOT modify any production code.
Adds proxy_B = instability_50 - v750_lambda_max_velocity entry gate.
Gate: SUPPRESS new entry when proxy_B < proxy_b_threshold
ALLOW new entry when proxy_B >= proxy_b_threshold
proxy_B is a 25-second leading indicator of eigenspace stress (AUC=0.715 at K=5).
High proxy_B = stress event incoming = good mean-reversion entry environment.
Low proxy_B = calm eigenspace = vel_div signal is likely false alarm.
Threshold is computed from the rolling window of observed proxy_B values.
"""
import sys, os
_dvae_dir = os.path.dirname(os.path.abspath(__file__))
_nd_root = os.path.dirname(_dvae_dir) # nautilus_dolphin/ outer dir (package root)
_proj_root = os.path.dirname(_nd_root) # project root
sys.path.insert(0, _nd_root)
sys.path.insert(0, _proj_root)
import numpy as np
from typing import Optional, Dict, List, Any
from nautilus_dolphin.nautilus.esf_alpha_orchestrator import NDAlphaEngine
class FlintGatedEngine(NDAlphaEngine):
"""
NDAlphaEngine with a proxy_B entry gate.
Additional constructor parameters:
proxy_b_threshold : float
Minimum proxy_B value required to allow a new entry.
Any scan where instability_50 - v750_lambda_max_velocity < threshold
will suppress the NEW ENTRY only (existing positions still managed).
proxy_b_percentile : float or None
If set (e.g. 0.5 = median), threshold is computed adaptively from a
warm-up window rather than using a fixed value. Overrides
proxy_b_threshold when enough warm-up data is collected.
warmup_bars : int
Number of bars to collect before activating the adaptive gate.
"""
def __init__(
self,
*args,
proxy_b_threshold: float = 0.0, # fixed threshold (default: > 0 = gate on positive proxy_B)
proxy_b_percentile: Optional[float] = None, # e.g. 0.5 for median adaptive gate
warmup_bars: int = 500,
**kwargs,
):
super().__init__(*args, **kwargs)
self._proxy_b_threshold = proxy_b_threshold
self._proxy_b_percentile = proxy_b_percentile
self._warmup_bars = warmup_bars
# Runtime state updated from process_day override
self._current_instability_50: float = 0.0
self._current_v750_vel_for_gate: float = 0.0
# Adaptive threshold history
self._proxy_b_history: List[float] = []
self._gate_active_threshold: float = proxy_b_threshold
# Stats
self.gate_suppressed: int = 0
self.gate_allowed: int = 0
def _compute_proxy_b(self) -> float:
return self._current_instability_50 - self._current_v750_vel_for_gate
def _update_gate_threshold(self, proxy_b: float):
"""Update rolling adaptive threshold if percentile mode is on."""
self._proxy_b_history.append(proxy_b)
if len(self._proxy_b_history) > 2000:
self._proxy_b_history = self._proxy_b_history[-1000:]
if (self._proxy_b_percentile is not None and
len(self._proxy_b_history) >= self._warmup_bars):
self._gate_active_threshold = float(
np.percentile(self._proxy_b_history, self._proxy_b_percentile * 100)
)
def process_day(
self,
date_str: str,
df,
asset_columns,
vol_regime_ok=None,
direction=None,
posture: str = 'APEX',
) -> dict:
"""Override: reads instability_50 from each row before calling parent step_bar."""
from nautilus_dolphin.nautilus.esf_alpha_orchestrator import NDAlphaEngine
# 1. Setup day (inherited)
self.begin_day(date_str, posture=posture, direction=direction)
bid = 0
for ri in range(len(df)):
row = df.iloc[ri]
vd = row.get('vel_div')
if vd is None or not np.isfinite(float(vd)):
self._global_bar_idx += 1; bid += 1; continue
v50_raw = row.get('v50_lambda_max_velocity')
v750_raw = row.get('v750_lambda_max_velocity')
v50_val = float(v50_raw) if (v50_raw is not None and np.isfinite(float(v50_raw))) else 0.0
v750_val = float(v750_raw) if (v750_raw is not None and np.isfinite(float(v750_raw))) else 0.0
# ── NEW: capture instability_50 for gate ─────────────────
inst_raw = row.get('instability_50')
inst_val = float(inst_raw) if (inst_raw is not None and np.isfinite(float(inst_raw))) else 0.0
self._current_instability_50 = inst_val
self._current_v750_vel_for_gate = v750_val
proxy_b = self._compute_proxy_b()
self._update_gate_threshold(proxy_b)
# ─────────────────────────────────────────────────────────
prices = {}
for ac in asset_columns:
p = row.get(ac)
if p is not None and p > 0 and np.isfinite(p):
prices[ac] = float(p)
if not prices:
self._global_bar_idx += 1; bid += 1; continue
vrok = bool(vol_regime_ok[ri]) if vol_regime_ok is not None else (bid >= 100)
self.step_bar(
bar_idx=ri,
vel_div=float(vd),
prices=prices,
vol_regime_ok=vrok,
v50_vel=v50_val,
v750_vel=v750_val,
)
bid += 1
return self.end_day()
def _try_entry(
self,
bar_idx: int,
vel_div: float,
prices: Dict[str, float],
price_histories: Optional[Dict[str, List[float]]],
v50_vel: float = 0.0,
v750_vel: float = 0.0,
) -> Optional[Dict]:
"""Override: apply proxy_B gate before allowing new entry."""
proxy_b = self._compute_proxy_b()
# Gate: suppress entry if proxy_B below threshold
if proxy_b < self._gate_active_threshold:
self.gate_suppressed += 1
return None
self.gate_allowed += 1
return super()._try_entry(bar_idx, vel_div, prices, price_histories, v50_vel, v750_vel)
def get_gate_stats(self) -> dict:
total = self.gate_suppressed + self.gate_allowed
return {
'gate_threshold': self._gate_active_threshold,
'gate_suppressed': self.gate_suppressed,
'gate_allowed': self.gate_allowed,
'gate_total_entry_attempts': total,
'suppression_rate': self.gate_suppressed / max(1, total),
}

View File

@@ -0,0 +1,44 @@
{
"macro_regime_k": 7,
"macro_regime_silhouettes": {
"3": 1.2346,
"4": 1.3428,
"5": 1.1801,
"6": 1.3357,
"7": 1.3577,
"8": 1.3035
},
"z1_leads_z0": {
"1": 0.0007,
"3": -0.0078,
"5": -0.0086,
"10": 0.0004,
"20": -0.0081
},
"z1_peak_lead_lag": 5,
"active_dims": 0,
"var_per_dim": [
0.00025703103098368845,
0.00028948736020975336,
0.00024541800878152527,
0.0002782960301000939,
0.01298944744570043,
0.008084571955901239,
0.004822071392686802,
0.008369066411074426,
0.0019036063690540516,
0.0120343102415987,
0.011469297945944847,
0.01545028485150473,
5.283625351179199e-10,
4.396326750003878e-08,
4.179382781434889e-09,
2.8163319663137794e-08,
2.004156954585684e-08,
7.610757411291218e-08,
4.65066037228969e-09,
6.595101181957703e-09
],
"recon_err_mean": 0.42131611033692357,
"recon_err_p95": 0.6568199701540868
}

View File

@@ -0,0 +1,44 @@
{
"macro_regime_k": 6,
"macro_regime_silhouettes": {
"3": 1.0784,
"4": 1.0369,
"5": 1.0727,
"6": 1.1377,
"7": 1.0712,
"8": 1.0434
},
"z1_leads_z0": {
"1": 0.002,
"3": -0.0007,
"5": 0.006,
"10": 0.0091,
"20": 0.0099
},
"z1_peak_lead_lag": 20,
"active_dims": 0,
"var_per_dim": [
0.00012800445580857145,
8.531996355383793e-05,
5.910444519925593e-05,
7.25133118984212e-05,
5.466937414344175e-06,
2.103502361258361e-07,
1.3829218736923569e-06,
7.117502941433482e-08,
2.696514269214737e-07,
5.3351005165789584e-08,
1.5573366927270033e-07,
6.82847625884739e-07,
2.3622843845548987e-08,
1.391215288082256e-07,
3.228828287565509e-08,
8.832248019511851e-08,
4.565268782682383e-08,
1.773469547532686e-07,
1.0306041981046993e-07,
3.908315856902533e-08
],
"recon_err_mean": 0.4274084299928307,
"recon_err_p95": 0.6560542069479147
}

View File

@@ -0,0 +1,44 @@
{
"macro_regime_k": 8,
"macro_regime_silhouettes": {
"3": 1.0825,
"4": 1.164,
"5": 1.1201,
"6": 1.045,
"7": 1.249,
"8": 1.2561
},
"z1_leads_z0": {
"1": 0.0049,
"3": -0.0254,
"5": 0.0066,
"10": 0.0009,
"20": -0.0013
},
"z1_peak_lead_lag": 3,
"active_dims": 0,
"var_per_dim": [
2.891610166279152e-05,
3.0922398955150796e-05,
2.6778158479914613e-05,
3.276855140176666e-05,
7.528364813807103e-06,
3.8058184945766717e-07,
8.343217846104335e-06,
8.610409182091088e-07,
5.164212481888014e-08,
4.2038235764690244e-07,
9.007637297207591e-07,
1.3331745890423244e-06,
8.58313445064302e-09,
2.855058658369656e-08,
7.697479886121003e-09,
3.012180655617942e-08,
1.625070986842035e-08,
7.986989673920088e-08,
5.23765968435441e-09,
1.1474841146856828e-08
],
"recon_err_mean": 0.424669903822917,
"recon_err_p95": 0.6535680228789759
}

View File

@@ -0,0 +1,141 @@
"""
convnext_5s_query.py — inference query against trained convnext_model_5s.json
Reports:
1. Per-channel reconstruction correlation (orig vs recon)
2. z-dim activity and spread
3. Top z-dims correlated with proxy_B (ch7)
Uses vbt_cache/*.parquet (5s scan corpus, C_in=8, no ExF).
"""
import os, sys, json, io
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8', errors='replace')
sys.stderr = io.TextIOWrapper(sys.stderr.buffer, encoding='utf-8', errors='replace')
import numpy as np
import glob
import pandas as pd
ROOT = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
DVAE_DIR = os.path.join(ROOT, 'nautilus_dolphin', 'dvae')
sys.path.insert(0, DVAE_DIR)
MODEL_PATH = os.path.join(DVAE_DIR, 'convnext_model_5s.json')
SCANS_DIR = os.path.join(ROOT, 'vbt_cache')
FEATURE_COLS = [
'v50_lambda_max_velocity', 'v150_lambda_max_velocity',
'v300_lambda_max_velocity', 'v750_lambda_max_velocity',
'vel_div', 'instability_50', 'instability_150',
]
CH_NAMES = FEATURE_COLS + ['proxy_B'] # 8 channels
T_WIN = 32
N_PROBES = 200 # more probes — 56 files, sample ~3-4 per file
# ── load model ──────────────────────────────────────────────────────────────
from convnext_dvae import ConvNeXtVAE
with open(MODEL_PATH) as f:
meta = json.load(f)
arch = meta.get('architecture', {})
model = ConvNeXtVAE(
C_in = arch.get('C_in', 8),
T_in = arch.get('T_in', 32),
z_dim = arch.get('z_dim', 32),
base_ch = arch.get('base_ch', 32),
n_blocks = arch.get('n_blocks', 3),
seed = 42,
)
model.load(MODEL_PATH)
norm_mean = np.array(meta['norm_mean']) if 'norm_mean' in meta else None
norm_std = np.array(meta['norm_std']) if 'norm_std' in meta else None
print(f"Model: epoch={meta.get('epoch')} val_loss={meta.get('val_loss', float('nan')):.5f}")
print(f" C_in={arch.get('C_in')} z_dim={arch.get('z_dim')} base_ch={arch.get('base_ch')}\n")
# ── build probe set ──────────────────────────────────────────────────────────
files = sorted(f for f in glob.glob(os.path.join(SCANS_DIR, '*.parquet'))
if 'catalog' not in f)
step = max(1, len(files) // (N_PROBES // 4)) # ~4 probes per file
probes_raw, proxy_B_vals = [], []
rng = np.random.default_rng(42)
for f in files[::step]:
try:
df = pd.read_parquet(f, columns=FEATURE_COLS).dropna()
if len(df) < T_WIN + 4: continue
# sample multiple starting positions per file
positions = rng.integers(0, len(df) - T_WIN, size=4)
for pos in positions:
arr = df[FEATURE_COLS].values[pos:pos+T_WIN].astype(np.float64) # (T, 7)
proxy_B = (arr[:, 5] - arr[:, 3]).reshape(-1, 1) # instability_50 - v750
arr8 = np.concatenate([arr, proxy_B], axis=1) # (T, 8)
if not np.isfinite(arr8).all(): continue
probes_raw.append(arr8.T) # (8, T)
proxy_B_vals.append(float(proxy_B.mean()))
if len(probes_raw) >= N_PROBES: break
except Exception:
pass
if len(probes_raw) >= N_PROBES: break
probes_raw = np.stack(probes_raw) # (N, 8, T)
proxy_B_arr = np.array(proxy_B_vals) # (N,)
print(f"Probe set: {probes_raw.shape} ({len(probes_raw)} windows × {probes_raw.shape[1]} ch × {T_WIN} steps)\n")
# ── normalise ────────────────────────────────────────────────────────────────
probes = probes_raw.copy()
if norm_mean is not None:
probes = (probes - norm_mean[None, :, None]) / norm_std[None, :, None]
np.clip(probes, -6.0, 6.0, out=probes)
# ── encode / decode ──────────────────────────────────────────────────────────
z_mu, z_logvar = model.encode(probes)
x_recon = model.decode(z_mu)
# ── 1. Per-channel reconstruction correlation ────────────────────────────────
print("── Per-channel reconstruction r (orig vs recon) ──────────────────")
for c, name in enumerate(CH_NAMES):
rs = []
for b in range(len(probes)):
o, r = probes[b, c], x_recon[b, c]
if o.std() > 1e-6 and r.std() > 1e-6:
rv = float(np.corrcoef(o, r)[0, 1])
if np.isfinite(rv): rs.append(rv)
mean_r = np.mean(rs) if rs else float('nan')
bar = '' * int(max(0, mean_r) * 20)
print(f" ch{c:2d} {name:<30s} r={mean_r:+.3f} {bar}")
# ── 2. z-dim activity ────────────────────────────────────────────────────────
z_std_per_dim = z_mu.std(0) # (D,)
z_active = int((z_std_per_dim > 0.01).sum())
z_post_std = float(np.exp(0.5 * z_logvar).mean())
print(f"\n── Latent space ──────────────────────────────────────────────────")
print(f" z_active_dims : {z_active} / {z_mu.shape[1]}")
print(f" z_post_std : {z_post_std:.4f} (>1 = posterior wider than prior)")
z_stds_sorted = sorted(enumerate(z_std_per_dim), key=lambda x: -x[1])
print(f" Top z-dim stds: " + " ".join(f"z[{i}]={s:.3f}" for i, s in z_stds_sorted[:8]))
# ── 3. z-dim × proxy_B correlation ──────────────────────────────────────────
print(f"\n── z-dim correlation with proxy_B (all active dims) ─────────────")
corrs = []
for d in range(z_mu.shape[1]):
if z_std_per_dim[d] > 0.01:
r = float(np.corrcoef(z_mu[:, d], proxy_B_arr)[0, 1])
if np.isfinite(r): corrs.append((abs(r), r, d))
corrs.sort(reverse=True)
print(f" (proxy_B mean={proxy_B_arr.mean():+.4f} std={proxy_B_arr.std():.4f})")
for _, r, d in corrs[:15]:
bar = '' * int(abs(r) * 30)
print(f" z[{d:2d}] r={r:+.4f} {bar}")
# ── 4. z-dim statistics ──────────────────────────────────────────────────────
print(f"\n── z-dim statistics (z_mu) ──────────────────────────────────────")
print(f" {'dim':>4} {'mean':>8} {'std':>8} {'min':>8} {'max':>8} {'r_proxyB':>10}")
for i, s in z_stds_sorted[:16]:
r_pb = float(np.corrcoef(z_mu[:, i], proxy_B_arr)[0, 1]) if s > 0.01 else float('nan')
print(f" z[{i:2d}] {z_mu[:, i].mean():>+8.4f} {s:>8.4f} "
f"{z_mu[:, i].min():>+8.4f} {z_mu[:, i].max():>+8.4f} {r_pb:>+10.4f}")
print(f"\nDone.")

View File

@@ -0,0 +1,172 @@
"""
convnext_5s_sensor.py — Inference wrapper for the 5s ConvNeXt-1D β-TCVAE.
Usage
-----
sensor = ConvNext5sSensor(model_path)
z_mu, z_post_std = sensor.encode_raw(arr)
# arr: (C_IN, T_WIN) float64
# z_mu: (z_dim,) float64 — latent mean
# z_post_std: float — mean posterior std (>1 = wide/uncertain)
z_mu, z_post_std = sensor.encode_scan_window(arr2d)
# arr2d: (T_WIN, C_IN) or (T_WIN, 7) — from scan parquet rows
# If 7 columns, proxy_B is appended as ch7.
Key differences from the 1m sensor (convnext_sensor.py):
- Model path: convnext_model_5s.json
- C_IN = 8 (7 FEATURE + proxy_B — NO ExF channels)
- No dvol_btc, fng, funding_btc channels
- FEATURE_COLS are the same 7 features as the 1m sensor
- proxy_B = instability_50 - v750_lambda_max_velocity (ch7, same formula as 1m)
Architecture: ConvNeXtVAE C_in=8 T_in=32 z_dim=32 base_ch=32 n_blocks=3
Input channels:
ch0 v50_lambda_max_velocity
ch1 v150_lambda_max_velocity
ch2 v300_lambda_max_velocity
ch3 v750_lambda_max_velocity
ch4 vel_div
ch5 instability_50
ch6 instability_150
ch7 proxy_B (= instability_50 - v750_lambda_max_velocity)
"""
import os
import sys
import json
import numpy as np
_DVAE_DIR = os.path.dirname(os.path.abspath(__file__))
if _DVAE_DIR not in sys.path:
sys.path.insert(0, _DVAE_DIR)
from convnext_dvae import ConvNeXtVAE
FEATURE_COLS = [
'v50_lambda_max_velocity',
'v150_lambda_max_velocity',
'v300_lambda_max_velocity',
'v750_lambda_max_velocity',
'vel_div',
'instability_50',
'instability_150',
]
T_WIN = 32
C_IN = 8 # 7 FEATURE + proxy_B (no ExF)
class ConvNext5sSensor:
"""
Stateless inference wrapper for the 5s ConvNeXt model.
No ExF channels — 8-channel input only.
Thread-safe (model weights are read-only numpy).
"""
def __init__(self, model_path: str):
with open(model_path) as f:
meta = json.load(f)
arch = meta.get('architecture', {})
self.model = ConvNeXtVAE(
C_in = arch.get('C_in', C_IN),
T_in = arch.get('T_in', T_WIN),
z_dim = arch.get('z_dim', 32),
base_ch = arch.get('base_ch', 32),
n_blocks = arch.get('n_blocks', 3),
seed = 42,
)
self.model.load(model_path)
self.norm_mean = np.array(meta['norm_mean'], dtype=np.float64) if 'norm_mean' in meta else None
self.norm_std = np.array(meta['norm_std'], dtype=np.float64) if 'norm_std' in meta else None
self.epoch = meta.get('epoch', '?')
self.val_loss = meta.get('val_loss', float('nan'))
self.z_dim = arch.get('z_dim', 32)
# ── low-level: encode a (C_IN, T_WIN) array ──────────────────────────────
def encode_raw(self, arr: np.ndarray):
"""
arr: (C_IN, T_WIN) float64, already in raw (un-normalised) units.
Returns z_mu (z_dim,), z_post_std float.
"""
x = arr[np.newaxis].astype(np.float64) # (1, C, T)
if self.norm_mean is not None:
x = (x - self.norm_mean[None, :, None]) / self.norm_std[None, :, None]
np.clip(x, -6.0, 6.0, out=x)
z_mu, z_logvar = self.model.encode(x) # (1, D)
z_post_std = float(np.exp(0.5 * z_logvar).mean())
return z_mu[0], z_post_std
# ── high-level: encode from a 2D scan array ───────────────────────────────
def encode_scan_window(self, arr2d: np.ndarray):
"""
arr2d: (T_WIN, C_IN) or (T_WIN, 7) — rows from scan parquet.
If arr2d has 7 columns, proxy_B (instability_50 - v750_lambda_max_velocity)
is appended as ch7 before encoding.
Returns
-------
z_mu : (z_dim,) float64
z_post_std : float (>1 suggests OOD regime)
"""
arr2d = np.asarray(arr2d, dtype=np.float64)
T_actual, n_cols = arr2d.shape
if n_cols == 7:
# Append proxy_B = instability_50 (col5) - v750_lambda_max_velocity (col3)
proxy_b = arr2d[:, 5] - arr2d[:, 3]
arr2d = np.concatenate([arr2d, proxy_b[:, np.newaxis]], axis=1) # (T, 8)
# Pad / trim to T_WIN rows (zero-pad at the start if shorter)
if T_actual < T_WIN:
pad = np.zeros((T_WIN - T_actual, C_IN), dtype=np.float64)
arr2d = np.concatenate([pad, arr2d], axis=0)
else:
arr2d = arr2d[-T_WIN:] # keep the most recent T_WIN rows
return self.encode_raw(arr2d.T) # (C_IN, T_WIN)
# ── find proxy_B dimension via correlation probe ──────────────────────────
def find_proxy_b_dim(self, probe_windows: np.ndarray):
"""
Given probe_windows of shape (N, C_IN, T_WIN), find the z-dim most
correlated with the mean proxy_B value (ch7 mean) across windows.
Parameters
----------
probe_windows : (N, C_IN, T_WIN) float64
Returns
-------
dim_idx : int — z-dim index with highest |r|
corr : float — Pearson r with proxy_B mean
"""
N = len(probe_windows)
if N == 0:
return 0, 0.0
proxy_b_means = probe_windows[:, 7, :].mean(axis=1) # (N,) — mean of ch7 per window
z_mus = []
for i in range(N):
z_mu, _ = self.encode_raw(probe_windows[i])
z_mus.append(z_mu)
z_mus = np.stack(z_mus, axis=0) # (N, z_dim)
# Pearson r between each z-dim and proxy_B mean
pb_centered = proxy_b_means - proxy_b_means.mean()
pb_std = pb_centered.std() + 1e-12
best_dim = 0
best_corr = 0.0
for d in range(z_mus.shape[1]):
zd = z_mus[:, d]
zd_c = zd - zd.mean()
zd_std = zd_c.std() + 1e-12
r = float((pb_centered * zd_c).mean() / (pb_std * zd_std))
if abs(r) > abs(best_corr):
best_corr = r
best_dim = d
return best_dim, best_corr

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,145 @@
"""
convnext_query.py — inference query against trained convnext_model.json
Reports:
1. Per-channel reconstruction correlation (orig vs recon)
2. z-dim activity and spread
3. Top z-dims correlated with proxy_B (ch7)
"""
import os, sys, json
import numpy as np
import glob
import pandas as pd
ROOT = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
DVAE_DIR = os.path.join(ROOT, 'nautilus_dolphin', 'dvae')
sys.path.insert(0, DVAE_DIR)
MODEL_PATH = os.path.join(DVAE_DIR, 'convnext_model.json')
KLINES_DIR = os.path.join(ROOT, 'vbt_cache_klines')
EIGENVALUES_PATH = r"C:\Users\Lenovo\Documents\- Dolphin NG HD (NG3)\correlation_arb512\eigenvalues"
EXF_NPZ_NAME = "scan_000001__Indicators.npz"
FEATURE_COLS = [
'v50_lambda_max_velocity', 'v150_lambda_max_velocity',
'v300_lambda_max_velocity', 'v750_lambda_max_velocity',
'vel_div', 'instability_50', 'instability_150',
]
EXF_COLS = ['dvol_btc', 'fng', 'funding_btc']
CH_NAMES = FEATURE_COLS + ['proxy_B'] + EXF_COLS # 11 channels
T_WIN = 32
N_PROBES = 100
# ── load model ──────────────────────────────────────────────────────────────
from convnext_dvae import ConvNeXtVAE
with open(MODEL_PATH) as f:
meta = json.load(f)
arch = meta.get('architecture', {})
model = ConvNeXtVAE(C_in=arch.get('C_in', 11), T_in=arch.get('T_in', 32),
z_dim=arch.get('z_dim', 32), base_ch=arch.get('base_ch', 32),
n_blocks=3, seed=42)
model.load(MODEL_PATH)
norm_mean = np.array(meta['norm_mean']) if 'norm_mean' in meta else None
norm_std = np.array(meta['norm_std']) if 'norm_std' in meta else None
print(f"Model: epoch={meta.get('epoch')} val_loss={meta.get('val_loss', float('nan')):.4f}")
print(f" C_in={arch.get('C_in')} z_dim={arch.get('z_dim')} base_ch={arch.get('base_ch')}\n")
# ── build probe set ──────────────────────────────────────────────────────────
_exf_idx = None
def get_exf_indices():
global _exf_idx
if _exf_idx is not None: return _exf_idx
for ds in sorted(os.listdir(EIGENVALUES_PATH)):
p = os.path.join(EIGENVALUES_PATH, ds, EXF_NPZ_NAME)
if os.path.exists(p):
try:
d = np.load(p, allow_pickle=True)
_exf_idx = {n: i for i, n in enumerate(d['api_names'])}
return _exf_idx
except Exception: continue
return {}
files = sorted(glob.glob(os.path.join(KLINES_DIR, '*.parquet')))
step = max(1, len(files) // N_PROBES)
idx_map = get_exf_indices()
probes_raw, proxy_B_vals = [], []
for f in files[::step]:
try:
df = pd.read_parquet(f, columns=FEATURE_COLS).dropna()
if len(df) < T_WIN + 10: continue
pos = len(df) // 2
arr = df[FEATURE_COLS].values[pos:pos+T_WIN].astype(np.float64)
proxy_B = (arr[:, 5] - arr[:, 3]).reshape(-1, 1)
arr = np.concatenate([arr, proxy_B], axis=1) # (T, 8)
exf = np.zeros((T_WIN, len(EXF_COLS)), dtype=np.float64)
date_str = os.path.basename(f).replace('.parquet', '')
npz_p = os.path.join(EIGENVALUES_PATH, date_str, EXF_NPZ_NAME)
if os.path.exists(npz_p) and idx_map:
d = np.load(npz_p, allow_pickle=True)
for ci, col in enumerate(EXF_COLS):
fi = idx_map.get(col, -1)
if fi >= 0 and bool(d['api_success'][fi]):
exf[:, ci] = float(d['api_indicators'][fi])
arr = np.concatenate([arr, exf], axis=1).T # (11, T)
probes_raw.append(arr)
proxy_B_vals.append(float(proxy_B.mean()))
except Exception:
pass
if len(probes_raw) >= N_PROBES: break
probes_raw = np.stack(probes_raw) # (N, 11, T)
proxy_B_arr = np.array(proxy_B_vals) # (N,)
print(f"Probe set: {probes_raw.shape} ({len(probes_raw)} windows × {probes_raw.shape[1]} ch × {T_WIN} steps)\n")
# ── normalise ────────────────────────────────────────────────────────────────
probes = probes_raw.copy()
if norm_mean is not None:
probes = (probes - norm_mean[None, :, None]) / norm_std[None, :, None]
np.clip(probes, -6.0, 6.0, out=probes)
# ── encode / decode ──────────────────────────────────────────────────────────
z_mu, z_logvar = model.encode(probes)
x_recon = model.decode(z_mu)
# ── 1. Per-channel reconstruction correlation ────────────────────────────────
print("── Per-channel reconstruction r (orig vs recon) ──────────────────")
for c, name in enumerate(CH_NAMES):
rs = []
for b in range(len(probes)):
o, r = probes[b, c], x_recon[b, c]
if o.std() > 1e-6 and r.std() > 1e-6:
rv = float(np.corrcoef(o, r)[0, 1])
if np.isfinite(rv): rs.append(rv)
mean_r = np.mean(rs) if rs else float('nan')
bar = '' * int(max(0, mean_r) * 20)
print(f" ch{c:2d} {name:<30s} r={mean_r:+.3f} {bar}")
# ── 2. z-dim activity ────────────────────────────────────────────────────────
z_std_per_dim = z_mu.std(0) # (D,)
z_active = int((z_std_per_dim > 0.01).sum())
z_post_std = float(np.exp(0.5 * z_logvar).mean())
print(f"\n── Latent space ──────────────────────────────────────────────────")
print(f" z_active_dims : {z_active} / {z_mu.shape[1]}")
print(f" z_post_std : {z_post_std:.4f} (>1 = posterior wider than prior)")
# ── 3. z-dim × proxy_B correlation ──────────────────────────────────────────
print(f"\n── z-dim correlation with proxy_B (top 10) ──────────────────────")
corrs = []
for d in range(z_mu.shape[1]):
if z_std_per_dim[d] > 0.01:
r = float(np.corrcoef(z_mu[:, d], proxy_B_arr)[0, 1])
if np.isfinite(r): corrs.append((abs(r), r, d))
corrs.sort(reverse=True)
for _, r, d in corrs[:10]:
bar = '' * int(abs(r) * 20)
print(f" z[{d:2d}] r={r:+.3f} {bar}")
print(f"\nDone.")

View File

@@ -0,0 +1,140 @@
"""
convnext_sensor.py — Inference wrapper for the trained ConvNeXt-1D β-TCVAE.
Usage
-----
sensor = ConvNextSensor(model_path)
z_mu, z_post_std = sensor.encode_window(df_1m, end_row)
# z_mu: (32,) float64 — latent mean for the 32-bar window ending at end_row
# z_post_std: float — mean posterior std (OOD indicator, >1 = wide/uncertain)
Key z-dim assignments (from convnext_query.py, ep=17 checkpoint):
z[10] r=+0.973 proxy_B (instability_50 - v750_velocity)
z[30] r=-0.968 proxy_B (anti-correlated)
z[24] r=+0.942 proxy_B
...10+ dims encoding proxy_B trajectory at >0.86
Architecture: ConvNeXtVAE C_in=11 T_in=32 z_dim=32 base_ch=32 n_blocks=3
Input channels:
ch0-3 v50/v150/v300/v750 lambda_max_velocity
ch4 vel_div
ch5 instability_50
ch6 instability_150
ch7 proxy_B (= instability_50 - v750_lambda_max_velocity)
ch8 dvol_btc (ExF, broadcast constant)
ch9 fng (ExF, broadcast constant)
ch10 funding_btc (ExF, broadcast constant)
"""
import os
import sys
import json
import numpy as np
_DVAE_DIR = os.path.dirname(os.path.abspath(__file__))
if _DVAE_DIR not in sys.path:
sys.path.insert(0, _DVAE_DIR)
from convnext_dvae import ConvNeXtVAE
FEATURE_COLS = [
'v50_lambda_max_velocity',
'v150_lambda_max_velocity',
'v300_lambda_max_velocity',
'v750_lambda_max_velocity',
'vel_div',
'instability_50',
'instability_150',
]
EXF_COLS = ['dvol_btc', 'fng', 'funding_btc']
T_WIN = 32
N_CH = 11 # 7 FEATURE + proxy_B + 3 ExF
# z-dim index of the primary proxy_B encoding (r=+0.973)
PROXY_B_DIM = 10
class ConvNextSensor:
"""
Stateless inference wrapper. Thread-safe (model weights are read-only numpy).
"""
def __init__(self, model_path: str):
with open(model_path) as f:
meta = json.load(f)
arch = meta.get('architecture', {})
self.model = ConvNeXtVAE(
C_in = arch.get('C_in', N_CH),
T_in = arch.get('T_in', T_WIN),
z_dim = arch.get('z_dim', 32),
base_ch = arch.get('base_ch', 32),
n_blocks = arch.get('n_blocks', 3),
seed = 42,
)
self.model.load(model_path)
self.norm_mean = np.array(meta['norm_mean'], dtype=np.float64) if 'norm_mean' in meta else None
self.norm_std = np.array(meta['norm_std'], dtype=np.float64) if 'norm_std' in meta else None
self.epoch = meta.get('epoch', '?')
self.val_loss = meta.get('val_loss', float('nan'))
self.z_dim = arch.get('z_dim', 32)
# ── low-level: encode a (1, N_CH, T_WIN) array ──────────────────────────
def encode_raw(self, arr: np.ndarray):
"""
arr: (N_CH, T_WIN) float64, already in raw (un-normalised) units.
Returns z_mu (z_dim,), z_post_std float.
"""
x = arr[np.newaxis].astype(np.float64) # (1, C, T)
if self.norm_mean is not None:
x = (x - self.norm_mean[None, :, None]) / self.norm_std[None, :, None]
np.clip(x, -6.0, 6.0, out=x)
z_mu, z_logvar = self.model.encode(x) # (1, D)
z_post_std = float(np.exp(0.5 * z_logvar).mean())
return z_mu[0], z_post_std
# ── high-level: encode from a 1m DataFrame row ──────────────────────────
def encode_window(self, df_1m, end_row: int,
exf_dvol: float = 0., exf_fng: float = 0.,
exf_funding: float = 0.):
"""
Build a (N_CH, T_WIN) window ending at end_row (inclusive) from df_1m.
Missing columns are treated as zero.
Parameters
----------
df_1m : DataFrame with FEATURE_COLS as columns
end_row : integer row index (loc-style), window = [end_row-T_WIN+1 : end_row+1]
exf_* : ExF scalars broadcast across the window (set to 0 if unavailable)
Returns
-------
z_mu : (z_dim,) float64
z_post_std : float (>1 suggests OOD regime)
"""
start = max(0, end_row - T_WIN + 1)
rows = df_1m.iloc[start : end_row + 1]
T_actual = len(rows)
arr = np.zeros((T_WIN, N_CH - 3), dtype=np.float64) # (T_WIN, 8)
for i, col in enumerate(FEATURE_COLS):
if col in rows.columns:
vals = rows[col].values.astype(np.float64)
arr[T_WIN - T_actual:, i] = vals
# proxy_B = instability_50 - v750_lambda_max_velocity (ch7)
arr[:, 7] = arr[:, 5] - arr[:, 3]
# ExF channels broadcast as scalar across T_WIN
exf = np.array([exf_dvol, exf_fng, exf_funding], dtype=np.float64)
full = np.concatenate([arr, np.tile(exf, (T_WIN, 1))], axis=1) # (T_WIN, 11)
return self.encode_raw(full.T) # (N_CH, T_WIN)
# ── convenience scalar: primary proxy_B z-dim ────────────────────────────
def z_proxy_b(self, df_1m, end_row: int, **exf_kwargs) -> float:
"""Return scalar z[PROXY_B_DIM] for the window ending at end_row."""
z_mu, _ = self.encode_window(df_1m, end_row, **exf_kwargs)
return float(z_mu[PROXY_B_DIM])

View File

@@ -0,0 +1,548 @@
"""
DOLPHIN Multi-Generation Corpus Builder (Memory-Efficient, 5-Tier)
====================================================================
Loads ALL available Dolphin data into a unified feature matrix.
TIERS (distinct, layered, can be frozen/trained independently):
Tier 0 (8 dims) ALWAYS — breadth (bull/bear), cyclic time, has_eigen flag
Tier 1 (20 dims) NG3+ — eigenvalue structure: 4 windows × 5 features
Tier 2 (50 dims) NG3+ — per-asset volatility cross-section (50 symbols)
Tier 3 (25 dims) NG3+ — ExF macro indicators (dvol, fng, funding, OI, etc.)
Tier 4 (8 dims) ALWAYS — EsoF: lunar, fibonacci, session, cycle (computed)
Total: 111 dims. Missing tiers are zero-filled; mask tracks availability.
Memory strategy:
- NEVER accumulate raw JSON dicts — parse → extract → discard immediately
- Write to memory-mapped numpy array (np.memmap) in fixed-size chunks
- Per-date ExF NPZ loaded once and reused for all scans of that day
- Pre-allocate output array based on estimated sample count
"""
import json
import re
import math
import numpy as np
from pathlib import Path
from datetime import datetime
from typing import Optional, Iterator, Tuple
# ── Paths ──────────────────────────────────────────────────────────────────
BASE = Path(r"C:\Users\Lenovo\Documents")
NG1_DIR = BASE / "- Dolphin NG"
NG2_DIR = BASE / "- Dolphin NG2"
NG4_DIR = BASE / "- DOLPHIN NG4" / "- Results"
NG5_DIR = BASE / "- Dolphin NG5"
NG3_EIGEN = BASE / "- Dolphin NG HD (NG3)" / "correlation_arb512" / "eigenvalues"
HERE = Path(__file__).parent
# ── Tier dimensions ────────────────────────────────────────────────────────
T0 = 8 # breadth + time + flag
T1 = 20 # eigenvalues (4 windows × 5)
T2 = 50 # per-asset volatility
T3 = 25 # ExF macro indicators
T4 = 8 # EsoF esoteric
DIMS = [T0, T1, T2, T3, T4]
TOTAL = sum(DIMS) # 111
OFF = [0, T0, T0+T1, T0+T1+T2, T0+T1+T2+T3] # slice offsets
WINDOWS = [50, 150, 300, 750]
EPS = 1e-8
# ── ExF indicator selection (from the 85-field NPZ, keep reliable ones) ───
EXF_FIELDS = [
'dvol_btc', 'dvol_eth', # implied vol
'fng', 'fng_prev', # fear & greed
'btc_dom', 'eth_dom', # dominance
'chg24_btc', 'chg24_eth', # 24h returns
'dispersion', 'correlation', # cross-market
'imbal_btc', 'imbal_eth', # OB imbalance
'funding_btc', 'funding_eth', # perp funding
'mvrv', # on-chain
'tvl', # DeFi
'pcr_vol', 'pcr_oi', # options
'basis', 'liq_proxy', # futures
'spread', 'vol24', # microstructure
'hashrate', # mining
'btc_price', # price level
'fng_vol', # FnG volatility component
]
assert len(EXF_FIELDS) == T3, f"EXF_FIELDS len={len(EXF_FIELDS)} != T3={T3}"
# ExF normalisation constants (robust: divide by median absolute scale)
EXF_SCALE = {
'dvol_btc': 50.0, 'dvol_eth': 50.0,
'fng': 50.0, 'fng_prev': 50.0,
'btc_dom': 50.0, 'eth_dom': 10.0,
'chg24_btc': 5.0, 'chg24_eth': 5.0,
'dispersion': 5.0, 'correlation': 1.0,
'imbal_btc': 1.0, 'imbal_eth': 1.0,
'funding_btc': 0.001, 'funding_eth': 0.001,
'mvrv': 3.0,
'tvl': 1e11,
'pcr_vol': 1.0, 'pcr_oi': 1.0,
'basis': 0.1, 'liq_proxy': 1.0,
'spread': 0.01, 'vol24': 1e10,
'hashrate': 1e9,
'btc_price': 1e5,
}
# ── Time helpers ───────────────────────────────────────────────────────────
def _parse_ts(s: str) -> Optional[datetime]:
for fmt in ("%Y-%m-%dT%H:%M:%S.%f", "%Y-%m-%dT%H:%M:%S",
"%Y-%m-%d %H:%M:%S.%f", "%Y-%m-%d %H:%M:%S"):
try:
return datetime.strptime(str(s)[:26], fmt)
except ValueError:
continue
return None
def _tier0(bull_pct: float, bear_pct: float, ts: datetime, has_eigen: bool) -> np.ndarray:
bull = np.clip(bull_pct / 100.0, 0, 1)
bear = np.clip(bear_pct / 100.0, 0, 1)
side = max(0.0, 1.0 - bull - bear)
h = ts.hour + ts.minute / 60.0
d = ts.weekday()
return np.array([
bull, bear, side,
math.sin(2 * math.pi * h / 24),
math.cos(2 * math.pi * h / 24),
math.sin(2 * math.pi * d / 7),
math.cos(2 * math.pi * d / 7),
1.0 if has_eigen else 0.0,
], dtype=np.float32)
def _tier1(windows: dict) -> Tuple[np.ndarray, bool]:
vec = np.zeros(T1, dtype=np.float32)
if not windows:
return vec, False
valid = False
for i, w in enumerate(WINDOWS):
wdata = windows.get(w) or windows.get(str(w)) or {}
td = wdata.get('tracking_data') or wdata
rs = wdata.get('regime_signals') or {}
lmax = float(td.get('lambda_max', 0) or 0)
if lmax > 0:
valid = True
vel = float(td.get('lambda_max_velocity', 0) or 0)
gap = float(td.get('eigenvalue_gap', 0) or 0)
inst = float(rs.get('instability_score', 0) or 0)
rtp = float(rs.get('regime_transition_probability', 0) or 0)
log_lmax = math.log(max(lmax, 1e-6))
vel_norm = np.clip(vel / (abs(lmax) + EPS), -5, 5)
gap_ratio = np.clip(gap / (lmax + EPS), 0, 10)
base = i * 5
vec[base] = np.float32(np.clip(log_lmax / 10.0, -3, 3))
vec[base+1] = np.float32(vel_norm)
vec[base+2] = np.float32(gap_ratio)
vec[base+3] = np.float32(np.clip(inst, 0, 1))
vec[base+4] = np.float32(np.clip(rtp, 0, 1))
return vec, valid
def _tier2(pricing: dict) -> Tuple[np.ndarray, bool]:
vec = np.zeros(T2, dtype=np.float32)
vol = (pricing or {}).get('volatility') or {}
if not vol:
return vec, False
vals = np.array(list(vol.values())[:T2], dtype=np.float32)
if len(vals) == 0:
return vec, False
mu, sd = vals.mean(), vals.std() + EPS
vals = np.clip((vals - mu) / sd, -5, 5)
n = min(T2, len(vals))
vec[:n] = vals[:n]
return vec, True
def _tier3(exf_lookup: Optional[dict]) -> np.ndarray:
"""Extract ExF Tier-3 vector from per-date indicator dict."""
vec = np.zeros(T3, dtype=np.float32)
if not exf_lookup:
return vec
for i, field in enumerate(EXF_FIELDS):
v = exf_lookup.get(field, 0.0) or 0.0
scale = EXF_SCALE.get(field, 1.0)
vec[i] = np.float32(np.clip(float(v) / scale, -10, 10))
return vec
def _tier4(ts) -> np.ndarray:
"""
EsoF Tier-4: 8 computed esoteric features from timestamp alone.
Accepts Unix float timestamp OR datetime object.
No external data needed — all derived from ts.
"""
import calendar as cal_mod
# Normalise to both float-seconds and datetime
if isinstance(ts, (int, float)):
ts_f = float(ts)
dt = datetime.utcfromtimestamp(ts_f)
else:
dt = ts
ts_f = dt.timestamp()
# Moon illumination approx (simplified Meeus formula)
# JD of Unix epoch (1970-01-01 00:00 UTC) = 2440587.5
jd = 2440587.5 + ts_f / 86400.0
D = jd - 2451545.0 # days since J2000.0
# Moon phase angle (degrees)
moon_age = (D % 29.53058867) / 29.53058867 # 0=new, 0.5=full
moon_illum = 0.5 * (1 - math.cos(2 * math.pi * moon_age))
# Mercury retrograde cycles (~3x/year, each ~21 days) — simplified
merc_cycle = (D % 115.88) / 115.88
merc_retro = 1.0 if 0.82 < merc_cycle < 1.0 else 0.0 # last ~18/115 of cycle
# Fibonacci time: minutes into day
mins = dt.hour * 60 + dt.minute
fib_mins = [1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, 987, 1440]
dists = [abs(mins - f) for f in fib_mins]
fib_proximity = 1.0 / (1.0 + min(dists) / 60.0) # 1=at fib, 0=far
# Session (0=Asia, 0.33=London, 0.67=NY, 1=Close)
h = dt.hour + dt.minute / 60.0
if 0 <= h < 7: session = 0.0
elif 7 <= h < 13: session = 0.33
elif 13 <= h < 21: session = 0.67
else: session = 1.0
# Market cycle position (annual)
doy = dt.timetuple().tm_yday
days_in_year = 366 if cal_mod.isleap(dt.year) else 365
cycle_pos = doy / days_in_year
# Day of week sin/cos (weekly cycle)
dow_sin = math.sin(2 * math.pi * dt.weekday() / 7)
dow_cos = math.cos(2 * math.pi * dt.weekday() / 7)
return np.array([
moon_illum, # lunar phase
moon_age, # 0=new, 0.5=full, 1=new
merc_retro, # binary: Mercury Rx
fib_proximity, # nearness to Fibonacci time
session, # liquidity session
cycle_pos, # annual cycle position
dow_sin, dow_cos, # weekly cycle
], dtype=np.float32)
# ── ExF NPZ loader (per-date, cached) ─────────────────────────────────────
class ExFCache:
"""Loads ExF NPZ once per date directory, provides field lookup."""
def __init__(self, eigen_base: Path):
self._base = eigen_base
self._current_date: Optional[str] = None
self._lookup: Optional[dict] = None
def get(self, date_str: str) -> Optional[dict]:
if date_str == self._current_date:
return self._lookup
self._current_date = date_str
self._lookup = None
date_dir = self._base / date_str
# Find ANY __Indicators.npz in this dir
npz_files = list(date_dir.glob('*__Indicators.npz'))
if not npz_files:
return None
try:
d = np.load(npz_files[0], allow_pickle=True)
names = list(d['api_names'])
vals = d['api_indicators']
ok = d['api_success']
self._lookup = {n: float(v) for n, v, s in zip(names, vals, ok) if s and float(v) != 0}
except Exception:
self._lookup = None
return self._lookup
# ── Streaming generators (memory-efficient) ───────────────────────────────
def _stream_ng1_ng2() -> Iterator[np.ndarray]:
import os
for ng_dir in [NG1_DIR, NG2_DIR]:
if not ng_dir.exists():
continue
# Use os.scandir (non-sorted) — much faster than sorted(rglob) on 300K+ files
# NG1/NG2 files are all at the top level
for entry in os.scandir(str(ng_dir)):
f = Path(entry.path)
if not (entry.name.startswith('regime_result_') and entry.name.endswith('.json')):
continue
try:
txt = f.read_text(encoding='utf-8', errors='replace')
d = json.loads(txt)
ts = _parse_ts(d.get('timestamp', ''))
if ts is None:
continue
bull = float(d.get('up_ratio', 0)) * 100
bear = float(d.get('down_ratio', 0)) * 100
t0 = _tier0(bull, bear, ts, False)
t4 = _tier4(ts)
row = np.zeros(TOTAL, dtype=np.float32)
row[OFF[0]:OFF[0]+T0] = t0
row[OFF[4]:OFF[4]+T4] = t4
yield row
except Exception:
continue
def _stream_ng4() -> Iterator[np.ndarray]:
if not NG4_DIR.exists():
return
log_re = re.compile(
r'(\d{4}-\d{2}-\d{2}T[\d:.]+Z).*REGIME STATUS: \w+ \| Bull: ([\d.]+)% Bear: ([\d.]+)%'
)
for f in sorted(NG4_DIR.glob('*.txt')):
try:
for line in f.read_text(encoding='utf-8', errors='replace').splitlines():
m = log_re.search(line)
if not m:
continue
ts = _parse_ts(m.group(1).replace('T', ' ').rstrip('Z'))
if ts is None:
continue
bull, bear = float(m.group(2)), float(m.group(3))
t0 = _tier0(bull, bear, ts, False)
t4 = _tier4(ts)
row = np.zeros(TOTAL, dtype=np.float32)
row[OFF[0]:OFF[0]+T0] = t0
row[OFF[4]:OFF[4]+T4] = t4
yield row
except Exception:
continue
def _stream_ng5_local() -> Iterator[np.ndarray]:
import os
if not NG5_DIR.exists():
return
for entry in os.scandir(str(NG5_DIR)):
f = Path(entry.path)
if not (entry.name.startswith('regime_result_') and entry.name.endswith('.json')):
continue
try:
d = json.loads(f.read_text(encoding='utf-8', errors='replace'))
ts = _parse_ts(str(d.get('timestamp', '')))
if ts is None:
continue
bull = float(d.get('bull_pct', 50))
bear = float(d.get('bear_pct', 50))
mwr = d.get('multi_window_results') or {}
pricing = d.get('pricing_data') or {}
t1, has_eigen = _tier1(mwr)
t2, has_price = _tier2(pricing)
t0 = _tier0(bull, bear, ts, has_eigen)
t4 = _tier4(ts)
row = np.zeros(TOTAL, dtype=np.float32)
row[OFF[0]:OFF[0]+T0] = t0
row[OFF[1]:OFF[1]+T1] = t1
row[OFF[2]:OFF[2]+T2] = t2
row[OFF[4]:OFF[4]+T4] = t4
# No ExF for NG5 local (no companion NPZ per scan)
yield row
except Exception:
continue
def _stream_ng3_scans(exf_cache: ExFCache,
date_from: str = '2025-12-31',
max_per_day: Optional[int] = None) -> Iterator[np.ndarray]:
"""
Stream NG3/NG5 scan JSONs one at a time — never accumulates in memory.
ExF loaded once per date from companion NPZ.
max_per_day: limit scans per day (subsample for very long training days).
"""
if not NG3_EIGEN.exists():
return
date_dirs = sorted(
d for d in NG3_EIGEN.iterdir()
if d.is_dir() and not d.name.endswith('_SKIP') and d.name >= date_from
)
for date_dir in date_dirs:
exf = exf_cache.get(date_dir.name)
t3 = _tier3(exf)
day_count = 0
for f in sorted(date_dir.glob('scan_*.json')):
if '__Indicators' in f.name:
continue
if max_per_day and day_count >= max_per_day:
break
try:
# Read and immediately parse — don't accumulate
txt = f.read_text(encoding='utf-8', errors='replace')
d = json.loads(txt)
ts = _parse_ts(str(d.get('timestamp', '')))
if ts is None:
continue
windows = d.get('windows') or d.get('multi_window_results') or {}
pricing = d.get('pricing_data') or {}
pc = pricing.get('price_changes', {})
if pc:
vs = list(pc.values())
bull = 100.0 * sum(1 for v in vs if float(v) > 0) / max(len(vs), 1)
bear = 100.0 * sum(1 for v in vs if float(v) < 0) / max(len(vs), 1)
else:
bull, bear = 50.0, 50.0
t1, has_eigen = _tier1(windows)
t2, _ = _tier2(pricing)
t0 = _tier0(bull, bear, ts, has_eigen)
t4 = _tier4(ts)
row = np.zeros(TOTAL, dtype=np.float32)
row[OFF[0]:OFF[0]+T0] = t0
row[OFF[1]:OFF[1]+T1] = t1
row[OFF[2]:OFF[2]+T2] = t2
row[OFF[3]:OFF[3]+T3] = t3 # same ExF for all scans of this day
row[OFF[4]:OFF[4]+T4] = t4
yield row
day_count += 1
del d, txt # explicit release
except Exception:
continue
# ── Master corpus builder ──────────────────────────────────────────────────
class DolphinCorpus:
"""
Unified DOLPHIN corpus across all generations, 5 tiers, 111 dims.
Attributes:
X : (N, 111) float32 — the feature matrix
mask : (N, 5) bool — [t0, t1_eigen, t2_price, t3_exf, t4_esof]
sources : (N,) int8 — 0=NG1/2, 1=NG4, 2=NG5-local, 3=NG3-scan
"""
DIMS = DIMS
TOTAL = TOTAL
OFF = OFF
def __init__(self):
self.X = None
self.mask = None
self.sources = None
def build(self,
ng3_date_from: str = '2025-12-31',
max_scans_per_day: Optional[int] = None,
max_per_source: Optional[int] = None,
max_ng5: int = 3_000,
chunk_size: int = 50_000,
verbose: bool = True) -> 'DolphinCorpus':
"""
Memory-efficient build using streaming generators.
chunk_size: accumulate this many rows before extending array.
max_per_source: cap rows from NG1/NG2/NG4 (breadth-only sources).
max_ng5: separate cap for NG5-local (files are larger, reads ~26/s).
"""
print("Building DOLPHIN multi-generation corpus (streaming)...", flush=True)
exf_cache = ExFCache(NG3_EIGEN)
# Per-source caps: NG5-local is separately capped (slow reads)
_caps = {
0: max_per_source, # NG1/NG2
1: max_per_source, # NG4
2: max_ng5, # NG5-local — separate low cap
3: None, # NG3-scan — limited by max_scans_per_day
}
sources_list = [
(0, _stream_ng1_ng2(), "NG1/NG2"),
(1, _stream_ng4(), "NG4"),
(2, _stream_ng5_local(), "NG5-local"),
(3, _stream_ng3_scans(exf_cache, ng3_date_from, max_scans_per_day), "NG3-scan"),
]
all_chunks, all_src = [], []
buf_rows, buf_src = [], []
total = 0
for src_id, gen, name in sources_list:
src_count = 0
cap = _caps.get(src_id)
for row in gen:
buf_rows.append(row)
buf_src.append(src_id)
src_count += 1
total += 1
if len(buf_rows) >= chunk_size:
all_chunks.append(np.array(buf_rows, dtype=np.float32))
all_src.extend(buf_src)
buf_rows.clear(); buf_src.clear()
if verbose:
print(f" {name}: {src_count:,} (total so far: {total:,})", flush=True)
if cap and src_count >= cap:
break
if verbose:
print(f" {name}: {src_count:,} samples", flush=True)
# Flush remainder
if buf_rows:
all_chunks.append(np.array(buf_rows, dtype=np.float32))
all_src.extend(buf_src)
self.X = np.vstack(all_chunks) if all_chunks else np.empty((0, TOTAL), dtype=np.float32)
self.sources = np.array(all_src, dtype=np.int8)
np.nan_to_num(self.X, copy=False, nan=0.0, posinf=0.0, neginf=0.0)
# Build mask from has_eigen flag (bit7 of T0) and non-zero tiers
has_eigen = self.X[:, OFF[0] + 7] > 0.5 # T0[-1]
has_price = np.any(self.X[:, OFF[2]:OFF[2]+T2] != 0, axis=1)
has_exf = np.any(self.X[:, OFF[3]:OFF[3]+T3] != 0, axis=1)
self.mask = np.column_stack([
np.ones(len(self.X), dtype=bool), # T0 always
has_eigen,
has_price,
has_exf,
np.ones(len(self.X), dtype=bool), # T4 always (computed)
])
if verbose:
print(f"\nCorpus summary:")
print(f" Total : {len(self.X):,}")
print(f" Shape : {self.X.shape} ({self.X.nbytes/1e6:.0f} MB)")
print(f" T1 eigen : {self.mask[:,1].sum():,} ({100*self.mask[:,1].mean():.1f}%)")
print(f" T2 price : {self.mask[:,2].sum():,} ({100*self.mask[:,2].mean():.1f}%)")
print(f" T3 exf : {self.mask[:,3].sum():,} ({100*self.mask[:,3].mean():.1f}%)")
return self
def save(self, path: str):
p = path if path.endswith('.npz') else path + '.npz'
np.savez_compressed(p, X=self.X, mask=self.mask, sources=self.sources)
print(f"Corpus saved: {p} ({self.X.nbytes/1e6:.0f} MB uncompressed, compressed ~10x)")
@classmethod
def load(cls, path: str) -> 'DolphinCorpus':
c = cls()
p = path if path.endswith('.npz') else path + '.npz'
d = np.load(p)
c.X, c.mask, c.sources = d['X'], d['mask'], d['sources']
print(f"Corpus loaded: {len(c.X):,} samples, {c.X.shape[1]} dims")
return c
# ── Tier slices ─────────────────────────────────────────────────────
def t0(self): return self.X[:, OFF[0]:OFF[0]+T0]
def t1(self): return self.X[:, OFF[1]:OFF[1]+T1]
def t2(self): return self.X[:, OFF[2]:OFF[2]+T2]
def t3(self): return self.X[:, OFF[3]:OFF[3]+T3]
def t4(self): return self.X[:, OFF[4]:OFF[4]+T4]
def tier_names(self):
return ['breadth+time', 'eigenvalues', 'per-asset-vol', 'ExF-macro', 'EsoF']
def describe(self):
print(f"Corpus: N={len(self.X):,} dims={TOTAL} ({self.X.nbytes/1e6:.0f}MB)")
print(f"Tiers: {list(zip(self.tier_names(), DIMS))}")
print(f"Masks: {[(t, self.mask[:,i].sum()) for i, t in enumerate(self.tier_names())]}")
src_names = {0: 'NG1/2', 1: 'NG4', 2: 'NG5-local', 3: 'NG3-scan'}
for sid, name in src_names.items():
n = (self.sources == sid).sum()
if n > 0:
print(f" {name:12s}: {n:,}")
if __name__ == '__main__':
import sys
max_per_day = int(sys.argv[1]) if len(sys.argv) > 1 else None
corpus = DolphinCorpus().build(verbose=True, max_scans_per_day=max_per_day)
corpus.save(str(HERE / 'corpus_cache'))
corpus.describe()

Binary file not shown.

View File

@@ -0,0 +1,88 @@
import json
import re
import os
from pathlib import Path
from datetime import datetime
BASE = Path(r"C:\Users\Lenovo\Documents")
DIRS = {
"NG1": BASE / "- Dolphin NG",
"NG2": BASE / "- Dolphin NG2",
"NG4": BASE / "- DOLPHIN NG4" / "- Results",
"NG5": BASE / "- Dolphin NG5",
"NG3": BASE / "- Dolphin NG HD (NG3)" / "correlation_arb512" / "eigenvalues"
}
def parse_ts(s):
for fmt in ("%Y-%m-%dT%H:%M:%S.%f", "%Y-%m-%dT%H:%M:%S",
"%Y-%m-%d %H:%M:%S.%f", "%Y-%m-%d %H:%M:%S",
"%Y-%m-%dT%H:%M:%SZ"):
try:
return datetime.strptime(str(s)[:26].replace('Z', ''), fmt)
except ValueError:
continue
return None
def scan_dir_json(d, pattern):
ts_list = []
if not d.exists(): return ts_list
for f in d.glob(pattern):
try:
with open(f, 'r', encoding='utf-8', errors='replace') as fb:
data = json.load(fb)
ts_str = data.get('timestamp')
if ts_str:
ts = parse_ts(ts_str)
if ts: ts_list.append(ts)
except: continue
return ts_list
def scan_ng4(d):
ts_list = []
if not d.exists(): return ts_list
log_re = re.compile(r'(\d{4}-\d{2}-\d{2}T[\d:.]+Z)')
for f in d.glob('*.txt'):
try:
with open(f, 'r', encoding='utf-8', errors='replace') as fb:
for line in fb:
m = log_re.search(line)
if m:
ts = parse_ts(m.group(1))
if ts: ts_list.append(ts)
except: continue
return ts_list
def scan_ng3(d):
ts_list = []
if not d.exists(): return ts_list
# Just check the first and last date directories to save time
subdirs = sorted([s for s in d.iterdir() if s.is_dir() and not s.name.endswith('_SKIP')])
if not subdirs: return ts_list
for subdir in [subdirs[0], subdirs[-1]]:
for f in subdir.glob('scan_*.json'):
if '__Indicators' in f.name: continue
try:
with open(f, 'r', encoding='utf-8', errors='replace') as fb:
data = json.load(fb)
ts_str = data.get('timestamp')
if ts_str:
ts = parse_ts(ts_str)
if ts: ts_list.append(ts)
except: continue
return ts_list
print("--- Data Archaeology Result ---")
for name, d in DIRS.items():
print(f"Checking {name} in {d}...")
if name in ["NG1", "NG2", "NG5"]:
times = scan_dir_json(d, 'regime_result_*.json')
elif name == "NG4":
times = scan_ng4(d)
elif name == "NG3":
times = scan_ng3(d)
if times:
print(f" {name}: {min(times)} to {max(times)} ({len(times)} samples found in scan)")
else:
print(f" {name}: No data found.")

View File

@@ -0,0 +1,153 @@
"""
What did the encoder actually learn?
Correlate z0/z1 latent dims with raw input features.
"""
import sys
sys.stdout.reconfigure(encoding='utf-8', errors='replace')
import numpy as np
from pathlib import Path
HERE = Path(__file__).parent
sys.path.insert(0, str(HERE))
from corpus_builder import DolphinCorpus, WINDOWS
from hierarchical_dvae import HierarchicalDVAE, T_OFF, TIER0_DIM, TIER1_DIM, TIER3_DIM
# ── Feature names ─────────────────────────────────────────────────────────
T0_NAMES = ['bull_pct', 'bear_pct', 'side_pct', 'sin_hour', 'cos_hour', 'sin_day', 'cos_day', 'has_eigen']
T1_NAMES = []
for w in WINDOWS:
for feat in ['log_lmax', 'vel_norm', 'gap_ratio', 'instability', 'rtp']:
T1_NAMES.append(f"w{w}_{feat}")
EXF_FIELDS = [
'dvol_btc', 'dvol_eth', 'fng', 'fng_prev', 'btc_dom', 'eth_dom',
'chg24_btc', 'chg24_eth', 'dispersion', 'correlation', 'imbal_btc', 'imbal_eth',
'funding_btc', 'funding_eth', 'mvrv', 'tvl', 'pcr_vol', 'pcr_oi',
'basis', 'liq_proxy', 'spread', 'vol24', 'hashrate', 'btc_price', 'fng_vol',
]
# ── Load ───────────────────────────────────────────────────────────────────
print("Loading corpus...")
corpus = DolphinCorpus.load(str(HERE / 'corpus_cache.npz'))
idx = corpus.mask[:, 1] # 16K eigen samples
X_e = corpus.X[idx]
mask_e = corpus.mask[idx]
print(f"Eigen subset: {len(X_e):,} samples")
print("Loading model...")
model = HierarchicalDVAE(hidden=128, beta=0.5, gamma=1.0, lam=1.0, seed=42)
model.fit_normaliser(corpus.X, corpus.mask)
# Load weights
d = np.load(str(HERE / 'hdvae_checkpoint.npz'), allow_pickle=True)
def load_enc(enc, name):
for i, layer in enumerate(enc.mlp.layers):
layer.W = d[f'{name}_mlp{i}_W']; layer.b = d[f'{name}_mlp{i}_b']
enc.mu_head.W = d[f'{name}_mu_W']; enc.mu_head.b = d[f'{name}_mu_b']
enc.lv_head.W = d[f'{name}_lv_W']; enc.lv_head.b = d[f'{name}_lv_b']
def load_dec(dec, name):
for i, layer in enumerate(dec.mlp.layers):
layer.W = d[f'{name}_mlp{i}_W']; layer.b = d[f'{name}_mlp{i}_b']
for n, e in [('enc0',model.enc0),('enc1',model.enc1),('enc2',model.enc2)]:
load_enc(e, n)
for n, dc in [('dec0',model.dec0),('dec1',model.dec1),('dec2',model.dec2)]:
load_dec(dc, n)
# ── Encode all 16K samples ─────────────────────────────────────────────────
print("Encoding...")
rng = np.random.RandomState(0)
BATCH = 512
mu0_all, mu1_all = [], []
for start in range(0, len(X_e), BATCH):
Xb = X_e[start:start+BATCH]
mb = mask_e[start:start+BATCH]
enc = model.encode(Xb, mb, rng)
mu0_all.append(enc['mu0'])
mu1_all.append(enc['mu1'])
mu0 = np.concatenate(mu0_all) # (N, 4)
mu1 = np.concatenate(mu1_all) # (N, 8)
print(f"\nmu0 stats: mean={mu0.mean(0).round(4)} std={mu0.std(0).round(4)}")
print(f"mu1 stats: mean={mu1.mean(0).round(4)} std={mu1.std(0).round(4)}")
# ── Raw features ──────────────────────────────────────────────────────────
t0_raw = X_e[:, T_OFF[0]:T_OFF[0]+TIER0_DIM]
t1_raw = X_e[:, T_OFF[1]:T_OFF[1]+TIER1_DIM]
t3_raw = X_e[:, T_OFF[3]:T_OFF[3]+TIER3_DIM]
# ── Correlation: z1 dims vs T1 features ───────────────────────────────────
print("\n" + "="*70)
print("z1 DIMS vs T1 FEATURES (top correlations per z1 dim)")
print("="*70)
for zd in range(8):
corrs = [np.corrcoef(mu1[:,zd], t1_raw[:,fd])[0,1] for fd in range(TIER1_DIM)]
corrs = np.array(corrs)
top3 = np.argsort(np.abs(corrs))[-3:][::-1]
var = mu1[:,zd].var()
print(f"z1[{zd}] var={var:.4f}: " + " ".join(f"{T1_NAMES[i]}={corrs[i]:+.3f}" for i in top3))
# ── Correlation: z0 dims vs T0 features ───────────────────────────────────
print("\n" + "="*70)
print("z0 DIMS vs T0 FEATURES (top correlations per z0 dim)")
print("="*70)
for zd in range(4):
corrs = [np.corrcoef(mu0[:,zd], t0_raw[:,fd])[0,1] for fd in range(TIER0_DIM)]
corrs = np.array(corrs)
top3 = np.argsort(np.abs(corrs))[-3:][::-1]
var = mu0[:,zd].var()
print(f"z0[{zd}] var={var:.5f}: " + " ".join(f"{T0_NAMES[i]}={corrs[i]:+.3f}" for i in top3))
# ── What is z1 actually distinguishing? ───────────────────────────────────
print("\n" + "="*70)
print("z1[4] (highest var=0.015): value distribution vs T1 raw ranges")
print("="*70)
z1_4 = mu1[:, 4] # dim with highest var (index 4 = z1[4])
# Actually find which z1 dim has highest variance
best_z1 = np.argmax(mu1.var(0))
z1_best = mu1[:, best_z1]
print(f"Best z1 dim: {best_z1}, var={mu1[:,best_z1].var():.4f}")
# Split into top/bottom 20% by z1 value
lo_mask = z1_best < np.percentile(z1_best, 20)
hi_mask = z1_best > np.percentile(z1_best, 80)
print(f"\nT1 feature means: LOW z1[{best_z1}] (bot20%) vs HIGH z1[{best_z1}] (top20%)")
print(f"{'Feature':<20} {'LOW':>8} {'HIGH':>8} {'diff':>8}")
for fd, name in enumerate(T1_NAMES):
lo_val = t1_raw[lo_mask, fd].mean()
hi_val = t1_raw[hi_mask, fd].mean()
diff = hi_val - lo_val
if abs(diff) > 0.02:
print(f" {name:<18} {lo_val:8.4f} {hi_val:8.4f} {diff:+8.4f}")
# ── ExF correlation check ─────────────────────────────────────────────────
print("\n" + "="*70)
print(f"z1[{best_z1}] vs ExF (T3) features")
print("="*70)
exf_corrs = [(np.corrcoef(z1_best, t3_raw[:,i])[0,1], EXF_FIELDS[i]) for i in range(min(25, t3_raw.shape[1]))]
exf_corrs.sort(key=lambda x: abs(x[0]), reverse=True)
for r, name in exf_corrs[:10]:
print(f" {name:<20} r={r:+.4f}")
# ── z0 clustering: what do the 7 clusters look like? ─────────────────────
print("\n" + "="*70)
print("z0 cluster analysis (k=7, what separates them?)")
print("="*70)
from scipy.cluster.vq import kmeans2
try:
centroids, labels = kmeans2(mu0, 7, seed=42, minit='points')
print(f"Cluster sizes: {np.bincount(labels)}")
print(f"\nCluster centroids (z0 space):")
for k in range(7):
c = centroids[k]
# What T0 features distinguish this cluster?
mask_k = labels == k
t0_k = t0_raw[mask_k].mean(0)
t1_k = t1_raw[mask_k].mean(0)
print(f"\n Cluster {k} (N={mask_k.sum():,}): z0={c.round(3)}")
print(f" T0: bull={t0_k[0]:.3f} bear={t0_k[1]:.3f} side={t0_k[2]:.3f}")
print(f" T1: log_lmax_w50={t1_k[0]:.3f} vel_norm_w50={t1_k[1]:+.3f} gap_ratio_w50={t1_k[2]:.3f} inst={t1_k[3]:.3f} rtp={t1_k[4]:.3f}")
except Exception as e:
print(f"Clustering failed: {e}")
print("\nDone.")

View File

@@ -0,0 +1,151 @@
"""
Task 3: E2E Precursor AUC Test.
Train FlintHDVAE (beta=0.1) on 80% of 16K T1 corpus.
Encode all samples → z (8-dim latent).
Build eigenspace stress labels at K=5 scans (25s): inst>p90 AND gap<p10.
Test logistic regression on z → stress labels (chronological OOS split).
Compare against proxy_B baseline (AUC=0.715 from flint_precursor_sweep.py).
Gate: AUC ≥ 0.65 → proceed to Task 4.
"""
import sys, os
sys.stdout.reconfigure(encoding='utf-8', errors='replace')
sys.path.insert(0, os.path.dirname(__file__))
sys.path.insert(0, r"C:\Users\Lenovo\Documents\- DOLPHIN NG HD HCM TSF Predict")
import numpy as np
from pathlib import Path
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import roc_auc_score, average_precision_score
HERE = Path(__file__).parent
# ── Load 16K eigen corpus ─────────────────────────────────────────
print("Loading 16K eigen corpus...")
from corpus_builder import DolphinCorpus, OFF, T1 as T1_DIM
corpus = DolphinCorpus.load(str(HERE / 'corpus_cache.npz'))
idx_mask = corpus.mask[:, 1]
X_e = corpus.X[idx_mask]
T1 = X_e[:, OFF[1]:OFF[1] + T1_DIM].copy() # (16607, 20)
N = len(T1)
print(f" N={N} T1 shape={T1.shape}")
# ── Feature shortcuts for proxy_B baseline ────────────────────────
inst_w50 = T1[:, 3]
vel_w750 = T1[:, 16]
gap_w50 = T1[:, 2]
proxy_B = inst_w50 - vel_w750
# ── Build stress labels: K=5 scans (25s), inst>p90, gap<p10 ──────
print("\nBuilding stress labels (K=5, inst>p90, gap<p10)...")
K = 5
inst_p90 = np.percentile(inst_w50, 90)
gap_p10 = np.percentile(gap_w50, 10)
print(f" inst_p90={inst_p90:.4f} gap_p10={gap_p10:.4f}")
labels = np.zeros(N, dtype=np.float32)
for i in range(N - K):
fi = inst_w50[i+1:i+1+K]
fg = gap_w50 [i+1:i+1+K]
if np.any(fi > inst_p90) and np.any(fg < gap_p10):
labels[i] = 1.0
pos_rate = labels.mean()
print(f" Positive rate: {pos_rate*100:.1f}% Positive count: {labels.sum():.0f}")
# ── Proxy B baseline (no model) ───────────────────────────────────
print("\n" + "="*55)
print("PROXY_B BASELINE (direct, no model)")
print("="*55)
pB_vals = proxy_B[:-K]
y_vals = labels[:-K]
valid = np.isfinite(pB_vals) & np.isfinite(y_vals)
# Chronological split (same as Task 3 below)
n_test = len(pB_vals) // 4
pB_test = pB_vals[-n_test:]
y_test = y_vals[-n_test:]
auc_pB = roc_auc_score(y_test, pB_test)
auc_pB = max(auc_pB, 1 - auc_pB)
print(f" proxy_B OOS AUC = {auc_pB:.4f}")
# ── Train FlintHDVAE ──────────────────────────────────────────────
print("\n" + "="*55)
print("TRAINING FlintHDVAE (beta=0.1, 40 epochs)")
print("="*55)
from flint_hd_vae import FlintHDVAE
# Chronological 80/20 train split for the VAE itself
n_vae_train = int(N * 0.8)
T1_vae_train = T1[:n_vae_train]
model = FlintHDVAE(input_dim=20, hd_dim=512, latent_dim=8,
beta=0.1, seed=42, use_flint_norm=False)
model.fit(T1_vae_train, epochs=40, lr=1e-3, batch_size=256,
verbose=True, warmup_frac=0.3)
# ── Encode full corpus → z ────────────────────────────────────────
print("\nEncoding full 16K corpus → z (8-dim)...")
z_all = model.encode(T1) # (16607, 8)
print(f" z shape: {z_all.shape}")
print(f" z range: [{z_all.min():.3f}, {z_all.max():.3f}]")
print(f" z var per dim: {z_all.var(0).round(3)}")
# ── Logistic regression: z → stress labels ───────────────────────
print("\n" + "="*55)
print("LOGISTIC REGRESSION: z_regime → stress labels (K=5)")
print("="*55)
X_lr = z_all[:-K]
y_lr = labels[:-K]
valid_lr = np.isfinite(X_lr).all(1) & np.isfinite(y_lr)
X_lr, y_lr = X_lr[valid_lr], y_lr[valid_lr]
n_test_lr = len(X_lr) // 4
X_train_lr = X_lr[:-n_test_lr]
X_test_lr = X_lr[-n_test_lr:]
y_train_lr = y_lr[:-n_test_lr]
y_test_lr = y_lr[-n_test_lr:]
print(f" Train: {len(X_train_lr)} Test: {len(X_test_lr)}")
print(f" Test pos rate: {y_test_lr.mean()*100:.1f}%")
lr_clf = LogisticRegression(class_weight='balanced', max_iter=500, C=0.1)
lr_clf.fit(X_train_lr, y_train_lr)
preds = lr_clf.predict_proba(X_test_lr)[:, 1]
auc_z = roc_auc_score(y_test_lr, preds)
auc_z = max(auc_z, 1 - auc_z)
ap_z = average_precision_score(y_test_lr, preds)
print(f" z-regime LogReg OOS AUC={auc_z:.4f} AvgPrecision={ap_z:.4f}")
# ── Combined: z + proxy_B ─────────────────────────────────────────
print("\n" + "="*55)
print("COMBINED: z_regime + proxy_B")
print("="*55)
X_comb = np.column_stack([z_all[:-K], proxy_B[:-K].reshape(-1,1)])[valid_lr]
X_c_train = X_comb[:-n_test_lr]
X_c_test = X_comb[-n_test_lr:]
lr_comb = LogisticRegression(class_weight='balanced', max_iter=500, C=0.1)
lr_comb.fit(X_c_train, y_train_lr)
preds_c = lr_comb.predict_proba(X_c_test)[:, 1]
auc_c = roc_auc_score(y_test_lr, preds_c)
auc_c = max(auc_c, 1 - auc_c)
print(f" Combined OOS AUC={auc_c:.4f}")
# ── Summary and Gate ──────────────────────────────────────────────
print("\n" + "="*55)
print("SUMMARY")
print("="*55)
print(f" proxy_B direct: AUC = {auc_pB:.4f}")
print(f" z_regime (VAE): AUC = {auc_z:.4f}")
print(f" z + proxy_B: AUC = {auc_c:.4f}")
GATE_AUC = 0.65
best_auc = max(auc_pB, auc_z, auc_c)
print(f"\n Gate threshold: AUC ≥ {GATE_AUC}")
if best_auc >= GATE_AUC:
print(f" GATE PASS: best AUC={best_auc:.4f}{GATE_AUC}")
print(" → Proceed to Task 4: fork AlphaSignalGenerator with proxy_B gate")
else:
print(f" GATE FAIL: best AUC={best_auc:.4f} < {GATE_AUC}")
print(" → Do NOT proceed with gate integration")

View File

@@ -0,0 +1,403 @@
"""
exp10_1m_keyframe.py — 1m trajectory keyframe gate test
=========================================================
Tests whether instability_150 z-score at 1m timescale (and/or D-VAE recon_err
at 1m) provides actionable scale modulation ON TOP OF D_LIQ_GOLD (proxy_B boost
already baked in).
Signal forms tested (7 configs):
0. Baseline — D_LIQ_GOLD unmodified (control)
1. A_hard — hard threshold on rolling i150 z-score
2. B_analogue — continuous tanh on rolling i150 z-score
3. C_hard — hard threshold on VAE recon_err z-score at 1m
4. D_analogue — continuous tanh on VAE recon_err z-score at 1m
5. AC_hard — A × C (both hard, multiplicative)
6. BD_analogue — B × D (both analogue, multiplicative)
Analogue formula (split tanh, asymmetric):
z >= 0: scale = 1 + UP_STRENGTH * tanh(z / K) → [1.0, ~1.15)
z < 0: scale = 1 + DOWN_STRENGTH * tanh(z / K) → (~0.50, 1.0)
At z=-1.22: ~0.64; z=-3: ~0.50 floor; z=+1.11: ~1.12; z=+3: ~1.15 ceiling.
Zero changes to production code. D_LIQ_GOLD engine forked via subclass.
"""
import sys, time, json, warnings
sys.stdout.reconfigure(encoding='utf-8', errors='replace')
warnings.filterwarnings('ignore')
from pathlib import Path
import numpy as np
import pandas as pd
ROOT = Path(__file__).parent.parent
sys.path.insert(0, str(ROOT))
from nautilus_dolphin.nautilus.alpha_asset_selector import compute_irp_nb, compute_ars_nb, rank_assets_irp_nb
from nautilus_dolphin.nautilus.alpha_bet_sizer import compute_sizing_nb
from nautilus_dolphin.nautilus.alpha_signal_generator import check_dc_nb
from nautilus_dolphin.nautilus.ob_features import (
OBFeatureEngine, compute_imbalance_nb, compute_depth_1pct_nb,
compute_depth_quality_nb, compute_fill_probability_nb, compute_spread_proxy_nb,
compute_depth_asymmetry_nb, compute_imbalance_persistence_nb,
compute_withdrawal_velocity_nb, compute_market_agreement_nb, compute_cascade_signal_nb,
)
from nautilus_dolphin.nautilus.ob_provider import MockOBProvider
from nautilus_dolphin.nautilus.adaptive_circuit_breaker import AdaptiveCircuitBreaker
from nautilus_dolphin.nautilus.proxy_boost_engine import LiquidationGuardEngine, create_d_liq_engine
from mc.mc_ml import DolphinForewarner
from dvae.titan_sensor import TitanSensor, build_feature_vector
# ── JIT warmup ────────────────────────────────────────────────────────────────
print("Warming up JIT...")
_p = np.array([1.,2.,3.], dtype=np.float64)
compute_irp_nb(_p,-1); compute_ars_nb(1.,.5,.01)
rank_assets_irp_nb(np.ones((10,2),dtype=np.float64),8,-1,5,500.,20,0.20)
compute_sizing_nb(-.03,-.02,-.05,3.,.5,5.,.20,True,True,0.,
np.zeros(4,dtype=np.int64),np.zeros(4,dtype=np.int64),
np.zeros(5,dtype=np.float64),0,-1,.01,.04)
check_dc_nb(_p,3,1,.75)
_b=np.array([100.,200.,300.,400.,500.],dtype=np.float64)
_a=np.array([110.,190.,310.,390.,510.],dtype=np.float64)
compute_imbalance_nb(_b,_a); compute_depth_1pct_nb(_b,_a)
compute_depth_quality_nb(210.,200.); compute_fill_probability_nb(1.)
compute_spread_proxy_nb(_b,_a); compute_depth_asymmetry_nb(_b,_a)
compute_imbalance_persistence_nb(np.array([.1,-.1],dtype=np.float64),2)
compute_withdrawal_velocity_nb(np.array([100.,110.],dtype=np.float64),1)
compute_market_agreement_nb(np.array([.1,-.05],dtype=np.float64),2)
compute_cascade_signal_nb(np.array([-.05,-.15],dtype=np.float64),2,-.10)
print(" JIT ready.")
# ── Paths ─────────────────────────────────────────────────────────────────────
VBT5s = Path(r"C:\Users\Lenovo\Documents\- DOLPHIN NG HD HCM TSF Predict\vbt_cache")
VBT1m = Path(r"C:\Users\Lenovo\Documents\- DOLPHIN NG HD HCM TSF Predict\vbt_cache_klines")
MODEL_PATH = Path(r"C:\Users\Lenovo\Documents\- DOLPHIN NG HD HCM TSF Predict\dvae_regime_model_TITAN_ULTRA_GD.json")
MC_MODELS = str(ROOT / "mc_results" / "models")
OUT_FILE = Path(__file__).parent / "exp10_1m_keyframe_results.json"
META_COLS = {'timestamp','scan_number','v50_lambda_max_velocity','v150_lambda_max_velocity',
'v300_lambda_max_velocity','v750_lambda_max_velocity','vel_div',
'instability_50','instability_150'}
# Base kwargs accepted by NDAlphaEngine (no boost/leverage-guard params)
BASE_ENGINE_KWARGS = dict(
initial_capital=25000., vel_div_threshold=-.02, vel_div_extreme=-.05,
min_leverage=.5, max_leverage=5., leverage_convexity=3.,
fraction=.20, fixed_tp_pct=.0095, stop_pct=1., max_hold_bars=120,
use_direction_confirm=True, dc_lookback_bars=7, dc_min_magnitude_bps=.75,
dc_skip_contradicts=True, dc_leverage_boost=1., dc_leverage_reduce=.5,
use_asset_selection=True, min_irp_alignment=.45,
use_sp_fees=True, use_sp_slippage=True,
sp_maker_entry_rate=.62, sp_maker_exit_rate=.50,
use_ob_edge=True, ob_edge_bps=5., ob_confirm_rate=.40,
lookback=100, use_alpha_layers=True, use_dynamic_leverage=True, seed=42,
)
# D_LIQ_GOLD-specific params passed explicitly to KeyframeGateEngine / create_d_liq_engine
D_LIQ_KWARGS = dict(
extended_soft_cap=8., extended_abs_cap=9., mc_leverage_ref=5.,
margin_buffer=.95, threshold=.35, alpha=1., adaptive_beta=True,
)
MC_BASE_CFG = {
'trial_id':0, 'vel_div_threshold':-.020, 'vel_div_extreme':-.050,
'use_direction_confirm':True, 'dc_lookback_bars':7, 'dc_min_magnitude_bps':.75,
'dc_skip_contradicts':True, 'dc_leverage_boost':1.00, 'dc_leverage_reduce':.50,
'vd_trend_lookback':10, 'min_leverage':.50, 'max_leverage':5.00,
'leverage_convexity':3.00, 'fraction':.20, 'use_alpha_layers':True,
'use_dynamic_leverage':True, 'fixed_tp_pct':.0095, 'stop_pct':1.00,
'max_hold_bars':120, 'use_sp_fees':True, 'use_sp_slippage':True,
'sp_maker_entry_rate':.62, 'sp_maker_exit_rate':.50, 'use_ob_edge':True,
'ob_edge_bps':5.00, 'ob_confirm_rate':.40, 'ob_imbalance_bias':-.09,
'ob_depth_scale':1.00, 'use_asset_selection':True, 'min_irp_alignment':.45,
'lookback':100, 'acb_beta_high':.80, 'acb_beta_low':.20, 'acb_w750_threshold_pct':60,
}
WINDOW_Z = 30 # rolling bars for z-score (30-min at 1m)
K_TANH = 1.5 # tanh curvature
UP_STR = 0.15 # max upside boost
DOWN_STR = 0.50 # max downside cut
# ── Scale functions ───────────────────────────────────────────────────────────
def hard_scale(z: float) -> float:
if z < 0.: return 0.5
if z > 1.11: return 1.15
return 1.0
def analogue_scale(z: float) -> float:
t = np.tanh(float(z) / K_TANH)
if z >= 0.:
return 1.0 + UP_STR * t
else:
return 1.0 + DOWN_STR * t
# ── Pre-compute 1m signals ────────────────────────────────────────────────────
def precompute_1m_signals(parquet_files_5s, sensor):
"""
For each day, build per-5s-bar arrays of:
z_roll[bar] : rolling z-score of instability_150 at 1m
z_recon[bar] : rolling z-score of VAE recon_err at 1m
Returns dict[date_str] -> {'z_roll': np.ndarray, 'z_recon': np.ndarray}
"""
print("Pre-computing 1m signals...")
signals = {}
for pf5 in parquet_files_5s:
ds = pf5.stem
pf1 = VBT1m / f"{ds}.parquet"
if not pf1.exists():
signals[ds] = None
continue
df5 = pd.read_parquet(pf5)
df1 = pd.read_parquet(pf1).replace([np.inf,-np.inf], np.nan).fillna(0.)
n5, n1 = len(df5), len(df1)
assets = [c for c in df1.columns if c not in META_COLS]
# 1m instability_150 rolling z-score
i150 = df1['instability_150'].values.copy() if 'instability_150' in df1.columns else np.zeros(n1)
z_roll_1m = np.zeros(n1)
for j in range(WINDOW_Z, n1):
seg = i150[max(0,j-WINDOW_Z):j]
mu, sigma = np.mean(seg), np.std(seg)
z_roll_1m[j] = (i150[j] - mu) / max(sigma, 1e-10)
# 1m VAE recon_err rolling z-score
recon_1m = np.zeros(n1)
for j in range(n1):
feat = build_feature_vector(df1, j, assets)
_, recon_err, _ = sensor.encode(feat)
recon_1m[j] = recon_err
# z-score recon_err
z_recon_1m = np.zeros(n1)
for j in range(WINDOW_Z, n1):
seg = recon_1m[max(0,j-WINDOW_Z):j]
mu, sigma = np.mean(seg), np.std(seg)
z_recon_1m[j] = (recon_1m[j] - mu) / max(sigma, 1e-10)
# Map 1m arrays to 5s bar indices (proportional)
z_roll_5s = np.zeros(n5)
z_recon_5s = np.zeros(n5)
for i in range(n5):
j = int(i * n1 / n5)
j = min(j, n1-1)
z_roll_5s[i] = z_roll_1m[j]
z_recon_5s[i] = z_recon_1m[j]
signals[ds] = {'z_roll': z_roll_5s, 'z_recon': z_recon_5s}
print(f" {ds}: {n1} 1m bars -> {n5} 5s bars "
f"z_roll=[{z_roll_5s.min():.2f},{z_roll_5s.max():.2f}] "
f"z_recon=[{z_recon_5s.min():.2f},{z_recon_5s.max():.2f}]")
return signals
# ── Keyframe engine subclass ──────────────────────────────────────────────────
class KeyframeGateEngine(LiquidationGuardEngine):
"""
Adds a 1m keyframe scale multiplier on top of D_LIQ_GOLD.
_pending_1m_scale is set per bar by the backtest loop before _try_entry fires.
"""
def __init__(self, scale_fn, **kwargs):
super().__init__(**kwargs)
self._scale_fn = scale_fn # callable(z_roll, z_recon) -> float
self._bar_z_roll : np.ndarray | None = None
self._bar_z_recon : np.ndarray | None = None
self._1m_scale_history: list = []
def set_1m_signals(self, z_roll: np.ndarray, z_recon: np.ndarray):
self._bar_z_roll = z_roll
self._bar_z_recon = z_recon
def _try_entry(self, bar_idx, vel_div, prices, price_histories,
v50_vel=0., v750_vel=0.):
result = super()._try_entry(bar_idx, vel_div, prices, price_histories,
v50_vel, v750_vel)
if result and self.position is not None:
zr = float(self._bar_z_roll[bar_idx]) if (self._bar_z_roll is not None and bar_idx < len(self._bar_z_roll)) else 0.
ze = float(self._bar_z_recon[bar_idx]) if (self._bar_z_recon is not None and bar_idx < len(self._bar_z_recon)) else 0.
s = float(self._scale_fn(zr, ze))
s = max(0.2, min(2.0, s)) # hard clip, safety
self.position.notional *= s
self._1m_scale_history.append(s)
return result
def reset(self):
super().reset()
self._1m_scale_history = []
# ── Scale function definitions ─────────────────────────────────────────────────
CONFIGS = {
"0_baseline": None, # no gate
"1_A_hard": lambda zr, ze: hard_scale(zr),
"2_B_analogue": lambda zr, ze: analogue_scale(zr),
"3_C_hard": lambda zr, ze: hard_scale(ze),
"4_D_analogue": lambda zr, ze: analogue_scale(ze),
"5_AC_hard": lambda zr, ze: hard_scale(zr) * hard_scale(ze),
"6_BD_analogue":lambda zr, ze: analogue_scale(zr) * analogue_scale(ze),
}
# ── Backtest runner ───────────────────────────────────────────────────────────
def run_one(config_name, scale_fn, parquet_files, pq_data, signals, vol_p60):
"""Run one backtest config. Returns result dict."""
OB_ASSETS = sorted({a for ds,(df,ac,_) in pq_data.items() for a in ac})
_mock_ob = MockOBProvider(
imbalance_bias=-.09, depth_scale=1., assets=OB_ASSETS,
imbalance_biases={"BTCUSDT":-.086,"ETHUSDT":-.092,"BNBUSDT":+.05,"SOLUSDT":+.05},
)
ob_eng = OBFeatureEngine(_mock_ob)
ob_eng.preload_date("mock", OB_ASSETS)
forewarner = DolphinForewarner(models_dir=MC_MODELS)
acb = AdaptiveCircuitBreaker()
acb.preload_w750([pf.stem for pf in parquet_files])
if scale_fn is None:
engine = create_d_liq_engine(**BASE_ENGINE_KWARGS)
else:
engine = KeyframeGateEngine(scale_fn=scale_fn, **BASE_ENGINE_KWARGS, **D_LIQ_KWARGS)
engine.set_ob_engine(ob_eng)
engine.set_acb(acb)
engine.set_mc_forewarner(forewarner, MC_BASE_CFG)
engine.set_esoteric_hazard_multiplier(0.)
t0 = time.time()
bar_global = 0
for pf in parquet_files:
ds = pf.stem
df, acols, dvol = pq_data[ds]
vol_ok = np.where(np.isfinite(dvol), dvol > vol_p60, False)
if scale_fn is not None and isinstance(engine, KeyframeGateEngine):
sig = signals.get(ds)
if sig:
engine.set_1m_signals(sig['z_roll'], sig['z_recon'])
else:
engine.set_1m_signals(np.zeros(len(df)), np.zeros(len(df)))
engine.process_day(ds, df, acols, vol_regime_ok=vol_ok)
bar_global += len(df)
elapsed = time.time() - t0
trades = engine.trade_history
roi = (engine.capital - 25000.) / 25000. * 100.
# drawdown
cap_curve = [25000.]
for t_ in sorted(trades, key=lambda x: getattr(x,'exit_bar',0)):
cap_curve.append(cap_curve[-1] + getattr(t_,'pnl_absolute',0.))
cap_arr = np.array(cap_curve)
peak = np.maximum.accumulate(cap_arr)
dd = float(np.max((peak - cap_arr) / (peak + 1e-10)) * 100.)
calmar = roi / max(dd, 1e-4)
scale_hist = getattr(engine, '_1m_scale_history', [])
res = {
'config': config_name,
'T': len(trades),
'ROI': round(roi, 4),
'DD': round(dd, 4),
'Calmar': round(calmar, 4),
'elapsed_s': round(elapsed, 1),
'scale_mean': round(np.mean(scale_hist), 4) if scale_hist else 1.0,
'scale_min': round(np.min(scale_hist), 4) if scale_hist else 1.0,
'scale_max': round(np.max(scale_hist), 4) if scale_hist else 1.0,
'n_scale_applied': len(scale_hist),
}
return res
# ── Main ──────────────────────────────────────────────────────────────────────
def main():
parquet_files = sorted(VBT5s.glob("*.parquet"))
parquet_files = [p for p in parquet_files if 'catalog' not in str(p)]
print(f"Dataset: {len(parquet_files)} days ({parquet_files[0].stem} to {parquet_files[-1].stem})")
print("Loading parquet data...")
pq_data = {}
all_assets = set()
for pf in parquet_files:
df = pd.read_parquet(pf)
ac = [c for c in df.columns if c not in META_COLS]
all_assets.update(ac)
bp = df['BTCUSDT'].values if 'BTCUSDT' in df.columns else None
dv = np.full(len(df), np.nan)
if bp is not None:
for i in range(50, len(bp)):
seg = bp[max(0,i-50):i]
if len(seg)>=10:
dv[i] = float(np.std(np.diff(seg)/seg[:-1]))
pq_data[pf.stem] = (df, ac, dv)
# vol_p60
all_vols=[]
for pf in parquet_files[:2]:
df=pd.read_parquet(pf)
if 'BTCUSDT' not in df.columns: continue
pr=df['BTCUSDT'].values
for i in range(60,len(pr)):
seg=pr[max(0,i-50):i]
if len(seg)>=10:
v=float(np.std(np.diff(seg)/seg[:-1]))
if v>0: all_vols.append(v)
vol_p60 = float(np.percentile(all_vols,60)) if all_vols else 0.
# Load sensor
print(f"\nLoading TitanSensor...")
sensor = TitanSensor(str(MODEL_PATH))
# Pre-compute 1m signals
signals = precompute_1m_signals(parquet_files, sensor)
n_missing = sum(1 for v in signals.values() if v is None)
print(f" 1m signals ready: {len(signals)-n_missing}/{len(signals)} days")
# Run all configs
print()
results = []
for name, fn in CONFIGS.items():
print(f"Running [{name}]...", flush=True)
r = run_one(name, fn, parquet_files, pq_data, signals, vol_p60)
results.append(r)
baseline_roi = results[0]['ROI'] if results else 0.
delta_roi = r['ROI'] - baseline_roi
delta_dd = r['DD'] - results[0]['DD'] if len(results)>1 else 0.
print(f" T={r['T']} ROI={r['ROI']:+.2f}% DD={r['DD']:.2f}% "
f"Calmar={r['Calmar']:.2f} "
f"(dROI={delta_roi:+.2f}pp dDD={delta_dd:+.2f}pp) "
f"scale_mean={r['scale_mean']:.3f} {r['elapsed_s']:.0f}s")
# Summary table
print()
print("="*90)
print(f"{'Config':<20} {'T':>5} {'ROI%':>8} {'DD%':>7} {'Calmar':>7} "
f"{'dROI':>7} {'dDD':>6} {'s_mean':>7}")
print("-"*90)
base = results[0]
for r in results:
dr = r['ROI'] - base['ROI']
dd = r['DD'] - base['DD']
print(f"{r['config']:<20} {r['T']:>5} {r['ROI']:>8.2f} {r['DD']:>7.2f} "
f"{r['Calmar']:>7.2f} {dr:>+7.2f} {dd:>+6.2f} {r['scale_mean']:>7.3f}")
# Save
with open(OUT_FILE,'w') as f:
json.dump({'baseline': base, 'results': results}, f, indent=2)
print(f"\nResults: {OUT_FILE}")
# Verdict
print("\n=== VERDICT ===")
best = max(results[1:], key=lambda x: x['Calmar'])
print(f"Best config: [{best['config']}] Calmar={best['Calmar']:.2f} "
f"ROI={best['ROI']:+.2f}% DD={best['DD']:.2f}%")
if best['Calmar'] > base['Calmar'] * 1.02:
print(" PROCEED: meaningful Calmar improvement vs baseline")
else:
print(" MARGINAL or NO improvement over D_LIQ_GOLD")
if __name__ == '__main__':
main()

View File

@@ -0,0 +1,100 @@
{
"baseline": {
"config": "0_baseline",
"T": 2155,
"ROI": 181.8069,
"DD": 23.6916,
"Calmar": 7.6739,
"elapsed_s": 339.8,
"scale_mean": 1.0,
"scale_min": 1.0,
"scale_max": 1.0,
"n_scale_applied": 0
},
"results": [
{
"config": "0_baseline",
"T": 2155,
"ROI": 181.8069,
"DD": 23.6916,
"Calmar": 7.6739,
"elapsed_s": 339.8,
"scale_mean": 1.0,
"scale_min": 1.0,
"scale_max": 1.0,
"n_scale_applied": 0
},
{
"config": "1_A_hard",
"T": 2155,
"ROI": 179.3283,
"DD": 23.6916,
"Calmar": 7.5693,
"elapsed_s": 312.4,
"scale_mean": 0.997,
"scale_min": 0.5,
"scale_max": 1.15,
"n_scale_applied": 2156
},
{
"config": "2_B_analogue",
"T": 2155,
"ROI": 182.3446,
"DD": 23.6916,
"Calmar": 7.6966,
"elapsed_s": 308.1,
"scale_mean": 0.9991,
"scale_min": 0.5181,
"scale_max": 1.1437,
"n_scale_applied": 2156
},
{
"config": "3_C_hard",
"T": 2155,
"ROI": 181.0274,
"DD": 23.6916,
"Calmar": 7.641,
"elapsed_s": 304.1,
"scale_mean": 0.9954,
"scale_min": 0.5,
"scale_max": 1.15,
"n_scale_applied": 2156
},
{
"config": "4_D_analogue",
"T": 2155,
"ROI": 181.2387,
"DD": 23.6916,
"Calmar": 7.6499,
"elapsed_s": 307.7,
"scale_mean": 0.9989,
"scale_min": 0.5532,
"scale_max": 1.1458,
"n_scale_applied": 2156
},
{
"config": "5_AC_hard",
"T": 2155,
"ROI": 178.0085,
"DD": 23.6916,
"Calmar": 7.5136,
"elapsed_s": 308.0,
"scale_mean": 0.9931,
"scale_min": 0.25,
"scale_max": 1.3225,
"n_scale_applied": 2156
},
{
"config": "6_BD_analogue",
"T": 2155,
"ROI": 181.7092,
"DD": 23.6916,
"Calmar": 7.6698,
"elapsed_s": 309.2,
"scale_mean": 0.9981,
"scale_min": 0.3919,
"scale_max": 1.2704,
"n_scale_applied": 2156
}
]
}

View File

@@ -0,0 +1,371 @@
"""
exp11_zrecon_inv.py — z_recon direction inversion test
=======================================================
Exp10 showed z_recon now works (C: 0.78pp vs 8.65pp broken, D: 0.57pp vs 1.32pp)
but the direction is WRONG: the current setup boosts on HIGH z_recon (OOD bars) and
cuts on LOW z_recon (normal bars), but OOD bars at entry should be cut (not boosted).
This experiment tests the INVERTED direction: use ze instead of ze for the recon signal.
D_inv: analogue_scale(ze) → CUT on OOD (high z_recon), BOOST on quiet bars
BD_inv: B × D_inv → combine z_roll analogue with inverted z_recon
Configs (4 total):
0. Baseline — D_LIQ_GOLD unmodified (control)
1. B_analogue — z_roll analogue (positive control, same as exp10 config 2)
2. D_inv_analogue — z_recon analogue, INVERTED direction (ze)
3. BD_inv — B_analogue × D_inv_analogue
Signal from exp10 that was POSITIVE: B_analogue (z_roll i150, +0.54pp)
Signal from exp10 that was NEGATIVE: D_analogue (z_recon, 0.57pp)
Hypothesis: D_inv (ze) should be positive since OOD = bad entry condition.
No production code changes. Same model path, same data.
"""
import sys, time, json, warnings
sys.stdout.reconfigure(encoding='utf-8', errors='replace')
warnings.filterwarnings('ignore')
from pathlib import Path
import numpy as np
import pandas as pd
ROOT = Path(__file__).parent.parent
sys.path.insert(0, str(ROOT))
from nautilus_dolphin.nautilus.alpha_asset_selector import compute_irp_nb, compute_ars_nb, rank_assets_irp_nb
from nautilus_dolphin.nautilus.alpha_bet_sizer import compute_sizing_nb
from nautilus_dolphin.nautilus.alpha_signal_generator import check_dc_nb
from nautilus_dolphin.nautilus.ob_features import (
OBFeatureEngine, compute_imbalance_nb, compute_depth_1pct_nb,
compute_depth_quality_nb, compute_fill_probability_nb, compute_spread_proxy_nb,
compute_depth_asymmetry_nb, compute_imbalance_persistence_nb,
compute_withdrawal_velocity_nb, compute_market_agreement_nb, compute_cascade_signal_nb,
)
from nautilus_dolphin.nautilus.ob_provider import MockOBProvider
from nautilus_dolphin.nautilus.adaptive_circuit_breaker import AdaptiveCircuitBreaker
from nautilus_dolphin.nautilus.proxy_boost_engine import LiquidationGuardEngine, create_d_liq_engine
from mc.mc_ml import DolphinForewarner
from dvae.titan_sensor import TitanSensor, build_feature_vector
# ── JIT warmup ────────────────────────────────────────────────────────────────
print("Warming up JIT...")
_p = np.array([1.,2.,3.], dtype=np.float64)
compute_irp_nb(_p,-1); compute_ars_nb(1.,.5,.01)
rank_assets_irp_nb(np.ones((10,2),dtype=np.float64),8,-1,5,500.,20,0.20)
compute_sizing_nb(-.03,-.02,-.05,3.,.5,5.,.20,True,True,0.,
np.zeros(4,dtype=np.int64),np.zeros(4,dtype=np.int64),
np.zeros(5,dtype=np.float64),0,-1,.01,.04)
check_dc_nb(_p,3,1,.75)
_b=np.array([100.,200.,300.,400.,500.],dtype=np.float64)
_a=np.array([110.,190.,310.,390.,510.],dtype=np.float64)
compute_imbalance_nb(_b,_a); compute_depth_1pct_nb(_b,_a)
compute_depth_quality_nb(210.,200.); compute_fill_probability_nb(1.)
compute_spread_proxy_nb(_b,_a); compute_depth_asymmetry_nb(_b,_a)
compute_imbalance_persistence_nb(np.array([.1,-.1],dtype=np.float64),2)
compute_withdrawal_velocity_nb(np.array([100.,110.],dtype=np.float64),1)
compute_market_agreement_nb(np.array([.1,-.05],dtype=np.float64),2)
compute_cascade_signal_nb(np.array([-.05,-.15],dtype=np.float64),2,-.10)
print(" JIT ready.")
# ── Paths ─────────────────────────────────────────────────────────────────────
VBT5s = Path(r"C:\Users\Lenovo\Documents\- DOLPHIN NG HD HCM TSF Predict\vbt_cache")
VBT1m = Path(r"C:\Users\Lenovo\Documents\- DOLPHIN NG HD HCM TSF Predict\vbt_cache_klines")
MODEL_PATH = Path(r"C:\Users\Lenovo\Documents\- DOLPHIN NG HD HCM TSF Predict\dvae_regime_model_TITAN_ULTRA_GD.json")
MC_MODELS = str(ROOT / "mc_results" / "models")
OUT_FILE = Path(__file__).parent / "exp11_zrecon_inv_results.json"
META_COLS = {'timestamp','scan_number','v50_lambda_max_velocity','v150_lambda_max_velocity',
'v300_lambda_max_velocity','v750_lambda_max_velocity','vel_div',
'instability_50','instability_150'}
BASE_ENGINE_KWARGS = dict(
initial_capital=25000., vel_div_threshold=-.02, vel_div_extreme=-.05,
min_leverage=.5, max_leverage=5., leverage_convexity=3.,
fraction=.20, fixed_tp_pct=.0095, stop_pct=1., max_hold_bars=120,
use_direction_confirm=True, dc_lookback_bars=7, dc_min_magnitude_bps=.75,
dc_skip_contradicts=True, dc_leverage_boost=1., dc_leverage_reduce=.5,
use_asset_selection=True, min_irp_alignment=.45,
use_sp_fees=True, use_sp_slippage=True,
sp_maker_entry_rate=.62, sp_maker_exit_rate=.50,
use_ob_edge=True, ob_edge_bps=5., ob_confirm_rate=.40,
lookback=100, use_alpha_layers=True, use_dynamic_leverage=True, seed=42,
)
D_LIQ_KWARGS = dict(
extended_soft_cap=8., extended_abs_cap=9., mc_leverage_ref=5.,
margin_buffer=.95, threshold=.35, alpha=1., adaptive_beta=True,
)
MC_BASE_CFG = {
'trial_id':0, 'vel_div_threshold':-.020, 'vel_div_extreme':-.050,
'use_direction_confirm':True, 'dc_lookback_bars':7, 'dc_min_magnitude_bps':.75,
'dc_skip_contradicts':True, 'dc_leverage_boost':1.00, 'dc_leverage_reduce':.50,
'vd_trend_lookback':10, 'min_leverage':.50, 'max_leverage':5.00,
'leverage_convexity':3.00, 'fraction':.20, 'use_alpha_layers':True,
'use_dynamic_leverage':True, 'fixed_tp_pct':.0095, 'stop_pct':1.00,
'max_hold_bars':120, 'use_sp_fees':True, 'use_sp_slippage':True,
'sp_maker_entry_rate':.62, 'sp_maker_exit_rate':.50, 'use_ob_edge':True,
'ob_edge_bps':5.00, 'ob_confirm_rate':.40, 'ob_imbalance_bias':-.09,
'ob_depth_scale':1.00, 'use_asset_selection':True, 'min_irp_alignment':.45,
'lookback':100, 'acb_beta_high':.80, 'acb_beta_low':.20, 'acb_w750_threshold_pct':60,
}
WINDOW_Z = 30
K_TANH = 1.5
UP_STR = 0.15
DOWN_STR = 0.50
def analogue_scale(z: float) -> float:
t = np.tanh(float(z) / K_TANH)
if z >= 0.:
return 1.0 + UP_STR * t
else:
return 1.0 + DOWN_STR * t
# ── Pre-compute 1m signals (same as exp10) ─────────────────────────────────
def precompute_1m_signals(parquet_files_5s, sensor):
print("Pre-computing 1m signals...")
signals = {}
for pf5 in parquet_files_5s:
ds = pf5.stem
pf1 = VBT1m / f"{ds}.parquet"
if not pf1.exists():
signals[ds] = None
continue
df5 = pd.read_parquet(pf5)
df1 = pd.read_parquet(pf1).replace([np.inf,-np.inf], np.nan).fillna(0.)
n5, n1 = len(df5), len(df1)
assets = [c for c in df1.columns if c not in META_COLS]
i150 = df1['instability_150'].values.copy() if 'instability_150' in df1.columns else np.zeros(n1)
z_roll_1m = np.zeros(n1)
for j in range(WINDOW_Z, n1):
seg = i150[max(0,j-WINDOW_Z):j]
mu, sigma = np.mean(seg), np.std(seg)
z_roll_1m[j] = (i150[j] - mu) / max(sigma, 1e-10)
recon_1m = np.zeros(n1)
for j in range(n1):
feat = build_feature_vector(df1, j, assets)
_, recon_err, _ = sensor.encode(feat)
recon_1m[j] = recon_err
z_recon_1m = np.zeros(n1)
for j in range(WINDOW_Z, n1):
seg = recon_1m[max(0,j-WINDOW_Z):j]
mu, sigma = np.mean(seg), np.std(seg)
z_recon_1m[j] = (recon_1m[j] - mu) / max(sigma, 1e-10)
z_roll_5s = np.zeros(n5)
z_recon_5s = np.zeros(n5)
for i in range(n5):
j = min(int(i * n1 / n5), n1-1)
z_roll_5s[i] = z_roll_1m[j]
z_recon_5s[i] = z_recon_1m[j]
signals[ds] = {'z_roll': z_roll_5s, 'z_recon': z_recon_5s}
print(f" {ds}: z_roll=[{z_roll_5s.min():.2f},{z_roll_5s.max():.2f}] "
f"z_recon=[{z_recon_5s.min():.2f},{z_recon_5s.max():.2f}]")
return signals
# ── Engine subclass (same as exp10) ──────────────────────────────────────────
class KeyframeGateEngine(LiquidationGuardEngine):
def __init__(self, scale_fn, **kwargs):
super().__init__(**kwargs)
self._scale_fn = scale_fn
self._bar_z_roll = None
self._bar_z_recon = None
self._1m_scale_history = []
def set_1m_signals(self, z_roll, z_recon):
self._bar_z_roll = z_roll
self._bar_z_recon = z_recon
def _try_entry(self, bar_idx, vel_div, prices, price_histories, v50_vel=0., v750_vel=0.):
result = super()._try_entry(bar_idx, vel_div, prices, price_histories, v50_vel, v750_vel)
if result and self.position is not None:
zr = float(self._bar_z_roll[bar_idx]) if (self._bar_z_roll is not None and bar_idx < len(self._bar_z_roll)) else 0.
ze = float(self._bar_z_recon[bar_idx]) if (self._bar_z_recon is not None and bar_idx < len(self._bar_z_recon)) else 0.
s = float(self._scale_fn(zr, ze))
s = max(0.2, min(2.0, s))
self.position.notional *= s
self._1m_scale_history.append(s)
return result
def reset(self):
super().reset()
self._1m_scale_history = []
# ── Configs: focus on inverted z_recon direction ──────────────────────────────
CONFIGS = {
"0_baseline": None,
"1_B_analogue": lambda zr, ze: analogue_scale(zr), # positive control
"2_D_inv_analogue":lambda zr, ze: analogue_scale(-ze), # INVERTED z_recon
"3_BD_inv": lambda zr, ze: analogue_scale(zr) * analogue_scale(-ze), # B × D_inv
}
def run_one(config_name, scale_fn, parquet_files, pq_data, signals, vol_p60):
OB_ASSETS = sorted({a for ds,(df,ac,_) in pq_data.items() for a in ac})
_mock_ob = MockOBProvider(
imbalance_bias=-.09, depth_scale=1., assets=OB_ASSETS,
imbalance_biases={"BTCUSDT":-.086,"ETHUSDT":-.092,"BNBUSDT":+.05,"SOLUSDT":+.05},
)
ob_eng = OBFeatureEngine(_mock_ob)
ob_eng.preload_date("mock", OB_ASSETS)
forewarner = DolphinForewarner(models_dir=MC_MODELS)
acb = AdaptiveCircuitBreaker()
acb.preload_w750([pf.stem for pf in parquet_files])
if scale_fn is None:
engine = create_d_liq_engine(**BASE_ENGINE_KWARGS)
else:
engine = KeyframeGateEngine(scale_fn=scale_fn, **BASE_ENGINE_KWARGS, **D_LIQ_KWARGS)
engine.set_ob_engine(ob_eng)
engine.set_acb(acb)
engine.set_mc_forewarner(forewarner, MC_BASE_CFG)
engine.set_esoteric_hazard_multiplier(0.)
t0 = time.time()
for pf in parquet_files:
ds = pf.stem
df, acols, dvol = pq_data[ds]
vol_ok = np.where(np.isfinite(dvol), dvol > vol_p60, False)
if scale_fn is not None and isinstance(engine, KeyframeGateEngine):
sig = signals.get(ds)
if sig:
engine.set_1m_signals(sig['z_roll'], sig['z_recon'])
else:
engine.set_1m_signals(np.zeros(len(df)), np.zeros(len(df)))
engine.process_day(ds, df, acols, vol_regime_ok=vol_ok)
elapsed = time.time() - t0
trades = engine.trade_history
roi = (engine.capital - 25000.) / 25000. * 100.
cap_curve = [25000.]
for t_ in sorted(trades, key=lambda x: getattr(x,'exit_bar',0)):
cap_curve.append(cap_curve[-1] + getattr(t_,'pnl_absolute',0.))
cap_arr = np.array(cap_curve)
peak = np.maximum.accumulate(cap_arr)
dd = float(np.max((peak - cap_arr) / (peak + 1e-10)) * 100.)
calmar = roi / max(dd, 1e-4)
scale_hist = getattr(engine, '_1m_scale_history', [])
return {
'config': config_name,
'T': len(trades),
'ROI': round(roi, 4),
'DD': round(dd, 4),
'Calmar': round(calmar, 4),
'elapsed_s': round(elapsed, 1),
'scale_mean': round(np.mean(scale_hist), 4) if scale_hist else 1.0,
'scale_min': round(np.min(scale_hist), 4) if scale_hist else 1.0,
'scale_max': round(np.max(scale_hist), 4) if scale_hist else 1.0,
'n_scale_applied': len(scale_hist),
}
def main():
parquet_files = sorted(VBT5s.glob("*.parquet"))
parquet_files = [p for p in parquet_files if 'catalog' not in str(p)]
print(f"Dataset: {len(parquet_files)} days")
print("Loading parquet data...")
pq_data = {}
for pf in parquet_files:
df = pd.read_parquet(pf)
ac = [c for c in df.columns if c not in META_COLS]
bp = df['BTCUSDT'].values if 'BTCUSDT' in df.columns else None
dv = np.full(len(df), np.nan)
if bp is not None:
for i in range(50, len(bp)):
seg = bp[max(0,i-50):i]
if len(seg) >= 10:
dv[i] = float(np.std(np.diff(seg)/seg[:-1]))
pq_data[pf.stem] = (df, ac, dv)
all_vols = []
for pf in parquet_files[:2]:
df = pd.read_parquet(pf)
if 'BTCUSDT' not in df.columns: continue
pr = df['BTCUSDT'].values
for i in range(60, len(pr)):
seg = pr[max(0,i-50):i]
if len(seg) >= 10:
v = float(np.std(np.diff(seg)/seg[:-1]))
if v > 0: all_vols.append(v)
vol_p60 = float(np.percentile(all_vols, 60)) if all_vols else 0.
print(f"\nLoading TitanSensor (GD-v2 with normalization)...")
sensor = TitanSensor(str(MODEL_PATH))
print(f" lstm_weights_valid={sensor.lstm_weights_valid} "
f"norm_mean is {'present' if sensor.norm_mean is not None else 'MISSING'}")
signals = precompute_1m_signals(parquet_files, sensor)
n_missing = sum(1 for v in signals.values() if v is None)
print(f" 1m signals ready: {len(signals)-n_missing}/{len(signals)} days")
print()
results = []
for name, fn in CONFIGS.items():
print(f"Running [{name}]...", flush=True)
r = run_one(name, fn, parquet_files, pq_data, signals, vol_p60)
results.append(r)
baseline_roi = results[0]['ROI']
delta_roi = r['ROI'] - baseline_roi
delta_dd = r['DD'] - results[0]['DD'] if len(results) > 1 else 0.
print(f" T={r['T']} ROI={r['ROI']:+.2f}% DD={r['DD']:.2f}% "
f"Calmar={r['Calmar']:.2f} "
f"(dROI={delta_roi:+.2f}pp dDD={delta_dd:+.2f}pp) "
f"scale_mean={r['scale_mean']:.3f} {r['elapsed_s']:.0f}s")
print()
print("=" * 90)
print(f"{'Config':<22} {'T':>5} {'ROI%':>8} {'DD%':>7} {'Calmar':>7} "
f"{'dROI':>7} {'dDD':>6} {'s_mean':>7}")
print("-" * 90)
base = results[0]
for r in results:
dr = r['ROI'] - base['ROI']
dd = r['DD'] - base['DD']
print(f"{r['config']:<22} {r['T']:>5} {r['ROI']:>8.2f} {r['DD']:>7.2f} "
f"{r['Calmar']:>7.2f} {dr:>+7.2f} {dd:>+6.2f} {r['scale_mean']:>7.3f}")
with open(OUT_FILE, 'w') as f:
json.dump({'baseline': base, 'results': results}, f, indent=2)
print(f"\nResults: {OUT_FILE}")
print("\n=== VERDICT ===")
best = max(results[1:], key=lambda x: x['Calmar'])
threshold = base['Calmar'] * 1.02
print(f"Best config: [{best['config']}] Calmar={best['Calmar']:.2f} "
f"ROI={best['ROI']:+.2f}% DD={best['DD']:.2f}%")
print(f"Threshold: Calmar > {threshold:.2f} (1.02× baseline {base['Calmar']:.2f})")
if best['Calmar'] > threshold:
print(" PROCEED: meaningful Calmar improvement vs baseline")
else:
print(" MARGINAL or NO improvement over D_LIQ_GOLD")
# Direction analysis
print("\n=== DIRECTION ANALYSIS ===")
d_fwd = next((r for r in results if '4_D_analogue' in r['config']), None)
d_inv = next((r for r in results if 'D_inv' in r['config']), None)
if d_inv:
print(f" D_inv_analogue (ze): ROI={d_inv['ROI']:+.2f}% "
f"Calmar={d_inv['Calmar']:.2f} dROI={d_inv['ROI']-base['ROI']:+.2f}pp")
print(f" vs exp10 D_analogue (+ze): approx dROI=0.57pp")
if d_inv['ROI'] - base['ROI'] > 0:
print(" CONFIRMED: inverted z_recon direction is POSITIVE — OOD = cut is right")
else:
print(" INCONCLUSIVE: inverted direction still negative")
if __name__ == '__main__':
main()

View File

@@ -0,0 +1,64 @@
{
"baseline": {
"config": "0_baseline",
"T": 2155,
"ROI": 181.8069,
"DD": 23.6916,
"Calmar": 7.6739,
"elapsed_s": 338.6,
"scale_mean": 1.0,
"scale_min": 1.0,
"scale_max": 1.0,
"n_scale_applied": 0
},
"results": [
{
"config": "0_baseline",
"T": 2155,
"ROI": 181.8069,
"DD": 23.6916,
"Calmar": 7.6739,
"elapsed_s": 338.6,
"scale_mean": 1.0,
"scale_min": 1.0,
"scale_max": 1.0,
"n_scale_applied": 0
},
{
"config": "1_B_analogue",
"T": 2155,
"ROI": 182.3446,
"DD": 23.6916,
"Calmar": 7.6966,
"elapsed_s": 318.3,
"scale_mean": 0.9991,
"scale_min": 0.5181,
"scale_max": 1.1437,
"n_scale_applied": 2156
},
{
"config": "2_D_inv_analogue",
"T": 2155,
"ROI": 180.6862,
"DD": 23.6916,
"Calmar": 7.6266,
"elapsed_s": 311.9,
"scale_mean": 0.9978,
"scale_min": 0.5139,
"scale_max": 1.134,
"n_scale_applied": 2156
},
{
"config": "3_BD_inv",
"T": 2155,
"ROI": 181.1797,
"DD": 23.6916,
"Calmar": 7.6474,
"elapsed_s": 313.8,
"scale_mean": 0.997,
"scale_min": 0.3503,
"scale_max": 1.2128,
"n_scale_applied": 2156
}
]
}

View File

@@ -0,0 +1,347 @@
"""
exp12_convnext_gate.py — ConvNeXt z-signal gate on D_LIQ_GOLD
==============================================================
exp10/11 used TitanSensor with broken LSTM weights → z_recon ~10^14 → noise.
This experiment uses the newly trained ConvNeXt-1D β-TCVAE (ep=17, val=19.26):
z[10] r=+0.973 with proxy_B (32-bar trajectory encoding)
z_post_std OOD indicator (>1 = uncertain/unusual regime)
The z[10] signal captures the 32-bar TRAJECTORY of proxy_B, whereas the current
D_LIQ_GOLD engine uses only the instantaneous proxy_B at entry. If trajectory
adds information, z[10] should produce a measurable Calmar improvement.
Configs (5 runs):
0. baseline — D_LIQ_GOLD unmodified (control)
1. z10_analogue — analogue_scale(z10) → boost high proxy_B, cut low
2. z10_inv — analogue_scale(-z10) → inverse direction test
3. zstd_gate — notional × max(0.4, 1 - z_post_std/4) OOD cut
4. z10_x_zstd — z10_analogue × zstd_gate combined
analogue_scale (same as exp10/11):
z >= 0 : 1 + UP_STR * tanh(z / K) ceiling ~1.15
z < 0 : 1 + DOWN_STR * tanh(z / K) floor ~0.50
Threshold test: Calmar > baseline × 1.02 (same as exp10/11)
Gold baseline: ROI=181.81% DD=17.65% Calmar=10.30 (D_LIQ_GOLD memory)
"""
import sys, time, json, warnings
sys.stdout.reconfigure(encoding='utf-8', errors='replace')
warnings.filterwarnings('ignore')
from pathlib import Path
import numpy as np
import pandas as pd
ROOT = Path(__file__).parent.parent
sys.path.insert(0, str(ROOT))
from nautilus_dolphin.nautilus.alpha_asset_selector import compute_irp_nb, compute_ars_nb, rank_assets_irp_nb
from nautilus_dolphin.nautilus.alpha_bet_sizer import compute_sizing_nb
from nautilus_dolphin.nautilus.alpha_signal_generator import check_dc_nb
from nautilus_dolphin.nautilus.ob_features import (
OBFeatureEngine, compute_imbalance_nb, compute_depth_1pct_nb,
compute_depth_quality_nb, compute_fill_probability_nb, compute_spread_proxy_nb,
compute_depth_asymmetry_nb, compute_imbalance_persistence_nb,
compute_withdrawal_velocity_nb, compute_market_agreement_nb, compute_cascade_signal_nb,
)
from nautilus_dolphin.nautilus.ob_provider import MockOBProvider
from nautilus_dolphin.nautilus.adaptive_circuit_breaker import AdaptiveCircuitBreaker
from nautilus_dolphin.nautilus.proxy_boost_engine import LiquidationGuardEngine, create_d_liq_engine
from mc.mc_ml import DolphinForewarner
from dvae.convnext_sensor import ConvNextSensor, PROXY_B_DIM
# ── JIT warmup ────────────────────────────────────────────────────────────────
print("Warming up JIT...")
_p = np.array([1.,2.,3.], dtype=np.float64)
compute_irp_nb(_p,-1); compute_ars_nb(1.,.5,.01)
rank_assets_irp_nb(np.ones((10,2),dtype=np.float64),8,-1,5,500.,20,0.20)
compute_sizing_nb(-.03,-.02,-.05,3.,.5,5.,.20,True,True,0.,
np.zeros(4,dtype=np.int64),np.zeros(4,dtype=np.int64),
np.zeros(5,dtype=np.float64),0,-1,.01,.04)
check_dc_nb(_p,3,1,.75)
_b=np.array([100.,200.,300.,400.,500.],dtype=np.float64)
_a=np.array([110.,190.,310.,390.,510.],dtype=np.float64)
compute_imbalance_nb(_b,_a); compute_depth_1pct_nb(_b,_a)
compute_depth_quality_nb(210.,200.); compute_fill_probability_nb(1.)
compute_spread_proxy_nb(_b,_a); compute_depth_asymmetry_nb(_b,_a)
compute_imbalance_persistence_nb(np.array([.1,-.1],dtype=np.float64),2)
compute_withdrawal_velocity_nb(np.array([100.,110.],dtype=np.float64),1)
compute_market_agreement_nb(np.array([.1,-.05],dtype=np.float64),2)
compute_cascade_signal_nb(np.array([-.05,-.15],dtype=np.float64),2,-.10)
print(" JIT ready.")
# ── Paths ─────────────────────────────────────────────────────────────────────
VBT5s = Path(r"C:\Users\Lenovo\Documents\- DOLPHIN NG HD HCM TSF Predict\vbt_cache")
VBT1m = Path(r"C:\Users\Lenovo\Documents\- DOLPHIN NG HD HCM TSF Predict\vbt_cache_klines")
MODEL_PATH = Path(r"C:\Users\Lenovo\Documents\- DOLPHIN NG HD HCM TSF Predict\nautilus_dolphin\dvae\convnext_model.json")
MC_MODELS = str(ROOT / "mc_results" / "models")
OUT_FILE = Path(__file__).parent / "exp12_convnext_gate_results.json"
META_COLS = {'timestamp','scan_number','v50_lambda_max_velocity','v150_lambda_max_velocity',
'v300_lambda_max_velocity','v750_lambda_max_velocity','vel_div',
'instability_50','instability_150'}
BASE_ENGINE_KWARGS = dict(
initial_capital=25000., vel_div_threshold=-.02, vel_div_extreme=-.05,
min_leverage=.5, max_leverage=5., leverage_convexity=3.,
fraction=.20, fixed_tp_pct=.0095, stop_pct=1., max_hold_bars=120,
use_direction_confirm=True, dc_lookback_bars=7, dc_min_magnitude_bps=.75,
dc_skip_contradicts=True, dc_leverage_boost=1., dc_leverage_reduce=.5,
use_asset_selection=True, min_irp_alignment=.45,
use_sp_fees=True, use_sp_slippage=True,
sp_maker_entry_rate=.62, sp_maker_exit_rate=.50,
use_ob_edge=True, ob_edge_bps=5., ob_confirm_rate=.40,
lookback=100, use_alpha_layers=True, use_dynamic_leverage=True, seed=42,
)
D_LIQ_KWARGS = dict(
extended_soft_cap=8., extended_abs_cap=9., mc_leverage_ref=5.,
margin_buffer=.95, threshold=.35, alpha=1., adaptive_beta=True,
)
MC_BASE_CFG = {
'trial_id':0, 'vel_div_threshold':-.020, 'vel_div_extreme':-.050,
'use_direction_confirm':True, 'dc_lookback_bars':7, 'dc_min_magnitude_bps':.75,
'dc_skip_contradicts':True, 'dc_leverage_boost':1.00, 'dc_leverage_reduce':.50,
'vd_trend_lookback':10, 'min_leverage':.50, 'max_leverage':5.00,
'leverage_convexity':3.00, 'fraction':.20, 'use_alpha_layers':True,
'use_dynamic_leverage':True, 'fixed_tp_pct':.0095, 'stop_pct':1.00,
'max_hold_bars':120, 'use_sp_fees':True, 'use_sp_slippage':True,
'sp_maker_entry_rate':.62, 'sp_maker_exit_rate':.50, 'use_ob_edge':True,
'ob_edge_bps':5.00, 'ob_confirm_rate':.40, 'ob_imbalance_bias':-.09,
'ob_depth_scale':1.00, 'use_asset_selection':True, 'min_irp_alignment':.45,
'lookback':100, 'acb_beta_high':.80, 'acb_beta_low':.20, 'acb_w750_threshold_pct':60,
}
K_TANH = 1.5
UP_STR = 0.15
DOWN_STR = 0.50
def analogue_scale(z: float) -> float:
t = np.tanh(float(z) / K_TANH)
return 1.0 + (UP_STR if z >= 0. else DOWN_STR) * t
def zstd_scale(zstd: float) -> float:
"""OOD cut: normal zstd~0.94, high zstd = uncertain regime → cut notional."""
return float(max(0.4, 1.0 - (zstd - 0.94) / 4.0))
# ── Pre-compute signals per day ───────────────────────────────────────────────
def precompute_signals(parquet_files_5s, sensor: ConvNextSensor):
print("Pre-computing ConvNext z signals from 1m data...")
signals = {}
for pf5 in parquet_files_5s:
ds = pf5.stem
pf1 = VBT1m / f"{ds}.parquet"
if not pf1.exists():
signals[ds] = None
continue
df1 = pd.read_parquet(pf1).replace([np.inf, -np.inf], np.nan).fillna(0.)
n1 = len(df1)
n5 = len(pd.read_parquet(pf5, columns=['timestamp']))
z10_1m = np.zeros(n1, dtype=np.float64)
zstd_1m = np.zeros(n1, dtype=np.float64)
for j in range(n1):
z_mu, z_post_std = sensor.encode_window(df1, j)
z10_1m[j] = z_mu[PROXY_B_DIM]
zstd_1m[j] = z_post_std
# Map 1m → 5s by nearest index
z10_5s = np.array([z10_1m[min(int(i * n1 / n5), n1-1)] for i in range(n5)])
zstd_5s = np.array([zstd_1m[min(int(i * n1 / n5), n5-1)] for i in range(n5)])
signals[ds] = {'z10': z10_5s, 'zstd': zstd_5s}
print(f" {ds}: z10=[{z10_5s.min():.2f},{z10_5s.max():.2f}] "
f"zstd=[{zstd_5s.min():.3f},{zstd_5s.max():.3f}]")
n_ok = sum(1 for v in signals.values() if v is not None)
print(f" Signals ready: {n_ok}/{len(signals)} days\n")
return signals
# ── Engine subclass ───────────────────────────────────────────────────────────
class ConvNextGateEngine(LiquidationGuardEngine):
def __init__(self, scale_fn, **kwargs):
super().__init__(**kwargs)
self._scale_fn = scale_fn
self._bar_z10 = None
self._bar_zstd = None
self._scale_history = []
def set_signals(self, z10: np.ndarray, zstd: np.ndarray):
self._bar_z10 = z10
self._bar_zstd = zstd
def _try_entry(self, bar_idx, vel_div, prices, price_histories, v50_vel=0., v750_vel=0.):
result = super()._try_entry(bar_idx, vel_div, prices, price_histories, v50_vel, v750_vel)
if result and self.position is not None:
z10 = float(self._bar_z10[bar_idx]) if (self._bar_z10 is not None and bar_idx < len(self._bar_z10)) else 0.
zstd = float(self._bar_zstd[bar_idx]) if (self._bar_zstd is not None and bar_idx < len(self._bar_zstd)) else 0.94
s = float(self._scale_fn(z10, zstd))
s = max(0.2, min(2.0, s))
self.position.notional *= s
self._scale_history.append(s)
return result
def reset(self):
super().reset()
self._scale_history = []
# ── Scale functions ───────────────────────────────────────────────────────────
CONFIGS = {
"0_baseline": None,
"1_z10_analogue": lambda z10, zstd: analogue_scale(z10),
"2_z10_inv": lambda z10, zstd: analogue_scale(-z10),
"3_zstd_gate": lambda z10, zstd: zstd_scale(zstd),
"4_z10_x_zstd": lambda z10, zstd: analogue_scale(z10) * zstd_scale(zstd),
}
def run_one(config_name, scale_fn, parquet_files, pq_data, signals, vol_p60):
OB_ASSETS = sorted({a for ds, (df, ac, _) in pq_data.items() for a in ac})
_mock_ob = MockOBProvider(
imbalance_bias=-.09, depth_scale=1., assets=OB_ASSETS,
imbalance_biases={"BTCUSDT":-.086,"ETHUSDT":-.092,"BNBUSDT":+.05,"SOLUSDT":+.05},
)
ob_eng = OBFeatureEngine(_mock_ob)
ob_eng.preload_date("mock", OB_ASSETS)
forewarner = DolphinForewarner(models_dir=MC_MODELS)
acb = AdaptiveCircuitBreaker()
acb.preload_w750([pf.stem for pf in parquet_files])
if scale_fn is None:
engine = create_d_liq_engine(**BASE_ENGINE_KWARGS)
else:
engine = ConvNextGateEngine(scale_fn=scale_fn, **BASE_ENGINE_KWARGS, **D_LIQ_KWARGS)
engine.set_ob_engine(ob_eng)
engine.set_acb(acb)
engine.set_mc_forewarner(forewarner, MC_BASE_CFG)
engine.set_esoteric_hazard_multiplier(0.)
t0 = time.time()
for pf in parquet_files:
ds = pf.stem
df, acols, dvol = pq_data[ds]
vol_ok = np.where(np.isfinite(dvol), dvol > vol_p60, False)
if scale_fn is not None and isinstance(engine, ConvNextGateEngine):
sig = signals.get(ds)
if sig:
engine.set_signals(sig['z10'], sig['zstd'])
else:
engine.set_signals(np.zeros(len(df)), np.full(len(df), 0.94))
engine.process_day(ds, df, acols, vol_regime_ok=vol_ok)
elapsed = time.time() - t0
trades = engine.trade_history
roi = (engine.capital - 25000.) / 25000. * 100.
cap_curve = [25000.]
for t_ in sorted(trades, key=lambda x: getattr(x, 'exit_bar', 0)):
cap_curve.append(cap_curve[-1] + getattr(t_, 'pnl_absolute', 0.))
cap_arr = np.array(cap_curve)
peak = np.maximum.accumulate(cap_arr)
dd = float(np.max((peak - cap_arr) / (peak + 1e-10)) * 100.)
calmar = roi / max(dd, 1e-4)
sh = getattr(engine, '_scale_history', [])
return {
'config': config_name,
'T': len(trades),
'ROI': round(roi, 4),
'DD': round(dd, 4),
'Calmar': round(calmar, 4),
'elapsed_s': round(elapsed, 1),
'scale_mean': round(float(np.mean(sh)), 4) if sh else 1.0,
'scale_min': round(float(np.min(sh)), 4) if sh else 1.0,
'scale_max': round(float(np.max(sh)), 4) if sh else 1.0,
'n_scaled': len(sh),
}
def main():
parquet_files = sorted(VBT5s.glob("*.parquet"))
parquet_files = [p for p in parquet_files if 'catalog' not in str(p)]
print(f"Dataset: {len(parquet_files)} days (5s scans)\n")
print("Loading ConvNextSensor...")
sensor = ConvNextSensor(str(MODEL_PATH))
print(f" epoch={sensor.epoch} val_loss={sensor.val_loss:.4f} z_dim={sensor.z_dim}\n")
signals = precompute_signals(parquet_files, sensor)
print("Loading 5s parquet data...")
pq_data = {}
for pf in parquet_files:
df = pd.read_parquet(pf)
ac = [c for c in df.columns if c not in META_COLS]
bp = df['BTCUSDT'].values if 'BTCUSDT' in df.columns else None
dv = np.full(len(df), np.nan)
if bp is not None:
for i in range(50, len(bp)):
seg = bp[max(0, i-50):i]
if len(seg) >= 10:
dv[i] = float(np.std(np.diff(seg) / seg[:-1]))
pq_data[pf.stem] = (df, ac, dv)
all_vols = []
for pf in parquet_files[:2]:
df = pd.read_parquet(pf)
if 'BTCUSDT' not in df.columns: continue
pr = df['BTCUSDT'].values
for i in range(60, len(pr)):
seg = pr[max(0, i-50):i]
if len(seg) >= 10:
v = float(np.std(np.diff(seg) / seg[:-1]))
if v > 0: all_vols.append(v)
vol_p60 = float(np.percentile(all_vols, 60)) if all_vols else 0.
print(f"\nRunning {len(CONFIGS)} configs...\n")
results = []
for name, fn in CONFIGS.items():
print(f"[{name}]", flush=True)
r = run_one(name, fn, parquet_files, pq_data, signals, vol_p60)
results.append(r)
base = results[0]
dr = r['ROI'] - base['ROI']
dd = r['DD'] - base['DD']
print(f" T={r['T']} ROI={r['ROI']:+.2f}% DD={r['DD']:.2f}% "
f"Calmar={r['Calmar']:.2f} dROI={dr:+.2f}pp dDD={dd:+.2f}pp "
f"s_mean={r['scale_mean']:.3f} ({r['elapsed_s']:.0f}s)\n")
print("=" * 95)
print(f"{'Config':<22} {'T':>5} {'ROI%':>8} {'DD%':>7} {'Calmar':>7} "
f"{'dROI':>7} {'dDD':>6} {'s_mean':>7}")
print("-" * 95)
base = results[0]
for r in results:
dr = r['ROI'] - base['ROI']
dd = r['DD'] - base['DD']
print(f"{r['config']:<22} {r['T']:>5} {r['ROI']:>8.2f} {r['DD']:>7.2f} "
f"{r['Calmar']:>7.2f} {dr:>+7.2f} {dd:>+6.2f} {r['scale_mean']:>7.3f}")
with open(OUT_FILE, 'w') as f:
json.dump({'baseline': base, 'results': results,
'model_epoch': sensor.epoch, 'model_val_loss': sensor.val_loss}, f, indent=2)
print(f"\nResults -> {OUT_FILE}")
print("\n=== VERDICT ===")
best = max(results[1:], key=lambda x: x['Calmar'])
threshold = base['Calmar'] * 1.02
print(f"Best: [{best['config']}] Calmar={best['Calmar']:.2f} "
f"ROI={best['ROI']:.2f}% DD={best['DD']:.2f}%")
print(f"Threshold: Calmar > {threshold:.2f} (1.02x baseline {base['Calmar']:.2f})")
if best['Calmar'] > threshold:
print(" SIGNAL CONFIRMED — proceed to exp13 (productionization)")
else:
print(" MARGINAL / NO improvement over D_LIQ_GOLD")
if __name__ == '__main__':
main()

View File

@@ -0,0 +1,78 @@
{
"baseline": {
"config": "0_baseline",
"T": 2155,
"ROI": 181.8069,
"DD": 23.6916,
"Calmar": 7.6739,
"elapsed_s": 411.0,
"scale_mean": 1.0883,
"scale_min": 1.0,
"scale_max": 1.63,
"n_scaled": 2156
},
"results": [
{
"config": "0_baseline",
"T": 2155,
"ROI": 181.8069,
"DD": 23.6916,
"Calmar": 7.6739,
"elapsed_s": 411.0,
"scale_mean": 1.0883,
"scale_min": 1.0,
"scale_max": 1.63,
"n_scaled": 2156
},
{
"config": "1_z10_analogue",
"T": 2155,
"ROI": 177.1364,
"DD": 23.6916,
"Calmar": 7.4768,
"elapsed_s": 429.5,
"scale_mean": 1.041,
"scale_min": 0.6597,
"scale_max": 1.63,
"n_scaled": 4312
},
{
"config": "2_z10_inv",
"T": 2155,
"ROI": 183.2151,
"DD": 23.6916,
"Calmar": 7.7333,
"elapsed_s": 517.9,
"scale_mean": 1.0451,
"scale_min": 1.0,
"scale_max": 1.63,
"n_scaled": 4312
},
{
"config": "3_zstd_gate",
"T": 2155,
"ROI": 181.7825,
"DD": 23.6916,
"Calmar": 7.6729,
"elapsed_s": 489.5,
"scale_mean": 1.0442,
"scale_min": 0.9955,
"scale_max": 1.63,
"n_scaled": 4312
},
{
"config": "4_z10_x_zstd",
"T": 2155,
"ROI": 177.1174,
"DD": 23.6916,
"Calmar": 7.476,
"elapsed_s": 504.2,
"scale_mean": 1.041,
"scale_min": 0.6568,
"scale_max": 1.63,
"n_scaled": 4312
}
],
"model_epoch": 17,
"model_val_loss": 19.260574247143158
}

View File

@@ -0,0 +1,363 @@
"""
exp13_model_sweep.py — Multi-model exp13 test harness.
For each model in the registry:
1. Auto-identify proxy_B dim (highest |r| correlation with raw proxy_B signal)
2. Validate calibration (always-positive in 56-day window required for exp13 scaling)
3. Run exp13 Phase 1 (14-day screening) + Phase 2 (full 56-day, top-k configs)
4. Save per-model results to exp13_sweep_<tag>_results.json
5. Print final comparison table across all models
All tests use IDENTICAL configs/window/threshold to exp13 v2 (the CONFIRMED baseline).
Threshold: Calmar > 7.83 (102% of D_LIQ_GOLD baseline 7.67 in the 56-day window)
Reference: v2 BOB — 9/20 PASS, best dROI=+4.59pp, best Calmar=7.87
Usage (from nautilus_dolphin/ dir):
python dvae/exp13_model_sweep.py # all available models in registry
python dvae/exp13_model_sweep.py --models v4 # single model
python dvae/exp13_model_sweep.py --models v4 v5 v6 # explicit list
python dvae/exp13_model_sweep.py --probe_only # dim probe only, no backtest
python dvae/exp13_model_sweep.py --subset 14 --top_k 20 # explicit Phase 1/2 params
Adding new models (v5, v6, v7, v8):
1. Transfer model JSON from DOLPHIN Linux to models/convnext_dvae_ML/
2. Uncomment (or add) the entry in MODEL_REGISTRY below
3. Re-run this script
"""
import sys, os, time, json, importlib, argparse
import io
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8', errors='replace', line_buffering=True)
import numpy as np
import pandas as pd
from pathlib import Path
ROOT = Path(__file__).resolve().parent.parent.parent
DVAE_DIR = ROOT / 'nautilus_dolphin' / 'dvae'
sys.path.insert(0, str(ROOT / 'nautilus_dolphin'))
# ── Model registry ─────────────────────────────────────────────────────────────
# Slot in v5/v6/v7/v8 when transferred from DOLPHIN Linux — just uncomment.
MODEL_REGISTRY = {
'v2_bob': ROOT / 'nautilus_dolphin' / 'dvae' / 'convnext_model_v2.json',
'v4': ROOT / 'models' / 'convnext_dvae_ML' / 'convnext_model_v4_ep22_best.json',
'v5': ROOT / 'models' / 'dolphin_training' / 'winning_models' / 'v5_ep28_best_total_loss.json',
'v6': ROOT / 'models' / 'dolphin_training' / 'winning_models' / 'v6_ep8_best_val_loss.json',
'v7': ROOT / 'models' / 'dolphin_training' / 'winning_models' / 'v7_ep10_best_generalization.json',
# v8: step=2 training (1.2M windows), only 2 epochs — val=34.92. Experimental.
'v8': ROOT / 'models' / 'dolphin_training' / 'dvae' / 'v8_step2.json',
# v6.5 (ANOMALOUS — DO NOT USE: broken during training per researcher note)
}
KLINES_DIR = ROOT / 'vbt_cache_klines'
DATE_START = '2025-12-31'
DATE_END = '2026-02-25'
CALMAR_THR = 7.83 # 102% of D_LIQ_GOLD baseline 7.67
FEATURE_COLS = [
'v50_lambda_max_velocity', 'v150_lambda_max_velocity',
'v300_lambda_max_velocity', 'v750_lambda_max_velocity',
'vel_div', 'instability_50', 'instability_150',
]
T_WIN = 32
# ── Proxy_B dim identification ─────────────────────────────────────────────────
from dvae.convnext_dvae import ConvNeXtVAE
def _load_model(path: Path):
with open(path) as f:
meta = json.load(f)
arch = meta['architecture']
m = ConvNeXtVAE(
C_in=arch['C_in'], T_in=arch['T_in'],
z_dim=arch['z_dim'], base_ch=arch['base_ch'],
n_blocks=arch.get('n_blocks', 3), seed=42,
)
m.load(str(path))
nm = np.array(meta['norm_mean']) if 'norm_mean' in meta else None
ns = np.array(meta['norm_std']) if 'norm_std' in meta else None
return m, nm, ns, meta
def _build_probe_set():
"""Sample probe windows from 56-day window; shared across all models."""
files = sorted(KLINES_DIR.glob('*.parquet'))
period = [f for f in files if DATE_START <= f.stem[:10] <= DATE_END]
rng = np.random.default_rng(42)
probes_raw, proxy_B_vals = [], []
step = max(1, len(period) // 60)
for f in period[::step]:
try:
df = pd.read_parquet(f, columns=FEATURE_COLS).dropna()
if len(df) < T_WIN + 10: continue
mid = len(df) // 2
pos = int(rng.integers(max(0, mid-30), min(len(df)-T_WIN, mid+30)))
arr = df[FEATURE_COLS].values[pos:pos+T_WIN].astype(np.float64)
proxy_B = (arr[:, 5] - arr[:, 3]).reshape(-1, 1)
exf = np.zeros((T_WIN, 3), dtype=np.float64)
arr11 = np.concatenate([arr, proxy_B, exf], axis=1).T # (11, T)
if not np.isfinite(arr11).all(): continue
probes_raw.append(arr11)
proxy_B_vals.append(float(proxy_B.mean()))
except Exception:
pass
return np.stack(probes_raw), np.array(proxy_B_vals)
def probe_model(tag: str, path: Path, probes_raw: np.ndarray,
proxy_B_arr: np.ndarray) -> dict:
"""Identify proxy_B dim and report calibration for one model."""
print(f"\n{'='*60}")
print(f" PROBE: {tag} ({path.name})")
print(f"{'='*60}")
model, nm, ns, meta = _load_model(path)
ep = meta.get('epoch', '?')
val = meta.get('val_loss', 0.0)
print(f" epoch={ep} val_loss={val:.5f}")
probes = probes_raw.copy()
if nm is not None:
probes = (probes - nm[None, :, None]) / ns[None, :, None]
np.clip(probes, -6., 6., out=probes)
z_mu, z_logvar = model.encode(probes)
z_std = z_mu.std(0)
corrs = []
for d in range(z_mu.shape[1]):
if z_std[d] > 0.01:
r = float(np.corrcoef(z_mu[:, d], proxy_B_arr)[0, 1])
if np.isfinite(r):
corrs.append((abs(r), r, d))
corrs.sort(reverse=True)
best_abs_r, best_r, best_dim = corrs[0] if corrs else (0.0, 0.0, -1)
z_best = z_mu[:, best_dim]
z_min, z_max = float(z_best.min()), float(z_best.max())
always_pos = z_min > 0
always_neg = z_max < 0
if always_pos: calib = 'ALWAYS_POSITIVE'
elif always_neg: calib = 'ALWAYS_NEGATIVE'
else: calib = f'MIXED[{z_min:+.3f},{z_max:+.3f}]'
q75, q25 = np.percentile(proxy_B_arr, 75), np.percentile(proxy_B_arr, 25)
z_hi = float(z_best[proxy_B_arr >= q75].mean())
z_lo = float(z_best[proxy_B_arr <= q25].mean())
sep = abs(z_hi - z_lo)
# Also find best POSITIVELY correlated dim (same sign as v2 z[13])
pos_corrs = [(abs_r, r, d) for abs_r, r, d in corrs if r > 0]
pos_dim = pos_corrs[0][2] if pos_corrs else best_dim
pos_r = pos_corrs[0][1] if pos_corrs else best_r
usable = always_pos and best_abs_r > 0.5
print(f" proxy_B dim : z[{best_dim}] r={best_r:+.4f} "
f"(best |r|) best positive: z[{pos_dim}] r={pos_r:+.4f}")
print(f" Top-5 : " + ' '.join(f'z[{d}]={r:+.3f}' for _,r,d in corrs[:5]))
print(f" Calibration : {calib} sep={sep:.4f}")
print(f" Usable : {'YES ✓' if usable else 'CAUTION ⚠ (will skip exp13 sweep)'}")
return {
'tag': tag, 'path': str(path),
'epoch': ep, 'val_loss': val,
'proxy_B_dim': best_dim, 'proxy_B_r': best_r,
'proxy_B_dim_pos': pos_dim, 'proxy_B_r_pos': pos_r,
'calibration': calib, 'always_positive': always_pos,
'separation': sep, 'top5': [(r,d) for _,r,d in corrs[:5]],
}
# ── exp13 runner per model ─────────────────────────────────────────────────────
def run_exp13_for_model(probe: dict, subset_days: int, top_k: int,
only_config: str = None) -> dict:
"""Patch MODEL_1M + PROXY_B_DIM into exp13, call main(), read results JSON."""
import dvae.exp13_multiscale_sweep as e13
importlib.reload(e13) # fresh state: clears cached signals from prior model
# Patch module-level constants AFTER reload
e13.MODEL_1M = Path(probe['path'])
e13.PROXY_B_DIM = probe['proxy_B_dim']
out_file = ROOT / f"exp13_sweep_{probe['tag']}_results.json"
e13.OUT_FILE = out_file
# Patch sys.argv so argparse inside main() picks up our params
sys.argv = [
'exp13_multiscale_sweep.py',
'--subset', str(subset_days),
'--top_k', str(top_k),
'--skip_sets', 'B,Bp', # skip 5s sets (no 5s model per model variant)
]
if only_config:
sys.argv += ['--only_config', only_config, '--skip_5s']
print(f"\n{''*60}")
print(f" EXP13: {probe['tag']} z[{probe['proxy_B_dim']}] r={probe['proxy_B_r']:+.4f}")
print(f" subset={subset_days}d top_k={top_k} out={out_file.name}")
print(f"{''*60}")
t0 = time.time()
e13.main()
elapsed = time.time() - t0
# Read saved results
if not out_file.exists():
print(f"[ERROR] Results file not written: {out_file}")
return {'tag': probe['tag'], 'error': 'no results file'}
with open(out_file) as f:
raw = json.load(f)
baseline_full = raw.get('phase2', {}).get('baseline_full') or raw.get('phase1_results', [{}])[0]
p2_list = raw.get('phase2', {}).get('results', [])
if not p2_list:
# full run (subset=0): use phase1_results as p2
p2_list = raw.get('phase1_results', [])
baseline_cal = (raw.get('phase2', {}).get('baseline_full') or {}).get('Calmar', 0.0)
n_pass = sum(1 for r in p2_list if r.get('Calmar', 0) > CALMAR_THR)
best = max(p2_list, key=lambda r: r.get('Calmar', 0)) if p2_list else {}
base_roi = (raw.get('phase2', {}).get('baseline_full') or {}).get('ROI', 0.0)
return {
'tag': probe['tag'],
'val_loss': probe['val_loss'],
'proxy_B_dim': probe['proxy_B_dim'],
'proxy_B_r': probe['proxy_B_r'],
'calibration': probe['calibration'],
'baseline_calmar': baseline_cal,
'baseline_roi': base_roi,
'n_phase2': len(p2_list),
'n_pass': n_pass,
'best_config': best.get('name', '?'),
'best_roi': best.get('ROI', 0.0),
'best_calmar': best.get('Calmar', 0.0),
'best_droi': best.get('ROI', 0.0) - base_roi,
'best_ddd': best.get('DD', 0.0) - (raw.get('phase2', {}).get('baseline_full') or {}).get('DD', 0.0),
'elapsed_s': round(elapsed),
'results_file': str(out_file),
}
# ── Comparison tables ──────────────────────────────────────────────────────────
def _print_probe_table(probes: dict):
print(f"\n{'='*72}")
print(f" MODEL PROBE SUMMARY")
print(f"{'='*72}")
print(f" {'Tag':10s} {'ValLoss':>8s} {'Dim':>5s} {'r':>7s} {'Sep':>6s} {'Calibration':22s} OK?")
print(f" {'-'*72}")
for tag, p in probes.items():
ok = '' if p['always_positive'] and abs(p['proxy_B_r']) > 0.5 else ''
print(f" {tag:10s} {p['val_loss']:8.4f} "
f"z[{p['proxy_B_dim']:2d}] {p['proxy_B_r']:+7.4f} "
f"{p['separation']:6.4f} {p['calibration']:22s} {ok}")
def _print_comparison_table(results: list):
if not results:
return
print(f"\n{'='*84}")
print(f" EXP13 MULTI-MODEL FINAL COMPARISON")
print(f" Threshold: Calmar > {CALMAR_THR} | Reference: v2_BOB — 9/20 PASS dROI=+4.59pp Cal=7.87")
print(f"{'='*84}")
print(f" {'Tag':10s} {'ValLoss':>8s} {'Dim':>5s} {'r':>7s} "
f"{'Pass':>6s} {'BestCal':>8s} {'BestdROI':>9s} {'BestdDD':>8s} Best Config")
print(f" {'-'*84}")
for r in sorted(results, key=lambda x: x.get('best_calmar', 0), reverse=True):
pass_str = f"{r['n_pass']:2d}/{r['n_phase2']}"
flag = '' if r['n_pass'] > 0 else ' '
print(f" {r['tag']:10s} {r['val_loss']:8.4f} "
f"z[{r['proxy_B_dim']:2d}] {r['proxy_B_r']:+7.4f} "
f"{pass_str:>6s} {flag} {r['best_calmar']:8.3f} "
f"{r['best_droi']:+9.2f}pp {r['best_ddd']:+8.2f}pp {r['best_config']}")
print(f" {''*84}")
print(f" {'v2_bob REF':10s} {'18.0024':>8s} z[13] {'+0.9332':>7s} "
f"{'9/20':>6s}{'7.870':>8s} {'+4.59':>9s}pp {'0.00':>8s}pp A_P5_M2_W1_a0.5")
# ── Main ───────────────────────────────────────────────────────────────────────
def main():
parser = argparse.ArgumentParser(description='Multi-model exp13 test harness')
parser.add_argument('--models', nargs='+', default=None,
help='Model tags to run (default: all available)')
parser.add_argument('--probe_only', action='store_true',
help='Dim probe only — skip exp13 sweep')
parser.add_argument('--subset', type=int, default=14,
help='Phase 1 days (default 14)')
parser.add_argument('--top_k', type=int, default=20,
help='Phase 2 top-k configs (default 20)')
parser.add_argument('--fast_check', type=str, default='',
help='Skip Phase 1; run just this config on full window. '
'Default known-winner: A_P5_M2_W1_a0.5')
args = parser.parse_args()
if args.fast_check == 'winner':
args.fast_check = 'A_P5_M2_W1_a0.5' # shorthand
# Select models
tags = args.models or list(MODEL_REGISTRY.keys())
available = {t: MODEL_REGISTRY[t] for t in tags
if t in MODEL_REGISTRY and Path(MODEL_REGISTRY[t]).exists()}
skipped = [t for t in tags if t not in MODEL_REGISTRY]
missing = [t for t in MODEL_REGISTRY if t not in available and
t in (args.models or list(MODEL_REGISTRY.keys()))]
if skipped: print(f"[WARN] Unknown tags: {skipped}")
if missing: print(f"[WARN] File not found (transfer from DOLPHIN Linux): {missing}")
if not available:
print("No model files found. Check MODEL_REGISTRY or --models flag."); return
print(f"\n{''*60}")
print(f" EXP13 MULTI-MODEL SWEEP")
print(f" Models : {list(available.keys())}")
print(f" Window : {DATE_START}{DATE_END}")
print(f" Threshold: Calmar > {CALMAR_THR}")
print(f" Phase1 : {args.subset} days Phase2 top-k: {args.top_k}")
print(f"{''*60}")
# Build shared probe set
print(f"\nBuilding probe set ({DATE_START}{DATE_END})...")
probes_raw, proxy_B_arr = _build_probe_set()
print(f" {len(probes_raw)} windows proxy_B: μ={proxy_B_arr.mean():+.4f} σ={proxy_B_arr.std():.4f}")
# Step 1: probe all models
probe_reports = {tag: probe_model(tag, path, probes_raw, proxy_B_arr)
for tag, path in available.items()}
_print_probe_table(probe_reports)
if args.probe_only:
return
# Step 2: run exp13 for each usable model
sweep_results = []
for tag, probe in probe_reports.items():
if not probe['always_positive']:
print(f"\n[SKIP] {tag}: calib={probe['calibration']} — not always-positive")
continue
try:
summary = run_exp13_for_model(probe, args.subset, args.top_k,
only_config=args.fast_check or None)
sweep_results.append(summary)
except Exception as ex:
import traceback
print(f"\n[ERROR] {tag}: {ex}")
traceback.print_exc()
_print_comparison_table(sweep_results)
# Save combined summary
out = ROOT / 'exp13_model_sweep_results.json'
with open(out, 'w') as f:
json.dump({
'probes': probe_reports,
'sweep': sweep_results,
'threshold': CALMAR_THR,
'window': {'start': DATE_START, 'end': DATE_END},
'ref_v2_bob': {'n_pass': 9, 'best_droi': 4.59, 'best_calmar': 7.87},
}, f, indent=2, default=str)
print(f"\nSummary → {out}")
if __name__ == '__main__':
main()

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,46 @@
"""
exp13_v2_launcher.py — Run exp13 multiscale sweep with convnext_model_v2.json (ep=13).
Changes vs default exp13:
- MODEL_1M → convnext_model_v2.json (calibration-fixed, always-positive z[13])
- PROXY_B_DIM → 13 (z[13] is proxy_B dim in v2, vs z[10] in ep=17)
Everything else identical to exp13_multiscale_sweep.py.
Results logged to ../../exp13_v2_screening_run.log
Usage (from nautilus_dolphin/ dir):
python dvae/exp13_v2_launcher.py --subset 14 --top_k 20 # Phase 1 screening
python dvae/exp13_v2_launcher.py --subset 0 --top_k 0 # Full 56-day run
Why v2 should be better:
ep=17 z[10]: ALWAYS NEGATIVE [-1.24, -0.30] → direct scaling configs hurt
v2 z[13]: ALWAYS POSITIVE [+0.17, +1.46] → direct scaling configs work correctly
Separation (proxy_B quartiles): 0.46 (ep=17) → 0.61 (v2) — 32% improvement
"""
import sys, os
from pathlib import Path
ROOT = Path(__file__).resolve().parent.parent.parent
sys.path.insert(0, str(ROOT / 'nautilus_dolphin'))
# ── patch model path and proxy_B dim before importing main ───────────────────
MODEL_V2 = ROOT / 'nautilus_dolphin' / 'dvae' / 'convnext_model_v2.json'
PROXY_B_V2 = 13 # z[13] r=+0.9332 for v2 ep=13 (auto-confirmed by proto_v2_query.py)
assert MODEL_V2.exists(), f"v2 model not found: {MODEL_V2}"
import dvae.exp13_multiscale_sweep as e13
import dvae.convnext_sensor as cs
# Patch module-level constants before main() runs
e13.MODEL_1M = MODEL_V2
e13.PROXY_B_DIM = PROXY_B_V2
cs.PROXY_B_DIM = PROXY_B_V2 # sensor module also exports this
print(f"[v2-launcher] MODEL_1M → {MODEL_V2.name}")
print(f"[v2-launcher] PROXY_B_DIM → {PROXY_B_V2} (was 10)")
print(f"[v2-launcher] v2 ep=13 val=18.002 z[13] r=+0.933 calibration=ALWAYS_POSITIVE")
print()
if __name__ == '__main__':
e13.main()

View File

@@ -0,0 +1,684 @@
"""
exp14_sweep.py — z[13] / z_post_std / resonance-delta leverage gate sweep.
Tests three signal families against D_LIQ_GOLD baseline (56-day 5s scan data):
Family A — z[13] leverage gate:
When z[13] (proxy_B dim, always-positive in Dec-Jan 2026) exceeds a threshold,
cap the effective soft leverage below the D_LIQ 8x default.
High z[13] = high proxy_B context = expect adverse excursion → be smaller.
Family B — z_post_std OOD gate:
When z_post_std exceeds a threshold (market window is OOD / unusual),
cap leverage conservatively regardless of direction.
Family C — 2D combined gate (z[13] AND z_post_std):
Both signals gate simultaneously; take the min of their implied caps.
Family D — Resonance delta gate:
delta = live_proxy_B implied_proxy_B(z[13]) [fitted linear map]
Three scenarios (see TODO.md exp14):
delta > thr → MORE turbulent than model expects → scale DOWN
delta < -thr → calmer than model expects → scale UP (carefully)
|delta| < resonance_thr → RESONANCE: two sensors agree → MAX CONFIDENCE
(disproportionately large weight: if both say danger → strong cap,
if both say calm → allow near-full leverage)
Family E — Combined best from A + B + D.
Baseline: D_LIQ_GOLD (soft=8x, hard=9x, mc_ref=5x, margin_buffer=0.95)
ROI=181.81% DD=17.65% Calmar=10.30 T=2155
Usage:
cd nautilus_dolphin/
python dvae/exp14_sweep.py --subset 14 --top_k 20 # Phase 1 (14-day screening)
python dvae/exp14_sweep.py --subset 0 --top_k 0 # Phase 2 (full 56 days)
"""
import sys, os, time, json, warnings, argparse
import io
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8', errors='replace')
warnings.filterwarnings('ignore')
import numpy as np
import pandas as pd
from pathlib import Path
ROOT = Path(__file__).resolve().parent.parent.parent
ND_ROOT = ROOT / 'nautilus_dolphin'
sys.path.insert(0, str(ND_ROOT))
from dvae.convnext_sensor import ConvNextSensor
from nautilus_dolphin.nautilus.proxy_boost_engine import (
LiquidationGuardEngine,
D_LIQ_SOFT_CAP, D_LIQ_ABS_CAP, D_LIQ_MC_REF, D_LIQ_MARGIN_BUF,
create_d_liq_engine,
)
from nautilus_dolphin.nautilus.ob_features import (
OBFeatureEngine, compute_imbalance_nb, compute_depth_1pct_nb,
compute_depth_quality_nb, compute_fill_probability_nb, compute_spread_proxy_nb,
compute_depth_asymmetry_nb, compute_imbalance_persistence_nb,
compute_withdrawal_velocity_nb, compute_market_agreement_nb, compute_cascade_signal_nb,
)
from nautilus_dolphin.nautilus.ob_provider import MockOBProvider
from nautilus_dolphin.nautilus.adaptive_circuit_breaker import AdaptiveCircuitBreaker
from nautilus_dolphin.nautilus.alpha_asset_selector import compute_irp_nb, compute_ars_nb, rank_assets_irp_nb
from nautilus_dolphin.nautilus.alpha_bet_sizer import compute_sizing_nb
from nautilus_dolphin.nautilus.alpha_signal_generator import check_dc_nb
from mc.mc_ml import DolphinForewarner
# ── JIT warmup ────────────────────────────────────────────────────────────────
print("Warming up JIT...")
_p = np.array([1., 2., 3.], dtype=np.float64)
compute_irp_nb(_p, -1); compute_ars_nb(1., .5, .01)
rank_assets_irp_nb(np.ones((10, 2), dtype=np.float64), 8, -1, 5, 500., 20, 0.20)
compute_sizing_nb(-.03, -.02, -.05, 3., .5, 5., .20, True, True, 0.,
np.zeros(4, dtype=np.int64), np.zeros(4, dtype=np.int64),
np.zeros(5, dtype=np.float64), 0, -1, .01, .04)
check_dc_nb(_p, 3, 1, .75)
_b = np.array([100., 200., 300., 400., 500.], dtype=np.float64)
_a = np.array([110., 190., 310., 390., 510.], dtype=np.float64)
compute_imbalance_nb(_b, _a); compute_depth_1pct_nb(_b, _a)
compute_depth_quality_nb(210., 200.); compute_fill_probability_nb(1.)
compute_spread_proxy_nb(_b, _a); compute_depth_asymmetry_nb(_b, _a)
compute_imbalance_persistence_nb(np.array([.1, -.1], dtype=np.float64), 2)
compute_withdrawal_velocity_nb(np.array([100., 110.], dtype=np.float64), 1)
compute_market_agreement_nb(np.array([.1, -.05], dtype=np.float64), 2)
compute_cascade_signal_nb(np.array([-.05, -.15], dtype=np.float64), 2, -.10)
print(" JIT ready.")
MODEL_V2 = ND_ROOT / 'dvae' / 'convnext_model_v2.json'
SCANS_DIR = ROOT / 'vbt_cache'
KLINES_DIR = ROOT / 'vbt_cache_klines'
MC_MODELS = str(ROOT / 'nautilus_dolphin' / 'mc_results' / 'models')
OUT_FILE = ROOT / 'exp14_results.json'
META_COLS = {
'timestamp', 'scan_number',
'v50_lambda_max_velocity', 'v150_lambda_max_velocity',
'v300_lambda_max_velocity', 'v750_lambda_max_velocity',
'vel_div', 'instability_50', 'instability_150',
}
FEATURE_COLS = [
'v50_lambda_max_velocity','v150_lambda_max_velocity',
'v300_lambda_max_velocity','v750_lambda_max_velocity',
'vel_div','instability_50','instability_150',
]
BASE_ENGINE_KWARGS = dict(
initial_capital=25000., vel_div_threshold=-.02, vel_div_extreme=-.05,
min_leverage=.5, max_leverage=5., leverage_convexity=3.,
fraction=.20, fixed_tp_pct=.0095, stop_pct=1., max_hold_bars=120,
use_direction_confirm=True, dc_lookback_bars=7, dc_min_magnitude_bps=.75,
dc_skip_contradicts=True, dc_leverage_boost=1., dc_leverage_reduce=.5,
use_asset_selection=True, min_irp_alignment=.45,
use_sp_fees=True, use_sp_slippage=True,
sp_maker_entry_rate=.62, sp_maker_exit_rate=.50,
use_ob_edge=True, ob_edge_bps=5., ob_confirm_rate=.40,
lookback=100, use_alpha_layers=True, use_dynamic_leverage=True, seed=42,
)
D_LIQ_KWARGS = dict(
extended_soft_cap=D_LIQ_SOFT_CAP, extended_abs_cap=D_LIQ_ABS_CAP,
mc_leverage_ref=D_LIQ_MC_REF, margin_buffer=D_LIQ_MARGIN_BUF,
threshold=.35, alpha=1., adaptive_beta=True,
)
MC_BASE_CFG = {
'trial_id': 0, 'vel_div_threshold': -.020, 'vel_div_extreme': -.050,
'use_direction_confirm': True, 'dc_lookback_bars': 7, 'dc_min_magnitude_bps': .75,
'dc_skip_contradicts': True, 'dc_leverage_boost': 1.00, 'dc_leverage_reduce': .50,
'vd_trend_lookback': 10, 'min_leverage': .50, 'max_leverage': 5.00,
'leverage_convexity': 3.00, 'fraction': .20, 'use_alpha_layers': True,
'use_dynamic_leverage': True, 'fixed_tp_pct': .0095, 'stop_pct': 1.00,
'max_hold_bars': 120, 'use_sp_fees': True, 'use_sp_slippage': True,
'sp_maker_entry_rate': .62, 'sp_maker_exit_rate': .50, 'use_ob_edge': True,
'ob_edge_bps': 5.00, 'ob_confirm_rate': .40, 'ob_imbalance_bias': -.09,
'ob_depth_scale': 1.00, 'use_asset_selection': True, 'min_irp_alignment': .45,
'lookback': 100, 'acb_beta_high': .80, 'acb_beta_low': .20,
'acb_w750_threshold_pct': 60,
}
T_WIN = 32
PROXY_B_DIM = 13 # z[13] = proxy_B dim for v2 ep=13 (r=+0.933)
# ── ZLeverageGateEngine ──────────────────────────────────────────────────────
class ZLeverageGateEngine(LiquidationGuardEngine):
"""
LiquidationGuardEngine subclass that modulates the effective soft leverage
cap based on daily z[13], z_post_std, and resonance-delta signals.
Call set_day_signals(z13, z_post_std, delta) before each begin_day().
"""
def __init__(self, *args,
z13_thr: float = 1.0, # z[13] above → reduce
z13_scale: float = 0.75, # scale when z13 > thr
std_thr: float = 99.0, # z_post_std above → reduce
std_scale: float = 0.75, # scale when std > thr
delta_thr: float = 99.0, # |delta| threshold (std units)
delta_danger_scale: float = 0.80, # scale when delta > thr (danger)
delta_calm_scale: float = 1.00, # scale when delta < -thr (calm)
resonance_thr: float = 99.0, # |delta| < this → resonance
resonance_scale: float= 1.00, # scale at resonance
**kwargs):
super().__init__(*args, **kwargs)
self.z13_thr = z13_thr
self.z13_scale = z13_scale
self.std_thr = std_thr
self.std_scale = std_scale
self.delta_thr = delta_thr
self.delta_danger_scale = delta_danger_scale
self.delta_calm_scale = delta_calm_scale
self.resonance_thr = resonance_thr
self.resonance_scale = resonance_scale
# daily signals (set before each begin_day)
self._z13_today = 0.0
self._z_std_today = 1.0
self._delta_today = 0.0
self._active_scale = 1.0
def set_day_signals(self, z13: float, z_post_std: float, delta: float):
self._z13_today = z13
self._z_std_today = z_post_std
self._delta_today = delta
def begin_day(self, date_str: str, posture: str = 'APEX', direction=None) -> None:
super().begin_day(date_str, posture, direction)
# compute effective scale
scale = 1.0
z13 = self._z13_today
std = self._z_std_today
d = self._delta_today
# Family A: z[13] gate
if z13 > self.z13_thr:
scale = min(scale, self.z13_scale)
# Family B: OOD gate
if std > self.std_thr:
scale = min(scale, self.std_scale)
# Family D: resonance-delta gate (three scenarios)
if self.resonance_thr < 99.0 and abs(d) < self.resonance_thr:
# RESONANCE: both sensors agree → apply resonance_scale
# (if resonance_scale < 1 and both say danger, this amplifies caution;
# if resonance_scale >= 1 both say calm, this restores confidence)
if z13 > self.z13_thr: # resonance confirms danger
scale = min(scale, self.resonance_scale)
else: # resonance confirms calm
scale = max(scale, self.resonance_scale)
elif self.delta_thr < 99.0:
if d > self.delta_thr:
scale = min(scale, self.delta_danger_scale) # Scenario 1: danger
elif d < -self.delta_thr:
scale = max(scale, self.delta_calm_scale) # Scenario 2: calm
self._active_scale = scale
gated = max(self._extended_soft_cap * scale, 1.0)
self.bet_sizer.max_leverage = gated
self.base_max_leverage = gated
def reset(self):
super().reset()
self._z13_today = 0.0
self._z_std_today = 1.0
self._delta_today = 0.0
self._active_scale = 1.0
# ── Config generation ────────────────────────────────────────────────────────
def build_configs():
cfgs = []
# Family A: z[13] gate only
for z13_thr in [0.5, 0.8, 1.0, 1.2]:
for scale in [0.60, 0.70, 0.80, 0.90]:
cfgs.append(dict(
name=f'A_z13t{z13_thr}_s{scale}',
z13_thr=z13_thr, z13_scale=scale,
std_thr=99.0, std_scale=1.0,
delta_thr=99.0, resonance_thr=99.0,
))
# Family B: z_post_std OOD gate only
for std_thr in [0.90, 1.00, 1.10, 1.20, 1.50]:
for scale in [0.60, 0.70, 0.80, 0.90]:
cfgs.append(dict(
name=f'B_stdt{std_thr}_s{scale}',
z13_thr=99.0, z13_scale=1.0,
std_thr=std_thr, std_scale=scale,
delta_thr=99.0, resonance_thr=99.0,
))
# Family C: 2D combined (z[13] + z_post_std)
for z13_thr in [0.8, 1.0]:
for std_thr in [1.0, 1.2]:
for scale in [0.70, 0.80]:
cfgs.append(dict(
name=f'C_z13t{z13_thr}_stdt{std_thr}_s{scale}',
z13_thr=z13_thr, z13_scale=scale,
std_thr=std_thr, std_scale=scale,
delta_thr=99.0, resonance_thr=99.0,
))
# Family D: delta gate (3 scenarios)
for delta_thr in [0.25, 0.50, 1.00]:
for dscale in [0.70, 0.80, 0.90]:
# With resonance: |delta| < 0.2*delta_thr → resonance
res_thr = delta_thr * 0.25
cfgs.append(dict(
name=f'D_dt{delta_thr}_ds{dscale}_res{res_thr:.2f}',
z13_thr=99.0, z13_scale=1.0,
std_thr=99.0, std_scale=1.0,
delta_thr=delta_thr,
delta_danger_scale=dscale,
delta_calm_scale=1.0,
resonance_thr=res_thr,
resonance_scale=dscale, # resonance confirms danger → same scale
))
# Without resonance distinction
cfgs.append(dict(
name=f'D_dt{delta_thr}_ds{dscale}_nores',
z13_thr=99.0, z13_scale=1.0,
std_thr=99.0, std_scale=1.0,
delta_thr=delta_thr,
delta_danger_scale=dscale,
delta_calm_scale=1.0,
resonance_thr=99.0,
))
# Family E: combined A + B + D (best-guess params)
for z13_thr, std_thr, delta_thr, scale in [
(0.8, 1.0, 0.5, 0.75),
(1.0, 1.2, 0.5, 0.80),
(0.8, 1.0, 0.25, 0.70),
(1.0, 1.0, 0.5, 0.75),
]:
cfgs.append(dict(
name=f'E_z{z13_thr}_s{std_thr}_d{delta_thr}_sc{scale}',
z13_thr=z13_thr, z13_scale=scale,
std_thr=std_thr, std_scale=scale,
delta_thr=delta_thr,
delta_danger_scale=scale,
delta_calm_scale=1.0,
resonance_thr=delta_thr * 0.25,
resonance_scale=scale,
))
return cfgs
# ── Signal precomputation ────────────────────────────────────────────────────
def precompute_daily_signals(parquet_files_1m, sensor: ConvNextSensor):
"""
For each daily klines file, compute:
z13_mean — mean z[13] over all T_WIN windows in the day
z_std_mean — mean z_post_std
proxy_b_mean — mean raw proxy_B (instability_50 - v750_lambda_max_velocity)
Returns dict: date_str → {z13, z_post_std, proxy_b_raw}
"""
daily = {}
for f in parquet_files_1m:
date_str = Path(f).stem[:10]
try:
df = pd.read_parquet(f, columns=FEATURE_COLS).dropna()
if len(df) < T_WIN + 5:
continue
z13_vals, std_vals, pb_vals = [], [], []
for start in range(0, len(df) - T_WIN, T_WIN // 2):
window = df.iloc[start:start + T_WIN]
if len(window) < T_WIN:
continue
pb_val = float((window['instability_50'] - window['v750_lambda_max_velocity']).mean())
try:
z_mu, z_post_std = sensor.encode_window(df, start + T_WIN)
z13_vals.append(float(z_mu[PROXY_B_DIM]))
std_vals.append(float(z_post_std))
pb_vals.append(pb_val)
except Exception:
pass
if z13_vals:
daily[date_str] = {
'z13': float(np.mean(z13_vals)),
'z_post_std': float(np.mean(std_vals)),
'proxy_b_raw': float(np.mean(pb_vals)),
}
except Exception as e:
pass
return daily
def fit_delta_regression(daily_signals: dict):
"""
Fit linear map: proxy_b_raw = a * z13 + b
Returns (a, b, delta_std) — delta_std used to normalise delta to z-score units.
"""
dates = sorted(daily_signals.keys())
z13 = np.array([daily_signals[d]['z13'] for d in dates])
pb = np.array([daily_signals[d]['proxy_b_raw'] for d in dates])
# OLS
A = np.column_stack([z13, np.ones(len(z13))])
result = np.linalg.lstsq(A, pb, rcond=None)
a, b = result[0]
pb_hat = a * z13 + b
delta_raw = pb - pb_hat
delta_std = float(np.std(delta_raw)) if len(delta_raw) > 2 else 1.0
print(f" Delta regression: proxy_B = {a:.4f}*z[13] + {b:.4f} "
f"r={float(np.corrcoef(z13, pb)[0,1]):.4f} delta_std={delta_std:.4f}")
return float(a), float(b), delta_std
def add_delta(daily_signals: dict, a: float, b: float, delta_std: float):
"""Add normalised delta (z-score units) to each day's signals."""
for d, v in daily_signals.items():
raw_delta = v['proxy_b_raw'] - (a * v['z13'] + b)
v['delta'] = raw_delta / (delta_std + 1e-9) # normalised
# ── pq_data / vol helpers (same pattern as exp13) ────────────────────────────
def _load_pq_data(parquet_files):
"""Load all 5s parquet files into pq_data dict (date_str → (df, acols, dvol))."""
print("Loading 5s parquet data...")
pq_data = {}
for pf in parquet_files:
pf = Path(pf)
df = pd.read_parquet(pf)
ac = [c for c in df.columns if c not in META_COLS]
bp = df['BTCUSDT'].values if 'BTCUSDT' in df.columns else None
dv = np.full(len(df), np.nan)
if bp is not None:
for i in range(50, len(bp)):
seg = bp[max(0, i - 50):i]
if len(seg) >= 10:
dv[i] = float(np.std(np.diff(seg) / seg[:-1]))
pq_data[pf.stem] = (df, ac, dv)
print(f" Loaded {len(pq_data)} days")
return pq_data
def _compute_vol_p60(parquet_files):
pq = _load_pq_data(parquet_files[:2]) if parquet_files else {}
vols = []
for _, (_, _, dv) in pq.items():
vols.extend(dv[np.isfinite(dv)].tolist())
return float(np.percentile(vols, 60)) if vols else 0.0
def _make_ob_acb(parquet_files_paths, pq_data: dict):
"""Create fresh OBFeatureEngine + ACB + Forewarner combo for one run."""
pf_list = [Path(p) for p in parquet_files_paths]
OB_ASSETS = sorted({a for ds, (_, ac, _) in pq_data.items() for a in ac})
if not OB_ASSETS:
OB_ASSETS = ['BTCUSDT', 'ETHUSDT', 'BNBUSDT', 'SOLUSDT']
mock_ob = MockOBProvider(
imbalance_bias=-.09, depth_scale=1., assets=OB_ASSETS,
imbalance_biases={
"BTCUSDT": -.086, "ETHUSDT": -.092,
"BNBUSDT": +.05, "SOLUSDT": +.05,
},
)
ob_eng = OBFeatureEngine(mock_ob)
ob_eng.preload_date("mock", OB_ASSETS)
forewarner = DolphinForewarner(models_dir=MC_MODELS)
acb = AdaptiveCircuitBreaker()
acb.preload_w750([pf.stem for pf in pf_list])
return ob_eng, acb, forewarner
def _compute_metrics(engine, elapsed):
"""Extract ROI/DD/Calmar/T from a finished engine."""
trades = engine.trade_history
roi = (engine.capital - 25000.) / 25000. * 100.
cap_curve = [25000.]
for t_ in sorted(trades, key=lambda x: getattr(x, 'exit_bar', 0)):
cap_curve.append(cap_curve[-1] + getattr(t_, 'pnl_absolute', 0.))
cap_arr = np.array(cap_curve)
peak = np.maximum.accumulate(cap_arr)
dd = float(np.max((peak - cap_arr) / (peak + 1e-10)) * 100.)
calmar = roi / max(dd, 1e-4)
sh = getattr(engine, '_scale_history', [])
return {
'T': len(trades),
'roi': round(roi, 4),
'dd': round(dd, 4),
'calmar': round(calmar, 4),
'elapsed_s': round(elapsed, 1),
'scale_mean': round(float(np.mean(sh)), 4) if sh else 1.0,
}
# ── Single config runner ──────────────────────────────────────────────────────
def run_one(cfg: dict, daily_signals: dict, pq_data: dict,
parquet_files: list, vol_p60: float,
subset_days: int = 0) -> dict:
"""Run ZLeverageGateEngine for one config on pre-loaded pq_data."""
files = [Path(f) for f in parquet_files]
if subset_days > 0:
files = files[:subset_days]
ob_eng, acb, forewarner = _make_ob_acb([str(f) for f in files], pq_data)
engine = ZLeverageGateEngine(
**BASE_ENGINE_KWARGS,
**D_LIQ_KWARGS,
z13_thr = cfg.get('z13_thr', 99.0),
z13_scale = cfg.get('z13_scale', 1.0),
std_thr = cfg.get('std_thr', 99.0),
std_scale = cfg.get('std_scale', 1.0),
delta_thr = cfg.get('delta_thr', 99.0),
delta_danger_scale = cfg.get('delta_danger_scale', 1.0),
delta_calm_scale = cfg.get('delta_calm_scale', 1.0),
resonance_thr = cfg.get('resonance_thr', 99.0),
resonance_scale = cfg.get('resonance_scale', 1.0),
)
engine.set_ob_engine(ob_eng)
engine.set_acb(acb)
engine.set_mc_forewarner(forewarner, MC_BASE_CFG)
engine.set_esoteric_hazard_multiplier(0.)
t0 = time.time()
for pf in files:
ds = pf.stem
if ds not in pq_data:
continue
df, acols, dvol = pq_data[ds]
vol_ok = np.where(np.isfinite(dvol), dvol > vol_p60, False)
sig = daily_signals.get(ds, {})
engine.set_day_signals(
z13 = sig.get('z13', 0.0),
z_post_std = sig.get('z_post_std', 1.0),
delta = sig.get('delta', 0.0),
)
engine.process_day(ds, df, acols, vol_regime_ok=vol_ok)
return _compute_metrics(engine, time.time() - t0)
# ── Baseline runner ───────────────────────────────────────────────────────────
def run_baseline(pq_data: dict, parquet_files: list, vol_p60: float,
subset_days: int = 0) -> dict:
"""Run D_LIQ_GOLD baseline (no gate) on pre-loaded pq_data."""
files = [Path(f) for f in parquet_files]
if subset_days > 0:
files = files[:subset_days]
ob_eng, acb, forewarner = _make_ob_acb([str(f) for f in files], pq_data)
engine = create_d_liq_engine(**BASE_ENGINE_KWARGS)
engine.set_ob_engine(ob_eng)
engine.set_acb(acb)
engine.set_mc_forewarner(forewarner, MC_BASE_CFG)
engine.set_esoteric_hazard_multiplier(0.)
t0 = time.time()
for pf in files:
ds = pf.stem
if ds not in pq_data:
continue
df, acols, dvol = pq_data[ds]
vol_ok = np.where(np.isfinite(dvol), dvol > vol_p60, False)
engine.process_day(ds, df, acols, vol_regime_ok=vol_ok)
return _compute_metrics(engine, time.time() - t0)
# ── Main ─────────────────────────────────────────────────────────────────────
def main():
ap = argparse.ArgumentParser()
ap.add_argument('--subset', type=int, default=14,
help='Days for Phase 1 screening (0=full 56 days)')
ap.add_argument('--top_k', type=int, default=20,
help='Top-K configs to validate in Phase 2 (0=skip Phase 2)')
args = ap.parse_args()
print(f"exp14_sweep model=v2(ep=13) subset={args.subset} top_k={args.top_k}")
print(f" PROXY_B_DIM={PROXY_B_DIM} Families: A(z13) B(std) C(2D) D(delta) E(combined)")
# ── Load sensor ──────────────────────────────────────────────────────────
print(f"\nLoading v2 sensor from {MODEL_V2.name}...")
assert MODEL_V2.exists(), f"Model not found: {MODEL_V2}"
sensor = ConvNextSensor(str(MODEL_V2))
print(f" ep={sensor.epoch} val={sensor.val_loss:.4f} z_dim={sensor.z_dim}")
# ── Load data ─────────────────────────────────────────────────────────────
print("\nLoading data files...")
scans_5s = sorted(Path(SCANS_DIR).glob('*.parquet'))
klines_1m = sorted(Path(KLINES_DIR).glob('*.parquet'))
# align to same 56-day window (2025-12-31 to 2026-02-25)
scans_5s = [f for f in scans_5s if '2025-12-31' <= f.stem[:10] <= '2026-02-25']
klines_1m = [f for f in klines_1m if '2025-12-31' <= f.stem[:10] <= '2026-02-25']
print(f" 5s scans: {len(scans_5s)} 1m klines: {len(klines_1m)}")
# ── Pre-load pq_data (once, reused for every run) ─────────────────────────
print("\nPre-loading 5s parquet data (done once for all runs)...")
pq_data_full = _load_pq_data([str(f) for f in scans_5s])
# vol_p60 from full dataset
all_vols = []
for _, (_, _, dv) in pq_data_full.items():
all_vols.extend(dv[np.isfinite(dv)].tolist())
vol_p60 = float(np.percentile(all_vols, 60)) if all_vols else 0.0
print(f" vol_p60={vol_p60:.6f}")
# ── Precompute daily signals ──────────────────────────────────────────────
print("\nPrecomputing daily z[13] / z_post_std signals from 1m klines...")
t0 = time.time()
daily_sigs = precompute_daily_signals([str(f) for f in klines_1m], sensor)
print(f" {len(daily_sigs)} days with signals ({time.time()-t0:.0f}s)")
print("\nFitting delta regression (proxy_B = a*z[13] + b)...")
a, b, delta_std = fit_delta_regression(daily_sigs)
add_delta(daily_sigs, a, b, delta_std)
deltas = [v['delta'] for v in daily_sigs.values()]
print(f" delta stats: mean={np.mean(deltas):+.3f} std={np.std(deltas):.3f} "
f"min={np.min(deltas):+.3f} max={np.max(deltas):+.3f}")
# ── Build configs ─────────────────────────────────────────────────────────
configs = build_configs()
print(f"\n{len(configs)} configs across 5 families")
# ── Baseline ──────────────────────────────────────────────────────────────
print("\nRunning baseline (D_LIQ_GOLD)...")
t0 = time.time()
baseline = run_baseline(pq_data_full, [str(f) for f in scans_5s], vol_p60, args.subset)
bROI = baseline.get('roi', 0.0)
bDD = baseline.get('dd', 0.0)
bCal = baseline.get('calmar', 0.0)
bT = baseline.get('T', 0)
print(f" Baseline: T={bT} ROI={bROI:.2f}% DD={bDD:.2f}% Calmar={bCal:.2f} "
f"({time.time()-t0:.0f}s)")
# ── Phase 1: screening ───────────────────────────────────────────────────
print(f"\n{'='*65}")
print(f"Phase 1 — screening {len(configs)} configs on {args.subset or 56}-day window")
print(f"{'='*65}")
results = []
for i, cfg in enumerate(configs):
t0 = time.time()
res = run_one(cfg, daily_sigs, pq_data_full, [str(f) for f in scans_5s],
vol_p60, args.subset)
roi = res.get('roi', 0.0)
dd = res.get('dd', 0.0)
cal = res.get('calmar', 0.0)
T = res.get('T', 0)
dROI = roi - bROI
dDD = dd - bDD
dCal = cal - bCal
elapsed = time.time() - t0
print(f"[{i+1:3d}/{len(configs)}] {cfg['name']}")
print(f" T={T} ROI={roi:.2f}% DD={dd:.2f}% Calmar={cal:.2f} "
f"dROI={dROI:+.2f}pp dDD={dDD:+.2f}pp dCal={dCal:+.2f} "
f"({elapsed:.0f}s)")
results.append({**cfg, 'roi': roi, 'dd': dd, 'calmar': cal, 'trades': T,
'dROI': dROI, 'dDD': dDD, 'dCal': dCal})
results.sort(key=lambda x: x['dROI'], reverse=True)
print(f"\nPhase 1 Top 10:")
for r in results[:10]:
print(f" dROI={r['dROI']:+.2f}pp ROI={r['roi']:.2f}% "
f"Cal={r['calmar']:.2f} {r['name']}")
# ── Phase 2: full validation ─────────────────────────────────────────────
if args.top_k > 0 and args.subset > 0:
top_cfgs = results[:args.top_k]
print(f"\n{'='*65}")
print(f"Phase 2 — validating top {len(top_cfgs)} configs on FULL 56 days")
print(f"{'='*65}")
print("\nRunning baseline (full 56 days)...")
t0 = time.time()
base_full = run_baseline(pq_data_full, [str(f) for f in scans_5s], vol_p60, 0)
bROI_f = base_full.get('roi', 0.0)
bDD_f = base_full.get('dd', 0.0)
bCal_f = base_full.get('calmar', 0.0)
bT_f = base_full.get('T', 0)
print(f" Baseline full: T={bT_f} ROI={bROI_f:.2f}% DD={bDD_f:.2f}% "
f"Calmar={bCal_f:.2f} ({time.time()-t0:.0f}s)")
p2_results = []
for i, cfg in enumerate(top_cfgs):
t0 = time.time()
res = run_one(cfg, daily_sigs, pq_data_full, [str(f) for f in scans_5s],
vol_p60, 0)
roi = res.get('roi', 0.0)
dd = res.get('dd', 0.0)
cal = res.get('calmar', 0.0)
T = res.get('T', 0)
dROI = roi - bROI_f
dDD = dd - bDD_f
dCal = cal - bCal_f
elapsed = time.time() - t0
print(f"[P2 {i+1:2d}/{len(top_cfgs)}] {cfg['name']}")
print(f" T={T} ROI={roi:.2f}% DD={dd:.2f}% Calmar={cal:.2f} "
f"dROI={dROI:+.2f}pp dDD={dDD:+.2f}pp dCal={dCal:+.2f} "
f"({elapsed:.0f}s)")
p2_results.append({**cfg, 'roi': roi, 'dd': dd, 'calmar': cal,
'trades': T, 'dROI': dROI, 'dDD': dDD, 'dCal': dCal})
p2_results.sort(key=lambda x: x['dROI'], reverse=True)
print(f"\nPhase 2 Final Ranking:")
for r in p2_results[:10]:
beat = r['calmar'] > bCal_f * 1.02
print(f" dROI={r['dROI']:+.2f}pp dCal={r['dCal']:+.2f} "
f"{'✓ BEATS' if beat else ''} baseline {r['name']}")
# Save results
out = {
'baseline_full': {'roi': bROI_f, 'dd': bDD_f, 'calmar': bCal_f, 'trades': bT_f},
'phase2': p2_results,
'delta_regression': {'a': a, 'b': b, 'delta_std': delta_std},
}
out_path = ROOT / 'exp14_results.json'
json.dump(out, open(out_path, 'w'), indent=2)
print(f"\nResults saved to {out_path}")
print(f"\n[DONE]")
if __name__ == '__main__':
main()

View File

@@ -0,0 +1,605 @@
"""
exp15_stop_gate.py — z[13]-gated per-trade stop tightening AND TP extension.
Tests whether per-trade exit overrides based on daily z[13] (proxy_B dim from v2 model)
can improve the D_LIQ_GOLD baseline.
Families:
A — Stop tightening only (high z13 → tight stop) [12 configs]
B — TP extension only (low z13 → higher TP) [20 configs]
C — Hold extension only (low z13 → more bars) [12 configs]
D — TP + Hold combined (low z13 → both) [12 configs]
E — Asymmetric bidirectional (HIGH→tight stop, LOW→higher TP) [6 configs]
Baseline: D_LIQ_GOLD (soft=8x, hard=9x, mc_ref=5x, margin_buffer=0.95)
Usage:
cd nautilus_dolphin/
python dvae/exp15_stop_gate.py --subset 14 --top_k 20 # Phase 1 (14-day screening)
python dvae/exp15_stop_gate.py --subset 0 --top_k 0 # Phase 2 (full 56 days)
"""
import sys, os, time, json, warnings, argparse
import io
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8', errors='replace', line_buffering=True)
warnings.filterwarnings('ignore')
import numpy as np
import pandas as pd
from pathlib import Path
ROOT = Path(__file__).resolve().parent.parent.parent
ND_ROOT = ROOT / 'nautilus_dolphin'
sys.path.insert(0, str(ND_ROOT))
from dvae.convnext_sensor import ConvNextSensor
from nautilus_dolphin.nautilus.proxy_boost_engine import (
LiquidationGuardEngine,
D_LIQ_SOFT_CAP, D_LIQ_ABS_CAP, D_LIQ_MC_REF, D_LIQ_MARGIN_BUF,
create_d_liq_engine,
)
from nautilus_dolphin.nautilus.ob_features import (
OBFeatureEngine, compute_imbalance_nb, compute_depth_1pct_nb,
compute_depth_quality_nb, compute_fill_probability_nb, compute_spread_proxy_nb,
compute_depth_asymmetry_nb, compute_imbalance_persistence_nb,
compute_withdrawal_velocity_nb, compute_market_agreement_nb, compute_cascade_signal_nb,
)
from nautilus_dolphin.nautilus.ob_provider import MockOBProvider
from nautilus_dolphin.nautilus.adaptive_circuit_breaker import AdaptiveCircuitBreaker
from nautilus_dolphin.nautilus.alpha_asset_selector import compute_irp_nb, compute_ars_nb, rank_assets_irp_nb
from nautilus_dolphin.nautilus.alpha_bet_sizer import compute_sizing_nb
from nautilus_dolphin.nautilus.alpha_signal_generator import check_dc_nb
from mc.mc_ml import DolphinForewarner
# ── JIT warmup ────────────────────────────────────────────────────────────────
print("Warming up JIT...")
_p = np.array([1., 2., 3.], dtype=np.float64)
compute_irp_nb(_p, -1); compute_ars_nb(1., .5, .01)
rank_assets_irp_nb(np.ones((10, 2), dtype=np.float64), 8, -1, 5, 500., 20, 0.20)
compute_sizing_nb(-.03, -.02, -.05, 3., .5, 5., .20, True, True, 0.,
np.zeros(4, dtype=np.int64), np.zeros(4, dtype=np.int64),
np.zeros(5, dtype=np.float64), 0, -1, .01, .04)
check_dc_nb(_p, 3, 1, .75)
_b = np.array([100., 200., 300., 400., 500.], dtype=np.float64)
_a = np.array([110., 190., 310., 390., 510.], dtype=np.float64)
compute_imbalance_nb(_b, _a); compute_depth_1pct_nb(_b, _a)
compute_depth_quality_nb(210., 200.); compute_fill_probability_nb(1.)
compute_spread_proxy_nb(_b, _a); compute_depth_asymmetry_nb(_b, _a)
compute_imbalance_persistence_nb(np.array([.1, -.1], dtype=np.float64), 2)
compute_withdrawal_velocity_nb(np.array([100., 110.], dtype=np.float64), 1)
compute_market_agreement_nb(np.array([.1, -.05], dtype=np.float64), 2)
compute_cascade_signal_nb(np.array([-.05, -.15], dtype=np.float64), 2, -.10)
print(" JIT ready.")
MODEL_V2 = ND_ROOT / 'dvae' / 'convnext_model_v2.json'
SCANS_DIR = ROOT / 'vbt_cache'
KLINES_DIR = ROOT / 'vbt_cache_klines'
MC_MODELS = str(ROOT / 'nautilus_dolphin' / 'mc_results' / 'models')
OUT_FILE = ROOT / 'exp15_results.json'
META_COLS = {
'timestamp', 'scan_number',
'v50_lambda_max_velocity', 'v150_lambda_max_velocity',
'v300_lambda_max_velocity', 'v750_lambda_max_velocity',
'vel_div', 'instability_50', 'instability_150',
}
FEATURE_COLS = [
'v50_lambda_max_velocity','v150_lambda_max_velocity',
'v300_lambda_max_velocity','v750_lambda_max_velocity',
'vel_div','instability_50','instability_150',
]
BASE_ENGINE_KWARGS = dict(
initial_capital=25000., vel_div_threshold=-.02, vel_div_extreme=-.05,
min_leverage=.5, max_leverage=5., leverage_convexity=3.,
fraction=.20, fixed_tp_pct=.0099, stop_pct=1., max_hold_bars=120,
use_direction_confirm=True, dc_lookback_bars=7, dc_min_magnitude_bps=.75,
dc_skip_contradicts=True, dc_leverage_boost=1., dc_leverage_reduce=.5,
use_asset_selection=True, min_irp_alignment=.45,
use_sp_fees=True, use_sp_slippage=True,
sp_maker_entry_rate=.62, sp_maker_exit_rate=.50,
use_ob_edge=True, ob_edge_bps=5., ob_confirm_rate=.40,
lookback=100, use_alpha_layers=True, use_dynamic_leverage=True, seed=42,
)
D_LIQ_KWARGS = dict(
extended_soft_cap=D_LIQ_SOFT_CAP, extended_abs_cap=D_LIQ_ABS_CAP,
mc_leverage_ref=D_LIQ_MC_REF, margin_buffer=D_LIQ_MARGIN_BUF,
threshold=.35, alpha=1., adaptive_beta=True,
)
MC_BASE_CFG = {
'trial_id': 0, 'vel_div_threshold': -.020, 'vel_div_extreme': -.050,
'use_direction_confirm': True, 'dc_lookback_bars': 7, 'dc_min_magnitude_bps': .75,
'dc_skip_contradicts': True, 'dc_leverage_boost': 1.00, 'dc_leverage_reduce': .50,
'vd_trend_lookback': 10, 'min_leverage': .50, 'max_leverage': 5.00,
'leverage_convexity': 3.00, 'fraction': .20, 'use_alpha_layers': True,
'use_dynamic_leverage': True, 'fixed_tp_pct': .0099, 'stop_pct': 1.00,
'max_hold_bars': 120, 'use_sp_fees': True, 'use_sp_slippage': True,
'sp_maker_entry_rate': .62, 'sp_maker_exit_rate': .50, 'use_ob_edge': True,
'ob_edge_bps': 5.00, 'ob_confirm_rate': .40, 'ob_imbalance_bias': -.09,
'ob_depth_scale': 1.00, 'use_asset_selection': True, 'min_irp_alignment': .45,
'lookback': 100, 'acb_beta_high': .80, 'acb_beta_low': .20,
'acb_w750_threshold_pct': 60,
}
T_WIN = 32
PROXY_B_DIM = 13 # z[13] = proxy_B dim for v2 ep=13 (r=+0.933)
# ── ZExitGateEngine ───────────────────────────────────────────────────────────
class ZExitGateEngine(LiquidationGuardEngine):
"""
Per-trade TP extension (low z13) and/or stop tightening (high z13).
Uses z[13] (proxy_B dim from v2 model) as a day-level regime signal:
HIGH z13 (> high_thr) = high adversity → tight stop (defense)
LOW z13 (< low_thr) = calm/trending → higher TP + extended hold (offense)
MID z13 = no override (baseline exit logic)
The _try_entry() override ensures overrides apply to EVERY entry on that day,
not just the first (which is what _pending_* would do if set only once).
"""
def __init__(self, *args,
# Stop tightening (high adversity)
high_thr: float = 99.0, # z13 > this → tight stop
tight_stop_pct: float = 0.005,
# TP extension (calm/trending)
low_thr: float = -99.0, # z13 < this → higher TP
wide_tp_pct: float = None, # None = no TP override
extended_hold: int = None, # None = no hold override
**kwargs):
super().__init__(*args, **kwargs)
self.high_thr = high_thr
self.tight_stop_pct = tight_stop_pct
self.low_thr = low_thr
self.wide_tp_pct = wide_tp_pct
self.extended_hold = extended_hold
self._z13_today = 0.0
self._n_stop_triggered = 0
self._n_tp_triggered = 0
self._n_hold_triggered = 0
def set_day_z13(self, z13: float):
self._z13_today = z13
def _try_entry(self, *args, **kwargs):
z = self._z13_today
# Set overrides fresh before EVERY entry (not just the first)
if z > self.high_thr:
self._pending_stop_override = self.tight_stop_pct
self._pending_tp_override = None
self._pending_max_hold_override = None
self._n_stop_triggered += 1
elif z < self.low_thr:
self._pending_stop_override = None
self._pending_tp_override = self.wide_tp_pct
self._pending_max_hold_override = self.extended_hold
self._n_tp_triggered += 1
if self.extended_hold:
self._n_hold_triggered += 1
else:
self._pending_stop_override = None
self._pending_tp_override = None
self._pending_max_hold_override = None
return super()._try_entry(*args, **kwargs)
def get_trigger_counts(self):
return {
'n_stop_triggered': self._n_stop_triggered,
'n_tp_triggered': self._n_tp_triggered,
'n_hold_triggered': self._n_hold_triggered,
}
# ── Config generation ─────────────────────────────────────────────────────────
def generate_configs():
"""Generate all 62 configs for exp15."""
configs = []
# FAMILY A — Stop tightening only [12 configs]
high_thrs = [0.5, 0.8, 1.0, 1.2]
tight_stops = [0.003, 0.005, 0.010]
for high_thr in high_thrs:
for tight_stop in tight_stops:
name = f'A_ht{high_thr}_stop{tight_stop}'
configs.append({
'name': name,
'family': 'A',
'high_thr': high_thr,
'tight_stop_pct': tight_stop,
'low_thr': -99.0,
'wide_tp_pct': None,
'extended_hold': None,
})
# FAMILY B — TP extension only [20 configs]
low_thrs = [-99.0, 0.3, 0.0, -0.3, -0.5]
wide_tps = [0.0110, 0.0120, 0.0130, 0.0150]
for low_thr in low_thrs:
for wide_tp in wide_tps:
name = f'B_lt{low_thr}_tp{wide_tp:.4f}'
configs.append({
'name': name,
'family': 'B',
'high_thr': 99.0,
'tight_stop_pct': 0.005,
'low_thr': low_thr,
'wide_tp_pct': wide_tp,
'extended_hold': None,
})
# FAMILY C — Hold extension only [12 configs]
low_thrs = [-99.0, 0.3, 0.0, -0.3]
extended_holds = [150, 180, 240]
for low_thr in low_thrs:
for hold in extended_holds:
name = f'C_lt{low_thr}_hold{hold}'
configs.append({
'name': name,
'family': 'C',
'high_thr': 99.0,
'tight_stop_pct': 0.005,
'low_thr': low_thr,
'wide_tp_pct': None,
'extended_hold': hold,
})
# FAMILY D — TP + Hold combined [12 configs]
combos = [
(-99.0, 0.0120, 150), (-99.0, 0.0130, 150), (-99.0, 0.0150, 180),
(-99.0, 0.0120, 180), (-99.0, 0.0130, 180), (-99.0, 0.0150, 240),
(0.3, 0.0120, 150), (0.3, 0.0130, 150), (0.3, 0.0150, 180),
(0.3, 0.0120, 180), (0.3, 0.0130, 180), (0.3, 0.0150, 240),
]
for low_thr, wide_tp, hold in combos:
name = f'D_lt{low_thr}_tp{wide_tp:.4f}_hold{hold}'
configs.append({
'name': name,
'family': 'D',
'high_thr': 99.0,
'tight_stop_pct': 0.005,
'low_thr': low_thr,
'wide_tp_pct': wide_tp,
'extended_hold': hold,
})
# FAMILY E — Asymmetric bidirectional [6 configs]
combos = [
(1.0, 0.005, 0.0, 0.0120, None),
(1.0, 0.005, 0.0, 0.0130, None),
(1.0, 0.005, -0.3, 0.0120, None),
(1.0, 0.005, -0.3, 0.0130, None),
(1.0, 0.005, 0.0, 0.0120, 150),
(1.0, 0.005, -0.3, 0.0130, 150),
]
for high_thr, tight_stop, low_thr, wide_tp, hold in combos:
name = f'E_ht{high_thr}_stop{tight_stop}_lt{low_thr}_tp{wide_tp:.4f}'
if hold:
name += f'_hold{hold}'
configs.append({
'name': name,
'family': 'E',
'high_thr': high_thr,
'tight_stop_pct': tight_stop,
'low_thr': low_thr,
'wide_tp_pct': wide_tp,
'extended_hold': hold,
})
return configs
# ── Data helpers (process_day pattern — same as exp14) ────────────────────────
def _load_pq_data(parquet_files):
"""Load all 5s parquet files into pq_data dict (date_str → (df, acols, dvol))."""
print("Loading 5s parquet data...")
pq_data = {}
for pf in parquet_files:
pf = Path(pf)
df = pd.read_parquet(pf)
ac = [c for c in df.columns if c not in META_COLS]
bp = df['BTCUSDT'].values if 'BTCUSDT' in df.columns else None
dv = np.full(len(df), np.nan)
if bp is not None:
for i in range(50, len(bp)):
seg = bp[max(0, i - 50):i]
if len(seg) >= 10:
dv[i] = float(np.std(np.diff(seg) / seg[:-1]))
pq_data[pf.stem] = (df, ac, dv)
print(f" Loaded {len(pq_data)} days")
return pq_data
def _make_ob_acb(parquet_files_paths, pq_data: dict):
"""Create fresh OBFeatureEngine + ACB + Forewarner combo for one run."""
pf_list = [Path(p) for p in parquet_files_paths]
OB_ASSETS = sorted({a for ds, (_, ac, _) in pq_data.items() for a in ac})
if not OB_ASSETS:
OB_ASSETS = ['BTCUSDT', 'ETHUSDT', 'BNBUSDT', 'SOLUSDT']
mock_ob = MockOBProvider(
imbalance_bias=-.09, depth_scale=1., assets=OB_ASSETS,
imbalance_biases={
"BTCUSDT": -.086, "ETHUSDT": -.092,
"BNBUSDT": +.05, "SOLUSDT": +.05,
},
)
ob_eng = OBFeatureEngine(mock_ob)
ob_eng.preload_date("mock", OB_ASSETS)
forewarner = DolphinForewarner(models_dir=MC_MODELS)
acb = AdaptiveCircuitBreaker()
acb.preload_w750([pf.stem for pf in pf_list])
return ob_eng, acb, forewarner
def _compute_metrics(engine, elapsed):
"""Extract ROI/DD/Calmar/T from a finished engine."""
trades = engine.trade_history
roi = (engine.capital - 25000.) / 25000. * 100.
cap_curve = [25000.]
for t_ in sorted(trades, key=lambda x: getattr(x, 'exit_bar', 0)):
cap_curve.append(cap_curve[-1] + getattr(t_, 'pnl_absolute', 0.))
cap_arr = np.array(cap_curve)
peak = np.maximum.accumulate(cap_arr)
dd = float(np.max((peak - cap_arr) / (peak + 1e-10)) * 100.)
calmar = roi / max(dd, 1e-4)
sh = getattr(engine, '_scale_history', [])
return {
'T': len(trades),
'roi': round(roi, 4),
'dd': round(dd, 4),
'calmar': round(calmar, 4),
'elapsed_s': round(elapsed, 1),
'scale_mean': round(float(np.mean(sh)), 4) if sh else 1.0,
}
def precompute_z13_per_day(parquet_files_1m, sensor):
"""
Compute daily mean z[13] from 1m klines files.
Returns dict: date_str → float (mean z[13] over T_WIN windows in that day)
"""
print("Precomputing daily z[13] from 1m klines...")
z13_by_date = {}
for f in parquet_files_1m:
date_str = Path(f).stem[:10]
try:
df = pd.read_parquet(f, columns=FEATURE_COLS).dropna()
if len(df) < T_WIN + 5:
continue
z13_vals = []
for start in range(0, len(df) - T_WIN, T_WIN // 2):
try:
z_mu, _ = sensor.encode_window(df, start + T_WIN)
z13_vals.append(float(z_mu[PROXY_B_DIM]))
except Exception:
pass
if z13_vals:
z13_by_date[date_str] = float(np.mean(z13_vals))
except Exception:
pass
print(f" {len(z13_by_date)} days with z[13]")
return z13_by_date
# ── Single config runner ───────────────────────────────────────────────────────
def run_one(cfg: dict, z13_by_date: dict, pq_data: dict,
parquet_files: list, vol_p60: float,
subset_days: int = 0) -> dict:
"""Run ZExitGateEngine for one config using process_day API."""
files = [Path(f) for f in parquet_files]
if subset_days > 0:
files = files[:subset_days]
ob_eng, acb, forewarner = _make_ob_acb([str(f) for f in files], pq_data)
engine = ZExitGateEngine(
**BASE_ENGINE_KWARGS,
**D_LIQ_KWARGS,
high_thr = cfg['high_thr'],
tight_stop_pct = cfg['tight_stop_pct'],
low_thr = cfg['low_thr'],
wide_tp_pct = cfg['wide_tp_pct'],
extended_hold = cfg['extended_hold'],
)
engine.set_ob_engine(ob_eng)
engine.set_acb(acb)
engine.set_mc_forewarner(forewarner, MC_BASE_CFG)
engine.set_esoteric_hazard_multiplier(0.)
t0 = time.time()
for pf in files:
ds = pf.stem
if ds not in pq_data:
continue
df, acols, dvol = pq_data[ds]
vol_ok = np.where(np.isfinite(dvol), dvol > vol_p60, False)
engine.set_day_z13(z13_by_date.get(ds, 0.0))
engine.process_day(ds, df, acols, vol_regime_ok=vol_ok)
result = _compute_metrics(engine, time.time() - t0)
result.update(engine.get_trigger_counts())
return result
def run_baseline(pq_data: dict, parquet_files: list, vol_p60: float,
subset_days: int = 0) -> dict:
"""Run D_LIQ_GOLD baseline (no override) on pre-loaded pq_data."""
files = [Path(f) for f in parquet_files]
if subset_days > 0:
files = files[:subset_days]
ob_eng, acb, forewarner = _make_ob_acb([str(f) for f in files], pq_data)
engine = create_d_liq_engine(**BASE_ENGINE_KWARGS)
engine.set_ob_engine(ob_eng)
engine.set_acb(acb)
engine.set_mc_forewarner(forewarner, MC_BASE_CFG)
engine.set_esoteric_hazard_multiplier(0.)
t0 = time.time()
for pf in files:
ds = pf.stem
if ds not in pq_data:
continue
df, acols, dvol = pq_data[ds]
vol_ok = np.where(np.isfinite(dvol), dvol > vol_p60, False)
engine.process_day(ds, df, acols, vol_regime_ok=vol_ok)
return _compute_metrics(engine, time.time() - t0)
# ── Main ─────────────────────────────────────────────────────────────────────
def main():
parser = argparse.ArgumentParser()
parser.add_argument('--subset', type=int, default=14, help='Days for Phase 1 (0=all)')
parser.add_argument('--top_k', type=int, default=20, help='Top configs for Phase 2')
args = parser.parse_args()
print("=" * 80)
print("exp15 — z[13]-Gated Exit Manager: Stop Tightening AND TP Extension")
print("=" * 80)
# ── Load sensor ──────────────────────────────────────────────────────────
print(f"\nLoading v2 model from {MODEL_V2}...")
assert MODEL_V2.exists(), f"Model not found: {MODEL_V2}"
sensor = ConvNextSensor(str(MODEL_V2))
print(f" Loaded: epoch={sensor.epoch} val_loss={sensor.val_loss:.4f} z_dim={sensor.z_dim}")
# ── Load data files ───────────────────────────────────────────────────────
print("\nLoading data files...")
scans_5s = sorted(Path(SCANS_DIR).glob('*.parquet'))
klines_1m = sorted(Path(KLINES_DIR).glob('*.parquet'))
scans_5s = [f for f in scans_5s if '2025-12-31' <= f.stem[:10] <= '2026-02-25']
klines_1m = [f for f in klines_1m if '2025-12-31' <= f.stem[:10] <= '2026-02-25']
print(f" 5s scans: {len(scans_5s)} 1m klines: {len(klines_1m)}")
# ── Pre-load pq_data (once, reused for every run) ─────────────────────────
print("\nPre-loading 5s parquet data (done once for all runs)...")
pq_data_full = _load_pq_data([str(f) for f in scans_5s])
all_vols = []
for _, (_, _, dv) in pq_data_full.items():
all_vols.extend(dv[np.isfinite(dv)].tolist())
vol_p60 = float(np.percentile(all_vols, 60)) if all_vols else 0.0
print(f" vol_p60={vol_p60:.6f}")
# ── Precompute z[13] per day ──────────────────────────────────────────────
z13_by_date = precompute_z13_per_day([str(f) for f in klines_1m], sensor)
# ── Generate configs ──────────────────────────────────────────────────────
configs = generate_configs()
print(f"\nTotal configs: {len(configs)}")
for family in ['A', 'B', 'C', 'D', 'E']:
n = len([c for c in configs if c['family'] == family])
print(f" Family {family}: {n} configs")
# ── Baseline ──────────────────────────────────────────────────────────────
print("\nRunning BASELINE (D_LIQ_GOLD)...")
t0 = time.time()
baseline = run_baseline(pq_data_full, [str(f) for f in scans_5s], vol_p60, args.subset)
bROI = baseline.get('roi', 0.0)
bDD = baseline.get('dd', 0.0)
bCal = baseline.get('calmar', 0.0)
bT = baseline.get('T', 0)
print(f" Baseline: T={bT} ROI={bROI:.2f}% DD={bDD:.2f}% Calmar={bCal:.2f} ({time.time()-t0:.0f}s)")
# ── Phase 1: screening ────────────────────────────────────────────────────
print(f"\n{'='*65}")
print(f"Phase 1 — screening {len(configs)} configs on {args.subset or 56}-day window")
print(f"{'='*65}")
results = []
for i, cfg in enumerate(configs):
t0 = time.time()
res = run_one(cfg, z13_by_date, pq_data_full, [str(f) for f in scans_5s],
vol_p60, args.subset)
roi = res.get('roi', 0.0)
dd = res.get('dd', 0.0)
cal = res.get('calmar', 0.0)
T = res.get('T', 0)
n_stop = res.get('n_stop_triggered', 0)
n_tp = res.get('n_tp_triggered', 0)
n_hold = res.get('n_hold_triggered', 0)
dROI = roi - bROI
dDD = dd - bDD
dCal = cal - bCal
elapsed = time.time() - t0
print(f"[{i+1:3d}/{len(configs)}] {cfg['name']}")
print(f" T={T} ROI={roi:.2f}% DD={dd:.2f}% Calmar={cal:.2f} "
f"dROI={dROI:+.2f}pp dDD={dDD:+.2f}pp dCal={dCal:+.2f} "
f"stop={n_stop} tp={n_tp} hold={n_hold} ({elapsed:.0f}s)")
results.append({**cfg, 'roi': roi, 'dd': dd, 'calmar': cal, 'trades': T,
'dROI': dROI, 'dDD': dDD, 'dCal': dCal,
'n_stop_triggered': n_stop, 'n_tp_triggered': n_tp,
'n_hold_triggered': n_hold})
results.sort(key=lambda x: x['dROI'], reverse=True)
print(f"\nPhase 1 Top 10:")
for r in results[:10]:
print(f" dROI={r['dROI']:+.2f}pp ROI={r['roi']:.2f}% "
f"Cal={r['calmar']:.2f} stop={r['n_stop_triggered']} {r['name']}")
# ── Phase 2: full validation ──────────────────────────────────────────────
p2_results = []
if args.top_k > 0 and args.subset > 0:
top_cfgs = [c for c in results[:args.top_k]]
print(f"\n{'='*65}")
print(f"Phase 2 — validating top {len(top_cfgs)} configs on FULL 56 days")
print(f"{'='*65}")
print("\nRunning baseline (full 56 days)...")
t0 = time.time()
base_full = run_baseline(pq_data_full, [str(f) for f in scans_5s], vol_p60, 0)
bROI_f = base_full.get('roi', 0.0)
bDD_f = base_full.get('dd', 0.0)
bCal_f = base_full.get('calmar', 0.0)
bT_f = base_full.get('T', 0)
print(f" Baseline full: T={bT_f} ROI={bROI_f:.2f}% DD={bDD_f:.2f}% "
f"Calmar={bCal_f:.2f} ({time.time()-t0:.0f}s)")
for i, cfg in enumerate(top_cfgs):
t0 = time.time()
res = run_one(cfg, z13_by_date, pq_data_full,
[str(f) for f in scans_5s], vol_p60, 0)
roi = res.get('roi', 0.0)
dd = res.get('dd', 0.0)
cal = res.get('calmar', 0.0)
T = res.get('T', 0)
n_stop = res.get('n_stop_triggered', 0)
n_tp = res.get('n_tp_triggered', 0)
dROI = roi - bROI_f
dDD = dd - bDD_f
dCal = cal - bCal_f
print(f"[P2 {i+1:2d}/{len(top_cfgs)}] {cfg['name']}")
print(f" T={T} ROI={roi:.2f}% DD={dd:.2f}% Calmar={cal:.2f} "
f"dROI={dROI:+.2f}pp dDD={dDD:+.2f}pp dCal={dCal:+.2f} "
f"stop={n_stop} tp={n_tp} ({time.time()-t0:.0f}s)")
p2_results.append({**cfg, 'roi': roi, 'dd': dd, 'calmar': cal, 'trades': T,
'dROI': dROI, 'dDD': dDD, 'dCal': dCal,
'n_stop_triggered': n_stop, 'n_tp_triggered': n_tp})
# ── Save results ──────────────────────────────────────────────────────────
output = {
'baseline_p1': baseline,
'p1_results': results,
'p2_results': p2_results,
'phase': '1+2' if p2_results else '1',
'n_configs': len(configs),
}
with open(OUT_FILE, 'w') as f:
json.dump(output, f, indent=2, default=str)
print(f"\nResults saved to {OUT_FILE}")
if p2_results:
p2_sorted = sorted(p2_results, key=lambda x: x['dROI'], reverse=True)
print(f"\nPhase 2 Top 5 by ROI delta:")
for r in p2_sorted[:5]:
print(f" dROI={r['dROI']:+.2f}pp DD={r['dd']:.2f}% Cal={r['calmar']:.2f} "
f"stop={r['n_stop_triggered']} {r['name']}")
print("\n[DONE]")
if __name__ == '__main__':
main()

View File

@@ -0,0 +1,197 @@
"""
Exp 1 — proxy_B-driven position sizing.
Instead of binary gating, scale bet_sizer.base_fraction proportionally to
the proxy_B percentile in a rolling window.
High proxy_B (stress incoming) → scale UP (better mean-reversion environment)
Low proxy_B (calm market) → scale DOWN (weaker signal)
Variants tested:
S1: [0.50x, 1.50x] linear, window=500
S2: [0.25x, 2.00x] linear, window=500 (more aggressive)
S3: [0.50x, 1.50x] linear, window=1000 (slower adaptation)
S4: [0.50x, 1.50x] clipped at p25/p75 (only extreme ends change)
Results logged to exp1_proxy_sizing_results.json.
"""
import sys, time
sys.stdout.reconfigure(encoding='utf-8', errors='replace')
from pathlib import Path
import numpy as np
_HERE = Path(__file__).resolve().parent
sys.path.insert(0, str(_HERE.parent))
from exp_shared import (
ensure_jit, ENGINE_KWARGS, GOLD,
load_data, load_forewarner, run_backtest, print_table, log_results
)
from nautilus_dolphin.nautilus.esf_alpha_orchestrator import NDAlphaEngine
# ── ProxyBSizedEngine ─────────────────────────────────────────────────────────
class ProxyBSizedEngine(NDAlphaEngine):
"""
NDAlphaEngine that scales base_fraction by rolling proxy_B percentile.
Parameters
----------
proxy_b_min_scale : float Minimum fraction multiplier (at p0 of proxy_B)
proxy_b_max_scale : float Maximum fraction multiplier (at p100 of proxy_B)
proxy_b_clip_low : float Percentile below which use min_scale (0=linear, 0.25=clip p25)
proxy_b_clip_high : float Percentile above which use max_scale
proxy_b_window : int Rolling history length for percentile
"""
def __init__(self, *args,
proxy_b_min_scale: float = 0.5,
proxy_b_max_scale: float = 1.5,
proxy_b_clip_low: float = 0.0,
proxy_b_clip_high: float = 1.0,
proxy_b_window: int = 500,
**kwargs):
super().__init__(*args, **kwargs)
self._pb_min = proxy_b_min_scale
self._pb_max = proxy_b_max_scale
self._pb_clip_lo = proxy_b_clip_low
self._pb_clip_hi = proxy_b_clip_high
self._pb_window = proxy_b_window
self._pb_history = []
self._current_inst50 = 0.0
self._current_v750 = 0.0
# Stats
self.sizing_scales = []
self.sizing_scale_mean = 1.0
def _proxy_b(self): return self._current_inst50 - self._current_v750
def _compute_scale(self):
pb = self._proxy_b()
self._pb_history.append(pb)
if len(self._pb_history) > self._pb_window * 2:
self._pb_history = self._pb_history[-self._pb_window:]
if len(self._pb_history) < 20:
return 1.0 # neutral until enough history
hist = np.array(self._pb_history[-self._pb_window:])
pct = float(np.mean(hist <= pb)) # empirical percentile of current pb
# Clip
pct = max(self._pb_clip_lo, min(self._pb_clip_hi, pct))
# Normalize pct into [0,1] between clip boundaries
span = self._pb_clip_hi - self._pb_clip_lo
if span < 1e-9: return 1.0
t = (pct - self._pb_clip_lo) / span
scale = self._pb_min + t * (self._pb_max - self._pb_min)
return float(scale)
def process_day(self, date_str, df, asset_columns,
vol_regime_ok=None, direction=None, posture='APEX'):
self.begin_day(date_str, posture=posture, direction=direction)
bid = 0
for ri in range(len(df)):
row = df.iloc[ri]
vd = row.get('vel_div')
if vd is None or not np.isfinite(float(vd)):
self._global_bar_idx += 1; bid += 1; continue
v50_raw = row.get('v50_lambda_max_velocity')
v750_raw = row.get('v750_lambda_max_velocity')
inst_raw = row.get('instability_50')
v50_val = float(v50_raw) if (v50_raw is not None and np.isfinite(float(v50_raw))) else 0.0
v750_val = float(v750_raw) if (v750_raw is not None and np.isfinite(float(v750_raw))) else 0.0
inst_val = float(inst_raw) if (inst_raw is not None and np.isfinite(float(inst_raw))) else 0.0
self._current_inst50 = inst_val
self._current_v750 = v750_val
prices = {}
for ac in asset_columns:
p = row.get(ac)
if p is not None and p > 0 and np.isfinite(p):
prices[ac] = float(p)
if not prices:
self._global_bar_idx += 1; bid += 1; continue
vrok = bool(vol_regime_ok[ri]) if vol_regime_ok is not None else (bid >= 100)
self.step_bar(bar_idx=ri, vel_div=float(vd), prices=prices,
vol_regime_ok=vrok, v50_vel=v50_val, v750_vel=v750_val)
bid += 1
# Update mean scale stat
if self.sizing_scales:
self.sizing_scale_mean = float(np.mean(self.sizing_scales))
return self.end_day()
def _try_entry(self, bar_idx, vel_div, prices, price_histories,
v50_vel=0.0, v750_vel=0.0):
scale = self._compute_scale()
self.sizing_scales.append(scale)
# Temporarily scale fraction
orig = self.bet_sizer.base_fraction
self.bet_sizer.base_fraction = orig * scale
result = super()._try_entry(bar_idx, vel_div, prices, price_histories,
v50_vel, v750_vel)
self.bet_sizer.base_fraction = orig
return result
# ── Experiment configs ────────────────────────────────────────────────────────
SIZING_VARIANTS = [
# (name, min_scale, max_scale, clip_lo, clip_hi, window)
('S1: [0.5x1.5x] lin w500', 0.50, 1.50, 0.0, 1.0, 500),
('S2: [0.25x2.0x] lin w500', 0.25, 2.00, 0.0, 1.0, 500),
('S3: [0.5x1.5x] lin w1000', 0.50, 1.50, 0.0, 1.0, 1000),
('S4: [0.5x1.5x] clip p25-p75', 0.50, 1.50, 0.25, 0.75, 500),
]
def main():
ensure_jit()
print("\nLoading data & forewarner...")
load_data()
fw = load_forewarner()
results = []
# Baseline (no sizing mod) — confirms alignment with gold
print("\n" + "="*60)
print("BASELINE (no proxy sizing)")
t0 = time.time()
r = run_backtest(lambda kw: NDAlphaEngine(**kw), 'Baseline (no sizing)', forewarner=fw)
r['elapsed'] = time.time() - t0
results.append(r)
print(f" {r['roi']:.2f}% PF={r['pf']:.4f} DD={r['dd']:.2f}% T={r['trades']} ({r['elapsed']:.0f}s)")
# Sizing variants
for vname, mn, mx, clo, chi, win in SIZING_VARIANTS:
print(f"\n{'='*60}\n{vname}")
t0 = time.time()
def factory(kw, mn=mn, mx=mx, clo=clo, chi=chi, win=win):
return ProxyBSizedEngine(**kw,
proxy_b_min_scale=mn, proxy_b_max_scale=mx,
proxy_b_clip_low=clo, proxy_b_clip_high=chi,
proxy_b_window=win)
r = run_backtest(factory, vname, forewarner=fw)
r['elapsed'] = time.time() - t0
if r.get('sizing_scale_mean'):
print(f" scale_mean={r['sizing_scale_mean']:.3f}")
print(f" {r['roi']:.2f}% PF={r['pf']:.4f} DD={r['dd']:.2f}% T={r['trades']} ({r['elapsed']:.0f}s)")
results.append(r)
print("\n" + "="*83)
print("EXP 1 — proxy_B POSITION SIZING RESULTS")
print("="*83)
print_table(results, gold=GOLD)
log_results(results, _HERE / 'exp1_proxy_sizing_results.json', meta={
'experiment': 'proxy_B position sizing',
'description': 'Scale base_fraction by rolling proxy_B percentile',
'proxy': 'instability_50 - v750_lambda_max_velocity',
})
if __name__ == '__main__':
main()

View File

@@ -0,0 +1,71 @@
{
"gold": {
"roi": 88.55,
"pf": 1.215,
"dd": 15.05,
"sharpe": 4.38,
"wr": 50.5,
"trades": 2155
},
"results": [
{
"name": "Baseline (no sizing)",
"roi": 88.54671933603525,
"pf": 1.21470506157439,
"dd": 15.046245386522427,
"wr": 50.48723897911833,
"sharpe": 4.378300370204196,
"trades": 2155,
"elapsed": 268.88467717170715
},
{
"name": "S1: [0.5x\u20131.5x] lin w500",
"roi": 91.48189487501385,
"pf": 1.1782078938827591,
"dd": 16.927834492523125,
"wr": 50.48723897911833,
"sharpe": 3.527834707408158,
"trades": 2155,
"sizing_scale_mean": 1.004486056844373,
"elapsed": 243.42204928398132
},
{
"name": "S2: [0.25x\u20132.0x] lin w500",
"roi": 105.5096611068238,
"pf": 1.1536536843959095,
"dd": 20.295829395125175,
"wr": 50.48723897911833,
"sharpe": 2.955778991462272,
"trades": 2155,
"sizing_scale_mean": 1.1328034858097293,
"elapsed": 244.23685836791992
},
{
"name": "S3: [0.5x\u20131.5x] lin w1000",
"roi": 89.49343433628508,
"pf": 1.176302347754926,
"dd": 16.690286456683094,
"wr": 50.48723897911833,
"sharpe": 3.5136533668662375,
"trades": 2155,
"sizing_scale_mean": 1.0001217537235905,
"elapsed": 231.25820136070251
},
{
"name": "S4: [0.5x\u20131.5x] clip p25-p75",
"roi": 87.12541575348651,
"pf": 1.1628357230291246,
"dd": 18.02585930450568,
"wr": 50.48723897911833,
"sharpe": 3.184294285082965,
"trades": 2155,
"sizing_scale_mean": 1.0189395626318845,
"elapsed": 225.79217648506165
}
],
"meta": {
"experiment": "proxy_B position sizing",
"description": "Scale base_fraction by rolling proxy_B percentile",
"proxy": "instability_50 - v750_lambda_max_velocity"
}
}

View File

@@ -0,0 +1,314 @@
"""
Exp 2 — proxy_B as premature exit signal, with shadow trades.
Post-hoc "what-if" analysis on the baseline trade set.
1. Run baseline engine; log per-day proxy_B and per-asset prices keyed by
(date_str, bar_idx) — the composite key that matches trade.entry_bar.
2. For each trade: find which day it was on (tracked by engine override),
then check if proxy_B dropped below threshold during the hold.
3. Compute early-exit PnL at the trigger bar using the CORRECT asset price.
4. Compare vs actual PnL.
Shadow insight: avg_pnl_delta = early_exit_pnl - actual_pnl
Positive → early exit would have been better
Negative → holding to natural exit was better (proxy_B is NOT a useful exit signal)
Thresholds tested (rolling percentile of proxy_B, window=500):
T1: exit if proxy_B < p10 (rare trigger)
T2: exit if proxy_B < p25 (moderate)
T3: exit if proxy_B < p50 (aggressive)
Logged to exp2_proxy_exit_results.json.
"""
import sys, time, json
sys.stdout.reconfigure(encoding='utf-8', errors='replace')
from pathlib import Path
import numpy as np
_HERE = Path(__file__).resolve().parent
sys.path.insert(0, str(_HERE.parent))
from exp_shared import (
ensure_jit, ENGINE_KWARGS, GOLD, load_data, load_forewarner, log_results
)
from nautilus_dolphin.nautilus.esf_alpha_orchestrator import NDAlphaEngine
from nautilus_dolphin.nautilus.adaptive_circuit_breaker import AdaptiveCircuitBreaker
# ── Engine that logs per-day proxy_B + asset prices + trade dates ─────────────
class ShadowLoggingEngine(NDAlphaEngine):
"""
NDAlphaEngine that captures:
- day_proxy[date][ri] = proxy_b value
- day_prices[date][ri][asset] = price
- trade_dates[trade_idx] = date_str of entry
"""
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.day_proxy = {} # date_str → {ri: proxy_b}
self.day_prices = {} # date_str → {ri: {asset: price}}
self._cur_date = None
self._n_trades_before = 0
self.trade_dates = [] # parallel list to trade_history, entry date per trade
def process_day(self, date_str, df, asset_columns,
vol_regime_ok=None, direction=None, posture='APEX'):
self._cur_date = date_str
self.day_proxy[date_str] = {}
self.day_prices[date_str] = {}
self._n_trades_before = len(self.trade_history)
self.begin_day(date_str, posture=posture, direction=direction)
bid = 0
for ri in range(len(df)):
row = df.iloc[ri]
vd = row.get('vel_div')
if vd is None or not np.isfinite(float(vd)):
self._global_bar_idx += 1; bid += 1; continue
def gf(col):
v = row.get(col)
if v is None: return 0.0
try: f = float(v); return f if np.isfinite(f) else 0.0
except: return 0.0
v50 = gf('v50_lambda_max_velocity')
v750 = gf('v750_lambda_max_velocity')
inst = gf('instability_50')
pb = inst - v750
self.day_proxy[date_str][ri] = pb
prices = {}
for ac in asset_columns:
p = row.get(ac)
if p is not None and p > 0 and np.isfinite(p):
prices[ac] = float(p)
self.day_prices[date_str][ri] = dict(prices)
if not prices:
self._global_bar_idx += 1; bid += 1; continue
vrok = bool(vol_regime_ok[ri]) if vol_regime_ok is not None else (bid >= 100)
self.step_bar(bar_idx=ri, vel_div=float(vd), prices=prices,
vol_regime_ok=vrok, v50_vel=v50, v750_vel=v750)
bid += 1
result = self.end_day()
# Tag new trades with this date
new_trades = self.trade_history[self._n_trades_before:]
for _ in new_trades:
self.trade_dates.append(date_str)
return result
# ── Shadow analysis ───────────────────────────────────────────────────────────
def shadow_analysis(eng, threshold_pct, window=500):
"""
For each trade, check if proxy_B dropped below rolling threshold
during hold period (same-day bars between entry_bar and exit_bar).
Uses the correct asset price for PnL computation.
"""
tr = eng.trade_history
dates = eng.trade_dates
if len(dates) < len(tr):
# Pad if any trades weren't tagged (shouldn't happen)
dates = dates + [None] * (len(tr) - len(dates))
# Build rolling proxy_B history across all days (chronological)
# We need a global chronological sequence for percentile computation
all_proxy_seq = []
for pf_stem in sorted(eng.day_proxy.keys()):
day_d = eng.day_proxy[pf_stem]
for ri in sorted(day_d.keys()):
all_proxy_seq.append((pf_stem, ri, day_d[ri]))
results = []
proxy_hist = [] # rolling window of ALL bars seen so far
# Build per-day sorted bar sequences for efficient lookup
day_bars = {d: sorted(eng.day_proxy[d].keys()) for d in eng.day_proxy}
# Build lookup: (date, ri) → index in all_proxy_seq (for rolling history)
seq_idx = {(s, r): i for i, (s, r, _) in enumerate(all_proxy_seq)}
for t, date in zip(tr, dates):
if date is None:
results.append(dict(triggered=False, actual_pnl=t.pnl_pct))
continue
entry_bar = int(t.entry_bar) if hasattr(t, 'entry_bar') else 0
exit_bar = int(t.exit_bar) if hasattr(t, 'exit_bar') else entry_bar
actual_pnl = float(t.pnl_pct) if hasattr(t, 'pnl_pct') else 0.0
entry_price = float(t.entry_price) if hasattr(t, 'entry_price') and t.entry_price else 0.0
direction = int(t.direction) if hasattr(t, 'direction') else -1
asset = t.asset if hasattr(t, 'asset') else 'BTCUSDT'
# Rolling threshold: use all bars BEFORE entry on this day
eidx = seq_idx.get((date, entry_bar), 0)
hist_window = [pb for (_, _, pb) in all_proxy_seq[max(0, eidx-window):eidx]]
if len(hist_window) < 20:
results.append(dict(triggered=False, actual_pnl=actual_pnl)); continue
threshold = float(np.percentile(hist_window, threshold_pct * 100))
# Find hold bars on the same day
if date not in day_bars:
results.append(dict(triggered=False, actual_pnl=actual_pnl)); continue
hold_bars = [ri for ri in day_bars[date]
if entry_bar < ri <= exit_bar]
triggered_bar = None
for ri in hold_bars:
if eng.day_proxy[date].get(ri, 999) < threshold:
triggered_bar = ri
break
if triggered_bar is None:
results.append(dict(triggered=False, actual_pnl=actual_pnl)); continue
# Correct early-exit price: same asset, triggered bar on same day
early_price = eng.day_prices[date].get(triggered_bar, {}).get(asset, 0.0)
if entry_price > 0 and early_price > 0:
early_pnl = direction * (early_price - entry_price) / entry_price
else:
results.append(dict(triggered=False, actual_pnl=actual_pnl)); continue
bars_saved = exit_bar - triggered_bar
results.append(dict(
triggered=True,
date=date, entry_bar=entry_bar, exit_bar=exit_bar,
triggered_bar=triggered_bar, bars_saved=bars_saved,
asset=asset, direction=direction,
entry_price=entry_price, early_price=early_price,
actual_pnl=actual_pnl,
early_exit_pnl=early_pnl,
pnl_delta=early_pnl - actual_pnl,
))
triggered = [r for r in results if r['triggered']]
if not triggered:
return dict(n_triggered=0, n_total=len(results), pct_triggered=0,
avg_actual_pnl_pct=0, avg_early_pnl_pct=0, avg_delta_pct=0,
early_better_rate=0, roi_impact_pp=0)
avg_actual = float(np.mean([r['actual_pnl'] for r in triggered]))
avg_early = float(np.mean([r['early_exit_pnl'] for r in triggered]))
avg_delta = float(np.mean([r['pnl_delta'] for r in triggered]))
early_better = float(np.mean([r['pnl_delta'] > 0 for r in triggered]))
avg_bars_saved = float(np.mean([r['bars_saved'] for r in triggered]))
# Estimated ROI impact (sum of pnl deltas × fraction × 100)
roi_impact = float(sum(r['pnl_delta'] for r in triggered) * 0.20 * 100)
# Per-exit-reason breakdown if available
return dict(
n_triggered=len(triggered),
n_total=len(results),
pct_triggered=len(triggered) / max(1, len(results)) * 100,
avg_actual_pnl_pct=avg_actual * 100,
avg_early_exit_pnl_pct=avg_early * 100,
avg_pnl_delta_pct=avg_delta * 100,
early_better_rate=early_better * 100,
avg_bars_saved=avg_bars_saved,
roi_impact_estimate_pp=roi_impact,
)
def main():
ensure_jit()
print("\nLoading data & forewarner...")
d = load_data()
fw = load_forewarner()
from exp_shared import ENGINE_KWARGS, MC_BASE_CFG
import math
print("\nRunning baseline with shadow logging...")
t0 = time.time()
kw = ENGINE_KWARGS.copy()
acb = AdaptiveCircuitBreaker()
acb.preload_w750(d['date_strings'])
eng = ShadowLoggingEngine(**kw)
eng.set_ob_engine(d['ob_eng'])
eng.set_acb(acb)
if fw: eng.set_mc_forewarner(fw, MC_BASE_CFG)
eng.set_esoteric_hazard_multiplier(0.0)
daily_caps, daily_pnls = [], []
for pf in d['parquet_files']:
ds = pf.stem
df, acols, dvol = d['pq_data'][ds]
cap_before = eng.capital
vol_ok = np.where(np.isfinite(dvol), dvol > d['vol_p60'], False)
eng.process_day(ds, df, acols, vol_regime_ok=vol_ok)
daily_caps.append(eng.capital)
daily_pnls.append(eng.capital - cap_before)
tr = eng.trade_history
print(f" Done in {time.time()-t0:.0f}s Trades={len(tr)} "
f"Tagged={len(eng.trade_dates)}")
# Confirm baseline metrics match gold
def _abs(t): return t.pnl_absolute if hasattr(t,'pnl_absolute') else t.pnl_pct*250.
wins = [t for t in tr if _abs(t) > 0]
pf = sum(_abs(t) for t in wins) / max(abs(sum(_abs(t) for t in [x for x in tr if _abs(x)<=0])),1e-9)
roi = (eng.capital - 25000) / 25000 * 100
print(f" Baseline: ROI={roi:.2f}% PF={pf:.4f} (gold: 88.55% / 1.215)")
THRESHOLDS = [
('T1: exit if proxy_B < p10', 0.10),
('T2: exit if proxy_B < p25', 0.25),
('T3: exit if proxy_B < p50', 0.50),
]
all_results = []
for tname, tpct in THRESHOLDS:
print(f"\n{tname}")
res = shadow_analysis(eng, threshold_pct=tpct, window=500)
res['name'] = tname
all_results.append(res)
print(f" Triggered: {res['n_triggered']}/{res['n_total']} "
f"({res['pct_triggered']:.1f}%)")
if res['n_triggered'] > 0:
print(f" Avg actual PnL: {res['avg_actual_pnl_pct']:+.4f}%")
print(f" Avg early-exit PnL: {res['avg_early_exit_pnl_pct']:+.4f}%")
print(f" Avg delta: {res['avg_pnl_delta_pct']:+.4f}% "
f"(+ = early exit BETTER)")
print(f" Early exit better: {res['early_better_rate']:.1f}% of triggered")
print(f" Avg bars saved: {res['avg_bars_saved']:.1f}")
print(f" Est. ROI impact: {res['roi_impact_estimate_pp']:+.2f}pp")
print("\n" + "="*75)
print("EXP 2 — SHADOW EXIT SUMMARY")
print("="*75)
print(f"{'Threshold':<35} {'Trig%':>6} {'AvgDelta%':>11} "
f"{'EarlyBetter%':>13} {'ROI_pp':>8}")
print('-'*75)
for r in all_results:
if r['n_triggered'] > 0:
print(f" {r['name']:<33} {r['pct_triggered']:>6.1f}% "
f"{r['avg_pnl_delta_pct']:>10.4f}% "
f"{r['early_better_rate']:>12.1f}% "
f"{r['roi_impact_estimate_pp']:>8.2f}pp")
else:
print(f" {r['name']:<33} (no triggers)")
verdict = all_results[0] if all_results else {}
if verdict.get('avg_pnl_delta_pct', -1) > 0:
print("\n → VERDICT: Early exit is BENEFICIAL (delta > 0)")
else:
print("\n → VERDICT: Holding to natural exit is BETTER (early exit hurts)")
log_results(all_results, _HERE / 'exp2_proxy_exit_results.json',
meta={'experiment': 'proxy_B exit shadow (corrected)',
'proxy': 'instability_50 - v750_lambda_max_velocity',
'n_trades': len(tr),
'baseline_roi': roi, 'baseline_pf': pf})
if __name__ == '__main__':
main()

View File

@@ -0,0 +1,55 @@
{
"gold": {
"roi": 88.55,
"pf": 1.215,
"dd": 15.05,
"sharpe": 4.38,
"wr": 50.5,
"trades": 2155
},
"results": [
{
"n_triggered": 32,
"n_total": 2155,
"pct_triggered": 1.4849187935034802,
"avg_actual_pnl_pct": 0.16971558008744564,
"avg_early_exit_pnl_pct": 0.0195519860418085,
"avg_pnl_delta_pct": -0.15016359404563714,
"early_better_rate": 37.5,
"avg_bars_saved": 91.6875,
"roi_impact_estimate_pp": -0.9610470018920778,
"name": "T1: exit if proxy_B < p10"
},
{
"n_triggered": 37,
"n_total": 2155,
"pct_triggered": 1.716937354988399,
"avg_actual_pnl_pct": -0.01204806624716285,
"avg_early_exit_pnl_pct": 0.023025551555410688,
"avg_pnl_delta_pct": 0.03507361780257347,
"early_better_rate": 40.54054054054054,
"avg_bars_saved": 102.70270270270271,
"roi_impact_estimate_pp": 0.2595447717390437,
"name": "T2: exit if proxy_B < p25"
},
{
"n_triggered": 46,
"n_total": 2155,
"pct_triggered": 2.134570765661253,
"avg_actual_pnl_pct": -0.0036840609630585178,
"avg_early_exit_pnl_pct": 0.012711500756466773,
"avg_pnl_delta_pct": 0.016395561719525237,
"early_better_rate": 43.47826086956522,
"avg_bars_saved": 103.47826086956522,
"roi_impact_estimate_pp": 0.1508391678196324,
"name": "T3: exit if proxy_B < p50"
}
],
"meta": {
"experiment": "proxy_B exit shadow (corrected)",
"proxy": "instability_50 - v750_lambda_max_velocity",
"n_trades": 2155,
"baseline_roi": 88.54671933603525,
"baseline_pf": 1.21470506157439
}
}

View File

@@ -0,0 +1,177 @@
{
"gold": {
"roi": 88.55,
"pf": 1.215,
"dd": 15.05,
"sharpe": 4.38,
"wr": 50.5,
"trades": 2155
},
"results": [
{
"name": "Baseline",
"roi": 88.54671933603525,
"pf": 1.21470506157439,
"dd": 15.046245386522427,
"wr": 50.48723897911833,
"sharpe": 4.378300370204196,
"trades": 2155,
"elapsed": 201.09715700149536
},
{
"name": "V50/gate/p50",
"roi": -21.583410235049012,
"pf": 0.8221680494935062,
"dd": 31.94312195232001,
"wr": 51.162790697674424,
"sharpe": -1.8251030771486862,
"trades": 473,
"gate_suppressed": 106766,
"gate_allowed": 106941,
"early_exits": 0,
"sizing_scale_mean": 1.0,
"elapsed": 338.78401374816895
},
{
"name": "B50/exit/p25",
"roi": 88.54671933603525,
"pf": 1.21470506157439,
"dd": 15.046245386522427,
"wr": 50.48723897911833,
"sharpe": 4.378300370204196,
"trades": 2155,
"gate_suppressed": 0,
"gate_allowed": 0,
"early_exits": 0,
"sizing_scale_mean": 1.0,
"elapsed": 225.31486630439758
},
{
"name": "V50/exit/p10",
"roi": 88.54671933603525,
"pf": 1.21470506157439,
"dd": 15.046245386522427,
"wr": 50.48723897911833,
"sharpe": 4.378300370204196,
"trades": 2155,
"gate_suppressed": 0,
"gate_allowed": 0,
"early_exits": 0,
"sizing_scale_mean": 1.0,
"elapsed": 234.80287218093872
},
{
"name": "B150/gate/p10",
"roi": -17.365740668643976,
"pf": 0.9411606181351053,
"dd": 28.995905550060424,
"wr": 49.43655071043606,
"sharpe": -1.7018177827882233,
"trades": 2041,
"gate_suppressed": 19655,
"gate_allowed": 41671,
"early_exits": 0,
"sizing_scale_mean": 1.0,
"elapsed": 203.65413403511047
},
{
"name": "B50/exit/p50",
"roi": 88.54671933603525,
"pf": 1.21470506157439,
"dd": 15.046245386522427,
"wr": 50.48723897911833,
"sharpe": 4.378300370204196,
"trades": 2155,
"gate_suppressed": 0,
"gate_allowed": 0,
"early_exits": 0,
"sizing_scale_mean": 1.0,
"elapsed": 220.37168407440186
},
{
"name": "B150/gate/p25",
"roi": -1.26185543931782,
"pf": 0.9961437285140536,
"dd": 28.25006238791456,
"wr": 49.69574036511156,
"sharpe": -0.10250195122285347,
"trades": 1972,
"gate_suppressed": 28717,
"gate_allowed": 38549,
"early_exits": 0,
"sizing_scale_mean": 1.0,
"elapsed": 198.88452696800232
},
{
"name": "V150/exit/p50",
"roi": 88.54671933603525,
"pf": 1.21470506157439,
"dd": 15.046245386522427,
"wr": 50.48723897911833,
"sharpe": 4.378300370204196,
"trades": 2155,
"gate_suppressed": 0,
"gate_allowed": 0,
"early_exits": 0,
"sizing_scale_mean": 1.0,
"elapsed": 219.81028938293457
},
{
"name": "V150/gate/p50",
"roi": -24.339020886800313,
"pf": 0.9079395481793519,
"dd": 31.97276290743111,
"wr": 49.57458876914351,
"sharpe": -1.6903434532243515,
"trades": 1763,
"gate_suppressed": 43805,
"gate_allowed": 43807,
"early_exits": 0,
"sizing_scale_mean": 1.0,
"elapsed": 210.28945112228394
},
{
"name": "V300/exit/p50",
"roi": 88.54671933603525,
"pf": 1.21470506157439,
"dd": 15.046245386522427,
"wr": 50.48723897911833,
"sharpe": 4.378300370204196,
"trades": 2155,
"gate_suppressed": 0,
"gate_allowed": 0,
"early_exits": 0,
"sizing_scale_mean": 1.0,
"elapsed": 277.53983092308044
},
{
"name": "V300/exit/p10",
"roi": 88.54671933603525,
"pf": 1.21470506157439,
"dd": 15.046245386522427,
"wr": 50.48723897911833,
"sharpe": 4.378300370204196,
"trades": 2155,
"gate_suppressed": 0,
"gate_allowed": 0,
"early_exits": 0,
"sizing_scale_mean": 1.0,
"elapsed": 331.95733642578125
}
],
"meta": {
"experiment": "exp3 longer proxies alpha engine validation",
"proxies_tested": [
"B50",
"B150",
"V50",
"V150",
"V300"
],
"modes_tested": [
"gate",
"size"
],
"note": "Top-2 per proxy from fast sweep, validated with full Alpha Engine"
}
}

View File

@@ -0,0 +1,487 @@
{
"gold": {
"roi": 88.55,
"pf": 1.215,
"dd": 15.05,
"sharpe": 4.38,
"wr": 50.5,
"trades": 2155
},
"results": [
{
"roi": 4.1611704733443,
"n_trades": 980,
"wr": 49.28571428571429,
"sharpe": 1.93441354271401,
"key": "V50/gate/p50",
"proxy": "V50",
"mode": "gate",
"threshold_pct": 0.5
},
{
"roi": 5.987937897605655,
"n_trades": 23891,
"wr": 44.81603951278724,
"sharpe": 1.868070095035376,
"key": "B50/exit/p25",
"proxy": "B50",
"mode": "exit",
"threshold_pct": 0.25
},
{
"roi": 4.203067324110643,
"n_trades": 21010,
"wr": 47.42503569728701,
"sharpe": 1.384370226310417,
"key": "V50/exit/p10",
"proxy": "V50",
"mode": "exit",
"threshold_pct": 0.1
},
{
"roi": 5.58761012089275,
"n_trades": 2701,
"wr": 51.05516475379489,
"sharpe": 1.2814598696709858,
"key": "B150/gate/p10",
"proxy": "B150",
"mode": "gate",
"threshold_pct": 0.1
},
{
"roi": 3.4221336926343993,
"n_trades": 35591,
"wr": 43.66272372228934,
"sharpe": 1.2616307481096092,
"key": "B50/exit/p50",
"proxy": "B50",
"mode": "exit",
"threshold_pct": 0.5
},
{
"roi": 5.284493475889573,
"n_trades": 2513,
"wr": 50.33824114604059,
"sharpe": 1.2409796685266818,
"key": "V50/gate/p10",
"proxy": "V50",
"mode": "gate",
"threshold_pct": 0.1
},
{
"roi": 5.03184471791609,
"n_trades": 2666,
"wr": 50.90022505626407,
"sharpe": 1.185462004289883,
"key": "B150/gate/p25",
"proxy": "B150",
"mode": "gate",
"threshold_pct": 0.25
},
{
"roi": 2.3342379236781508,
"n_trades": 46747,
"wr": 43.26480843690504,
"sharpe": 1.1764578378224364,
"key": "V150/exit/p50",
"proxy": "V150",
"mode": "exit",
"threshold_pct": 0.5
},
{
"roi": 4.952775652124575,
"n_trades": 2508,
"wr": 50.75757575757576,
"sharpe": 1.1752617362993856,
"key": "V150/gate/p50",
"proxy": "V150",
"mode": "gate",
"threshold_pct": 0.5
},
{
"roi": 5.018065726503962,
"n_trades": 2636,
"wr": 51.32776934749621,
"sharpe": 1.169177602635925,
"key": "V150/gate/p25",
"proxy": "V150",
"mode": "gate",
"threshold_pct": 0.25
},
{
"roi": 2.4957565643144886,
"n_trades": 47409,
"wr": 43.352528001012466,
"sharpe": 1.1535924003772087,
"key": "V300/exit/p50",
"proxy": "V300",
"mode": "exit",
"threshold_pct": 0.5
},
{
"roi": 3.1810450835647153,
"n_trades": 35753,
"wr": 45.64372220512964,
"sharpe": 1.139549016505284,
"key": "V50/exit/p25",
"proxy": "V50",
"mode": "exit",
"threshold_pct": 0.25
},
{
"roi": 4.723458959017712,
"n_trades": 2638,
"wr": 50.98559514783927,
"sharpe": 1.1183448102112654,
"key": "B150/gate/p50",
"proxy": "B150",
"mode": "gate",
"threshold_pct": 0.5
},
{
"roi": 2.467370511701228,
"n_trades": 45738,
"wr": 43.90878481787573,
"sharpe": 1.1130988979290424,
"key": "V50/exit/p50",
"proxy": "V50",
"mode": "exit",
"threshold_pct": 0.5
},
{
"roi": 4.786060351579402,
"n_trades": 2710,
"wr": 50.29520295202951,
"sharpe": 1.101965763536016,
"key": "B50/gate/p25",
"proxy": "B50",
"mode": "gate",
"threshold_pct": 0.25
},
{
"roi": 3.277471365843976,
"n_trades": 21040,
"wr": 47.010456273764255,
"sharpe": 1.0787267995098386,
"key": "V150/exit/p10",
"proxy": "V150",
"mode": "exit",
"threshold_pct": 0.1
},
{
"roi": 4.606708671848225,
"n_trades": 2707,
"wr": 50.794237162910974,
"sharpe": 1.0621148741933593,
"key": "V150/gate/p10",
"proxy": "V150",
"mode": "gate",
"threshold_pct": 0.1
},
{
"roi": 4.509796198592064,
"n_trades": 2676,
"wr": 50.26158445440957,
"sharpe": 1.0469315281956466,
"key": "B50/gate/p50",
"proxy": "B50",
"mode": "gate",
"threshold_pct": 0.5
},
{
"roi": 3.329600505098229,
"n_trades": 20777,
"wr": 46.69105260624729,
"sharpe": 1.0358050478322955,
"key": "V300/exit/p10",
"proxy": "V300",
"mode": "exit",
"threshold_pct": 0.1
},
{
"roi": 4.4105891591622814,
"n_trades": 2727,
"wr": 49.98166483314998,
"sharpe": 1.0171663918985434,
"key": "B50/gate/p10",
"proxy": "B50",
"mode": "gate",
"threshold_pct": 0.1
},
{
"roi": 4.330032781297288,
"n_trades": 2518,
"wr": 50.35742652899127,
"sharpe": 1.010921034579114,
"key": "V300/gate/p50",
"proxy": "V300",
"mode": "gate",
"threshold_pct": 0.5
},
{
"roi": 4.367799680215123,
"n_trades": 2758,
"wr": 49.41986947063089,
"sharpe": 1.0021989361716817,
"key": "BASELINE",
"proxy": "-",
"mode": "-",
"threshold_pct": 0
},
{
"roi": 4.367799680215123,
"n_trades": 2758,
"wr": 49.41986947063089,
"sharpe": 1.0021989361716817,
"key": "B50/size/p10",
"proxy": "B50",
"mode": "size",
"threshold_pct": 0.1
},
{
"roi": 4.367799680215123,
"n_trades": 2758,
"wr": 49.41986947063089,
"sharpe": 1.0021989361716817,
"key": "B50/size/p25",
"proxy": "B50",
"mode": "size",
"threshold_pct": 0.25
},
{
"roi": 4.367799680215123,
"n_trades": 2758,
"wr": 49.41986947063089,
"sharpe": 1.0021989361716817,
"key": "B50/size/p50",
"proxy": "B50",
"mode": "size",
"threshold_pct": 0.5
},
{
"roi": 3.605522815282547,
"n_trades": 2758,
"wr": 49.41986947063089,
"sharpe": 1.0021989361716817,
"key": "B150/size/p10",
"proxy": "B150",
"mode": "size",
"threshold_pct": 0.1
},
{
"roi": 3.605522815282547,
"n_trades": 2758,
"wr": 49.41986947063089,
"sharpe": 1.0021989361716817,
"key": "B150/size/p25",
"proxy": "B150",
"mode": "size",
"threshold_pct": 0.25
},
{
"roi": 3.605522815282547,
"n_trades": 2758,
"wr": 49.41986947063089,
"sharpe": 1.0021989361716817,
"key": "B150/size/p50",
"proxy": "B150",
"mode": "size",
"threshold_pct": 0.5
},
{
"roi": 2.95312814355122,
"n_trades": 2758,
"wr": 49.41986947063089,
"sharpe": 1.0021989361716817,
"key": "V50/size/p10",
"proxy": "V50",
"mode": "size",
"threshold_pct": 0.1
},
{
"roi": 2.95312814355122,
"n_trades": 2758,
"wr": 49.41986947063089,
"sharpe": 1.0021989361716817,
"key": "V50/size/p25",
"proxy": "V50",
"mode": "size",
"threshold_pct": 0.25
},
{
"roi": 2.95312814355122,
"n_trades": 2758,
"wr": 49.41986947063089,
"sharpe": 1.0021989361716817,
"key": "V50/size/p50",
"proxy": "V50",
"mode": "size",
"threshold_pct": 0.5
},
{
"roi": 4.472629706358244,
"n_trades": 2758,
"wr": 49.41986947063089,
"sharpe": 1.0021989361716817,
"key": "V150/size/p10",
"proxy": "V150",
"mode": "size",
"threshold_pct": 0.1
},
{
"roi": 4.472629706358244,
"n_trades": 2758,
"wr": 49.41986947063089,
"sharpe": 1.0021989361716817,
"key": "V150/size/p25",
"proxy": "V150",
"mode": "size",
"threshold_pct": 0.25
},
{
"roi": 4.472629706358244,
"n_trades": 2758,
"wr": 49.41986947063089,
"sharpe": 1.0021989361716817,
"key": "V150/size/p50",
"proxy": "V150",
"mode": "size",
"threshold_pct": 0.5
},
{
"roi": 3.999178962700034,
"n_trades": 2758,
"wr": 49.41986947063089,
"sharpe": 1.0021989361716817,
"key": "V300/size/p10",
"proxy": "V300",
"mode": "size",
"threshold_pct": 0.1
},
{
"roi": 3.999178962700034,
"n_trades": 2758,
"wr": 49.41986947063089,
"sharpe": 1.0021989361716817,
"key": "V300/size/p25",
"proxy": "V300",
"mode": "size",
"threshold_pct": 0.25
},
{
"roi": 3.999178962700034,
"n_trades": 2758,
"wr": 49.41986947063089,
"sharpe": 1.0021989361716817,
"key": "V300/size/p50",
"proxy": "V300",
"mode": "size",
"threshold_pct": 0.5
},
{
"roi": 3.626451614638837,
"n_trades": 18495,
"wr": 45.909705325763724,
"sharpe": 0.9461650652992963,
"key": "B50/exit/p10",
"proxy": "B50",
"mode": "exit",
"threshold_pct": 0.1
},
{
"roi": 3.2466809104833683,
"n_trades": 2704,
"wr": 50.18491124260355,
"sharpe": 0.7527502723318003,
"key": "V300/gate/p10",
"proxy": "V300",
"mode": "gate",
"threshold_pct": 0.1
},
{
"roi": 1.9983873882996273,
"n_trades": 36697,
"wr": 44.91647818622776,
"sharpe": 0.7217401163231677,
"key": "V300/exit/p25",
"proxy": "V300",
"mode": "exit",
"threshold_pct": 0.25
},
{
"roi": 2.673177338382371,
"n_trades": 2629,
"wr": 50.741726892354514,
"sharpe": 0.6360878704943932,
"key": "V300/gate/p25",
"proxy": "V300",
"mode": "gate",
"threshold_pct": 0.25
},
{
"roi": 1.3332860487727194,
"n_trades": 1630,
"wr": 48.527607361963184,
"sharpe": 0.46969397860867373,
"key": "V50/gate/p25",
"proxy": "V50",
"mode": "gate",
"threshold_pct": 0.25
},
{
"roi": 1.050754188104408,
"n_trades": 23222,
"wr": 44.74205494789424,
"sharpe": 0.36190958694118786,
"key": "B150/exit/p10",
"proxy": "B150",
"mode": "exit",
"threshold_pct": 0.1
},
{
"roi": 0.40765391258732464,
"n_trades": 36596,
"wr": 44.81910591321456,
"sharpe": 0.14824077173202238,
"key": "V150/exit/p25",
"proxy": "V150",
"mode": "exit",
"threshold_pct": 0.25
},
{
"roi": 0.30344434330278336,
"n_trades": 31821,
"wr": 43.88297036548192,
"sharpe": 0.13013963603284184,
"key": "B150/exit/p50",
"proxy": "B150",
"mode": "exit",
"threshold_pct": 0.5
},
{
"roi": -0.4172667101121519,
"n_trades": 27212,
"wr": 44.11656622078495,
"sharpe": -0.13889774059718565,
"key": "B150/exit/p25",
"proxy": "B150",
"mode": "exit",
"threshold_pct": 0.25
}
],
"meta": {
"experiment": "exp3 fast numpy sweep",
"n_bars": 346740,
"baseline": {
"roi": 4.367799680215123,
"n_trades": 2758,
"wr": 49.41986947063089,
"sharpe": 1.0021989361716817,
"key": "BASELINE",
"proxy": "-",
"mode": "-",
"threshold_pct": 0
},
"note": "simplified SHORT-only, no fees, no leverage"
}
}

View File

@@ -0,0 +1,419 @@
"""
Exp 3 — Longer-window proxies × three modes (gate / size / exit).
Available proxy signals from scan parquets:
proxy_B50 = instability_50 - v750_lambda_max_velocity (original)
proxy_B150 = instability_150 - v750_lambda_max_velocity (longer instability window)
proxy_V50 = v50_lambda_max_velocity - v750_lambda_max_velocity (vel divergence short)
proxy_V150 = v150_lambda_max_velocity - v750_lambda_max_velocity (vel divergence medium)
proxy_V300 = v300_lambda_max_velocity - v750_lambda_max_velocity (vel divergence long)
For each proxy, test:
MODE_GATE: binary suppress entry when proxy < rolling threshold
MODE_SIZE: scale fraction [0.5x, 1.5x] by proxy percentile
MODE_EXIT: (shadow analysis) early exit when proxy < rolling threshold
Run order:
Step 1 — fast numpy sweep across all proxy × mode × threshold
(no Alpha Engine, simplified TP/max_hold model, ~seconds per config)
Step 2 — top-2 configs per proxy validated with full Alpha Engine (~200s each)
Results: exp3_fast_sweep_results.json + exp3_alpha_engine_results.json
"""
import sys, time, math, json
sys.stdout.reconfigure(encoding='utf-8', errors='replace')
from pathlib import Path
import numpy as np
import pandas as pd
_HERE = Path(__file__).resolve().parent
sys.path.insert(0, str(_HERE.parent))
from exp_shared import (
ensure_jit, ENGINE_KWARGS, GOLD, VBT_DIR, META_COLS,
load_data, load_forewarner, run_backtest, print_table, log_results
)
from nautilus_dolphin.nautilus.esf_alpha_orchestrator import NDAlphaEngine
# ── Proxy definitions ─────────────────────────────────────────────────────────
PROXY_DEFS = {
'B50': lambda row: _get(row,'instability_50') - _get(row,'v750_lambda_max_velocity'),
'B150': lambda row: _get(row,'instability_150') - _get(row,'v750_lambda_max_velocity'),
'V50': lambda row: _get(row,'v50_lambda_max_velocity') - _get(row,'v750_lambda_max_velocity'),
'V150': lambda row: _get(row,'v150_lambda_max_velocity') - _get(row,'v750_lambda_max_velocity'),
'V300': lambda row: _get(row,'v300_lambda_max_velocity') - _get(row,'v750_lambda_max_velocity'),
}
def _get(row, col, default=0.0):
v = row.get(col)
if v is None: return default
try:
f = float(v)
return f if np.isfinite(f) else default
except Exception:
return default
# ── Step 1: Fast numpy sweep ──────────────────────────────────────────────────
def fast_sweep():
"""
Vectorized sweep across all proxies × modes × thresholds.
Uses simplified backtest: vel_div < -0.02 entry, fixed 0.95% TP, 120-bar max hold.
Single asset (BTCUSDT), no fees, no leverage dynamics.
~100x faster than Alpha Engine.
"""
print("\n" + "="*65)
print("STEP 1 — FAST NUMPY SWEEP (simplified, no Alpha Engine)")
print("="*65)
d = load_data()
TP = 0.0095 # 99bps take profit
MH = 120 # max hold bars
VDT = -0.02 # vel_div entry threshold
# Build concatenated scan data across all days
all_rows = []
for pf in d['parquet_files']:
df, _, _ = d['pq_data'][pf.stem]
for ri in range(len(df)):
row = df.iloc[ri]
r = {c: row.get(c) for c in ['vel_div','BTCUSDT',
'v50_lambda_max_velocity','v150_lambda_max_velocity',
'v300_lambda_max_velocity','v750_lambda_max_velocity',
'instability_50','instability_150']}
all_rows.append(r)
N = len(all_rows)
vd = np.array([_get(r,'vel_div',np.nan) for r in all_rows])
price = np.array([_get(r,'BTCUSDT',np.nan) for r in all_rows])
# Precompute all proxy arrays
proxy_arrays = {}
for pname, pfn in PROXY_DEFS.items():
proxy_arrays[pname] = np.array([pfn(r) for r in all_rows])
def simplified_backtest(entry_mask, proxy_arr, mode, threshold_pct, window=500):
"""
mode: 'gate' | 'size' | 'exit'
entry_mask: boolean array of candidate entries
Returns: ROI, n_trades, win_rate
"""
capital = 1.0
in_position = False
entry_bar = 0
entry_p = 0.0
pb_hist = []
trades = []
scale = 1.0
for i in range(N):
pb = proxy_arr[i]
if np.isfinite(pb):
pb_hist.append(pb)
hist_window = pb_hist[-window:] if len(pb_hist) >= window else pb_hist
# Rolling threshold
if len(hist_window) >= 20:
thr = float(np.percentile(hist_window, threshold_pct * 100))
else:
thr = -999.0
if in_position:
if np.isnan(price[i]) or entry_p <= 0:
in_position = False; continue
ret = (price[i] - entry_p) / entry_p # LONG direction (for backtest)
# direction=-1 (SHORT) — vel_div < 0 = eigenspace stress = SHORT signal
pnl_pct = -ret # SHORT
bars_held = i - entry_bar
exited = False
# Proxy-based exit (mode='exit')
if mode == 'exit' and np.isfinite(pb) and pb < thr:
exited = True
# Natural exits
if not exited and pnl_pct >= TP:
exited = True
if not exited and bars_held >= MH:
exited = True
if exited:
pos_size = scale * 0.20
trade_pnl = capital * pos_size * pnl_pct
capital += trade_pnl
trades.append(pnl_pct)
in_position = False
else:
if (not np.isnan(vd[i]) and entry_mask[i] and
not np.isnan(price[i]) and price[i] > 0):
# Gate mode: skip if proxy below threshold
if mode == 'gate' and np.isfinite(pb) and pb < thr:
continue
# Sizing mode: compute scale
if mode == 'size' and len(hist_window) >= 20:
pct = float(np.mean(np.array(hist_window) <= pb)) if np.isfinite(pb) else 0.5
scale = 0.5 + pct * 1.0 # linear [0.5, 1.5]
else:
scale = 1.0
in_position = True
entry_bar = i
entry_p = price[i]
n = len(trades)
if n == 0: return dict(roi=0, n_trades=0, wr=0, sharpe=0)
roi = (capital - 1.0) * 100.0
arr = np.array(trades)
wr = float(np.mean(arr > 0)) * 100
sh = float(arr.mean() / (arr.std() + 1e-9) * math.sqrt(n))
return dict(roi=roi, n_trades=n, wr=wr, sharpe=sh)
entry_mask = (np.isfinite(vd)) & (vd < VDT)
MODES = ['gate', 'size', 'exit']
THRESHOLDS = [0.10, 0.25, 0.50]
sweep_results = []
best_by_proxy = {}
for pname in PROXY_DEFS:
parr = proxy_arrays[pname]
for mode in MODES:
for tpct in THRESHOLDS:
key = f"{pname}/{mode}/p{int(tpct*100)}"
res = simplified_backtest(entry_mask, parr, mode, tpct)
res['key'] = key; res['proxy'] = pname
res['mode'] = mode; res['threshold_pct'] = tpct
sweep_results.append(res)
# Baseline (no proxy modification)
base = simplified_backtest(entry_mask, proxy_arrays['B50'], 'size', 0.0)
base['key'] = 'BASELINE'; base['proxy'] = '-'; base['mode'] = '-'; base['threshold_pct'] = 0
sweep_results.insert(0, base)
# Sort by Sharpe
ranked = sorted(sweep_results, key=lambda r: r.get('sharpe', -999), reverse=True)
print(f"\n{'Key':<30} {'ROI%':>7} {'Trades':>7} {'WR%':>6} {'Sharpe':>8}")
print('-'*60)
print(f"{'BASELINE':<30} {base['roi']:>7.2f} {base['n_trades']:>7d} "
f"{base['wr']:>6.1f}% {base['sharpe']:>8.4f}")
print('-'*60)
for r in ranked[:20]:
if r['key'] == 'BASELINE': continue
marker = ' ◄ TOP' if ranked.index(r) <= 5 else ''
print(f"{r['key']:<30} {r['roi']:>7.2f} {r['n_trades']:>7d} "
f"{r['wr']:>6.1f}% {r['sharpe']:>8.4f}{marker}")
log_results(
ranked,
_HERE / 'exp3_fast_sweep_results.json',
gold=None,
meta={'experiment': 'exp3 fast numpy sweep', 'n_bars': N,
'baseline': base, 'note': 'simplified SHORT-only, no fees, no leverage'}
)
# Return top configs for Alpha Engine validation (top 2 per proxy)
top_configs = []
seen_proxies = {}
for r in ranked:
if r['key'] == 'BASELINE': continue
pn = r['proxy']
if pn not in seen_proxies:
seen_proxies[pn] = 0
if seen_proxies[pn] < 2:
top_configs.append(r)
seen_proxies[pn] += 1
return top_configs, ranked[0] # top_configs for AE validation, baseline ref
# ── Step 2: Alpha Engine validation of top configs ────────────────────────────
class MultiProxyEngine(NDAlphaEngine):
"""Generic engine parameterised by proxy function + mode."""
def __init__(self, *args, proxy_name='B50', mode='gate',
threshold_pct=0.25, window=500,
size_min=0.5, size_max=1.5, **kwargs):
super().__init__(*args, **kwargs)
self._proxy_name = proxy_name
self._mode = mode
self._threshold_pct = threshold_pct
self._window = window
self._size_min = size_min
self._size_max = size_max
self._pb_history = []
self._current_vals = {}
# Stats
self.gate_suppressed = 0
self.gate_allowed = 0
self.early_exits = 0
self.sizing_scales = []
def _proxy(self):
v = self._current_vals
if self._proxy_name == 'B50':
return v.get('i50',0.) - v.get('v750',0.)
elif self._proxy_name == 'B150':
return v.get('i150',0.) - v.get('v750',0.)
elif self._proxy_name == 'V50':
return v.get('v50',0.) - v.get('v750',0.)
elif self._proxy_name == 'V150':
return v.get('v150',0.) - v.get('v750',0.)
elif self._proxy_name == 'V300':
return v.get('v300',0.) - v.get('v750',0.)
return 0.0
def _rolling_threshold(self):
h = self._pb_history[-self._window:]
if len(h) < 20: return -999.0
return float(np.percentile(h, self._threshold_pct * 100))
def _rolling_pct(self, pb):
h = np.array(self._pb_history[-self._window:])
if len(h) < 20: return 0.5
return float(np.mean(h <= pb))
def process_day(self, date_str, df, asset_columns,
vol_regime_ok=None, direction=None, posture='APEX'):
self.begin_day(date_str, posture=posture, direction=direction)
bid = 0
for ri in range(len(df)):
row = df.iloc[ri]
vd = row.get('vel_div')
if vd is None or not np.isfinite(float(vd)):
self._global_bar_idx += 1; bid += 1; continue
def gf(col):
v = row.get(col)
if v is None: return 0.0
try:
f = float(v)
return f if np.isfinite(f) else 0.0
except Exception: return 0.0
self._current_vals = dict(
i50=gf('instability_50'), i150=gf('instability_150'),
v50=gf('v50_lambda_max_velocity'), v150=gf('v150_lambda_max_velocity'),
v300=gf('v300_lambda_max_velocity'), v750=gf('v750_lambda_max_velocity'),
)
pb = self._proxy()
self._pb_history.append(pb)
prices = {}
for ac in asset_columns:
p = row.get(ac)
if p is not None and p > 0 and np.isfinite(p):
prices[ac] = float(p)
if not prices:
self._global_bar_idx += 1; bid += 1; continue
vrok = bool(vol_regime_ok[ri]) if vol_regime_ok is not None else (bid >= 100)
self.step_bar(bar_idx=ri, vel_div=float(vd), prices=prices,
vol_regime_ok=vrok,
v50_vel=self._current_vals['v50'],
v750_vel=self._current_vals['v750'])
bid += 1
return self.end_day()
def _try_entry(self, bar_idx, vel_div, prices, price_histories,
v50_vel=0.0, v750_vel=0.0):
pb = self._proxy()
thr = self._rolling_threshold()
if self._mode == 'gate':
if pb < thr:
self.gate_suppressed += 1
return None
self.gate_allowed += 1
elif self._mode == 'size':
pct = self._rolling_pct(pb)
scale = self._size_min + pct * (self._size_max - self._size_min)
self.sizing_scales.append(scale)
orig = self.bet_sizer.base_fraction
self.bet_sizer.base_fraction = orig * scale
result = super()._try_entry(bar_idx, vel_div, prices, price_histories,
v50_vel, v750_vel)
self.bet_sizer.base_fraction = orig
return result
return super()._try_entry(bar_idx, vel_div, prices, price_histories,
v50_vel, v750_vel)
@property
def sizing_scale_mean(self):
return float(np.mean(self.sizing_scales)) if self.sizing_scales else 1.0
def validate_with_alpha_engine(top_configs, forewarner):
print("\n" + "="*65)
print("STEP 2 — ALPHA ENGINE VALIDATION (top configs)")
print("="*65)
ae_results = []
# Baseline first
print("\nBaseline...")
t0 = time.time()
r = run_backtest(lambda kw: NDAlphaEngine(**kw), 'Baseline', forewarner=forewarner)
r['elapsed'] = time.time() - t0
ae_results.append(r)
print(f" {r['roi']:.2f}% PF={r['pf']:.4f} DD={r['dd']:.2f}% ({r['elapsed']:.0f}s)")
for cfg in top_configs:
pn = cfg['proxy']
mode = cfg['mode']
tpct = cfg['threshold_pct']
name = f"{pn}/{mode}/p{int(tpct*100)}"
print(f"\n{name} (sweep rank: Sharpe={cfg['sharpe']:.4f})")
t0 = time.time()
def factory(kw, pn=pn, mode=mode, tpct=tpct):
return MultiProxyEngine(**kw, proxy_name=pn, mode=mode,
threshold_pct=tpct, window=500)
r = run_backtest(factory, name, forewarner=forewarner)
r['elapsed'] = time.time() - t0
ae_results.append(r)
print(f" {r['roi']:.2f}% PF={r['pf']:.4f} DD={r['dd']:.2f}% ({r['elapsed']:.0f}s)")
print("\n" + "="*83)
print("EXP 3 — ALPHA ENGINE RESULTS")
print_table(ae_results, gold=GOLD)
return ae_results
def main():
ensure_jit()
print("\nLoading data & forewarner...")
load_data()
fw = load_forewarner()
top_configs, baseline_ref = fast_sweep()
print(f"\nFast sweep done. Top {len(top_configs)} configs selected for AE validation.")
print(f"Fast baseline: ROI={baseline_ref['roi']:.2f}% Sharpe={baseline_ref['sharpe']:.4f}")
ae_results = validate_with_alpha_engine(top_configs, fw)
log_results(
ae_results,
_HERE / 'exp3_alpha_engine_results.json',
meta={
'experiment': 'exp3 longer proxies alpha engine validation',
'proxies_tested': list(PROXY_DEFS.keys()),
'modes_tested': ['gate','size'], # exit=shadow only, done in exp2
'note': 'Top-2 per proxy from fast sweep, validated with full Alpha Engine',
}
)
if __name__ == '__main__':
main()

View File

@@ -0,0 +1,505 @@
"""
Exp 4 — proxy_B Coupling & Orthogonality Sweep
Research questions:
1. Is proxy_B orthogonal to the entry signal (vel_div) and other system state?
2. Can proxy_B be *coupled* to existing system parameters to reduce DD without
reducing ROI? (position scale, hold limit, stop gate, rising-proxy exit)
3. Does proxy_B predict trades that will hit large adverse excursions (MAE)?
Method: retroactive shadow analysis on the full 2155-trade baseline.
- One full AE run with extended logging (per-bar proxy_B + vel_div + prices)
- All coupling tests applied post-hoc: O(N_trades) per config → < 1s for 150+ configs
- Focus metric: DD reduction with ROI >= gold * 0.95
Note on stop_pct=1.0 in gold config:
The engine has stop_pct=1.0 (100% — effectively no stop). Trades exit via:
fixed_tp (0.95%), max_hold_bars (120), or direction-reversal signal.
This means MAE can be large before trades recover → proxy-gated stop is meaningful.
Coupling modes:
A. scale_suppress: scale down position when proxy_B high at entry
B. scale_boost: scale up position when proxy_B low at entry
C. hold_limit: exit at fraction of natural hold when proxy_B_max exceeds threshold
D. rising_exit: exit early when proxy_B trajectory during hold is strongly rising
E. pure_stop: retroactive stop simulation (benchmark, no proxy coupling)
F. gated_stop: stop applies ONLY when proxy_B at entry exceeds threshold
Statistical tests:
- Pearson + Spearman: proxy_B vs vel_div, pnl, MAE
- Mann-Whitney U: worst-10% trades vs rest on proxy_B_entry
"""
import sys, time, json, math
sys.stdout.reconfigure(encoding='utf-8', errors='replace')
from pathlib import Path
import numpy as np
from collections import defaultdict
_HERE = Path(__file__).resolve().parent
sys.path.insert(0, str(_HERE.parent))
from exp_shared import (
ensure_jit, ENGINE_KWARGS, GOLD, MC_BASE_CFG,
load_data, load_forewarner, log_results
)
from nautilus_dolphin.nautilus.esf_alpha_orchestrator import NDAlphaEngine
from nautilus_dolphin.nautilus.adaptive_circuit_breaker import AdaptiveCircuitBreaker
# ── Extended shadow engine ────────────────────────────────────────────────────
class CouplingEngine(NDAlphaEngine):
"""Runs baseline + captures per-bar: proxy_B, vel_div, asset prices."""
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.day_proxy = {} # date_str → {ri: proxy_B}
self.day_veldiv = {} # date_str → {ri: vel_div}
self.day_prices = {} # date_str → {ri: {asset: price}}
self._n_before = 0
self.trade_dates = [] # parallel to trade_history
def process_day(self, date_str, df, asset_columns,
vol_regime_ok=None, direction=None, posture='APEX'):
self.day_proxy[date_str] = {}
self.day_veldiv[date_str] = {}
self.day_prices[date_str] = {}
self._n_before = len(self.trade_history)
self.begin_day(date_str, posture=posture, direction=direction)
bid = 0
for ri in range(len(df)):
row = df.iloc[ri]
vd = row.get('vel_div')
if vd is None or not np.isfinite(float(vd)):
self._global_bar_idx += 1; bid += 1; continue
def gf(col):
v = row.get(col)
if v is None: return 0.0
try: return float(v) if np.isfinite(float(v)) else 0.0
except: return 0.0
v50 = gf('v50_lambda_max_velocity')
v750 = gf('v750_lambda_max_velocity')
inst = gf('instability_50')
pb = inst - v750
self.day_proxy[date_str][ri] = pb
self.day_veldiv[date_str][ri] = float(vd)
prices = {}
for ac in asset_columns:
p = row.get(ac)
if p is not None and p > 0 and np.isfinite(float(p)):
prices[ac] = float(p)
self.day_prices[date_str][ri] = prices
if not prices:
self._global_bar_idx += 1; bid += 1; continue
vrok = bool(vol_regime_ok[ri]) if vol_regime_ok is not None else (bid >= 100)
self.step_bar(bar_idx=ri, vel_div=float(vd), prices=prices,
vol_regime_ok=vrok, v50_vel=v50, v750_vel=v750)
bid += 1
self.end_day()
for _ in self.trade_history[self._n_before:]:
self.trade_dates.append(date_str)
# ── Build shadow data ─────────────────────────────────────────────────────────
def build_shadow(d, fw):
kw = ENGINE_KWARGS.copy()
acb = AdaptiveCircuitBreaker()
acb.preload_w750(d['date_strings'])
eng = CouplingEngine(**kw)
eng.set_ob_engine(d['ob_eng'])
eng.set_acb(acb)
if fw: eng.set_mc_forewarner(fw, MC_BASE_CFG)
eng.set_esoteric_hazard_multiplier(0.0)
for pf in d['parquet_files']:
ds = pf.stem
df, acols, dvol = d['pq_data'][ds]
vol_ok = np.where(np.isfinite(dvol), dvol > d['vol_p60'], False)
eng.process_day(ds, df, acols, vol_regime_ok=vol_ok)
tr = eng.trade_history
roi = (eng.capital - 25000) / 25000 * 100
print(f" Shadow run: ROI={roi:.2f}% Trades={len(tr)}"
f" Tagged={len(eng.trade_dates)}")
return eng, tr
# ── Feature extraction ────────────────────────────────────────────────────────
def extract_features(eng, tr):
"""Per-trade features for coupling analysis."""
feats = []
for t, date in zip(tr, eng.trade_dates):
if date is None:
continue
entry_bar = int(t.entry_bar)
exit_bar = int(getattr(t, 'exit_bar', entry_bar))
direction = int(t.direction)
asset = t.asset
pnl_frac = float(t.pnl_pct) # fraction (not %)
pnl_abs = float(t.pnl_absolute) if hasattr(t, 'pnl_absolute') else pnl_frac * 250.
entry_price = float(getattr(t, 'entry_price', 0) or 0)
pb_entry = eng.day_proxy.get(date, {}).get(entry_bar, np.nan)
vd_entry = eng.day_veldiv.get(date, {}).get(entry_bar, np.nan)
# Hold bars (in-trade, exclusive of entry)
hold_bars = sorted(ri for ri in eng.day_proxy.get(date, {})
if entry_bar < ri <= exit_bar)
pb_hold = [eng.day_proxy[date][ri] for ri in hold_bars]
pb_max = max(pb_hold) if pb_hold else (pb_entry if np.isfinite(pb_entry) else 0.0)
pb_traj = (pb_hold[-1] - pb_hold[0]) if len(pb_hold) > 1 else 0.0
# Max adverse excursion (MAE) — negative = loss
mae = 0.0
if entry_price > 0:
for ri in hold_bars:
p = eng.day_prices.get(date, {}).get(ri, {}).get(asset, 0.0)
if p > 0:
exc = direction * (p - entry_price) / entry_price
if exc < mae:
mae = exc
# Early exit prices at hold fraction 0.25, 0.50, 0.75
early = {}
for frac in (0.25, 0.50, 0.75):
target = entry_bar + max(1, int(frac * (exit_bar - entry_bar)))
avail = [ri for ri in hold_bars if ri >= target]
if avail and entry_price > 0:
p = eng.day_prices.get(date, {}).get(avail[0], {}).get(asset, 0.0)
if p > 0:
early[frac] = direction * (p - entry_price) / entry_price
continue
early[frac] = pnl_frac # fallback: no change
feats.append(dict(
date=date,
hold_bars=exit_bar - entry_bar,
direction=direction,
pnl_frac=pnl_frac,
pnl_abs=pnl_abs,
pb_entry=pb_entry,
vd_entry=vd_entry,
pb_max=pb_max,
pb_traj=pb_traj,
mae=mae,
e25=early[0.25],
e50=early[0.50],
e75=early[0.75],
))
return feats
# ── Orthogonality analysis ────────────────────────────────────────────────────
def orthogonality_analysis(feats):
from scipy.stats import pearsonr, spearmanr, mannwhitneyu
valid = [f for f in feats if np.isfinite(f['pb_entry']) and np.isfinite(f['vd_entry'])]
pb_e = np.array([f['pb_entry'] for f in valid])
vd_e = np.array([f['vd_entry'] for f in valid])
pnl = np.array([f['pnl_frac'] for f in valid])
mae = np.array([f['mae'] for f in valid])
pb_mx = np.array([f['pb_max'] for f in valid])
hold = np.array([f['hold_bars'] for f in valid])
print(f"\n N valid (finite pb_entry + vd_entry): {len(valid)}/{len(feats)}")
print(f" proxy_B stats: mean={pb_e.mean():.4f} std={pb_e.std():.4f} "
f"p10={np.percentile(pb_e,10):.4f} p90={np.percentile(pb_e,90):.4f}")
print(f" vel_div stats: mean={vd_e.mean():.4f} std={vd_e.std():.4f}")
print()
pairs = [
('pb_entry', pb_e, 'vel_div_entry', vd_e),
('pb_entry', pb_e, 'pnl_frac', pnl),
('pb_entry', pb_e, 'mae', mae),
('pb_entry', pb_e, 'hold_bars', hold),
('pb_max', pb_mx, 'pnl_frac', pnl),
('pb_max', pb_mx, 'mae', mae),
]
corr_res = {}
for na, a, nb, b in pairs:
pr, pp = pearsonr(a, b)
sr, sp = spearmanr(a, b)
sig = '***' if pp < 0.001 else '**' if pp < 0.01 else '*' if pp < 0.05 else 'ns'
print(f" corr({na}, {nb}): Pearson r={pr:+.4f} p={pp:.4f} {sig:3s}"
f" Spearman rho={sr:+.4f}")
corr_res[f'{na}_vs_{nb}'] = dict(pearson=float(pr), p=float(pp),
spearman=float(sr), sig=sig)
# Mann-Whitney: is proxy_B different for worst-10% trades vs rest?
print()
for label, metric in [('worst_pnl_10pct', pnl), ('worst_mae_10pct', mae)]:
cut = np.percentile(metric, 10)
mask_w = metric <= cut
pb_w = pb_e[mask_w]
pb_r = pb_e[~mask_w]
stat, p = mannwhitneyu(pb_w, pb_r, alternative='two-sided')
sig = '***' if p < 0.001 else '**' if p < 0.01 else '*' if p < 0.05 else 'ns'
print(f" MW {label}: pb_entry worst={pb_w.mean():.4f} rest={pb_r.mean():.4f} "
f"p={p:.4f} {sig}")
corr_res[f'mw_{label}'] = dict(stat=float(stat), p=float(p),
mean_worst=float(pb_w.mean()),
mean_rest=float(pb_r.mean()), sig=sig)
return corr_res
# ── Coupling sweep ────────────────────────────────────────────────────────────
def _dd_roi(new_pnl_abs, date_order, date_to_trades):
"""Retroactive DD and ROI from modified per-trade PnL array."""
cap, peak, max_dd = 25000.0, 25000.0, 0.0
total = 0.0
for d in date_order:
for i in date_to_trades[d]:
cap += new_pnl_abs[i]
total += new_pnl_abs[i]
if cap > peak: peak = cap
dd = (peak - cap) / peak * 100.0
if dd > max_dd: max_dd = dd
return total / 25000. * 100., max_dd
def coupling_sweep(feats, n_max=None):
N = len(feats)
if n_max: feats = feats[:n_max]
# ---- Arrays ----
pnl_abs = np.array([f['pnl_abs'] for f in feats])
pnl_frac = np.array([f['pnl_frac'] for f in feats])
pb_entry = np.array([f['pb_entry'] for f in feats])
pb_max = np.array([f['pb_max'] for f in feats])
pb_traj = np.array([f['pb_traj'] for f in feats])
mae = np.array([f['mae'] for f in feats])
e25 = np.array([f['e25'] for f in feats])
e50 = np.array([f['e50'] for f in feats])
e75 = np.array([f['e75'] for f in feats])
# Replace NaN pb with median
pb_med = float(np.nanmedian(pb_entry))
pb_entry_c = np.where(np.isfinite(pb_entry), pb_entry, pb_med)
pb_max_c = np.where(np.isfinite(pb_max), pb_max, pb_med)
# Percentile ranks (0=low, 1=high)
def prank(x):
r = np.argsort(np.argsort(x)).astype(float)
return r / max(len(r) - 1, 1)
rk_e = prank(pb_entry_c)
rk_mx = prank(pb_max_c)
rk_tr = prank(pb_traj)
# Date ordering for DD computation
dates_list = [f['date'] for f in feats]
date_order = sorted(set(dates_list))
date_to_trades = defaultdict(list)
for i, d in enumerate(dates_list):
date_to_trades[d].append(i)
base_roi, base_dd = _dd_roi(pnl_abs, date_order, date_to_trades)
# Helper: new_pnl_abs from early exit fraction
def _early_abs(early_frac_arr):
ratio = np.where(np.abs(pnl_frac) > 1e-9,
early_frac_arr / pnl_frac, 1.0)
ratio = np.clip(ratio, -5.0, 5.0)
return pnl_abs * ratio
configs = []
def add(name, new_pnl, **meta):
roi, dd = _dd_roi(new_pnl, date_order, date_to_trades)
configs.append(dict(name=name, roi=roi, dd=dd,
roi_delta=roi - base_roi,
dd_delta=dd - base_dd,
**meta))
# ─── Mode A: scale_suppress — scale down when proxy_B high ───────────────
for sig_name, rk in [('pb_entry', rk_e), ('pb_max', rk_mx), ('pb_traj', rk_tr)]:
for thr in [0.50, 0.65, 0.75, 0.85]:
for alpha in [0.5, 1.0, 2.0]:
for s_min in [0.0, 0.25, 0.5]:
scales = np.maximum(s_min, 1.0 - alpha * np.maximum(0, rk - thr))
add(f'A/{sig_name}/thr{thr}/a{alpha}/min{s_min}',
pnl_abs * scales,
mode='scale_suppress', signal=sig_name,
thr=thr, alpha=alpha, s_min=s_min,
scale_mean=float(scales.mean()))
# ─── Mode B: scale_boost — scale up when proxy_B low ─────────────────────
for sig_name, rk in [('pb_entry', rk_e)]:
for thr in [0.25, 0.35, 0.50]:
for alpha in [0.5, 1.0]:
scales = 1.0 + alpha * np.maximum(0, thr - rk)
add(f'B/{sig_name}/thr{thr}/a{alpha}',
pnl_abs * scales,
mode='scale_boost', signal=sig_name,
thr=thr, alpha=alpha,
scale_mean=float(scales.mean()))
# ─── Mode C: hold_limit — exit early when pb_max high ────────────────────
for frac, early_arr in [(0.25, e25), (0.50, e50), (0.75, e75)]:
for thr_pct in [0.65, 0.75, 0.85, 0.90]:
thr_abs = np.percentile(pb_max_c, thr_pct * 100)
trigger = pb_max_c > thr_abs
new_pnl_f = np.where(trigger, early_arr, pnl_frac)
n_trig = int(trigger.sum())
add(f'C/frac{frac}/pbmax_p{thr_pct}',
_early_abs(new_pnl_f),
mode='hold_limit', frac=frac, thr_pct=thr_pct, n_triggered=n_trig)
# ─── Mode D: rising_exit — exit early when pb trajectory strongly up ──────
for frac, early_arr in [(0.25, e25), (0.50, e50)]:
for thr_pct in [0.70, 0.80, 0.90]:
thr_abs = np.percentile(pb_traj, thr_pct * 100)
trigger = pb_traj > thr_abs
new_pnl_f = np.where(trigger, early_arr, pnl_frac)
n_trig = int(trigger.sum())
add(f'D/frac{frac}/traj_p{thr_pct}',
_early_abs(new_pnl_f),
mode='rising_exit', frac=frac, thr_pct=thr_pct, n_triggered=n_trig)
# ─── Mode E: pure_stop — retroactive stop (no proxy, benchmark) ──────────
for stop_p in [0.003, 0.005, 0.008, 0.010, 0.015, 0.020, 0.030]:
# mae < -stop_p → exit was stopped; clamp pnl_frac to -stop_p
stopped = mae < -stop_p
new_pnl_f = np.where(stopped, -stop_p, pnl_frac)
n_trig = int(stopped.sum())
add(f'E/stop_{stop_p:.3f}',
_early_abs(new_pnl_f),
mode='pure_stop', stop_pct=stop_p, n_triggered=n_trig)
# ─── Mode F: gated_stop — stop applies only when pb_entry high ───────────
for stop_p in [0.005, 0.008, 0.010, 0.015]:
for gate_pct in [0.50, 0.60, 0.75, 0.85]:
gate_thr = np.percentile(pb_entry_c, gate_pct * 100)
gated = pb_entry_c > gate_thr
stopped = gated & (mae < -stop_p)
new_pnl_f = np.where(stopped, -stop_p, pnl_frac)
n_trig = int(stopped.sum())
add(f'F/stop_{stop_p:.3f}/gate_p{gate_pct}',
_early_abs(new_pnl_f),
mode='gated_stop', stop_pct=stop_p, gate_pct=gate_pct,
n_triggered=n_trig)
return base_roi, base_dd, configs
# ── Main ──────────────────────────────────────────────────────────────────────
def main():
ensure_jit()
print("\nLoading data & forewarner...")
d = load_data()
fw = load_forewarner()
print("\nBuilding shadow data (one full AE run)...")
t0 = time.time()
eng, tr = build_shadow(d, fw)
print(f" Built in {time.time()-t0:.0f}s")
print("\nExtracting per-trade features...")
feats = extract_features(eng, tr)
print(f" {len(feats)} trades with valid features")
# ── Orthogonality ─────────────────────────────────────────────────────────
print("\n" + "="*60)
print("ORTHOGONALITY ANALYSIS")
print("="*60)
corr_res = orthogonality_analysis(feats)
# ── Coupling sweep ────────────────────────────────────────────────────────
print("\n" + "="*60)
print(f"COUPLING SWEEP (N={len(feats)} trades)")
print("="*60)
t1 = time.time()
base_roi, base_dd, configs = coupling_sweep(feats)
print(f" Tested {len(configs)} configs in {time.time()-t1:.2f}s")
print(f" Baseline: ROI={base_roi:.2f}% DD={base_dd:.2f}%")
# ── Find DD-reduction candidates ──────────────────────────────────────────
GOLD_ROI = GOLD['roi']
GOLD_DD = GOLD['dd']
ROI_FLOOR = GOLD_ROI * 0.95 # allow at most -5% ROI cost
candidates = [c for c in configs
if c['dd'] < GOLD_DD and c['roi'] >= ROI_FLOOR]
candidates.sort(key=lambda c: (c['dd_delta'], -c['roi_delta']))
print(f"\n Configs with DD < {GOLD_DD:.2f}% AND ROI >= {ROI_FLOOR:.1f}%: "
f"{len(candidates)}")
# Also find absolute best DD reduction regardless of ROI
by_dd = sorted(configs, key=lambda c: c['dd'])[:10]
# Print tables
def hdr():
print(f"\n {'Config':<45} {'ROI%':>7} {'DD%':>6} {'ΔROI':>7} {'ΔDD':>7}"
f" {'mode':<14}")
print(' ' + '-'*90)
def row(c):
extra = ''
if 'n_triggered' in c: extra = f" trig={c['n_triggered']}"
if 'scale_mean' in c: extra = f" smean={c['scale_mean']:.3f}"
print(f" {c['name']:<45} {c['roi']:>7.2f} {c['dd']:>6.2f} "
f"{c['roi_delta']:>+7.2f} {c['dd_delta']:>+7.2f} "
f"{c.get('mode',''):<14}{extra}")
print(f"\n *** GOLD ***: ROI={GOLD_ROI:.2f}% DD={GOLD_DD:.2f}%")
if candidates:
print("\n ── DD < gold AND ROI >= 95% gold ──")
hdr()
for c in candidates[:20]:
row(c)
else:
print("\n (no configs meet both criteria)")
print("\n ── Top 10 by lowest DD (regardless of ROI) ──")
hdr()
for c in by_dd:
row(c)
# ── Summary by mode ───────────────────────────────────────────────────────
from itertools import groupby
print("\n ── Best config per mode (by DD delta, ROI >= floor) ──")
hdr()
by_mode = defaultdict(list)
for c in configs:
by_mode[c.get('mode', 'other')].append(c)
for mode, cs in sorted(by_mode.items()):
best = min(cs, key=lambda c: c['dd'])
row(best)
# ── Log results ───────────────────────────────────────────────────────────
out = _HERE / 'exp4_proxy_coupling_results.json'
payload = {
'gold': GOLD,
'baseline': dict(roi=base_roi, dd=base_dd),
'orthogonality': corr_res,
'n_configs_tested': len(configs),
'dd_reduction_candidates': candidates[:20],
'top10_by_dd': by_dd,
'best_per_mode': {
mode: min(cs, key=lambda c: c['dd'])
for mode, cs in by_mode.items()
},
'all_configs': configs,
}
out.parent.mkdir(parents=True, exist_ok=True)
with open(out, 'w', encoding='utf-8') as f:
json.dump(payload, f, indent=2)
print(f"\n Logged → {out}")
if __name__ == '__main__':
main()

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,212 @@
"""
Exp 5 — Two-pass β VAE training.
The question: does high-β pass (β=4) to "map features" followed by low-β pass
(β=0.1) for "fidelity" outperform single-pass β=0.1?
Theory:
Pass 1 (high β): forces encoder to compress — ideally clusters similar market
states together, even at cost of reconstruction quality.
Acts as a structured initializer.
Pass 2 (low β): fine-tunes with more fidelity, starting from the structured
initializer rather than random weights.
We test three variants:
A. Single-pass β=0.1 (baseline, AUC≈0.6918 from flint_precursor_sweep)
B. Two-pass sequential: β=4 (20ep) → β=0.1 (20ep) on same model
C. Two-pass sequential: β=2 (20ep) → β=0.1 (20ep) (softer first pass)
D. Dual encoder: β=4 encoder + β=0.1 encoder, z concatenated (16-dim total)
Metric: OOS AUC for eigenspace stress prediction (K=5, same as e2e_precursor_auc.py).
Gate: if two-pass AUC > single-pass AUC + 0.02 → meaningful improvement.
Note on β=12 (the user's original suggestion):
β=12 would cause complete posterior collapse even with warmup (β=6 collapsed at 0/20 dims).
β=4 is the practical upper bound where some structure survives.
We test β=2 and β=4 to find the sweet spot.
"""
import sys
sys.stdout.reconfigure(encoding='utf-8', errors='replace')
from pathlib import Path
import numpy as np
_HERE = Path(__file__).resolve().parent
sys.path.insert(0, str(_HERE))
_CORPUS_PATH = str(_HERE / 'corpus_cache.npz')
# ── Load T1 corpus ────────────────────────────────────────────────────────────
print("Loading 16K eigen corpus...")
from corpus_builder import DolphinCorpus, OFF, T1 as T1_DIM
corpus = DolphinCorpus.load(_CORPUS_PATH)
mask = corpus.mask[:, 1]
X_e = corpus.X[mask]
T1_data = X_e[:, OFF[1]:OFF[1]+T1_DIM].copy() # (16607, 20)
N = len(T1_data)
print(f" N={N} T1 shape={T1_data.shape}")
# ── Stress labels (K=5) ───────────────────────────────────────────────────────
K = 5
inst_w50 = T1_data[:, 3]
gap_w50 = T1_data[:, 2]
vel_w750 = T1_data[:, 16]
inst_p90 = np.percentile(inst_w50, 90)
gap_p10 = np.percentile(gap_w50, 10)
labels = np.zeros(N, dtype=np.float32)
for i in range(N - K):
if np.any(inst_w50[i+1:i+1+K] > inst_p90) and np.any(gap_w50[i+1:i+1+K] < gap_p10):
labels[i] = 1.0
print(f" Stress labels: {labels.mean()*100:.1f}% positive")
# Chronological split
n_test = N // 4
idx_tr = slice(0, N - n_test)
idx_te = slice(N - n_test, N)
# ── AUC helpers ───────────────────────────────────────────────────────────────
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import roc_auc_score
def eval_auc(z_all, labels, n_test):
X_lr = z_all[:-K]; y_lr = labels[:-K]
valid = np.isfinite(X_lr).all(1) & np.isfinite(y_lr)
X_lr, y_lr = X_lr[valid], y_lr[valid]
n = len(X_lr) // 4
X_tr, X_te = X_lr[:-n], X_lr[-n:]
y_tr, y_te = y_lr[:-n], y_lr[-n:]
clf = LogisticRegression(class_weight='balanced', max_iter=500, C=0.1)
clf.fit(X_tr, y_tr)
preds = clf.predict_proba(X_te)[:,1]
auc = roc_auc_score(y_te, preds)
return max(auc, 1-auc)
# ── Import FlintHDVAE ─────────────────────────────────────────────────────────
from flint_hd_vae import FlintHDVAE
def build_model(seed=42):
return FlintHDVAE(input_dim=20, hd_dim=512, latent_dim=8,
beta=0.1, seed=seed, use_flint_norm=False)
n_vae_train = int(N * 0.8)
T1_train = T1_data[:n_vae_train]
results = {}
# ── Variant A: Single-pass β=0.1 (baseline) ──────────────────────────────────
print("\n" + "="*55)
print("A. SINGLE-PASS β=0.1 (baseline)")
print("="*55)
m_a = build_model(seed=42)
m_a.fit(T1_train, epochs=40, lr=1e-3, batch_size=256, verbose=True, warmup_frac=0.3)
z_a = m_a.encode(T1_data)
print(f" z var per dim: {z_a.var(0).round(3)}")
print(f" Active dims (var>0.1): {int((z_a.var(0)>0.1).sum())}/8")
auc_a = eval_auc(z_a, labels, n_test)
print(f" OOS AUC = {auc_a:.4f}")
results['A_single_pass_b0.1'] = dict(auc=auc_a, active_dims=int((z_a.var(0)>0.1).sum()),
z_var=z_a.var(0).tolist())
# ── Variant B: Two-pass β=4 → β=0.1 ─────────────────────────────────────────
print("\n" + "="*55)
print("B. TWO-PASS β=4 (20ep) → β=0.1 (20ep)")
print("="*55)
m_b = build_model(seed=42)
print(" Pass 1: β=4, 20 epochs")
m_b.beta = 4.0
m_b.fit(T1_train, epochs=20, lr=1e-3, batch_size=256, verbose=True, warmup_frac=0.3)
print(" Pass 2: β=0.1, 20 epochs (continuing from Pass 1 weights)")
m_b.beta = 0.1
m_b.fit(T1_train, epochs=20, lr=5e-4, batch_size=256, verbose=True, warmup_frac=0.1)
z_b = m_b.encode(T1_data)
print(f" z var per dim: {z_b.var(0).round(3)}")
print(f" Active dims (var>0.1): {int((z_b.var(0)>0.1).sum())}/8")
auc_b = eval_auc(z_b, labels, n_test)
print(f" OOS AUC = {auc_b:.4f} (vs A: {auc_b-auc_a:+.4f})")
results['B_twopass_b4_b0.1'] = dict(auc=auc_b, active_dims=int((z_b.var(0)>0.1).sum()),
z_var=z_b.var(0).tolist())
# ── Variant C: Two-pass β=2 → β=0.1 ─────────────────────────────────────────
print("\n" + "="*55)
print("C. TWO-PASS β=2 (20ep) → β=0.1 (20ep)")
print("="*55)
m_c = build_model(seed=42)
print(" Pass 1: β=2, 20 epochs")
m_c.beta = 2.0
m_c.fit(T1_train, epochs=20, lr=1e-3, batch_size=256, verbose=True, warmup_frac=0.3)
print(" Pass 2: β=0.1, 20 epochs")
m_c.beta = 0.1
m_c.fit(T1_train, epochs=20, lr=5e-4, batch_size=256, verbose=True, warmup_frac=0.1)
z_c = m_c.encode(T1_data)
print(f" z var per dim: {z_c.var(0).round(3)}")
print(f" Active dims (var>0.1): {int((z_c.var(0)>0.1).sum())}/8")
auc_c = eval_auc(z_c, labels, n_test)
print(f" OOS AUC = {auc_c:.4f} (vs A: {auc_c-auc_a:+.4f})")
results['C_twopass_b2_b0.1'] = dict(auc=auc_c, active_dims=int((z_c.var(0)>0.1).sum()),
z_var=z_c.var(0).tolist())
# ── Variant D: Dual encoder (β=4 ‖ β=0.1, z concatenated) ───────────────────
print("\n" + "="*55)
print("D. DUAL ENCODER: β=4 encoder ‖ β=0.1 encoder (z concat → 16-dim)")
print("="*55)
m_d_hi = build_model(seed=42)
m_d_hi.beta = 4.0
print(" Training β=4 encoder (20 epochs)...")
m_d_hi.fit(T1_train, epochs=20, lr=1e-3, batch_size=256, verbose=False, warmup_frac=0.3)
m_d_lo = build_model(seed=123)
m_d_lo.beta = 0.1
print(" Training β=0.1 encoder (40 epochs)...")
m_d_lo.fit(T1_train, epochs=40, lr=1e-3, batch_size=256, verbose=False, warmup_frac=0.3)
z_hi = m_d_hi.encode(T1_data) # (N, 8)
z_lo = m_d_lo.encode(T1_data) # (N, 8)
z_d = np.concatenate([z_hi, z_lo], axis=1) # (N, 16)
print(f" β=4 z var: {z_hi.var(0).round(3)}")
print(f" β=0.1 z var: {z_lo.var(0).round(3)}")
print(f" Combined z shape: {z_d.shape}")
auc_d = eval_auc(z_d, labels, n_test)
print(f" OOS AUC = {auc_d:.4f} (vs A: {auc_d-auc_a:+.4f})")
results['D_dual_b4_b0.1'] = dict(auc=auc_d,
active_dims_hi=int((z_hi.var(0)>0.1).sum()),
active_dims_lo=int((z_lo.var(0)>0.1).sum()),
z_var_hi=z_hi.var(0).tolist(), z_var_lo=z_lo.var(0).tolist())
# ── Summary ───────────────────────────────────────────────────────────────────
GATE = 0.02 # improvement threshold
print("\n" + "="*55)
print("EXP 5 — TWO-PASS β SUMMARY")
print("="*55)
print(f"{'Variant':<35} {'AUC':>8} {'vs A':>8} {'ActiveDims':>11}")
print('-'*65)
for k, v in results.items():
ad = v.get('active_dims', v.get('active_dims_lo', '?'))
delta = v['auc'] - auc_a
flag = ' ◄ GAIN' if delta >= GATE else ('' if delta > 0 else '')
print(f" {k:<33} {v['auc']:>8.4f} {delta:>+8.4f} {str(ad):>11}{flag}")
best = max(results, key=lambda k: results[k]['auc'])
best_auc = results[best]['auc']
print(f"\n Best: {best} AUC={best_auc:.4f}")
if best_auc - auc_a >= GATE:
print(f" GATE PASS: improvement {best_auc-auc_a:+.4f}{GATE}")
print(f" → Two-pass training IS beneficial. Adopt for FlintHDVAE.")
else:
print(f" GATE FAIL: best improvement {best_auc-auc_a:+.4f} < {GATE}")
print(f" → Two-pass training offers NO meaningful gain on this dataset.")
# Save
import json
out = _HERE / 'exp5_dvae_twopass_results.json'
with open(out, 'w', encoding='utf-8') as f:
json.dump({'results': results, 'baseline_auc': float(auc_a),
'gate_threshold': GATE, 'winner': best,
'note': 'beta=12 not tested (collapses; beta=6 already showed 0/20 active dims)'}, f, indent=2)
print(f"\n Logged → {out}")

View File

@@ -0,0 +1,75 @@
{
"results": {
"A_single_pass_b0.1": {
"auc": 0.6918441493851568,
"active_dims": 8,
"z_var": [
0.33613626115314765,
0.4036545839396876,
0.46812200154628386,
0.7897261382354528,
0.30868460378586354,
0.6207298112610948,
0.654486990717734,
0.6487686368882809
]
},
"B_twopass_b4_b0.1": {
"auc": 0.685815978891897,
"active_dims": 8,
"z_var": [
0.6463562800257637,
0.794515080808024,
0.6627231420838106,
0.3360878581568057,
0.5536564847674447,
0.47145868605069,
0.6024979573116871,
0.15243441499985075
]
},
"C_twopass_b2_b0.1": {
"auc": 0.6876680380817412,
"active_dims": 8,
"z_var": [
0.13887391434276264,
0.6846345552164088,
0.5075111792873924,
0.6646689437418141,
0.1307330087594524,
0.5100345972756644,
0.7035295234945932,
0.7401098727154677
]
},
"D_dual_b4_b0.1": {
"auc": 0.6771870187460258,
"active_dims_hi": 1,
"active_dims_lo": 8,
"z_var_hi": [
0.016414329885322456,
0.13325390881853802,
0.00914966636326114,
0.015184221145285375,
0.012848108781067335,
0.00813838316387418,
0.023348222620207693,
0.027649003388626463
],
"z_var_lo": [
0.5964793155326009,
0.506480565733225,
0.14274180282824042,
0.3696797264506598,
0.7152758659328332,
0.4816811876862482,
0.45584810995992486,
0.7347258490907227
]
}
},
"baseline_auc": 0.6918441493851568,
"gate_threshold": 0.02,
"winner": "A_single_pass_b0.1",
"note": "beta=12 not tested (collapses; beta=6 already showed 0/20 active dims)"
}

View File

@@ -0,0 +1,323 @@
"""
Exp 6 — Stop-Tightening: Global vs proxy_B-Gated Stop Test
Context:
proxy_B = instability_50 v750_lambda_max_velocity
AUC=0.715 for eigenspace stress, r=+0.42 (p=0.003) with intraday MAE.
Retroactive pure_stop benchmark (stop=0.003) showed +1.36pp ROI, 0.14pp DD, 18 triggers.
Hypothesis: gated stop (only when proxy_B_entry > gate_pct percentile) should
preferentially protect worst-MAE trades while leaving the rest alone.
Configs tested:
A. Baseline — stop_pct=1.0 (gold control; must reproduce gold metrics)
B. Global stop sweep — stop_pct ∈ [0.003, 0.005, 0.010] (3 configs)
C. proxy_B gated — gate_pct × tight_stop: [0.65, 0.75] × [0.005, 0.010] = 4 configs
Total: 8 full AE runs.
Focus metric: DD < 15.05% AND ROI >= 84.1% (95% of gold=88.55%).
Also tracked: n_override_set (entries that received tight stop), stop_exits, exit_reason breakdown.
Results logged to exp6_stop_test_results.json.
"""
import sys, time, json, math
sys.stdout.reconfigure(encoding='utf-8', errors='replace')
from pathlib import Path
import numpy as np
_HERE = Path(__file__).resolve().parent
sys.path.insert(0, str(_HERE.parent))
from exp_shared import (
ensure_jit, ENGINE_KWARGS, GOLD, MC_BASE_CFG,
load_data, load_forewarner, log_results, print_table,
)
from nautilus_dolphin.nautilus.esf_alpha_orchestrator import NDAlphaEngine
from nautilus_dolphin.nautilus.adaptive_circuit_breaker import AdaptiveCircuitBreaker
# ── proxy_B Gated Stop Engine ─────────────────────────────────────────────────
class ProxyStopGatedEngine(NDAlphaEngine):
"""
Extends NDAlphaEngine with proxy_B-gated per-trade stop logic.
At each bar, proxy_B = instability_50 - v750_lambda_max_velocity is computed
and tracked in a rolling 500-bar history.
At entry, if proxy_B_at_entry > percentile(history, gate_pct):
→ sets _pending_stop_override = tight_stop
Otherwise _pending_stop_override = None (global stop_pct=1.0 applies).
Efficiency hypothesis: gated stop should reduce DD with less ROI cost per unit
of DD reduction vs global stop (since it preferentially targets high-MAE trades).
"""
def __init__(self, *args, gate_pct: float = 0.75, tight_stop: float = 0.005, **kwargs):
super().__init__(*args, **kwargs)
self.gate_pct = gate_pct
self.tight_stop = tight_stop
self._current_proxy_b: float = 0.0
self._proxy_b_history: list = [] # rolling 500-bar window
self.n_override_set: int = 0 # entries where tight stop was applied
def process_day(self, date_str, df, asset_columns,
vol_regime_ok=None, direction=None, posture='APEX'):
self.begin_day(date_str, posture=posture, direction=direction)
bid = 0
for ri in range(len(df)):
row = df.iloc[ri]
vd = row.get('vel_div')
if vd is None or not np.isfinite(float(vd)):
self._global_bar_idx += 1; bid += 1; continue
def gf(col):
v = row.get(col)
if v is None: return 0.0
try: return float(v) if np.isfinite(float(v)) else 0.0
except: return 0.0
v50 = gf('v50_lambda_max_velocity')
v750 = gf('v750_lambda_max_velocity')
inst = gf('instability_50')
pb = inst - v750
# Update proxy_B state before step_bar so _try_entry can read it
self._current_proxy_b = pb
self._proxy_b_history.append(pb)
if len(self._proxy_b_history) > 500:
self._proxy_b_history = self._proxy_b_history[-500:]
prices = {}
for ac in asset_columns:
p = row.get(ac)
if p is not None and p > 0 and np.isfinite(float(p)):
prices[ac] = float(p)
if not prices:
self._global_bar_idx += 1; bid += 1; continue
vrok = bool(vol_regime_ok[ri]) if vol_regime_ok is not None else (bid >= 100)
self.step_bar(bar_idx=ri, vel_div=float(vd), prices=prices,
vol_regime_ok=vrok, v50_vel=v50, v750_vel=v750)
bid += 1
return self.end_day()
def _try_entry(self, bar_idx, vel_div, prices, price_histories,
v50_vel=0.0, v750_vel=0.0):
# Gate: set _pending_stop_override when proxy_B is elevated
if len(self._proxy_b_history) >= 20:
threshold = np.percentile(self._proxy_b_history, self.gate_pct * 100.0)
if self._current_proxy_b > threshold:
self._pending_stop_override = self.tight_stop
self.n_override_set += 1
else:
self._pending_stop_override = None
else:
self._pending_stop_override = None
return super()._try_entry(bar_idx, vel_div, prices, price_histories, v50_vel, v750_vel)
def reset(self):
super().reset()
self._current_proxy_b = 0.0
self._proxy_b_history = []
self.n_override_set = 0
# ── Run harness ───────────────────────────────────────────────────────────────
def _run(engine_factory, name, d, fw):
"""
Full 55-day backtest using the shared data dict `d`.
Returns metrics dict with additional stop-specific fields.
"""
from nautilus_dolphin.nautilus.adaptive_circuit_breaker import AdaptiveCircuitBreaker
import pandas as pd
kw = ENGINE_KWARGS.copy()
acb = AdaptiveCircuitBreaker()
acb.preload_w750(d['date_strings'])
eng = engine_factory(kw)
eng.set_ob_engine(d['ob_eng'])
eng.set_acb(acb)
if fw is not None:
eng.set_mc_forewarner(fw, MC_BASE_CFG)
eng.set_esoteric_hazard_multiplier(0.0)
daily_caps, daily_pnls = [], []
for pf in d['parquet_files']:
ds = pf.stem
df, acols, dvol = d['pq_data'][ds]
cap_before = eng.capital
vol_ok = np.where(np.isfinite(dvol), dvol > d['vol_p60'], False)
eng.process_day(ds, df, acols, vol_regime_ok=vol_ok)
daily_caps.append(eng.capital)
daily_pnls.append(eng.capital - cap_before)
tr = eng.trade_history
n = len(tr)
roi = (eng.capital - 25000.0) / 25000.0 * 100.0
if n == 0:
return dict(name=name, roi=roi, pf=0.0, dd=0.0, wr=0.0, sharpe=0.0, trades=0,
stop_exits=0, n_override_set=0, exit_reasons={})
def _abs(t): return t.pnl_absolute if hasattr(t, 'pnl_absolute') else t.pnl_pct * 250.0
wins = [t for t in tr if _abs(t) > 0]
losses = [t for t in tr if _abs(t) <= 0]
wr = len(wins) / n * 100.0
pf = sum(_abs(t) for t in wins) / max(abs(sum(_abs(t) for t in losses)), 1e-9)
peak_cap, max_dd = 25000.0, 0.0
for cap in daily_caps:
peak_cap = max(peak_cap, cap)
max_dd = max(max_dd, (peak_cap - cap) / peak_cap * 100.0)
dr = np.array([p / 25000.0 * 100.0 for p in daily_pnls])
sharpe = float(dr.mean() / (dr.std() + 1e-9) * math.sqrt(365)) if len(dr) > 1 else 0.0
# Exit reason breakdown
exit_reasons = {}
for t in tr:
r = t.exit_reason if hasattr(t, 'exit_reason') else 'UNKNOWN'
exit_reasons[r] = exit_reasons.get(r, 0) + 1
# Stop-specific stats
stop_exits = getattr(eng, 'stop_exits', 0)
n_override_set = getattr(eng, 'n_override_set', 0)
# Efficiency: ROI cost per DD unit reduced vs baseline
# (computed in main after baseline is known)
return dict(
name=name,
roi=roi, pf=pf, dd=max_dd, wr=wr, sharpe=sharpe, trades=n,
stop_exits=stop_exits,
n_override_set=n_override_set,
stop_trigger_rate=stop_exits / n if n > 0 else 0.0,
override_trigger_rate=n_override_set / n if n > 0 else 0.0,
exit_reasons=exit_reasons,
)
# ── Main ──────────────────────────────────────────────────────────────────────
def main():
t_start = time.time()
print("=" * 70)
print("Exp 6 — Stop-Tightening: Global vs proxy_B-Gated Stop Test")
print("=" * 70)
ensure_jit()
d = load_data()
fw = load_forewarner()
configs = []
# A. Baseline
configs.append(("A_baseline", lambda kw: NDAlphaEngine(**kw)))
# B. Global stop sweep
for sp in [0.003, 0.005, 0.010]:
sp_str = f"{sp:.3f}"
def _make_global(stop_val):
def _factory(kw):
k2 = dict(kw); k2['stop_pct'] = stop_val
return NDAlphaEngine(**k2)
return _factory
configs.append((f"B_global_stop={sp_str}", _make_global(sp)))
# C. proxy_B gated stop
for gate_pct in [0.65, 0.75]:
for tight_stop in [0.005, 0.010]:
tag = f"C_gated_gate={gate_pct:.2f}_stop={tight_stop:.3f}"
def _make_gated(gp, ts):
def _factory(kw):
return ProxyStopGatedEngine(gate_pct=gp, tight_stop=ts, **kw)
return _factory
configs.append((tag, _make_gated(gate_pct, tight_stop)))
results = []
for i, (name, factory) in enumerate(configs):
t0 = time.time()
print(f"\n[{i+1}/{len(configs)}] {name} ...")
res = _run(factory, name, d, fw)
elapsed = time.time() - t0
print(f" ROI={res['roi']:.2f}% PF={res['pf']:.4f} DD={res['dd']:.2f}% "
f"WR={res['wr']:.2f}% Sharpe={res['sharpe']:.3f} Trades={res['trades']} "
f"stop_exits={res['stop_exits']} n_override={res['n_override_set']} "
f"({elapsed:.0f}s)")
results.append(res)
# Verification check
baseline = results[0]
print(f"\n{'='*70}")
print(f"VERIFICATION — Baseline vs Gold:")
print(f" ROI: {baseline['roi']:.2f}% (gold={GOLD['roi']:.2f}%)")
print(f" PF: {baseline['pf']:.4f} (gold={GOLD['pf']:.4f})")
print(f" DD: {baseline['dd']:.2f}% (gold={GOLD['dd']:.2f}%)")
print(f" Trades: {baseline['trades']} (gold={GOLD['trades']})")
gold_match = (
abs(baseline['roi'] - GOLD['roi']) < 0.5 and
abs(baseline['pf'] - GOLD['pf']) < 0.005 and
abs(baseline['dd'] - GOLD['dd']) < 0.5 and
abs(baseline['trades'] - GOLD['trades']) < 10
)
print(f" Match: {'PASS ✓' if gold_match else 'FAIL ✗ — check engine state'}")
# Efficiency analysis vs baseline
base_roi = baseline['roi']
base_dd = baseline['dd']
target_roi = GOLD['roi'] * 0.95 # 84.1%
target_dd = GOLD['dd'] # 15.05%
print(f"\n{'='*70}")
print(f"EFFICIENCY TABLE (target: DD<{target_dd:.2f}% AND ROI>={target_roi:.1f}%)")
print(f"{'Config':<42} {'ROI%':>7} {'PF':>6} {'DD%':>6} {'ΔDD':>6} {'ΔROI':>6} {'stops':>6} {'ovrd':>6} {'OK':>4}")
print('-' * 90)
for r in results:
delta_roi = r['roi'] - base_roi
delta_dd = r['dd'] - base_dd
ok = 'Y' if (r['dd'] < target_dd and r['roi'] >= target_roi) else 'N'
stops_str = str(r['stop_exits'])
ovrd_str = str(r['n_override_set']) if r['n_override_set'] > 0 else '-'
print(f"{r['name']:<42} {r['roi']:>7.2f} {r['pf']:>6.4f} {r['dd']:>6.2f} "
f"{delta_dd:>+6.2f} {delta_roi:>+6.2f} {stops_str:>6} {ovrd_str:>6} {ok:>4}")
# Gated vs global efficiency: ROI cost per DD-point gained
print(f"\n{'='*70}")
print("EFFICIENCY RATIO (|ΔROI| / |ΔDD|) — lower = better DD reduction per ROI cost")
for r in results[1:]: # skip baseline
delta_roi = r['roi'] - base_roi
delta_dd = r['dd'] - base_dd
if abs(delta_dd) > 0.01:
eff = abs(delta_roi) / abs(delta_dd)
print(f" {r['name']}: |ΔROI/ΔDD| = {eff:.3f} (ΔROI={delta_roi:+.2f}%, ΔDD={delta_dd:+.2f}%)")
else:
print(f" {r['name']}: ΔDD≈0, no ratio")
# Exit reason breakdown for all configs
print(f"\n{'='*70}")
print("EXIT REASON BREAKDOWN:")
for r in results:
reasons = r.get('exit_reasons', {})
parts = ', '.join(f"{k}={v}" for k, v in sorted(reasons.items()))
print(f" {r['name']}: {parts}")
# Save results
outfile = _HERE / "exp6_stop_test_results.json"
log_results(results, outfile, gold=GOLD, meta={
"exp": "exp6",
"hypothesis": "proxy_B gated stop reduces DD with less ROI cost per unit vs global stop",
"total_elapsed_s": round(time.time() - t_start, 1),
"gold_match": gold_match,
})
total = time.time() - t_start
print(f"\nTotal elapsed: {total/60:.1f} min")
print("Done.")
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,161 @@
{
"gold": {
"roi": 88.55,
"pf": 1.215,
"dd": 15.05,
"sharpe": 4.38,
"wr": 50.5,
"trades": 2155
},
"results": [
{
"name": "A_baseline",
"roi": 88.54671933603525,
"pf": 1.21470506157439,
"dd": 15.046245386522427,
"wr": 50.48723897911833,
"sharpe": 4.378300370204196,
"trades": 2155,
"stop_exits": 0,
"n_override_set": 0,
"stop_trigger_rate": 0.0,
"override_trigger_rate": 0.0,
"exit_reasons": {
"MAX_HOLD": 1831,
"FIXED_TP": 324
}
},
{
"name": "B_global_stop=0.003",
"roi": -15.311007798855163,
"pf": 0.9421959562747195,
"dd": 23.306320544359586,
"wr": 38.9917695473251,
"sharpe": -1.9388716865680473,
"trades": 2916,
"stop_exits": 1415,
"n_override_set": 0,
"stop_trigger_rate": 0.48525377229080935,
"override_trigger_rate": 0.0,
"exit_reasons": {
"MAX_HOLD": 1139,
"STOP_LOSS": 1415,
"FIXED_TP": 362
}
},
{
"name": "B_global_stop=0.005",
"roi": 13.487121498751875,
"pf": 1.0360002475944843,
"dd": 22.29505159453218,
"wr": 46.31410256410257,
"sharpe": 0.9740585229386011,
"trades": 2496,
"stop_exits": 760,
"n_override_set": 0,
"stop_trigger_rate": 0.30448717948717946,
"override_trigger_rate": 0.0,
"exit_reasons": {
"MAX_HOLD": 1401,
"STOP_LOSS": 760,
"FIXED_TP": 335
}
},
{
"name": "B_global_stop=0.010",
"roi": 25.89578156145128,
"pf": 1.0719795562785166,
"dd": 15.20336058967519,
"wr": 49.137549756744804,
"sharpe": 1.9134302248896795,
"trades": 2261,
"stop_exits": 266,
"n_override_set": 0,
"stop_trigger_rate": 0.11764705882352941,
"override_trigger_rate": 0.0,
"exit_reasons": {
"MAX_HOLD": 1672,
"STOP_LOSS": 266,
"FIXED_TP": 323
}
},
{
"name": "C_gated_gate=0.65_stop=0.005",
"roi": 16.24755153825665,
"pf": 1.0443608125156127,
"dd": 17.778859717952795,
"wr": 49.166666666666664,
"sharpe": 0.9174633345434939,
"trades": 2280,
"stop_exits": 252,
"n_override_set": 18283,
"stop_trigger_rate": 0.11052631578947368,
"override_trigger_rate": 8.018859649122806,
"exit_reasons": {
"MAX_HOLD": 1688,
"STOP_LOSS": 252,
"FIXED_TP": 340
}
},
{
"name": "C_gated_gate=0.65_stop=0.010",
"roi": 35.43917845607325,
"pf": 1.0942200211535686,
"dd": 19.418041939977915,
"wr": 49.954337899543376,
"sharpe": 2.108930895745746,
"trades": 2190,
"stop_exits": 97,
"n_override_set": 17216,
"stop_trigger_rate": 0.04429223744292238,
"override_trigger_rate": 7.861187214611872,
"exit_reasons": {
"MAX_HOLD": 1771,
"FIXED_TP": 322,
"STOP_LOSS": 97
}
},
{
"name": "C_gated_gate=0.75_stop=0.005",
"roi": 21.775022456874765,
"pf": 1.0614653153826534,
"dd": 18.745303697036412,
"wr": 49.20424403183024,
"sharpe": 1.2948006583469642,
"trades": 2262,
"stop_exits": 219,
"n_override_set": 15232,
"stop_trigger_rate": 0.09681697612732096,
"override_trigger_rate": 6.733863837312113,
"exit_reasons": {
"MAX_HOLD": 1705,
"STOP_LOSS": 219,
"FIXED_TP": 338
}
},
{
"name": "C_gated_gate=0.75_stop=0.010",
"roi": 38.422502318822396,
"pf": 1.101746374983589,
"dd": 19.416920166163333,
"wr": 49.70278920896205,
"sharpe": 2.2247675548672463,
"trades": 2187,
"stop_exits": 85,
"n_override_set": 14320,
"stop_trigger_rate": 0.038866026520347506,
"override_trigger_rate": 6.547782350251486,
"exit_reasons": {
"MAX_HOLD": 1776,
"FIXED_TP": 326,
"STOP_LOSS": 85
}
}
],
"meta": {
"exp": "exp6",
"hypothesis": "proxy_B gated stop reduces DD with less ROI cost per unit vs global stop",
"total_elapsed_s": 1876.4,
"gold_match": true
}
}

View File

@@ -0,0 +1,487 @@
"""
Exp 7 — Live proxy_B Coupling: Scale, Hold-Limit, Rising-Exit
Context:
proxy_B = instability_50 - v750_lambda_max_velocity
AUC=0.715 for eigenspace stress, r=+0.42 (p=0.003) with intraday MAE.
Orthogonal to vel_div (r=-0.03, ns).
Exp6 confirmed: stop-tightening is DEAD (re-entry cascade, worse DD always).
Exp4 retroactive modes A/B/C/D were 98% invalid (entry_bar alignment bug —
entry_bar = global_bar_idx but day_proxy keyed by local ri).
This experiment is the FIRST valid live test of those four coupling modes.
Modes tested:
B. scale_boost: size UP when proxy_B is LOW at entry (calm = better quality)
A. scale_suppress: size DOWN when proxy_B is HIGH at entry (stress = worse quality)
C. hold_limit: exit early when proxy_B_max during hold exceeds threshold
D. rising_exit: exit early when current proxy_B rises above threshold during hold
Implementation notes:
- Modes B and A: post-entry notional scaling. No timing change → no re-entry dynamics.
ProxyScaleEngine._try_entry calls super() then scales self.position.notional.
- Modes C and D: in process_day loop, per-bar condition check BEFORE step_bar.
If triggered: _execute_exit(force reason) → position cleared → step_bar enters fresh.
No re-entry on same bar (process_bar checks bar_idx > _last_exit_bar).
- proxy_B history: rolling 500-bar window, minimum 20 samples before any gating activates.
- threshold at entry: np.percentile(history, thr_pct * 100) computed from rolling window.
Configs:
Baseline: 1
scale_boost (B): 4 (thr in {0.25, 0.35} x alpha in {0.5, 1.0})
scale_suppress (A): 4 (thr in {0.75, 0.85} x alpha in {0.5, 1.0}, s_min=0.25)
hold_limit (C): 3 (frac=0.5, thr_pct in {0.65, 0.75, 0.85})
rising_exit (D): 3 (frac=0.5, thr_pct in {0.65, 0.75, 0.85})
Total: 15 configs x ~240s = ~60 min
Focus metric: DD < 15.05% AND ROI >= 84.1% (95% of gold=88.55%)
Results logged to exp7_live_coupling_results.json.
"""
import sys, time, json, math
sys.stdout.reconfigure(encoding='utf-8', errors='replace')
from pathlib import Path
import numpy as np
_HERE = Path(__file__).resolve().parent
sys.path.insert(0, str(_HERE.parent))
from exp_shared import (
ensure_jit, ENGINE_KWARGS, GOLD, MC_BASE_CFG,
load_data, load_forewarner, log_results, print_table,
)
from nautilus_dolphin.nautilus.esf_alpha_orchestrator import NDAlphaEngine
from nautilus_dolphin.nautilus.adaptive_circuit_breaker import AdaptiveCircuitBreaker
# ── Base: proxy_B per-bar tracking ───────────────────────────────────────────
class ProxyBaseEngine(NDAlphaEngine):
"""Tracks proxy_B = instability_50 - v750_lambda_max_velocity per bar."""
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self._current_proxy_b: float = 0.0
self._proxy_b_history: list = [] # rolling 500-bar window
def _update_proxy(self, inst: float, v750: float) -> float:
pb = inst - v750
self._current_proxy_b = pb
self._proxy_b_history.append(pb)
if len(self._proxy_b_history) > 500:
self._proxy_b_history = self._proxy_b_history[-500:]
return pb
def _proxy_prank(self) -> float:
"""Percentile rank of current proxy_B in history (0=low, 1=high)."""
if not self._proxy_b_history:
return 0.5
n = len(self._proxy_b_history)
return sum(v < self._current_proxy_b for v in self._proxy_b_history) / n
def _proxy_threshold(self, thr_pct: float) -> float:
"""Absolute threshold: percentile thr_pct of rolling history."""
if len(self._proxy_b_history) < 20:
return float('inf')
return float(np.percentile(self._proxy_b_history, thr_pct * 100.0))
def process_day(self, date_str, df, asset_columns,
vol_regime_ok=None, direction=None, posture='APEX'):
self.begin_day(date_str, posture=posture, direction=direction)
bid = 0
for ri in range(len(df)):
row = df.iloc[ri]
vd = row.get('vel_div')
if vd is None or not np.isfinite(float(vd)):
self._global_bar_idx += 1; bid += 1; continue
def gf(col):
v = row.get(col)
if v is None: return 0.0
try: return float(v) if np.isfinite(float(v)) else 0.0
except: return 0.0
v50 = gf('v50_lambda_max_velocity')
v750 = gf('v750_lambda_max_velocity')
inst = gf('instability_50')
self._update_proxy(inst, v750)
prices = {}
for ac in asset_columns:
p = row.get(ac)
if p is not None and p > 0 and np.isfinite(float(p)):
prices[ac] = float(p)
if not prices:
self._global_bar_idx += 1; bid += 1; continue
vrok = bool(vol_regime_ok[ri]) if vol_regime_ok is not None else (bid >= 100)
self._per_bar_hook(ri, float(vd), prices, v50, v750, vrok)
self.step_bar(bar_idx=ri, vel_div=float(vd), prices=prices,
vol_regime_ok=vrok, v50_vel=v50, v750_vel=v750)
bid += 1
return self.end_day()
def _per_bar_hook(self, ri, vd, prices, v50, v750, vrok):
"""Override in subclasses to inject pre-step_bar logic."""
pass
# ── Mode B/A: position scaling ────────────────────────────────────────────────
class ProxyScaleEngine(ProxyBaseEngine):
"""
Mode B (scale_boost): size UP when proxy_B is LOW (calm entry conditions).
Mode A (scale_suppress): size DOWN when proxy_B is HIGH (stress entry conditions).
Scale factor computed from percentile rank of current proxy_B in rolling history.
boost: scale = 1.0 + alpha * max(0, threshold - prank) [rk < thr → boost]
suppress: scale = max(s_min, 1.0 - alpha * max(0, prank - threshold)) [rk > thr → reduce]
Applied post-entry by multiplying self.position.notional directly.
Does NOT change timing → no re-entry cascade dynamics.
"""
def __init__(self, *args, mode: str = 'boost', threshold: float = 0.35,
alpha: float = 1.0, s_min: float = 0.25, **kwargs):
super().__init__(*args, **kwargs)
assert mode in ('boost', 'suppress')
self.mode = mode
self.threshold = threshold
self.alpha = alpha
self.s_min = s_min
self._scale_history: list = []
@property
def sizing_scale_mean(self) -> float:
return float(np.mean(self._scale_history)) if self._scale_history else 1.0
def _try_entry(self, bar_idx, vel_div, prices, price_histories,
v50_vel=0.0, v750_vel=0.0):
result = super()._try_entry(bar_idx, vel_div, prices, price_histories,
v50_vel, v750_vel)
if result and self.position:
prank = self._proxy_prank()
if self.mode == 'boost':
scale = 1.0 + self.alpha * max(0.0, self.threshold - prank)
else: # suppress
scale = max(self.s_min, 1.0 - self.alpha * max(0.0, prank - self.threshold))
self.position.notional *= scale
self._scale_history.append(scale)
return result
def reset(self):
super().reset()
self._scale_history = []
# ── Mode C: hold_limit ────────────────────────────────────────────────────────
class ProxyHoldLimitEngine(ProxyBaseEngine):
"""
Exit early when:
- proxy_B during hold has exceeded thr_pct-percentile threshold
- AND bars_held >= frac * max_hold_bars
Threshold computed at entry time from rolling proxy_B history.
Tracks max proxy_B seen since entry; fires as soon as condition is met.
"""
def __init__(self, *args, frac: float = 0.5, thr_pct: float = 0.75, **kwargs):
super().__init__(*args, **kwargs)
self.frac = frac
self.thr_pct = thr_pct
self._pb_hold_threshold: float = float('inf') # set at entry
self._pb_max_so_far: float = 0.0 # tracked during hold
self.early_exits: int = 0
def _try_entry(self, bar_idx, vel_div, prices, price_histories,
v50_vel=0.0, v750_vel=0.0):
result = super()._try_entry(bar_idx, vel_div, prices, price_histories,
v50_vel, v750_vel)
if result:
self._pb_hold_threshold = self._proxy_threshold(self.thr_pct)
self._pb_max_so_far = self._current_proxy_b
return result
def _per_bar_hook(self, ri, vd, prices, v50, v750, vrok):
if self.position is None:
return
# Track proxy_B max during hold
self._pb_max_so_far = max(self._pb_max_so_far, self._current_proxy_b)
bars_held = self._global_bar_idx - self.position.entry_bar
min_bars = int(self.frac * self.exit_manager.max_hold_bars)
if (bars_held >= min_bars
and self._pb_max_so_far > self._pb_hold_threshold
and self.position.asset in prices):
self.position.current_price = prices[self.position.asset]
self._execute_exit("PROXY_HOLD_LIMIT", self._global_bar_idx,
bars_held=bars_held)
self._pb_max_so_far = 0.0
self._pb_hold_threshold = float('inf')
self.early_exits += 1
def reset(self):
super().reset()
self._pb_hold_threshold = float('inf')
self._pb_max_so_far = 0.0
self.early_exits = 0
# ── Mode D: rising_exit ───────────────────────────────────────────────────────
class ProxyRisingExitEngine(ProxyBaseEngine):
"""
Exit early when:
- current proxy_B exceeds thr_pct-percentile of rolling history
- AND bars_held >= frac * max_hold_bars
Interpretation: proxy_B has risen to an elevated level during hold →
eigenspace stress is actively high → exit before MAE deepens further.
Threshold computed at entry time from rolling proxy_B history.
"""
def __init__(self, *args, frac: float = 0.5, thr_pct: float = 0.80, **kwargs):
super().__init__(*args, **kwargs)
self.frac = frac
self.thr_pct = thr_pct
self._pb_entry_threshold: float = float('inf') # set at entry
self.early_exits: int = 0
def _try_entry(self, bar_idx, vel_div, prices, price_histories,
v50_vel=0.0, v750_vel=0.0):
result = super()._try_entry(bar_idx, vel_div, prices, price_histories,
v50_vel, v750_vel)
if result:
self._pb_entry_threshold = self._proxy_threshold(self.thr_pct)
return result
def _per_bar_hook(self, ri, vd, prices, v50, v750, vrok):
if self.position is None:
return
bars_held = self._global_bar_idx - self.position.entry_bar
min_bars = int(self.frac * self.exit_manager.max_hold_bars)
if (bars_held >= min_bars
and self._current_proxy_b > self._pb_entry_threshold
and self.position.asset in prices):
self.position.current_price = prices[self.position.asset]
self._execute_exit("PROXY_RISING_EXIT", self._global_bar_idx,
bars_held=bars_held)
self._pb_entry_threshold = float('inf')
self.early_exits += 1
def reset(self):
super().reset()
self._pb_entry_threshold = float('inf')
self.early_exits = 0
# ── Run harness ───────────────────────────────────────────────────────────────
def _run(engine_factory, name, d, fw):
"""Full 55-day backtest. Returns gold-comparable metrics + coupling-specific stats."""
kw = ENGINE_KWARGS.copy()
acb = AdaptiveCircuitBreaker()
acb.preload_w750(d['date_strings'])
eng = engine_factory(kw)
eng.set_ob_engine(d['ob_eng'])
eng.set_acb(acb)
if fw is not None:
eng.set_mc_forewarner(fw, MC_BASE_CFG)
eng.set_esoteric_hazard_multiplier(0.0)
daily_caps, daily_pnls = [], []
for pf in d['parquet_files']:
ds = pf.stem
df, acols, dvol = d['pq_data'][ds]
cap_before = eng.capital
vol_ok = np.where(np.isfinite(dvol), dvol > d['vol_p60'], False)
eng.process_day(ds, df, acols, vol_regime_ok=vol_ok)
daily_caps.append(eng.capital)
daily_pnls.append(eng.capital - cap_before)
tr = eng.trade_history
n = len(tr)
roi = (eng.capital - 25000.0) / 25000.0 * 100.0
if n == 0:
return dict(name=name, roi=roi, pf=0.0, dd=0.0, wr=0.0, sharpe=0.0,
trades=0, early_exits=0, sizing_scale_mean=1.0, exit_reasons={})
def _abs(t): return t.pnl_absolute if hasattr(t, 'pnl_absolute') else t.pnl_pct * 250.0
wins = [t for t in tr if _abs(t) > 0]
losses = [t for t in tr if _abs(t) <= 0]
wr = len(wins) / n * 100.0
pf = sum(_abs(t) for t in wins) / max(abs(sum(_abs(t) for t in losses)), 1e-9)
peak_cap, max_dd = 25000.0, 0.0
for cap in daily_caps:
peak_cap = max(peak_cap, cap)
max_dd = max(max_dd, (peak_cap - cap) / peak_cap * 100.0)
dr = np.array([p / 25000.0 * 100.0 for p in daily_pnls])
sharpe = float(dr.mean() / (dr.std() + 1e-9) * math.sqrt(365)) if len(dr) > 1 else 0.0
exit_reasons = {}
for t in tr:
r = t.exit_reason if hasattr(t, 'exit_reason') else 'UNKNOWN'
exit_reasons[r] = exit_reasons.get(r, 0) + 1
early_exits = getattr(eng, 'early_exits', 0)
sizing_scale_mean = getattr(eng, 'sizing_scale_mean', 1.0)
return dict(
name=name, roi=roi, pf=pf, dd=max_dd, wr=wr, sharpe=sharpe, trades=n,
early_exits=early_exits,
early_exit_rate=early_exits / n if n > 0 else 0.0,
sizing_scale_mean=sizing_scale_mean,
exit_reasons=exit_reasons,
)
# ── Main ──────────────────────────────────────────────────────────────────────
def main():
t_start = time.time()
print("=" * 72)
print("Exp 7 — Live proxy_B Coupling: Scale + Hold-Limit + Rising-Exit")
print("Note: First valid live test — exp4 retroactive was 98% median-filled.")
print("=" * 72)
ensure_jit()
d = load_data()
fw = load_forewarner()
configs = []
# Baseline
configs.append(("A00_baseline", lambda kw: NDAlphaEngine(**kw)))
# Mode B: scale_boost — size UP when proxy_B is LOW
for thr in [0.25, 0.35]:
for alpha in [0.5, 1.0]:
tag = f"B_boost_thr={thr:.2f}_a={alpha:.1f}"
def _make_boost(t, a):
return lambda kw: ProxyScaleEngine(mode='boost', threshold=t, alpha=a, **kw)
configs.append((tag, _make_boost(thr, alpha)))
# Mode A: scale_suppress — size DOWN when proxy_B is HIGH
for thr in [0.75, 0.85]:
for alpha in [0.5, 1.0]:
tag = f"A_suppress_thr={thr:.2f}_a={alpha:.1f}_smin=0.25"
def _make_suppress(t, a):
return lambda kw: ProxyScaleEngine(mode='suppress', threshold=t, alpha=a, s_min=0.25, **kw)
configs.append((tag, _make_suppress(thr, alpha)))
# Mode C: hold_limit — exit early when pb_max during hold exceeds threshold
for thr_pct in [0.65, 0.75, 0.85]:
tag = f"C_hold_limit_frac=0.5_thr={thr_pct:.2f}"
def _make_hold(tp):
return lambda kw: ProxyHoldLimitEngine(frac=0.5, thr_pct=tp, **kw)
configs.append((tag, _make_hold(thr_pct)))
# Mode D: rising_exit — exit early when current pb exceeds threshold during hold
for thr_pct in [0.65, 0.75, 0.85]:
tag = f"D_rising_exit_frac=0.5_thr={thr_pct:.2f}"
def _make_rising(tp):
return lambda kw: ProxyRisingExitEngine(frac=0.5, thr_pct=tp, **kw)
configs.append((tag, _make_rising(thr_pct)))
results = []
for i, (name, factory) in enumerate(configs):
t0 = time.time()
print(f"\n[{i+1}/{len(configs)}] {name} ...")
res = _run(factory, name, d, fw)
elapsed = time.time() - t0
extra = ""
if res['early_exits'] > 0:
extra = f" early_exits={res['early_exits']}"
if abs(res['sizing_scale_mean'] - 1.0) > 0.001:
extra += f" scale_mean={res['sizing_scale_mean']:.4f}"
print(f" ROI={res['roi']:.2f}% PF={res['pf']:.4f} DD={res['dd']:.2f}% "
f"WR={res['wr']:.2f}% Sharpe={res['sharpe']:.3f} Trades={res['trades']}"
f"{extra} ({elapsed:.0f}s)")
results.append(res)
# Verification
baseline = results[0]
gold_match = (
abs(baseline['roi'] - GOLD['roi']) < 0.5 and
abs(baseline['pf'] - GOLD['pf']) < 0.005 and
abs(baseline['dd'] - GOLD['dd']) < 0.5 and
abs(baseline['trades'] - GOLD['trades']) < 10
)
print(f"\n{'='*72}")
print(f"VERIFICATION: baseline ROI={baseline['roi']:.2f}% DD={baseline['dd']:.2f}%"
f" Trades={baseline['trades']} Match={'PASS ✓' if gold_match else 'FAIL ✗'}")
base_roi = baseline['roi']
base_dd = baseline['dd']
target_roi = GOLD['roi'] * 0.95 # 84.12%
target_dd = GOLD['dd'] # 15.05%
print(f"\n{'='*72}")
print(f"RESULTS TABLE (target: DD<{target_dd:.2f}% AND ROI>={target_roi:.1f}%)")
hdr = f"{'Config':<46} {'ROI%':>7} {'PF':>6} {'DD%':>6} {'ΔDD':>6} {'ΔROI':>6} {'extra':>20} {'OK':>4}"
print(hdr)
print('-' * 100)
for r in results:
delta_roi = r['roi'] - base_roi
delta_dd = r['dd'] - base_dd
ok = 'Y' if (r['dd'] < target_dd and r['roi'] >= target_roi) else 'N'
extra = ''
if r['early_exits'] > 0:
extra = f"early={r['early_exits']}"
elif abs(r['sizing_scale_mean'] - 1.0) > 0.001:
extra = f"scale={r['sizing_scale_mean']:.4f}"
print(f"{r['name']:<46} {r['roi']:>7.2f} {r['pf']:>6.4f} {r['dd']:>6.2f} "
f"{delta_dd:>+6.2f} {delta_roi:>+6.2f} {extra:>20} {ok:>4}")
# Summary by mode
print(f"\n{'='*72}")
print("BEST PER MODE:")
def best_dd(group):
return min(group, key=lambda r: r['dd'])
groups = {'baseline': [], 'B_boost': [], 'A_suppress': [],
'C_hold_limit': [], 'D_rising_exit': []}
for r in results:
for k in groups:
if r['name'].startswith(k.split('_')[0]) or r['name'].startswith(k):
groups[k].append(r); break
for mode, rs in groups.items():
if not rs: continue
b = best_dd(rs)
delta_roi = b['roi'] - base_roi
delta_dd = b['dd'] - base_dd
ok = 'Y' if (b['dd'] < target_dd and b['roi'] >= target_roi) else 'N'
print(f" {mode:<16} best: {b['name']:<46} "
f"DD={b['dd']:.2f}% ({delta_dd:+.2f}) ROI={b['roi']:.2f}% ({delta_roi:+.2f}) [{ok}]")
# Exit breakdown
print(f"\n{'='*72}")
print("EXIT REASON BREAKDOWN:")
for r in results:
reasons = r.get('exit_reasons', {})
parts = ', '.join(f"{k}={v}" for k, v in sorted(reasons.items()))
print(f" {r['name']:<46}: {parts}")
# Save
outfile = _HERE / "exp7_live_coupling_results.json"
log_results(results, outfile, gold=GOLD, meta={
"exp": "exp7",
"note": "First valid live test of modes A/B/C/D — exp4 retroactive was invalid (entry_bar bug)",
"total_elapsed_s": round(time.time() - t_start, 1),
"gold_match": gold_match,
"modes_tested": ["B_scale_boost", "A_scale_suppress", "C_hold_limit", "D_rising_exit"],
})
total = time.time() - t_start
print(f"\nTotal elapsed: {total/60:.1f} min")
print("Done.")
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,268 @@
{
"gold": {
"roi": 88.55,
"pf": 1.215,
"dd": 15.05,
"sharpe": 4.38,
"wr": 50.5,
"trades": 2155
},
"results": [
{
"name": "A00_baseline",
"roi": 88.54671933603525,
"pf": 1.21470506157439,
"dd": 15.046245386522427,
"wr": 50.48723897911833,
"sharpe": 4.378300370204196,
"trades": 2155,
"early_exits": 0,
"early_exit_rate": 0.0,
"sizing_scale_mean": 1.0,
"exit_reasons": {
"MAX_HOLD": 1831,
"FIXED_TP": 324
}
},
{
"name": "B_boost_thr=0.25_a=0.5",
"roi": 90.29753941684339,
"pf": 1.2160924709583105,
"dd": 14.821932254796943,
"wr": 50.48723897911833,
"sharpe": 4.4424955513000555,
"trades": 2155,
"early_exits": 0,
"early_exit_rate": 0.0,
"sizing_scale_mean": 1.0192115027829314,
"exit_reasons": {
"MAX_HOLD": 1831,
"FIXED_TP": 324
}
},
{
"name": "B_boost_thr=0.25_a=1.0",
"roi": 92.0514388467092,
"pf": 1.2174432679504197,
"dd": 14.597213085690708,
"wr": 50.48723897911833,
"sharpe": 4.503982033834149,
"trades": 2155,
"early_exits": 0,
"early_exit_rate": 0.0,
"sizing_scale_mean": 1.0384230055658628,
"exit_reasons": {
"MAX_HOLD": 1831,
"FIXED_TP": 324
}
},
{
"name": "B_boost_thr=0.35_a=0.5",
"roi": 91.07372858237454,
"pf": 1.2163247655700182,
"dd": 14.779598850291558,
"wr": 50.48723897911833,
"sharpe": 4.468643300574364,
"trades": 2155,
"early_exits": 0,
"early_exit_rate": 0.0,
"sizing_scale_mean": 1.0309155844155844,
"exit_reasons": {
"MAX_HOLD": 1831,
"FIXED_TP": 324
}
},
{
"name": "B_boost_thr=0.35_a=1.0",
"roi": 93.60513448956341,
"pf": 1.2178720236994938,
"dd": 14.512635180033598,
"wr": 50.48723897911833,
"sharpe": 4.55213195481462,
"trades": 2155,
"early_exits": 0,
"early_exit_rate": 0.0,
"sizing_scale_mean": 1.0618311688311688,
"exit_reasons": {
"MAX_HOLD": 1831,
"FIXED_TP": 324
}
},
{
"name": "A_suppress_thr=0.75_a=0.5_smin=0.25",
"roi": 85.79811260804453,
"pf": 1.219430737394839,
"dd": 15.170218464648395,
"wr": 50.48723897911833,
"sharpe": 4.408411370917985,
"trades": 2155,
"early_exits": 0,
"early_exit_rate": 0.0,
"sizing_scale_mean": 0.9757196349867308,
"exit_reasons": {
"MAX_HOLD": 1831,
"FIXED_TP": 324
}
},
{
"name": "A_suppress_thr=0.75_a=1.0_smin=0.25",
"roi": 83.04374070648592,
"pf": 1.2243226504262263,
"dd": 15.294576696634937,
"wr": 50.48723897911833,
"sharpe": 4.4289241379844855,
"trades": 2155,
"early_exits": 0,
"early_exit_rate": 0.0,
"sizing_scale_mean": 0.9514392699734614,
"exit_reasons": {
"MAX_HOLD": 1831,
"FIXED_TP": 324
}
},
{
"name": "A_suppress_thr=0.85_a=0.5_smin=0.25",
"roi": 87.17621721501192,
"pf": 1.2165304302812532,
"dd": 15.089608437921182,
"wr": 50.48723897911833,
"sharpe": 4.367963006765593,
"trades": 2155,
"early_exits": 0,
"early_exit_rate": 0.0,
"sizing_scale_mean": 0.9898321240014754,
"exit_reasons": {
"MAX_HOLD": 1831,
"FIXED_TP": 324
}
},
{
"name": "A_suppress_thr=0.85_a=1.0_smin=0.25",
"roi": 85.80516356740421,
"pf": 1.2183755554509148,
"dd": 15.133102187143244,
"wr": 50.48723897911833,
"sharpe": 4.355216195176627,
"trades": 2155,
"early_exits": 0,
"early_exit_rate": 0.0,
"sizing_scale_mean": 0.9796642480029509,
"exit_reasons": {
"MAX_HOLD": 1831,
"FIXED_TP": 324
}
},
{
"name": "C_hold_limit_frac=0.5_thr=0.65",
"roi": 0.9592460335863725,
"pf": 1.0026219317176224,
"dd": 18.646234282343325,
"wr": 48.607446496628555,
"sharpe": 0.0679900506250511,
"trades": 3411,
"early_exits": 3119,
"early_exit_rate": 0.9143946056874817,
"sizing_scale_mean": 1.0,
"exit_reasons": {
"PROXY_HOLD_LIMIT": 3119,
"FIXED_TP": 292
}
},
{
"name": "C_hold_limit_frac=0.5_thr=0.75",
"roi": 0.7052670847762929,
"pf": 1.0019280264886439,
"dd": 18.646234282343325,
"wr": 48.54881266490765,
"sharpe": 0.04992241140682829,
"trades": 3411,
"early_exits": 3118,
"early_exit_rate": 0.9141014365288772,
"sizing_scale_mean": 1.0,
"exit_reasons": {
"PROXY_HOLD_LIMIT": 3118,
"FIXED_TP": 292,
"MAX_HOLD": 1
}
},
{
"name": "C_hold_limit_frac=0.5_thr=0.85",
"roi": 6.409886467879581,
"pf": 1.0168573807902554,
"dd": 18.6546936527647,
"wr": 48.31031442844549,
"sharpe": 0.43074844271123575,
"trades": 3403,
"early_exits": 3106,
"early_exit_rate": 0.9127240669997061,
"sizing_scale_mean": 1.0,
"exit_reasons": {
"PROXY_HOLD_LIMIT": 3106,
"FIXED_TP": 291,
"MAX_HOLD": 6
}
},
{
"name": "D_rising_exit_frac=0.5_thr=0.65",
"roi": -10.113161555686325,
"pf": 0.97085496374074,
"dd": 20.80672837099466,
"wr": 48.660042155977116,
"sharpe": -0.8631426863593179,
"trades": 3321,
"early_exits": 3030,
"early_exit_rate": 0.912375790424571,
"sizing_scale_mean": 1.0,
"exit_reasons": {
"PROXY_RISING_EXIT": 3030,
"FIXED_TP": 291
}
},
{
"name": "D_rising_exit_frac=0.5_thr=0.75",
"roi": -8.07049307300517,
"pf": 0.9758642421190082,
"dd": 21.290045839782977,
"wr": 48.26336454243431,
"sharpe": -0.7095429390652619,
"trades": 3311,
"early_exits": 3011,
"early_exit_rate": 0.9093929326487467,
"sizing_scale_mean": 1.0,
"exit_reasons": {
"PROXY_RISING_EXIT": 3011,
"FIXED_TP": 298,
"MAX_HOLD": 2
}
},
{
"name": "D_rising_exit_frac=0.5_thr=0.85",
"roi": -10.447002613929028,
"pf": 0.9700434423059277,
"dd": 21.932685654212428,
"wr": 48.518284993694834,
"sharpe": -0.8193432726522849,
"trades": 3172,
"early_exits": 2843,
"early_exit_rate": 0.8962799495586381,
"sizing_scale_mean": 1.0,
"exit_reasons": {
"PROXY_RISING_EXIT": 2843,
"FIXED_TP": 287,
"MAX_HOLD": 42
}
}
],
"meta": {
"exp": "exp7",
"note": "First valid live test of modes A/B/C/D \u2014 exp4 retroactive was invalid (entry_bar bug)",
"total_elapsed_s": 3650.6,
"gold_match": true,
"modes_tested": [
"B_scale_boost",
"A_scale_suppress",
"C_hold_limit",
"D_rising_exit"
]
}
}

View File

@@ -0,0 +1,425 @@
"""
Exp 8 — scale_boost Robustness & Adaptive Parameterization
Two questions from exp7 scale_boost winner (thr=0.35, a=1.0):
Q1. Is it overfitting? (+5pp ROI AND -0.54pp DD on same 55 days it was found)
Test: temporal split — first-half (days 127) vs second-half (days 2855)
If improvement holds in BOTH halves independently, it's structurally real.
If only one half drives it, the result is temporally fragile.
Q2. Are threshold and alpha regime-dependent?
Hypothesis: proxy_B is more discriminating in high-eigenvalue-regime days
(high ACB beta). On those days, "calm" entries should receive stronger boost,
and the threshold for "what qualifies as calm" should be tighter.
Adaptive formulas (using ACB state available in _try_entry as self._day_base_boost
and self._day_beta):
alpha_eff = alpha * day_base_boost (more boost on stressed days)
thr_eff = threshold / day_base_boost (tighter gate on stressed days)
Both together: combine both adjustments
Also test dvol-proxy adaptation: use day_beta directly as a continuous scaler.
Configs:
0. Baseline
1. Fixed: thr=0.35, a=1.0 (exp7 winner — must reproduce exp7 results)
2. Adaptive-alpha: alpha_eff = 1.0 * day_base_boost, thr fixed at 0.35
3. Adaptive-threshold: thr_eff = 0.35 / day_base_boost, alpha fixed at 1.0
4. Adaptive-both: both formulas combined
5. Beta-scaled alpha: alpha_eff = 1.0 * (1 + day_beta), thr fixed at 0.35
(day_beta is the ACB eigenvalue signal; more direct than base_boost)
Results include:
- Full 55-day metrics (standard)
- First-half (days 127) and second-half (days 2855) metrics split out
to test temporal stability of the DD reduction
- Per-day scale distribution analysis
Results logged to exp8_boost_robustness_results.json
"""
import sys, time, json, math
sys.stdout.reconfigure(encoding='utf-8', errors='replace')
from pathlib import Path
import numpy as np
_HERE = Path(__file__).resolve().parent
sys.path.insert(0, str(_HERE.parent))
from exp_shared import (
ensure_jit, ENGINE_KWARGS, GOLD, MC_BASE_CFG,
load_data, load_forewarner, log_results,
)
from nautilus_dolphin.nautilus.esf_alpha_orchestrator import NDAlphaEngine
from nautilus_dolphin.nautilus.adaptive_circuit_breaker import AdaptiveCircuitBreaker
# ── Re-use ProxyBaseEngine from exp7 (copy-minimal) ──────────────────────────
class ProxyBaseEngine(NDAlphaEngine):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self._current_proxy_b: float = 0.0
self._proxy_b_history: list = []
def _update_proxy(self, inst: float, v750: float) -> float:
pb = inst - v750
self._current_proxy_b = pb
self._proxy_b_history.append(pb)
if len(self._proxy_b_history) > 500:
self._proxy_b_history = self._proxy_b_history[-500:]
return pb
def _proxy_prank(self) -> float:
if not self._proxy_b_history:
return 0.5
n = len(self._proxy_b_history)
return sum(v < self._current_proxy_b for v in self._proxy_b_history) / n
def process_day(self, date_str, df, asset_columns,
vol_regime_ok=None, direction=None, posture='APEX'):
self.begin_day(date_str, posture=posture, direction=direction)
bid = 0
for ri in range(len(df)):
row = df.iloc[ri]
vd = row.get('vel_div')
if vd is None or not np.isfinite(float(vd)):
self._global_bar_idx += 1; bid += 1; continue
def gf(col):
v = row.get(col)
if v is None: return 0.0
try: return float(v) if np.isfinite(float(v)) else 0.0
except: return 0.0
v50 = gf('v50_lambda_max_velocity')
v750 = gf('v750_lambda_max_velocity')
inst = gf('instability_50')
self._update_proxy(inst, v750)
prices = {}
for ac in asset_columns:
p = row.get(ac)
if p is not None and p > 0 and np.isfinite(float(p)):
prices[ac] = float(p)
if not prices:
self._global_bar_idx += 1; bid += 1; continue
vrok = bool(vol_regime_ok[ri]) if vol_regime_ok is not None else (bid >= 100)
self.step_bar(bar_idx=ri, vel_div=float(vd), prices=prices,
vol_regime_ok=vrok, v50_vel=v50, v750_vel=v750)
bid += 1
return self.end_day()
# ── Adaptive scale_boost engine ───────────────────────────────────────────────
class AdaptiveBoostEngine(ProxyBaseEngine):
"""
scale_boost with optionally regime-adaptive threshold and alpha.
Fixed mode (adaptive_alpha=False, adaptive_thr=False, adaptive_beta=False):
scale = 1 + alpha * max(0, threshold - prank)
Identical to exp7 ProxyScaleEngine(mode='boost').
Adaptive modes use ACB state (self._day_base_boost, self._day_beta)
which is set by begin_day() before any _try_entry calls in that day:
adaptive_alpha: alpha_eff = alpha * day_base_boost
→ High-boost day (stressed eigenspace regime) → stronger boost on calm entries
→ Low-boost day → modest boost
adaptive_thr: thr_eff = threshold / day_base_boost
→ High-boost day → lower threshold → more selective (only very calm entries qualify)
→ Low-boost day → higher threshold → more entries qualify
adaptive_beta: alpha_eff = alpha * (1 + day_beta)
→ day_beta is the ACB's direct eigenvalue signal (0 when inactive)
→ More discriminating on days where eigenvalue regime is active
Parameters can be combined freely.
"""
def __init__(self, *args,
threshold: float = 0.35,
alpha: float = 1.0,
adaptive_alpha: bool = False,
adaptive_thr: bool = False,
adaptive_beta: bool = False,
**kwargs):
super().__init__(*args, **kwargs)
self.threshold = threshold
self.alpha = alpha
self.adaptive_alpha = adaptive_alpha
self.adaptive_thr = adaptive_thr
self.adaptive_beta = adaptive_beta
self._scale_history: list = []
self._alpha_eff_history: list = []
self._thr_eff_history: list = []
@property
def sizing_scale_mean(self) -> float:
return float(np.mean(self._scale_history)) if self._scale_history else 1.0
def _try_entry(self, bar_idx, vel_div, prices, price_histories,
v50_vel=0.0, v750_vel=0.0):
result = super()._try_entry(bar_idx, vel_div, prices, price_histories,
v50_vel, v750_vel)
if result and self.position:
boost = max(1.0, getattr(self, '_day_base_boost', 1.0))
beta = max(0.0, getattr(self, '_day_beta', 0.0))
# Effective parameters
alpha_eff = self.alpha
if self.adaptive_alpha:
alpha_eff *= boost # more boost on stressed-regime days
if self.adaptive_beta:
alpha_eff *= (1.0 + beta) # beta signal scales aggression
thr_eff = self.threshold
if self.adaptive_thr:
# High boost → lower threshold → be more selective about "calm"
thr_eff = self.threshold / max(1.0, boost)
prank = self._proxy_prank()
scale = 1.0 + alpha_eff * max(0.0, thr_eff - prank)
self.position.notional *= scale
self._scale_history.append(scale)
self._alpha_eff_history.append(alpha_eff)
self._thr_eff_history.append(thr_eff)
return result
def reset(self):
super().reset()
self._scale_history = []
self._alpha_eff_history = []
self._thr_eff_history = []
# ── Run harness with half-split ───────────────────────────────────────────────
def _run(engine_factory, name, d, fw):
"""Full run + temporal split (first vs second half of days)."""
kw = ENGINE_KWARGS.copy()
acb = AdaptiveCircuitBreaker()
acb.preload_w750(d['date_strings'])
eng = engine_factory(kw)
eng.set_ob_engine(d['ob_eng'])
eng.set_acb(acb)
if fw is not None:
eng.set_mc_forewarner(fw, MC_BASE_CFG)
eng.set_esoteric_hazard_multiplier(0.0)
pf_list = d['parquet_files']
n_days = len(pf_list)
half = n_days // 2 # split point
daily_caps, daily_pnls = [], []
half_caps = [[], []] # [first_half, second_half]
half_pnls = [[], []]
half_trades_n = [0, 0]
for i, pf in enumerate(pf_list):
ds = pf.stem
df, acols, dvol = d['pq_data'][ds]
cap_before = eng.capital
vol_ok = np.where(np.isfinite(dvol), dvol > d['vol_p60'], False)
eng.process_day(ds, df, acols, vol_regime_ok=vol_ok)
cap_after = eng.capital
daily_caps.append(cap_after)
daily_pnls.append(cap_after - cap_before)
h = 0 if i < half else 1
half_caps[h].append(cap_after)
half_pnls[h].append(cap_after - cap_before)
tr = eng.trade_history
n = len(tr)
roi = (eng.capital - 25000.0) / 25000.0 * 100.0
def _metrics(caps, pnls, start_cap=25000.0):
"""Compute metrics for a sub-period given daily capitals and a starting capital."""
if not caps:
return dict(roi=0.0, dd=0.0, sharpe=0.0)
peak = start_cap
max_dd = 0.0
for c in caps:
peak = max(peak, c)
max_dd = max(max_dd, (peak - c) / peak * 100.0)
total_pnl = sum(pnls)
roi_sub = total_pnl / start_cap * 100.0
dr = np.array([p / start_cap * 100.0 for p in pnls])
sharpe = float(dr.mean() / (dr.std() + 1e-9) * math.sqrt(365)) if len(dr) > 1 else 0.0
return dict(roi=roi_sub, dd=max_dd, sharpe=sharpe, n_days=len(caps))
if n == 0:
return dict(name=name, roi=roi, pf=0.0, dd=0.0, wr=0.0, sharpe=0.0,
trades=0, sizing_scale_mean=1.0)
def _abs(t): return t.pnl_absolute if hasattr(t, 'pnl_absolute') else t.pnl_pct * 250.0
wins = [t for t in tr if _abs(t) > 0]
losses = [t for t in tr if _abs(t) <= 0]
wr = len(wins) / n * 100.0
pf_val = sum(_abs(t) for t in wins) / max(abs(sum(_abs(t) for t in losses)), 1e-9)
peak_cap, max_dd = 25000.0, 0.0
for cap in daily_caps:
peak_cap = max(peak_cap, cap)
max_dd = max(max_dd, (peak_cap - cap) / peak_cap * 100.0)
dr = np.array([p / 25000.0 * 100.0 for p in daily_pnls])
sharpe = float(dr.mean() / (dr.std() + 1e-9) * math.sqrt(365)) if len(dr) > 1 else 0.0
# First/second half split — using capital at end of first-half as baseline for second half
cap_at_halftime = half_caps[0][-1] if half_caps[0] else 25000.0
h1 = _metrics(half_caps[0], half_pnls[0], start_cap=25000.0)
h2 = _metrics(half_caps[1], half_pnls[1], start_cap=cap_at_halftime)
sizing_scale_mean = getattr(eng, 'sizing_scale_mean', 1.0)
# Alpha/threshold eff distributions for adaptive engines
alpha_mean = 1.0
thr_mean = 0.35
eng_ae = eng if isinstance(eng, AdaptiveBoostEngine) else None
if eng_ae:
if eng_ae._alpha_eff_history:
alpha_mean = float(np.mean(eng_ae._alpha_eff_history))
if eng_ae._thr_eff_history:
thr_mean = float(np.mean(eng_ae._thr_eff_history))
return dict(
name=name,
roi=roi, pf=pf_val, dd=max_dd, wr=wr, sharpe=sharpe, trades=n,
sizing_scale_mean=sizing_scale_mean,
alpha_eff_mean=alpha_mean,
thr_eff_mean=thr_mean,
# Temporal split
h1_roi=h1['roi'], h1_dd=h1['dd'], h1_sharpe=h1['sharpe'],
h2_roi=h2['roi'], h2_dd=h2['dd'], h2_sharpe=h2['sharpe'],
split_days=(half, n_days - half),
)
# ── Main ──────────────────────────────────────────────────────────────────────
def main():
t_start = time.time()
print("=" * 74)
print("Exp 8 — scale_boost Robustness & Adaptive Parameterization")
print("=" * 74)
ensure_jit()
d = load_data()
fw = load_forewarner()
configs = [
("0_baseline",
lambda kw: NDAlphaEngine(**kw)),
("1_fixed_thr035_a1.0",
lambda kw: AdaptiveBoostEngine(threshold=0.35, alpha=1.0, **kw)),
("2_adaptive_alpha__thr035_a1.0xboost",
lambda kw: AdaptiveBoostEngine(threshold=0.35, alpha=1.0,
adaptive_alpha=True, **kw)),
("3_adaptive_thr__thr035/boost_a1.0",
lambda kw: AdaptiveBoostEngine(threshold=0.35, alpha=1.0,
adaptive_thr=True, **kw)),
("4_adaptive_both__thr/boost_axboost",
lambda kw: AdaptiveBoostEngine(threshold=0.35, alpha=1.0,
adaptive_alpha=True, adaptive_thr=True, **kw)),
("5_adaptive_beta__thr035_ax(1+beta)",
lambda kw: AdaptiveBoostEngine(threshold=0.35, alpha=1.0,
adaptive_beta=True, **kw)),
]
results = []
for i, (name, factory) in enumerate(configs):
t0 = time.time()
print(f"\n[{i+1}/{len(configs)}] {name} ...")
res = _run(factory, name, d, fw)
elapsed = time.time() - t0
print(f" ROI={res['roi']:.2f}% PF={res['pf']:.4f} DD={res['dd']:.2f}%"
f" WR={res['wr']:.2f}% Sharpe={res['sharpe']:.3f} Trades={res['trades']}"
f" scale={res['sizing_scale_mean']:.4f} alpha_eff={res['alpha_eff_mean']:.4f}"
f" ({elapsed:.0f}s)")
print(f" H1(days 1-{res['split_days'][0]}): ROI={res['h1_roi']:.2f}%"
f" DD={res['h1_dd']:.2f}% Sharpe={res['h1_sharpe']:.3f}")
print(f" H2(days {res['split_days'][0]+1}-{sum(res['split_days'])}): ROI={res['h2_roi']:.2f}%"
f" DD={res['h2_dd']:.2f}% Sharpe={res['h2_sharpe']:.3f}")
results.append(res)
# Baseline verification
b = results[0]
fixed = results[1]
gold_match = (abs(b['roi'] - GOLD['roi']) < 0.5 and abs(b['dd'] - GOLD['dd']) < 0.5
and abs(b['trades'] - GOLD['trades']) < 10)
fixed_match = (abs(fixed['roi'] - 93.61) < 0.5 and abs(fixed['dd'] - 14.51) < 0.5)
print(f"\n{'='*74}")
print(f"VERIFICATION:")
print(f" Baseline vs gold: {'PASS ✓' if gold_match else 'FAIL ✗'} "
f"(ROI={b['roi']:.2f}% DD={b['dd']:.2f}%)")
print(f" Fixed vs exp7 winner: {'PASS ✓' if fixed_match else 'FAIL ✗'} "
f"(ROI={fixed['roi']:.2f}% DD={fixed['dd']:.2f}%)")
print(f"\n{'='*74}")
print(f"FULL-PERIOD RESULTS (target: DD<15.05% AND ROI>=84.1%)")
hdr = f"{'Config':<46} {'ROI%':>7} {'PF':>6} {'DD%':>6} {'ΔDD':>6} {'ΔROI':>6} {'scale':>7} {'alpha':>7} {'OK':>4}"
print(hdr); print('-' * 98)
base_roi = b['roi']; base_dd = b['dd']
for r in results:
dROI = r['roi'] - base_roi; dDD = r['dd'] - base_dd
ok = 'Y' if (r['dd'] < GOLD['dd'] and r['roi'] >= GOLD['roi'] * 0.95) else 'N'
print(f"{r['name']:<46} {r['roi']:>7.2f} {r['pf']:>6.4f} {r['dd']:>6.2f} "
f"{dDD:>+6.2f} {dROI:>+6.2f} {r['sizing_scale_mean']:>7.4f} "
f"{r['alpha_eff_mean']:>7.4f} {ok:>4}")
print(f"\n{'='*74}")
print("TEMPORAL SPLIT — Overfitting check (does improvement hold in both halves?)")
h_days = results[0]['split_days']
print(f"Split: H1=days 1{h_days[0]}, H2=days {h_days[0]+1}{sum(h_days)}")
print(f"{'Config':<46} {'H1 ROI':>8} {'H1 DD':>7} {'H2 ROI':>8} {'H2 DD':>7} "
f"{'ΔH1DD':>7} {'ΔH2DD':>7}")
print('-' * 98)
b_h1dd = b['h1_dd']; b_h2dd = b['h2_dd']
for r in results:
dH1 = r['h1_dd'] - b_h1dd; dH2 = r['h2_dd'] - b_h2dd
print(f"{r['name']:<46} {r['h1_roi']:>8.2f} {r['h1_dd']:>7.2f} "
f"{r['h2_roi']:>8.2f} {r['h2_dd']:>7.2f} {dH1:>+7.2f} {dH2:>+7.2f}")
print(f"\n{'='*74}")
print("OVERFITTING VERDICT:")
for r in results[1:]:
h1_better = r['h1_dd'] < b_h1dd
h2_better = r['h2_dd'] < b_h2dd
both = h1_better and h2_better
neither = (not h1_better) and (not h2_better)
verdict = "BOTH halves improve DD ✓" if both else \
"NEITHER half improves DD ✗" if neither else \
f"Mixed: H1={'' if h1_better else ''} H2={'' if h2_better else ''}"
print(f" {r['name']:<46}: {verdict}")
# Adaptive summary
print(f"\n{'='*74}")
print("ADAPTIVE PARAMETERIZATION — alpha_eff and thr_eff distributions:")
for r in results[2:]:
print(f" {r['name']:<46}: alpha_eff_mean={r['alpha_eff_mean']:.4f}"
f" thr_eff_mean={r['thr_eff_mean']:.4f}")
outfile = _HERE / "exp8_boost_robustness_results.json"
log_results(results, outfile, gold=GOLD, meta={
"exp": "exp8",
"question": "Is scale_boost overfitting? Are threshold/alpha regime-dependent?",
"total_elapsed_s": round(time.time() - t_start, 1),
"gold_match": gold_match,
"fixed_match": fixed_match,
})
total = time.time() - t_start
print(f"\nTotal elapsed: {total/60:.1f} min")
print("Done.")
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,151 @@
{
"gold": {
"roi": 88.55,
"pf": 1.215,
"dd": 15.05,
"sharpe": 4.38,
"wr": 50.5,
"trades": 2155
},
"results": [
{
"name": "0_baseline",
"roi": 88.54671933603525,
"pf": 1.21470506157439,
"dd": 15.046245386522427,
"wr": 50.48723897911833,
"sharpe": 4.378300370204196,
"trades": 2155,
"sizing_scale_mean": 1.0,
"alpha_eff_mean": 1.0,
"thr_eff_mean": 0.35,
"h1_roi": 37.632584444436795,
"h1_dd": 15.046245386522427,
"h1_sharpe": 3.9603631793850136,
"h2_roi": 36.992791421534946,
"h2_dd": 9.830290816036223,
"h2_sharpe": 4.769183844351268,
"split_days": [
28,
28
]
},
{
"name": "1_fixed_thr035_a1.0",
"roi": 93.60513448956341,
"pf": 1.2178720236994938,
"dd": 14.512635180033598,
"wr": 50.48723897911833,
"sharpe": 4.55213195481462,
"trades": 2155,
"sizing_scale_mean": 1.0618311688311688,
"alpha_eff_mean": 1.0,
"thr_eff_mean": 0.3500000000000001,
"h1_roi": 37.87397445144304,
"h1_dd": 14.512635180033598,
"h1_sharpe": 3.8950967482396135,
"h2_roi": 40.42181293449837,
"h2_dd": 9.52090561815603,
"h2_sharpe": 5.164864302890685,
"split_days": [
28,
28
]
},
{
"name": "2_adaptive_alpha__thr035_a1.0xboost",
"roi": 93.40183251473115,
"pf": 1.21653391966412,
"dd": 14.512635180033612,
"wr": 50.48723897911833,
"sharpe": 4.538463370694225,
"trades": 2155,
"sizing_scale_mean": 1.069598136684172,
"alpha_eff_mean": 1.1152435704345516,
"thr_eff_mean": 0.3500000000000001,
"h1_roi": 37.863444837785416,
"h1_dd": 14.512635180033612,
"h1_sharpe": 3.863961184840122,
"h2_roi": 40.285071755093604,
"h2_dd": 9.653060108073454,
"h2_sharpe": 5.171823779134293,
"split_days": [
28,
28
]
},
{
"name": "3_adaptive_thr__thr035/boost_a1.0",
"roi": 94.13033277329988,
"pf": 1.2198467641016866,
"dd": 14.512635180033612,
"wr": 50.48723897911833,
"sharpe": 4.577159191163701,
"trades": 2155,
"sizing_scale_mean": 1.0548980466896074,
"alpha_eff_mean": 1.0,
"thr_eff_mean": 0.32186136027605455,
"h1_roi": 38.028871469969275,
"h1_dd": 14.512635180033612,
"h1_sharpe": 3.934631717815155,
"h2_roi": 40.6447294003534,
"h2_dd": 9.419616583596177,
"h2_sharpe": 5.173301424346994,
"split_days": [
28,
28
]
},
{
"name": "4_adaptive_both__thr/boost_axboost",
"roi": 94.11394527938835,
"pf": 1.2192367820323635,
"dd": 14.51263518003362,
"wr": 50.48723897911833,
"sharpe": 4.5739830546529925,
"trades": 2155,
"sizing_scale_mean": 1.0596575120382845,
"alpha_eff_mean": 1.1152435704345516,
"thr_eff_mean": 0.32186136027605455,
"h1_roi": 38.07558919307389,
"h1_dd": 14.51263518003362,
"h1_sharpe": 3.9200708724235827,
"h2_roi": 40.58527391685063,
"h2_dd": 9.471525859621199,
"h2_sharpe": 5.182956035866156,
"split_days": [
28,
28
]
},
{
"name": "5_adaptive_beta__thr035_ax(1+beta)",
"roi": 96.55083977704362,
"pf": 1.2201203038704769,
"dd": 14.321954772270876,
"wr": 50.48723897911833,
"sharpe": 4.631491961453057,
"trades": 2155,
"sizing_scale_mean": 1.0883296846011132,
"alpha_eff_mean": 1.428756957328386,
"thr_eff_mean": 0.3500000000000001,
"h1_roi": 38.80519726574197,
"h1_dd": 14.321954772270876,
"h1_sharpe": 3.9158586921478613,
"h2_roi": 41.60193108673579,
"h2_dd": 9.569030250306442,
"h2_sharpe": 5.302551495714016,
"split_days": [
28,
28
]
}
],
"meta": {
"exp": "exp8",
"question": "Is scale_boost overfitting? Are threshold/alpha regime-dependent?",
"total_elapsed_s": 1401.0,
"gold_match": true,
"fixed_match": true
}
}

View File

@@ -0,0 +1,408 @@
"""
Exp 9 — Extended Leverage Ceiling Test
Motivation:
GOLD (adaptive_beta) achieved DD=14.32% (0.72pp vs Silver=15.05%). The question is whether
this headroom permits raising the leverage ceiling above the current 5x soft / 6x hard cap
without exceeding Silver's DD budget — or ideally staying below GOLD's DD while gaining ROI.
Architecture recap (DO NOT MODIFY any of the below):
bet_sizer.max_leverage (5.0) → convex curve soft cap — strength_score^3 tops out here
base_max_leverage (5.0) → base for ACB regime excursions
abs_max_leverage (6.0) → hard ceiling: min(base*regime_mult, abs_max)
ACB regime_size_mult (1.01.6) → can push raw_leverage above bet_sizer.max_leverage
→ Current actual range: 0.5x 6.0x (6x hard cap is binding on extreme signals)
Fork strategy:
ExtendedLeverageEngine(AdaptiveBoostEngine) — subclass only, zero parent modification.
Sets all three caps consistently: bet_sizer.max_leverage = base_max_leverage = extended_soft_cap,
abs_max_leverage = extended_abs_cap.
Example at 7.0/8.0: extreme signal gets bet_sizer→7.0 × regime_mult≈1.2 = 8.4, clamped at 8.0.
MC-Forewarner interaction (CRITICAL):
begin_day() feeds MC: mc_cfg['max_leverage'] = base_max_leverage * day_base_boost
MC was trained at max_leverage 5.06.0 (champion region). At 8x+ it's outside training
distribution → One-Class SVM envelope_score likely < -1.0 → RED → regime_dd_halt=True
→ ZERO trades that day.
Two modes tested:
mc_ref=5.0 ("decoupled"): MC sees original 5.0x reference → same verdicts as GOLD
Pure effect of higher ceiling with MC behavior unchanged
mc_ref=soft_cap ("coupled"): MC sees actual new cap → may flag RED/ORANGE on more days
Tests "what would MC do if it knew about the new leverage"
ExtendedLeverageEngine.begin_day() temporarily swaps base_max_leverage to mc_leverage_ref
before calling super().begin_day(), then restores extended caps for actual trading.
This is the ONLY divergence from parent logic.
Test configs:
A: 5.0/6.0 mc_ref=5.0 — GOLD reference (must reproduce 96.55% / 14.32%)
B: 6.0/7.0 mc_ref=5.0 — +1x soft, MC decoupled
C: 7.0/8.0 mc_ref=5.0 — +2x soft, MC decoupled
D: 8.0/9.0 mc_ref=5.0 — +3x soft, MC decoupled
E: 9.0/10.0 mc_ref=5.0 — +4x soft, MC decoupled
F: 6.0/7.0 mc_ref=6.0 — +1x soft, MC coupled
G: 7.0/8.0 mc_ref=7.0 — +2x soft, MC coupled
H: 8.0/9.0 mc_ref=8.0 — +3x soft, MC coupled
Total: 8 runs × ~250s ≈ 35 min
Key metrics:
ROI%, DD%, PF, Trades, avg_leverage (mean realized leverage per trade)
MC status breakdown: RED days / ORANGE days / OK days / halted days
Δ vs GOLD: ΔROI, ΔDD
Results → exp9_leverage_ceiling_results.json
"""
import sys, time, json, math
sys.stdout.reconfigure(encoding='utf-8', errors='replace')
from pathlib import Path
import numpy as np
_HERE = Path(__file__).resolve().parent
sys.path.insert(0, str(_HERE.parent))
from exp_shared import (
ensure_jit, ENGINE_KWARGS, GOLD, MC_BASE_CFG,
load_data, load_forewarner, log_results,
)
from nautilus_dolphin.nautilus.proxy_boost_engine import AdaptiveBoostEngine, DEFAULT_THRESHOLD, DEFAULT_ALPHA
from nautilus_dolphin.nautilus.adaptive_circuit_breaker import AdaptiveCircuitBreaker
# ── GOLD reference for this experiment (adaptive_beta, not the old silver) ───
_GOLD_EXP9 = dict(roi=96.55, dd=14.32, trades=2155) # adaptive_beta GOLD
_SILVER = dict(roi=88.55, dd=15.05, trades=2155) # former gold = regression floor
# ── ExtendedLeverageEngine — subclass only, ZERO parent modification ──────────
class ExtendedLeverageEngine(AdaptiveBoostEngine):
"""
Extended leverage ceiling fork of AdaptiveBoostEngine (adaptive_beta GOLD).
Three new constructor params vs parent:
extended_soft_cap : replaces max_leverage (5.0) and base_max_leverage
extended_abs_cap : replaces abs_max_leverage (6.0) — hard ceiling
mc_leverage_ref : what to feed the MC-Forewarner (independent of actual caps)
If None → MC sees extended_soft_cap (fully coupled)
Set to 5.0 → MC always sees original 5x reference (decoupled)
ALL existing leverage logic (convex curve, ACB regime_size_mult, STALKER 2x cap,
proxy_B scale_boost, etc.) is completely untouched. Only the three cap values change.
"""
def __init__(
self,
*args,
extended_soft_cap: float = 5.0,
extended_abs_cap: float = 6.0,
mc_leverage_ref: float = None,
**kwargs,
):
# Inject extended caps as max_leverage / abs_max_leverage so parent stores them
kwargs['max_leverage'] = extended_soft_cap
kwargs['abs_max_leverage'] = extended_abs_cap
super().__init__(*args, **kwargs)
# Explicitly ensure all three cap locations are consistent
# (parent __init__ sets base_max_leverage = max_leverage and bet_sizer.max_leverage
# from the kwargs, but we set explicitly for clarity)
self.bet_sizer.max_leverage = extended_soft_cap
self.base_max_leverage = extended_soft_cap
self.abs_max_leverage = extended_abs_cap
self._extended_soft_cap = extended_soft_cap
self._extended_abs_cap = extended_abs_cap
# MC reference: 5.0 = decoupled (MC sees original gold reference)
# extended_soft_cap = coupled (MC sees actual new cap)
self._mc_leverage_ref = mc_leverage_ref if mc_leverage_ref is not None else extended_soft_cap
# Per-day MC verdict monitoring
self.mc_monitor = dict(red=0, orange=0, ok=0, halted=0, total=0)
def begin_day(self, date_str: str, posture: str = 'APEX', direction=None) -> None:
"""
Temporarily expose mc_leverage_ref to the MC-Forewarner assessment,
then restore the true extended caps for actual trading.
The parent begin_day() computes:
mc_cfg['max_leverage'] = self.base_max_leverage * self._day_base_boost
By setting base_max_leverage = mc_leverage_ref before the call, MC sees the
reference leverage. After super().begin_day() returns, regime_dd_halt and
_day_mc_scale are already set correctly for that reference. We then restore
the true extended caps so _try_entry uses the full new ceiling.
"""
# Save true extended caps
_true_base = self.base_max_leverage
_true_abs = self.abs_max_leverage
_true_sizer = self.bet_sizer.max_leverage
# Temporarily expose mc_leverage_ref to parent's MC assessment
self.base_max_leverage = self._mc_leverage_ref
self.bet_sizer.max_leverage = self._mc_leverage_ref
self.abs_max_leverage = self._mc_leverage_ref # only affects _try_entry, safe to temp-set
super().begin_day(date_str, posture=posture, direction=direction)
# Restore true extended caps — all trading this day uses extended ceiling
self.base_max_leverage = _true_base
self.bet_sizer.max_leverage = _true_sizer
self.abs_max_leverage = _true_abs
# Record MC verdict for monitoring
self.mc_monitor['total'] += 1
status = self._day_mc_status
if status == 'RED':
self.mc_monitor['red'] += 1
elif status == 'ORANGE':
self.mc_monitor['orange'] += 1
else:
self.mc_monitor['ok'] += 1
if self.regime_dd_halt:
self.mc_monitor['halted'] += 1
def reset(self):
super().reset()
# NDAlphaEngine.reset() rebuilds bet_sizer from self.bet_sizer.max_leverage
# (which at reset time = _extended_soft_cap, so it rebuilds correctly).
# But we re-apply explicitly to be safe.
self.bet_sizer.max_leverage = self._extended_soft_cap
self.base_max_leverage = self._extended_soft_cap
self.abs_max_leverage = self._extended_abs_cap
# Reset monitoring
self.mc_monitor = dict(red=0, orange=0, ok=0, halted=0, total=0)
# ── Run harness ───────────────────────────────────────────────────────────────
def _run(engine_factory, name, d, fw):
"""Full 55-day run + MC monitoring + avg_leverage tracking."""
kw = ENGINE_KWARGS.copy()
acb = AdaptiveCircuitBreaker()
acb.preload_w750(d['date_strings'])
eng = engine_factory(kw)
eng.set_ob_engine(d['ob_eng'])
eng.set_acb(acb)
if fw is not None:
eng.set_mc_forewarner(fw, MC_BASE_CFG)
eng.set_esoteric_hazard_multiplier(0.0)
daily_caps, daily_pnls = [], []
for pf in d['parquet_files']:
ds = pf.stem
df, acols, dvol = d['pq_data'][ds]
cap_before = eng.capital
vol_ok = np.where(np.isfinite(dvol), dvol > d['vol_p60'], False)
eng.process_day(ds, df, acols, vol_regime_ok=vol_ok)
daily_caps.append(eng.capital)
daily_pnls.append(eng.capital - cap_before)
tr = eng.trade_history
n = len(tr)
roi = (eng.capital - 25000.0) / 25000.0 * 100.0
if n == 0:
mc_mon = getattr(eng, 'mc_monitor', {})
return dict(name=name, roi=roi, pf=0.0, dd=0.0, wr=0.0, sharpe=0.0,
trades=0, avg_leverage=0.0, sizing_scale_mean=1.0,
mc_monitor=mc_mon)
def _abs(t): return t.pnl_absolute if hasattr(t, 'pnl_absolute') else t.pnl_pct * 250.0
wins = [t for t in tr if _abs(t) > 0]
losses = [t for t in tr if _abs(t) <= 0]
wr = len(wins) / n * 100.0
pf_val = sum(_abs(t) for t in wins) / max(abs(sum(_abs(t) for t in losses)), 1e-9)
peak_cap, max_dd = 25000.0, 0.0
for cap in daily_caps:
peak_cap = max(peak_cap, cap)
max_dd = max(max_dd, (peak_cap - cap) / peak_cap * 100.0)
dr = np.array([p / 25000.0 * 100.0 for p in daily_pnls])
sharpe = float(dr.mean() / (dr.std() + 1e-9) * math.sqrt(365)) if len(dr) > 1 else 0.0
# Realized leverage stats
lev_vals = [t.leverage for t in tr if hasattr(t, 'leverage') and t.leverage > 0]
avg_lev = float(np.mean(lev_vals)) if lev_vals else 0.0
max_lev = float(np.max(lev_vals)) if lev_vals else 0.0
lev_p90 = float(np.percentile(lev_vals, 90)) if lev_vals else 0.0
lev_at_cap = sum(1 for v in lev_vals if v >= (eng.abs_max_leverage - 0.05))
pct_at_cap = lev_at_cap / len(lev_vals) * 100.0 if lev_vals else 0.0
# proxy_B scale stats
scale_mean = getattr(eng, 'sizing_scale_mean', 1.0)
# MC monitoring
mc_mon = getattr(eng, 'mc_monitor', {})
return dict(
name=name,
roi=roi, pf=pf_val, dd=max_dd, wr=wr, sharpe=sharpe, trades=n,
avg_leverage=avg_lev, max_leverage_realized=max_lev,
lev_p90=lev_p90,
pct_at_hard_cap=pct_at_cap,
sizing_scale_mean=scale_mean,
mc_monitor=mc_mon,
)
# ── Main ─────────────────────────────────────────────────────────────────────
def main():
t_start = time.time()
print("=" * 76)
print("Exp 9 — Extended Leverage Ceiling Test (fork of GOLD adaptive_beta)")
print("=" * 76)
print(f" GOLD ref : ROI={_GOLD_EXP9['roi']:.2f}% DD={_GOLD_EXP9['dd']:.2f}% (adaptive_beta)")
print(f" SILVER ref: ROI={_SILVER['roi']:.2f}% DD={_SILVER['dd']:.2f}% (former gold, regression floor)")
ensure_jit()
d = load_data()
fw = load_forewarner()
# Config: (label, extended_soft_cap, extended_abs_cap, mc_leverage_ref)
configs = [
# ── Decoupled (MC sees original 5.0x reference across all runs) ──────────
("A_5.0/6.0_mc5.0_GOLD-ref", 5.0, 6.0, 5.0), # must reproduce GOLD 96.55%
("B_6.0/7.0_mc5.0_decoupled", 6.0, 7.0, 5.0),
("C_7.0/8.0_mc5.0_decoupled", 7.0, 8.0, 5.0),
("D_8.0/9.0_mc5.0_decoupled", 8.0, 9.0, 5.0),
("E_9.0/10.0_mc5.0_decoupled", 9.0, 10.0, 5.0),
# ── Coupled (MC sees actual new cap → tests MC's response) ───────────────
("F_6.0/7.0_mc6.0_coupled", 6.0, 7.0, 6.0),
("G_7.0/8.0_mc7.0_coupled", 7.0, 8.0, 7.0),
("H_8.0/9.0_mc8.0_coupled", 8.0, 9.0, 8.0),
]
# adaptive_beta proxy_B params (match GOLD exactly)
_PROXY_KWARGS = dict(threshold=DEFAULT_THRESHOLD, alpha=DEFAULT_ALPHA,
adaptive_beta=True, adaptive_alpha=False, adaptive_thr=False)
results = []
for i, (label, soft, hard, mc_ref) in enumerate(configs):
t0 = time.time()
print(f"\n[{i+1}/{len(configs)}] {label} ...")
def _factory(kw, s=soft, h=hard, r=mc_ref):
return ExtendedLeverageEngine(
extended_soft_cap=s,
extended_abs_cap=h,
mc_leverage_ref=r,
**_PROXY_KWARGS,
**kw,
)
res = _run(_factory, label, d, fw)
elapsed = time.time() - t0
mc = res['mc_monitor']
mc_str = (f" MC: ok={mc.get('ok',0)} orange={mc.get('orange',0)} "
f"red={mc.get('red',0)} halted={mc.get('halted',0)}/{mc.get('total',0)}")
dROI = res['roi'] - _GOLD_EXP9['roi']
dDD = res['dd'] - _GOLD_EXP9['dd']
print(f" ROI={res['roi']:>7.2f}% (Δ{dROI:+.2f}pp) DD={res['dd']:>6.2f}% (Δ{dDD:+.2f}pp) "
f"PF={res['pf']:.4f} Trades={res['trades']}")
print(f" avg_lev={res['avg_leverage']:.2f}x p90={res['lev_p90']:.2f}x "
f"max={res['max_leverage_realized']:.2f}x at_hard_cap={res['pct_at_hard_cap']:.1f}% "
f"scale_mean={res['sizing_scale_mean']:.4f}")
print(f"{mc_str} ({elapsed:.0f}s)")
results.append(res)
# ── Verification ─────────────────────────────────────────────────────────
gold_ref = results[0]
gold_ok = (abs(gold_ref['roi'] - _GOLD_EXP9['roi']) < 0.5 and
abs(gold_ref['dd'] - _GOLD_EXP9['dd']) < 0.5 and
abs(gold_ref['trades'] - _GOLD_EXP9['trades']) < 10)
print(f"\n{'='*76}")
print(f"GOLD REFERENCE VERIFICATION: {'PASS ✓' if gold_ok else 'FAIL ✗'}")
print(f" Expected: ROI={_GOLD_EXP9['roi']:.2f}% DD={_GOLD_EXP9['dd']:.2f}% "
f"Got: ROI={gold_ref['roi']:.2f}% DD={gold_ref['dd']:.2f}%")
# ── Results table ─────────────────────────────────────────────────────────
print(f"\n{'='*76}")
print("FULL RESULTS (vs GOLD = adaptive_beta 96.55% / 14.32%)")
print(f"{'Config':<38} {'ROI%':>7} {'ΔROI':>6} {'DD%':>6} {'ΔDD':>6} "
f"{'Trades':>7} {'avgLev':>7} {'p90Lev':>7} {'@cap%':>6} {'OK?':>4}")
print('-' * 100)
for r in results:
dROI = r['roi'] - _GOLD_EXP9['roi']
dDD = r['dd'] - _GOLD_EXP9['dd']
# Pass: better than GOLD on DD, not worse than SILVER on ROI
ok = ('Y' if r['dd'] < _GOLD_EXP9['dd'] and r['roi'] >= _SILVER['roi'] else
'G' if r['dd'] < _SILVER['dd'] and r['roi'] >= _SILVER['roi'] else
'N')
print(f"{r['name']:<38} {r['roi']:>7.2f} {dROI:>+6.2f} {r['dd']:>6.2f} {dDD:>+6.2f} "
f"{r['trades']:>7} {r['avg_leverage']:>7.2f} {r['lev_p90']:>7.2f} "
f"{r['pct_at_hard_cap']:>6.1f} {ok:>4}")
print(" OK=Y: beats GOLD on DD + above SILVER ROI | OK=G: beats SILVER on both | OK=N: fail")
# ── MC interaction table ──────────────────────────────────────────────────
print(f"\n{'='*76}")
print("MC-FOREWARNER INTERACTION (per-day breakdown)")
print(f"{'Config':<38} {'OK':>5} {'ORG':>5} {'RED':>5} {'HALT':>5} {'TOTAL':>6} {'halt%':>7}")
print('-' * 75)
for r in results:
mc = r['mc_monitor']
total = mc.get('total', 0)
halt = mc.get('halted', 0)
halt_pct = halt / total * 100.0 if total else 0.0
print(f"{r['name']:<38} {mc.get('ok',0):>5} {mc.get('orange',0):>5} "
f"{mc.get('red',0):>5} {halt:>5} {total:>6} {halt_pct:>7.1f}%")
# ── Decoupled vs Coupled delta at each leverage level ────────────────────
print(f"\n{'='*76}")
print("MC COUPLING EFFECT (decoupled vs coupled at same leverage level)")
pairs = [
("B_6.0/7.0", "F_6.0/7.0"),
("C_7.0/8.0", "G_7.0/8.0"),
("D_8.0/9.0", "H_8.0/9.0"),
]
rmap = {r['name'][:10]: r for r in results}
for dec_label, coup_label in pairs:
dec = next((r for r in results if dec_label in r['name']), None)
cop = next((r for r in results if coup_label in r['name']), None)
if dec and cop:
halt_dec = dec['mc_monitor'].get('halted', 0)
halt_cop = cop['mc_monitor'].get('halted', 0)
print(f" {dec_label}: decoupled ROI={dec['roi']:.2f}% DD={dec['dd']:.2f}% halted={halt_dec}d")
print(f" {coup_label}: coupled ROI={cop['roi']:.2f}% DD={cop['dd']:.2f}% halted={halt_cop}d")
print(f" MC coupling cost: ΔROI={cop['roi']-dec['roi']:+.2f}pp ΔDD={cop['dd']-dec['dd']:+.2f}pp "
f"Δhalted={halt_cop-halt_dec:+d}d")
print()
# ── Best config summary ───────────────────────────────────────────────────
print(f"{'='*76}")
decoupled = [r for r in results if 'decoupled' in r['name'] or 'GOLD' in r['name']]
best_roi = max(decoupled, key=lambda r: r['roi'])
best_dd = min(decoupled, key=lambda r: r['dd'])
best_combined = max(decoupled, key=lambda r: r['roi'] - r['dd']) # ROIDD as proxy
print(f"BEST (decoupled only — pure leverage effect):")
print(f" Best ROI: {best_roi['name']} ROI={best_roi['roi']:.2f}% DD={best_roi['dd']:.2f}%")
print(f" Best DD: {best_dd['name']} ROI={best_dd['roi']:.2f}% DD={best_dd['dd']:.2f}%")
print(f" Best RD: {best_combined['name']} ROI={best_combined['roi']:.2f}% DD={best_combined['dd']:.2f}%")
# ── Log ──────────────────────────────────────────────────────────────────
outfile = _HERE / "exp9_leverage_ceiling_results.json"
log_results(results, outfile, gold=_GOLD_EXP9, meta={
"exp": "exp9",
"question": "Does adaptive_beta DD headroom permit higher leverage ceiling?",
"silver": _SILVER,
"total_elapsed_s": round(time.time() - t_start, 1),
"gold_ref_ok": gold_ok,
"note_mc": (
"Decoupled runs (mc_ref=5.0): MC assesses at original 5x reference — "
"pure leverage cap test. Coupled runs: MC sees actual new cap — tests MC response."
),
})
total = time.time() - t_start
print(f"\nTotal elapsed: {total / 60:.1f} min")
print("Done.")
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,189 @@
{
"gold": {
"roi": 96.55,
"dd": 14.32,
"trades": 2155
},
"results": [
{
"name": "A_5.0/6.0_mc5.0_GOLD-ref",
"roi": 96.55083977704362,
"pf": 1.2201203038704769,
"dd": 14.321954772270876,
"wr": 50.48723897911833,
"sharpe": 4.631491961453057,
"trades": 2155,
"avg_leverage": 3.1901002888648518,
"max_leverage_realized": 6.0,
"lev_p90": 6.0,
"pct_at_hard_cap": 40.324825986078885,
"sizing_scale_mean": 1.0883296846011132,
"mc_monitor": {
"red": 0,
"orange": 0,
"ok": 56,
"halted": 0,
"total": 56
}
},
{
"name": "B_6.0/7.0_mc5.0_decoupled",
"roi": 119.33687073886617,
"pf": 1.2185272940196565,
"dd": 16.916071652089094,
"wr": 50.48723897911833,
"sharpe": 4.550677484437249,
"trades": 2155,
"avg_leverage": 3.588114025875353,
"max_leverage_realized": 7.0,
"lev_p90": 7.0,
"pct_at_hard_cap": 39.443155452436194,
"sizing_scale_mean": 1.0883296846011132,
"mc_monitor": {
"red": 0,
"orange": 0,
"ok": 56,
"halted": 0,
"total": 56
}
},
{
"name": "C_7.0/8.0_mc5.0_decoupled",
"roi": 141.79672282249857,
"pf": 1.2193254974796508,
"dd": 18.318263045357885,
"wr": 50.48723897911833,
"sharpe": 4.517317168361109,
"trades": 2155,
"avg_leverage": 3.8905395845230593,
"max_leverage_realized": 8.0,
"lev_p90": 8.0,
"pct_at_hard_cap": 20.51044083526682,
"sizing_scale_mean": 1.0883296846011132,
"mc_monitor": {
"red": 0,
"orange": 0,
"ok": 56,
"halted": 0,
"total": 56
}
},
{
"name": "D_8.0/9.0_mc5.0_decoupled",
"roi": 162.27551845884568,
"pf": 1.2215406999889398,
"dd": 18.43672091284768,
"wr": 50.48723897911833,
"sharpe": 4.490863883629943,
"trades": 2155,
"avg_leverage": 4.093742876621359,
"max_leverage_realized": 9.0,
"lev_p90": 9.0,
"pct_at_hard_cap": 20.139211136890953,
"sizing_scale_mean": 1.0883296846011132,
"mc_monitor": {
"red": 0,
"orange": 0,
"ok": 56,
"halted": 0,
"total": 56
}
},
{
"name": "E_9.0/10.0_mc5.0_decoupled",
"roi": 183.99742787632874,
"pf": 1.222401072093006,
"dd": 18.556892309537158,
"wr": 50.48723897911833,
"sharpe": 4.427325011926956,
"trades": 2155,
"avg_leverage": 4.293314913419678,
"max_leverage_realized": 10.0,
"lev_p90": 10.0,
"pct_at_hard_cap": 19.814385150812065,
"sizing_scale_mean": 1.0883296846011132,
"mc_monitor": {
"red": 0,
"orange": 0,
"ok": 56,
"halted": 0,
"total": 56
}
},
{
"name": "F_6.0/7.0_mc6.0_coupled",
"roi": 119.33687073886617,
"pf": 1.2185272940196565,
"dd": 16.916071652089094,
"wr": 50.48723897911833,
"sharpe": 4.550677484437249,
"trades": 2155,
"avg_leverage": 3.588114025875353,
"max_leverage_realized": 7.0,
"lev_p90": 7.0,
"pct_at_hard_cap": 39.443155452436194,
"sizing_scale_mean": 1.0883296846011132,
"mc_monitor": {
"red": 0,
"orange": 0,
"ok": 56,
"halted": 0,
"total": 56
}
},
{
"name": "G_7.0/8.0_mc7.0_coupled",
"roi": 141.79672282249857,
"pf": 1.2193254974796508,
"dd": 18.318263045357885,
"wr": 50.48723897911833,
"sharpe": 4.517317168361109,
"trades": 2155,
"avg_leverage": 3.8905395845230593,
"max_leverage_realized": 8.0,
"lev_p90": 8.0,
"pct_at_hard_cap": 20.51044083526682,
"sizing_scale_mean": 1.0883296846011132,
"mc_monitor": {
"red": 0,
"orange": 0,
"ok": 56,
"halted": 0,
"total": 56
}
},
{
"name": "H_8.0/9.0_mc8.0_coupled",
"roi": 162.27551845884568,
"pf": 1.2215406999889398,
"dd": 18.43672091284768,
"wr": 50.48723897911833,
"sharpe": 4.490863883629943,
"trades": 2155,
"avg_leverage": 4.093742876621359,
"max_leverage_realized": 9.0,
"lev_p90": 9.0,
"pct_at_hard_cap": 20.139211136890953,
"sizing_scale_mean": 1.0883296846011132,
"mc_monitor": {
"red": 0,
"orange": 0,
"ok": 56,
"halted": 0,
"total": 56
}
}
],
"meta": {
"exp": "exp9",
"question": "Does adaptive_beta DD headroom permit higher leverage ceiling?",
"silver": {
"roi": 88.55,
"dd": 15.05,
"trades": 2155
},
"total_elapsed_s": 2697.7,
"gold_ref_ok": true,
"note_mc": "Decoupled runs (mc_ref=5.0): MC assesses at original 5x reference \u2014 pure leverage cap test. Coupled runs: MC sees actual new cap \u2014 tests MC response."
}
}

View File

@@ -0,0 +1,279 @@
"""
Exp 9b — Liquidation Guard on Extended Leverage Configs
Exp9 found a DD plateau after 7x soft cap: each additional leverage unit above 7x
costs only +0.12pp DD while adding ~20pp ROI. However, exp9 does NOT model exchange
liquidation: the existing stop_pct=1.0 is effectively disabled, and a position at
10x leverage would be force-closed by the exchange after a 10% adverse move.
This experiment adds a per-trade liquidation floor using the _pending_stop_override hook:
stop_override = (1.0 / abs_cap) * 0.95 (95% of exchange margin = early warning floor)
6/7x config: fires if price moves >13.6% against position in the hold window
7/8x config: >11.9%
8/9x config: >10.6%
9/10x config: >9.5%
All of these thresholds are wide relative to a 10-min BTC hold — the 55-day gold dataset
almost certainly contains 0 such events for B/C, possibly 0-1 for D/E.
The goal is to CONFIRM that exp9 results are not artefacts of missing liquidation model.
If results are identical to exp9: liquidation risk is immaterial for this dataset.
If results differ: we know exactly which config/day triggered it and the cost.
Configs run (skip A = GOLD reference, already verified in exp9):
B_liq: 6/7x mc_ref=5.0 + liquidation guard at 13.6%
C_liq: 7/8x mc_ref=5.0 + liquidation guard at 11.9%
D_liq: 8/9x mc_ref=5.0 + liquidation guard at 10.6%
E_liq: 9/10x mc_ref=5.0 + liquidation guard at 9.5%
Total: 4 runs × ~250s ≈ 17 min
Results → exp9b_liquidation_guard_results.json
"""
import sys, time, json, math
sys.stdout.reconfigure(encoding='utf-8', errors='replace')
from pathlib import Path
import numpy as np
_HERE = Path(__file__).resolve().parent
sys.path.insert(0, str(_HERE.parent))
from exp_shared import (
ensure_jit, ENGINE_KWARGS, GOLD, MC_BASE_CFG,
load_data, load_forewarner, log_results,
)
from exp9_leverage_ceiling import ExtendedLeverageEngine
from nautilus_dolphin.nautilus.adaptive_circuit_breaker import AdaptiveCircuitBreaker
from nautilus_dolphin.nautilus.proxy_boost_engine import DEFAULT_THRESHOLD, DEFAULT_ALPHA
# Known exp9 results for comparison
_EXP9 = {
'B': dict(roi=119.34, dd=16.92, trades=2155),
'C': dict(roi=141.80, dd=18.32, trades=2155),
'D': dict(roi=162.28, dd=18.44, trades=2155),
'E': dict(roi=184.00, dd=18.56, trades=2155),
}
_GOLD_EXP9 = dict(roi=96.55, dd=14.32, trades=2155)
# ── LiquidationGuardEngine ────────────────────────────────────────────────────
class LiquidationGuardEngine(ExtendedLeverageEngine):
"""
Adds an exchange-liquidation floor stop to ExtendedLeverageEngine.
For each entry, sets _pending_stop_override = (1/abs_cap) * margin_buffer
BEFORE calling super()._try_entry(). The NDAlphaEngine._try_entry() consumes
this and passes it to exit_manager.setup_position() as stop_pct_override.
The exit_manager then monitors: if pnl_pct < -stop_pct_override → EXIT (liquidation).
Using abs_cap (hard ceiling) slightly underestimates the actual liquidation price,
i.e. we exit slightly before the exchange would — a conservative model.
liquidation_stops counter tracks how many times this fires.
"""
def __init__(self, *args, margin_buffer: float = 0.95, **kwargs):
super().__init__(*args, **kwargs)
self.margin_buffer = margin_buffer
self._liq_stop_pct = (1.0 / self._extended_abs_cap) * margin_buffer
self.liquidation_stops = 0
def _try_entry(self, bar_idx, vel_div, prices, price_histories,
v50_vel=0.0, v750_vel=0.0):
# Arm the liquidation floor before parent entry logic consumes it
self._pending_stop_override = self._liq_stop_pct
result = super()._try_entry(bar_idx, vel_div, prices, price_histories,
v50_vel, v750_vel)
return result
def _execute_exit(self, reason, bar_idx, pnl_pct_raw=0.0, bars_held=0):
if reason == 'STOP_LOSS':
self.liquidation_stops += 1
return super()._execute_exit(reason, bar_idx, pnl_pct_raw, bars_held)
def reset(self):
super().reset()
self._liq_stop_pct = (1.0 / self._extended_abs_cap) * self.margin_buffer
self.liquidation_stops = 0
# ── Run harness ───────────────────────────────────────────────────────────────
def _run(engine_factory, name, d, fw):
kw = ENGINE_KWARGS.copy()
acb = AdaptiveCircuitBreaker()
acb.preload_w750(d['date_strings'])
eng = engine_factory(kw)
eng.set_ob_engine(d['ob_eng'])
eng.set_acb(acb)
if fw is not None:
eng.set_mc_forewarner(fw, MC_BASE_CFG)
eng.set_esoteric_hazard_multiplier(0.0)
daily_caps, daily_pnls = [], []
for pf in d['parquet_files']:
ds = pf.stem
df, acols, dvol = d['pq_data'][ds]
cap_before = eng.capital
vol_ok = np.where(np.isfinite(dvol), dvol > d['vol_p60'], False)
eng.process_day(ds, df, acols, vol_regime_ok=vol_ok)
daily_caps.append(eng.capital)
daily_pnls.append(eng.capital - cap_before)
tr = eng.trade_history
n = len(tr)
roi = (eng.capital - 25000.0) / 25000.0 * 100.0
liq_stops = getattr(eng, 'liquidation_stops', 0)
if n == 0:
return dict(name=name, roi=roi, pf=0.0, dd=0.0, wr=0.0, sharpe=0.0,
trades=0, avg_leverage=0.0, liq_stops=liq_stops,
liq_stop_pct=getattr(eng, '_liq_stop_pct', 0.0))
def _abs(t): return t.pnl_absolute if hasattr(t, 'pnl_absolute') else t.pnl_pct * 250.0
wins = [t for t in tr if _abs(t) > 0]
losses = [t for t in tr if _abs(t) <= 0]
wr = len(wins) / n * 100.0
pf_val = sum(_abs(t) for t in wins) / max(abs(sum(_abs(t) for t in losses)), 1e-9)
peak_cap, max_dd = 25000.0, 0.0
for cap in daily_caps:
peak_cap = max(peak_cap, cap)
max_dd = max(max_dd, (peak_cap - cap) / peak_cap * 100.0)
dr = np.array([p / 25000.0 * 100.0 for p in daily_pnls])
sharpe = float(dr.mean() / (dr.std() + 1e-9) * math.sqrt(365)) if len(dr) > 1 else 0.0
lev_vals = [t.leverage for t in tr if hasattr(t, 'leverage') and t.leverage > 0]
avg_lev = float(np.mean(lev_vals)) if lev_vals else 0.0
return dict(
name=name, roi=roi, pf=pf_val, dd=max_dd, wr=wr, sharpe=sharpe,
trades=n, avg_leverage=avg_lev,
liq_stops=liq_stops,
liq_stop_pct=getattr(eng, '_liq_stop_pct', 0.0),
liq_stop_rate_pct=liq_stops / n * 100.0 if n else 0.0,
)
# ── Main ─────────────────────────────────────────────────────────────────────
def main():
t_start = time.time()
print("=" * 76)
print("Exp 9b — Liquidation Guard on Extended Leverage Configs")
print("=" * 76)
print(" Adding exchange-liquidation floor stop to exp9 B/C/D/E configs.")
print(" Stop fires if adverse move exceeds (1/abs_cap)*0.95 of position.")
ensure_jit()
d = load_data()
fw = load_forewarner()
_PROXY = dict(threshold=DEFAULT_THRESHOLD, alpha=DEFAULT_ALPHA,
adaptive_beta=True, adaptive_alpha=False, adaptive_thr=False)
# (label, soft_cap, abs_cap, mc_ref, exp9_key)
configs = [
("B_liq_6/7x_liqstop13.6%", 6.0, 7.0, 5.0, 'B'),
("C_liq_7/8x_liqstop11.9%", 7.0, 8.0, 5.0, 'C'),
("D_liq_8/9x_liqstop10.6%", 8.0, 9.0, 5.0, 'D'),
("E_liq_9/10x_liqstop9.5%", 9.0, 10.0, 5.0, 'E'),
]
results = []
for i, (label, soft, hard, mc_ref, exp9_key) in enumerate(configs):
t0 = time.time()
liq_pct = (1.0 / hard) * 0.95 * 100
print(f"\n[{i+1}/{len(configs)}] {label} (floor={liq_pct:.1f}%) ...")
def _factory(kw, s=soft, h=hard, r=mc_ref):
return LiquidationGuardEngine(
extended_soft_cap=s, extended_abs_cap=h, mc_leverage_ref=r,
margin_buffer=0.95,
**_PROXY, **kw,
)
res = _run(_factory, label, d, fw)
elapsed = time.time() - t0
ref = _EXP9[exp9_key]
dROI = res['roi'] - ref['roi']
dDD = res['dd'] - ref['dd']
print(f" ROI={res['roi']:>8.2f}% (vs exp9: {dROI:+.2f}pp) "
f"DD={res['dd']:>6.2f}% (vs exp9: {dDD:+.2f}pp) "
f"Trades={res['trades']}")
print(f" avg_lev={res['avg_leverage']:.2f}x "
f"liquidation_stops={res['liq_stops']} "
f"liq_rate={res['liq_stop_rate_pct']:.2f}% ({elapsed:.0f}s)")
results.append(res)
# ── Summary ──────────────────────────────────────────────────────────────
print(f"\n{'='*76}")
print("LIQUIDATION GUARD IMPACT vs EXP9 (unguarded)")
print(f"{'Config':<34} {'ROI(9b)':>8} {'ROI(9)':>8} {'ΔROI':>7} "
f"{'DD(9b)':>7} {'DD(9)':>7} {'ΔDD':>6} {'LiqStops':>9}")
print('-' * 90)
for r in results:
key = r['name'][0] # B/C/D/E
ref = _EXP9[key]
dROI = r['roi'] - ref['roi']
dDD = r['dd'] - ref['dd']
identical = (abs(dROI) < 0.01 and abs(dDD) < 0.01 and r['liq_stops'] == 0)
flag = '✓ identical' if identical else '✗ CHANGED'
print(f"{r['name']:<34} {r['roi']:>8.2f} {ref['roi']:>8.2f} {dROI:>+7.2f} "
f"{r['dd']:>7.2f} {ref['dd']:>7.2f} {dDD:>+6.2f} "
f"{r['liq_stops']:>6} {flag}")
# ── Compounding with liquidation-adjusted numbers ─────────────────────────
print(f"\n{'='*76}")
print("COMPOUNDING TABLE — liquidation-adjusted (starting $25k, 56-day periods)")
all_configs = [
("GOLD 5/6x", _GOLD_EXP9['roi'], _GOLD_EXP9['dd']),
] + [
(r['name'][:10], r['roi'], r['dd']) for r in results
]
print(f"{'Config':<20} {'ROI%':>7} {'DD%':>6} {'Calmar':>7} "
f"{'3per(~5mo)':>12} {'6per(~1yr)':>12} {'12per(~2yr)':>13}")
print('-' * 85)
for label, roi, dd in all_configs:
mult = 1.0 + roi / 100.0
calmar = roi / dd if dd > 0 else 0
v3 = 25000 * mult**3
v6 = 25000 * mult**6
v12 = 25000 * mult**12
print(f"{label:<20} {roi:>7.2f} {dd:>6.2f} {calmar:>7.2f} "
f"${v3:>11,.0f} ${v6:>11,.0f} ${v12:>12,.0f}")
print(f"\n{'='*76}")
any_changed = any(r['liq_stops'] > 0 for r in results)
if not any_changed:
print("VERDICT: Zero liquidation stops fired across all configs.")
print(" Exp9 results are accurate — liquidation risk is immaterial for this 55-day dataset.")
print(" The compounding numbers from exp9 stand as-is.")
else:
changed = [r for r in results if r['liq_stops'] > 0]
print(f"VERDICT: {sum(r['liq_stops'] for r in results)} liquidation stop(s) fired.")
for r in changed:
print(f" {r['name']}: {r['liq_stops']} stops, ΔROI={r['roi']-_EXP9[r['name'][0]]['roi']:+.2f}pp")
outfile = _HERE / "exp9b_liquidation_guard_results.json"
log_results(results, outfile, gold=_GOLD_EXP9, meta={
"exp": "exp9b",
"question": "Do liquidation stops fire in 55-day dataset? Are exp9 results accurate?",
"exp9_reference": _EXP9,
"total_elapsed_s": round(time.time() - t_start, 1),
})
total = time.time() - t_start
print(f"\nTotal elapsed: {total / 60:.1f} min")
print("Done.")
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,88 @@
{
"gold": {
"roi": 96.55,
"dd": 14.32,
"trades": 2155
},
"results": [
{
"name": "B_liq_6/7x_liqstop13.6%",
"roi": 117.60890852798336,
"pf": 1.2199931287037944,
"dd": 15.800313739426617,
"wr": 50.672853828306266,
"sharpe": 4.592967533169852,
"trades": 2155,
"avg_leverage": 3.501351353769574,
"liq_stops": 1,
"liq_stop_pct": 0.1357142857142857,
"liq_stop_rate_pct": 0.04640371229698376
},
{
"name": "C_liq_7/8x_liqstop11.9%",
"roi": 139.72300151188082,
"pf": 1.2286392091015288,
"dd": 16.7760059114392,
"wr": 50.672853828306266,
"sharpe": 4.844497641085453,
"trades": 2155,
"avg_leverage": 3.7301880355925894,
"liq_stops": 1,
"liq_stop_pct": 0.11875,
"liq_stop_rate_pct": 0.04640371229698376
},
{
"name": "D_liq_8/9x_liqstop10.6%",
"roi": 147.2832528932534,
"pf": 1.233340905262188,
"dd": 16.776005911439114,
"wr": 50.672853828306266,
"sharpe": 4.995057984165502,
"trades": 2155,
"avg_leverage": 3.795150347589037,
"liq_stops": 1,
"liq_stop_pct": 0.10555555555555554,
"liq_stop_rate_pct": 0.04640371229698376
},
{
"name": "E_liq_9/10x_liqstop9.5%",
"roi": 112.16063020117491,
"pf": 1.1952757754808747,
"dd": 30.685728293170744,
"wr": 50.69573283858998,
"sharpe": 3.9564143841036885,
"trades": 2156,
"avg_leverage": 3.854065962587942,
"liq_stops": 5,
"liq_stop_pct": 0.095,
"liq_stop_rate_pct": 0.2319109461966605
}
],
"meta": {
"exp": "exp9b",
"question": "Do liquidation stops fire in 55-day dataset? Are exp9 results accurate?",
"exp9_reference": {
"B": {
"roi": 119.34,
"dd": 16.92,
"trades": 2155
},
"C": {
"roi": 141.8,
"dd": 18.32,
"trades": 2155
},
"D": {
"roi": 162.28,
"dd": 18.44,
"trades": 2155
},
"E": {
"roi": 184.0,
"dd": 18.56,
"trades": 2155
}
},
"total_elapsed_s": 1877.3
}
}

View File

@@ -0,0 +1,202 @@
{
"gold": {
"roi": 88.55,
"pf": 1.215,
"dd": 15.05,
"sharpe": 4.38,
"wr": 50.5,
"trades": 2155
},
"results": [
{
"name": "d_liq_H1 (days 0-27)",
"roi": 76.14224142505569,
"dd": 16.77600591143919,
"calmar": 4.538758619126139,
"trades": 1050,
"liq_stops": 1,
"days": 28,
"mc_red": 0,
"mc_halted": 0
},
{
"name": "abeta_H1 (days 0-27)",
"roi": 38.57335970751784,
"dd": 14.321954772270908,
"calmar": 2.693302717461493,
"trades": 1050,
"liq_stops": 0,
"days": 28,
"mc_red": 0,
"mc_halted": 0
},
{
"name": "d_liq_H2 (days 28-55)",
"roi": 59.928495551638946,
"dd": 15.381215784261638,
"calmar": 3.896213172755756,
"trades": 1103,
"liq_stops": 0,
"days": 28,
"mc_red": 0,
"mc_halted": 0
},
{
"name": "abeta_H2 (days 28-55)",
"roi": 42.578652854670715,
"dd": 10.236414991759771,
"calmar": 4.159527812124281,
"trades": 1103,
"liq_stops": 0,
"days": 28,
"mc_red": 0,
"mc_halted": 0
},
{
"name": "d_liq_Q1",
"roi": 3.353675329427657,
"dd": 16.77600591143919,
"calmar": 0.199909045522025,
"trades": 532,
"liq_stops": 0,
"days": 14,
"mc_red": 0,
"mc_halted": 0
},
{
"name": "abeta_Q1",
"roi": 1.1727921993959172,
"dd": 13.884446888829856,
"calmar": 0.08446805326753329,
"trades": 532,
"liq_stops": 0,
"days": 14,
"mc_red": 0,
"mc_halted": 0
},
{
"name": "d_liq_Q2",
"roi": 70.82653734416076,
"dd": 12.810959705505464,
"calmar": 5.528589502449477,
"trades": 517,
"liq_stops": 1,
"days": 14,
"mc_red": 0,
"mc_halted": 0
},
{
"name": "abeta_Q2",
"roi": 38.781648637136215,
"dd": 14.437901577945617,
"calmar": 2.686100083711368,
"trades": 517,
"liq_stops": 0,
"days": 14,
"mc_red": 0,
"mc_halted": 0
},
{
"name": "d_liq_Q3",
"roi": 55.34503692873527,
"dd": 15.381215784261757,
"calmar": 3.598222514072325,
"trades": 465,
"liq_stops": 0,
"days": 14,
"mc_red": 0,
"mc_halted": 0
},
{
"name": "abeta_Q3",
"roi": 38.41268117672217,
"dd": 10.236414991759771,
"calmar": 3.7525521588997766,
"trades": 465,
"liq_stops": 0,
"days": 14,
"mc_red": 0,
"mc_halted": 0
},
{
"name": "d_liq_Q4",
"roi": 3.1074038301600084,
"dd": 12.866819616903355,
"calmar": 0.2415051988509857,
"trades": 637,
"liq_stops": 0,
"days": 14,
"mc_red": 0,
"mc_halted": 0
},
{
"name": "abeta_Q4",
"roi": 3.1287720381663533,
"dd": 8.984276378639036,
"calmar": 0.3482497539373681,
"trades": 637,
"liq_stops": 0,
"days": 14,
"mc_red": 0,
"mc_halted": 0
},
{
"name": "d_liq_buf0.80",
"roi": 169.47614903222345,
"dd": 20.78163843773938,
"calmar": 8.15509082885666,
"trades": 2156,
"liq_stops": 5,
"days": 56,
"mc_red": 0,
"mc_halted": 0,
"margin_buffer": 0.8
},
{
"name": "d_liq_buf0.90",
"roi": 167.655698815112,
"dd": 16.77600591143919,
"calmar": 9.993779192745233,
"trades": 2155,
"liq_stops": 2,
"days": 56,
"mc_red": 0,
"mc_halted": 0,
"margin_buffer": 0.9
},
{
"name": "d_liq_buf0.95",
"roi": 166.83246014782176,
"dd": 16.77600591143919,
"calmar": 9.944706804976885,
"trades": 2155,
"liq_stops": 1,
"days": 56,
"mc_red": 0,
"mc_halted": 0,
"margin_buffer": 0.95
},
{
"name": "d_liq_buf1.00",
"roi": 166.83246014782176,
"dd": 16.77600591143919,
"calmar": 9.944706804976885,
"trades": 2155,
"liq_stops": 1,
"days": 56,
"mc_red": 0,
"mc_halted": 0,
"margin_buffer": 1.0
}
],
"meta": {
"exp": "exp9c",
"question": "Is D_LIQ_GOLD robust across time windows and parameter perturbations?",
"split_passes": "1/2",
"quarter_passes": "2/4",
"buf_roi_range_pp": 2.644,
"buf_dd_range_pp": 4.006,
"all_pass": false,
"total_elapsed_s": 5657.9
}
}

View File

@@ -0,0 +1,296 @@
"""
Exp 9c — Overfitting Validation for D_LIQ_GOLD
Battery of tests designed to expose any period-specific bias in the D_LIQ_GOLD result
(8x/9x + liquidation guard, exp9b: ROI=181.81%, DD=17.65%, Calmar=10.30).
Three test families:
1. TEMPORAL SPLIT (H1/H2)
Same split as exp8 adaptive_beta validation (days 0-27 vs days 28-55).
Each half: fresh engine, fresh capital=$25k, cold start.
Pass criterion: Calmar(d_liq) > Calmar(adaptive_beta) in BOTH halves.
If d_liq only wins in one half → period-specific, do NOT flip default.
2. QUARTERLY SPLIT (Q1/Q2/Q3/Q4)
Four independent ~14-day windows.
Finer-grained: reveals if any single quarter is carrying the full result.
Pass criterion: d_liq Calmar consistently above adaptive_beta across quarters.
3. MARGIN BUFFER SENSITIVITY
Test margin_buffer = 0.80, 0.90, 0.95 (gold), 1.00 on the full period.
Confirms the specific 10.6% floor is not cherry-picked.
Pass criterion: ROI/DD metrics stable across ±0.15 variation in buffer.
Reference benchmarks:
D_LIQ_GOLD (full period): ROI=181.81%, DD=17.65%, Calmar=10.30
adaptive_beta (full): ROI= 96.55%, DD=14.32%, Calmar= 6.74
Results → exp9c_overfitting_results.json
"""
import sys, time, json, math
sys.stdout.reconfigure(encoding='utf-8', errors='replace')
from pathlib import Path
import numpy as np
_HERE = Path(__file__).resolve().parent
sys.path.insert(0, str(_HERE.parent))
from exp_shared import (
ensure_jit, ENGINE_KWARGS, MC_BASE_CFG,
load_data, load_forewarner, log_results,
)
from nautilus_dolphin.nautilus.proxy_boost_engine import (
AdaptiveBoostEngine, LiquidationGuardEngine,
DEFAULT_THRESHOLD, DEFAULT_ALPHA,
D_LIQ_SOFT_CAP, D_LIQ_ABS_CAP, D_LIQ_MC_REF,
)
from nautilus_dolphin.nautilus.adaptive_circuit_breaker import AdaptiveCircuitBreaker
_D_LIQ_FULL = dict(roi=181.81, dd=17.65, calmar=10.30, trades=2155)
_ABETA_FULL = dict(roi= 96.55, dd=14.32, calmar= 6.74, trades=2155)
_PROXY = dict(threshold=DEFAULT_THRESHOLD, alpha=DEFAULT_ALPHA,
adaptive_beta=True, adaptive_alpha=False, adaptive_thr=False)
# ── Engine factories ──────────────────────────────────────────────────────────
def _make_dliq(kw, margin_buffer=0.95):
return LiquidationGuardEngine(
extended_soft_cap=D_LIQ_SOFT_CAP,
extended_abs_cap=D_LIQ_ABS_CAP,
mc_leverage_ref=D_LIQ_MC_REF,
margin_buffer=margin_buffer,
**_PROXY, **kw,
)
def _make_abeta(kw):
return AdaptiveBoostEngine(**_PROXY, **kw)
# ── Run harness (window-aware) ────────────────────────────────────────────────
def _run_window(engine_factory, name, d, fw, day_indices):
"""Run a sub-period backtest over the given day index slice."""
kw = ENGINE_KWARGS.copy()
acb = AdaptiveCircuitBreaker()
# Preload full date list for proper w750 context even in sub-period runs
acb.preload_w750(d['date_strings'])
eng = engine_factory(kw)
eng.set_ob_engine(d['ob_eng'])
eng.set_acb(acb)
if fw is not None:
eng.set_mc_forewarner(fw, MC_BASE_CFG)
eng.set_esoteric_hazard_multiplier(0.0)
daily_caps, daily_pnls = [], []
pf_list = d['parquet_files']
for idx in day_indices:
pf = pf_list[idx]
ds = pf.stem
df, acols, dvol = d['pq_data'][ds]
cap_before = eng.capital
vol_ok = np.where(np.isfinite(dvol), dvol > d['vol_p60'], False)
eng.process_day(ds, df, acols, vol_regime_ok=vol_ok)
daily_caps.append(eng.capital)
daily_pnls.append(eng.capital - cap_before)
tr = eng.trade_history
n = len(tr)
roi = (eng.capital - 25000.0) / 25000.0 * 100.0
liq_stops = getattr(eng, 'liquidation_stops', 0)
mc_mon = getattr(eng, 'mc_monitor', {})
if n == 0:
return dict(name=name, roi=roi, dd=0.0, calmar=0.0, trades=0,
liq_stops=liq_stops, days=len(day_indices))
def _abs(t): return t.pnl_absolute if hasattr(t, 'pnl_absolute') else t.pnl_pct * 250.0
wins = [t for t in tr if _abs(t) > 0]
losses = [t for t in tr if _abs(t) <= 0]
peak_cap, max_dd = 25000.0, 0.0
for cap in daily_caps:
peak_cap = max(peak_cap, cap)
max_dd = max(max_dd, (peak_cap - cap) / peak_cap * 100.0)
calmar = roi / max_dd if max_dd > 0 else 0.0
return dict(
name=name, roi=roi, dd=max_dd, calmar=calmar, trades=n,
liq_stops=liq_stops, days=len(day_indices),
mc_red=mc_mon.get('red', 0), mc_halted=mc_mon.get('halted', 0),
)
def _compare(dliq_r, abeta_r, window_label):
"""Print head-to-head for one window."""
d_roi = dliq_r['roi'] - abeta_r['roi']
d_dd = dliq_r['dd'] - abeta_r['dd']
d_cal = dliq_r['calmar'] - abeta_r['calmar']
liq = dliq_r.get('liq_stops', 0)
verdict = 'PASS' if dliq_r['calmar'] > abeta_r['calmar'] else 'FAIL'
print(f" {window_label:<18} d_liq {dliq_r['roi']:>7.2f}% / {dliq_r['dd']:>5.2f}% "
f"cal={dliq_r['calmar']:.2f} | abeta {abeta_r['roi']:>7.2f}% / {abeta_r['dd']:>5.2f}% "
f"cal={abeta_r['calmar']:.2f} | ΔROI={d_roi:+.2f} ΔDD={d_dd:+.2f} ΔCal={d_cal:+.2f} "
f"liq={liq} [{verdict}]")
return verdict == 'PASS'
# ── Main ─────────────────────────────────────────────────────────────────────
def main():
t_start = time.time()
print("=" * 80)
print("Exp 9c — D_LIQ_GOLD Overfitting Validation")
print("=" * 80)
ensure_jit()
d = load_data()
fw = load_forewarner()
n_days = len(d['parquet_files'])
print(f" Dataset: {n_days} trading days")
# Day index windows
all_idx = list(range(n_days))
mid = n_days // 2
h1_idx = all_idx[:mid]
h2_idx = all_idx[mid:]
q_size = n_days // 4
q_idx = [all_idx[i*q_size : (i+1)*q_size] for i in range(4)]
# Last quarter gets any remainder
q_idx[3] = all_idx[3*q_size:]
print(f" H1: days 0{mid-1} ({len(h1_idx)}d) "
f"H2: days {mid}{n_days-1} ({len(h2_idx)}d)")
print(f" Q1:{len(q_idx[0])}d Q2:{len(q_idx[1])}d "
f"Q3:{len(q_idx[2])}d Q4:{len(q_idx[3])}d")
results_all = []
pass_counts = {'split': 0, 'split_total': 0,
'quarter': 0, 'quarter_total': 0}
# ── FAMILY 1: Temporal split H1/H2 ───────────────────────────────────────
print(f"\n{'='*80}")
print("FAMILY 1 — Temporal Split H1/H2")
print(f"{'='*80}")
for label, idx in [('H1 (days 0-27)', h1_idx), ('H2 (days 28-55)', h2_idx)]:
t0 = time.time()
print(f"\n {label}:")
dliq_r = _run_window(lambda kw: _make_dliq(kw), f'd_liq_{label}', d, fw, idx)
abeta_r = _run_window(lambda kw: _make_abeta(kw), f'abeta_{label}', d, fw, idx)
elapsed = time.time() - t0
passed = _compare(dliq_r, abeta_r, label)
print(f" trades: d_liq={dliq_r['trades']} abeta={abeta_r['trades']} ({elapsed:.0f}s)")
results_all += [dliq_r, abeta_r]
pass_counts['split'] += int(passed)
pass_counts['split_total'] += 1
split_verdict = ('PASS ✓' if pass_counts['split'] == pass_counts['split_total']
else f"PARTIAL ({pass_counts['split']}/{pass_counts['split_total']})")
print(f"\n H1/H2 SPLIT VERDICT: {split_verdict}")
# ── FAMILY 2: Quarterly split ─────────────────────────────────────────────
print(f"\n{'='*80}")
print("FAMILY 2 — Quarterly Split (Q1/Q2/Q3/Q4)")
print(f"{'='*80}")
for qi, idx in enumerate(q_idx, 1):
label = f'Q{qi} (days {idx[0]}-{idx[-1]})'
t0 = time.time()
print(f"\n {label}:")
dliq_r = _run_window(lambda kw: _make_dliq(kw), f'd_liq_Q{qi}', d, fw, idx)
abeta_r = _run_window(lambda kw: _make_abeta(kw), f'abeta_Q{qi}', d, fw, idx)
elapsed = time.time() - t0
passed = _compare(dliq_r, abeta_r, label)
print(f" trades: d_liq={dliq_r['trades']} abeta={abeta_r['trades']} ({elapsed:.0f}s)")
results_all += [dliq_r, abeta_r]
pass_counts['quarter'] += int(passed)
pass_counts['quarter_total'] += 1
quarter_verdict = ('PASS ✓' if pass_counts['quarter'] == pass_counts['quarter_total']
else f"PARTIAL ({pass_counts['quarter']}/{pass_counts['quarter_total']})")
print(f"\n QUARTERLY VERDICT: {quarter_verdict}")
# ── FAMILY 3: Margin buffer sensitivity (full period) ─────────────────────
print(f"\n{'='*80}")
print("FAMILY 3 — Margin Buffer Sensitivity (full period, d_liq only)")
print(f"{'='*80}")
print(f" Floor = (1/abs_cap) * buffer | abs_cap=9.0")
print(f" {'Buffer':>8} {'Floor%':>7} {'ROI%':>8} {'DD%':>6} {'Calmar':>7} "
f"{'liq_stops':>10} {'ΔROI vs gold':>13}")
buf_results = []
for buf in [0.80, 0.90, 0.95, 1.00]:
t0 = time.time()
floor_pct = (1.0 / D_LIQ_ABS_CAP) * buf * 100
r = _run_window(lambda kw, b=buf: _make_dliq(kw, margin_buffer=b),
f'd_liq_buf{buf:.2f}', d, fw, all_idx)
elapsed = time.time() - t0
d_roi = r['roi'] - _D_LIQ_FULL['roi']
marker = ' ← GOLD' if abs(buf - 0.95) < 0.001 else ''
print(f" {buf:>8.2f} {floor_pct:>6.1f}% {r['roi']:>8.2f} {r['dd']:>6.2f} "
f"{r['calmar']:>7.2f} {r['liq_stops']:>10} {d_roi:>+13.2f}pp ({elapsed:.0f}s){marker}")
r['margin_buffer'] = buf
buf_results.append(r)
results_all.append(r)
# Stability check: ROI range across buffers
buf_rois = [r['roi'] for r in buf_results]
roi_range = max(buf_rois) - min(buf_rois)
buf_dds = [r['dd'] for r in buf_results]
dd_range = max(buf_dds) - min(buf_dds)
buf_stable = roi_range < 10.0 and dd_range < 2.0
print(f"\n ROI range across buffers: {roi_range:.2f}pp "
f"DD range: {dd_range:.2f}pp "
f"['STABLE ✓' if buf_stable else 'UNSTABLE ✗']")
# ── SUMMARY ───────────────────────────────────────────────────────────────
total_passes = pass_counts['split'] + pass_counts['quarter']
total_tests = pass_counts['split_total'] + pass_counts['quarter_total']
print(f"\n{'='*80}")
print("OVERFITTING VALIDATION SUMMARY")
print(f"{'='*80}")
print(f" Temporal split (H1/H2): {pass_counts['split']}/{pass_counts['split_total']} {split_verdict}")
print(f" Quarterly split (Q1-Q4): {pass_counts['quarter']}/{pass_counts['quarter_total']} {quarter_verdict}")
print(f" Margin buffer stability: {'STABLE ✓' if buf_stable else 'UNSTABLE ✗'} "
f"(ROI range={roi_range:.1f}pp, DD range={dd_range:.1f}pp)")
print()
all_pass = (total_passes == total_tests and buf_stable)
if all_pass:
print(" VERDICT: ALL TESTS PASS ✓")
print(" D_LIQ_GOLD is robust. Calmar advantage holds across all time windows.")
print(" Margin buffer choice is not critical. Safe to set as DEFAULT.")
else:
print(" VERDICT: SOME TESTS FAIL ✗")
print(f" {total_passes}/{total_tests} split windows passed, "
f"buffer stable={buf_stable}.")
print(" Do NOT flip default until failures are investigated.")
outfile = _HERE / "exp9c_overfitting_results.json"
log_results(results_all, outfile, meta={
"exp": "exp9c",
"question": "Is D_LIQ_GOLD robust across time windows and parameter perturbations?",
"split_passes": f"{pass_counts['split']}/{pass_counts['split_total']}",
"quarter_passes": f"{pass_counts['quarter']}/{pass_counts['quarter_total']}",
"buf_roi_range_pp": round(roi_range, 3),
"buf_dd_range_pp": round(dd_range, 3),
"all_pass": all_pass,
"total_elapsed_s": round(time.time() - t_start, 1),
})
print(f"\nTotal elapsed: {(time.time()-t_start)/60:.1f} min")
print("Done.")
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,277 @@
"""
Shared infrastructure for proxy-B experiments (exp1exp3, fast sweep).
Provides: data loading, run_backtest() with gold-matching metrics, log_results().
Gold baseline (2026-03-14 confirmed):
ROI=+88.55%, PF=1.215, DD=15.05%, Sharpe=4.38, WR=50.5%, Trades=2155
"""
import sys, time, math, json
sys.stdout.reconfigure(encoding='utf-8', errors='replace')
from pathlib import Path
import numpy as np
import pandas as pd
_HERE = Path(__file__).resolve().parent
_ND_ROOT = _HERE.parent
sys.path.insert(0, str(_ND_ROOT))
# ── Lazy JIT warmup (done once per process) ──────────────────────────────────
_jit_done = False
def ensure_jit():
global _jit_done
if _jit_done: return
print("JIT warmup...")
t0 = time.time()
from nautilus_dolphin.nautilus.alpha_asset_selector import compute_irp_nb, compute_ars_nb, rank_assets_irp_nb
from nautilus_dolphin.nautilus.alpha_bet_sizer import compute_sizing_nb
from nautilus_dolphin.nautilus.alpha_signal_generator import check_dc_nb
from nautilus_dolphin.nautilus.ob_features import (
compute_imbalance_nb, compute_depth_1pct_nb, compute_market_agreement_nb,
compute_cascade_signal_nb,
)
_p = np.array([1.0, 2.0, 3.0], dtype=np.float64)
compute_irp_nb(_p, -1); compute_ars_nb(1.0, 0.5, 0.01)
rank_assets_irp_nb(np.ones((10, 2), dtype=np.float64), 8, -1, 5, 500.0, 20, 0.20)
compute_sizing_nb(-0.03,-0.02,-0.05,3.0,0.5,5.0,0.20,True,True,0.0,
np.zeros(4,dtype=np.int64),np.zeros(4,dtype=np.int64),
np.zeros(5,dtype=np.float64),0,-1,0.01,0.04)
check_dc_nb(_p, 3, 1, 0.75)
_b = np.array([100.,200.,300.,400.,500.], dtype=np.float64)
_a = np.array([110.,190.,310.,390.,510.], dtype=np.float64)
compute_imbalance_nb(_b,_a); compute_depth_1pct_nb(_b,_a)
compute_market_agreement_nb(np.array([0.1,-0.05],dtype=np.float64),2)
compute_cascade_signal_nb(np.array([-0.05,-0.15],dtype=np.float64),2,-0.10)
print(f" JIT: {time.time()-t0:.1f}s")
_jit_done = True
# ── Paths ─────────────────────────────────────────────────────────────────────
VBT_DIR = Path(r"C:\Users\Lenovo\Documents\- DOLPHIN NG HD HCM TSF Predict\vbt_cache")
MC_MODELS_DIR= str(Path(r"C:\Users\Lenovo\Documents\- DOLPHIN NG HD HCM TSF Predict\nautilus_dolphin\mc_results\models"))
META_COLS = {'timestamp','scan_number','v50_lambda_max_velocity','v150_lambda_max_velocity',
'v300_lambda_max_velocity','v750_lambda_max_velocity','vel_div',
'instability_50','instability_150'}
ENGINE_KWARGS = dict(
initial_capital=25000.0, vel_div_threshold=-0.02, vel_div_extreme=-0.05,
min_leverage=0.5, max_leverage=5.0, leverage_convexity=3.0,
fraction=0.20, fixed_tp_pct=0.0095, stop_pct=1.0, max_hold_bars=120,
use_direction_confirm=True, dc_lookback_bars=7, dc_min_magnitude_bps=0.75,
dc_skip_contradicts=True, dc_leverage_boost=1.0, dc_leverage_reduce=0.5,
use_asset_selection=True, min_irp_alignment=0.45,
use_sp_fees=True, use_sp_slippage=True,
sp_maker_entry_rate=0.62, sp_maker_exit_rate=0.50,
use_ob_edge=True, ob_edge_bps=5.0, ob_confirm_rate=0.40,
lookback=100, use_alpha_layers=True, use_dynamic_leverage=True, seed=42,
)
MC_BASE_CFG = {
'trial_id':0, 'vel_div_threshold':-0.020, 'vel_div_extreme':-0.050,
'use_direction_confirm':True, 'dc_lookback_bars':7, 'dc_min_magnitude_bps':0.75,
'dc_skip_contradicts':True, 'dc_leverage_boost':1.00, 'dc_leverage_reduce':0.50,
'vd_trend_lookback':10, 'min_leverage':0.50, 'max_leverage':5.00,
'leverage_convexity':3.00, 'fraction':0.20, 'use_alpha_layers':True,
'use_dynamic_leverage':True, 'fixed_tp_pct':0.0095, 'stop_pct':1.00,
'max_hold_bars':120, 'use_sp_fees':True, 'use_sp_slippage':True,
'sp_maker_entry_rate':0.62, 'sp_maker_exit_rate':0.50, 'use_ob_edge':True,
'ob_edge_bps':5.00, 'ob_confirm_rate':0.40, 'ob_imbalance_bias':-0.09,
'ob_depth_scale':1.00, 'use_asset_selection':True, 'min_irp_alignment':0.45,
'lookback':100, 'acb_beta_high':0.80, 'acb_beta_low':0.20, 'acb_w750_threshold_pct':60,
}
GOLD = dict(roi=88.55, pf=1.215, dd=15.05, sharpe=4.38, wr=50.5, trades=2155)
# ── Data loading (cached per process) ────────────────────────────────────────
_data_cache = {}
def load_data():
"""Load gold-standard data: float64 pq_data, correct vol_p60 (2-file, offset-60), 48 OB assets."""
from nautilus_dolphin.nautilus.ob_features import OBFeatureEngine
from nautilus_dolphin.nautilus.ob_provider import MockOBProvider
parquet_files = sorted(VBT_DIR.glob("*.parquet"))
parquet_files = [p for p in parquet_files if 'catalog' not in str(p)]
date_strings = [p.stem for p in parquet_files]
# GOLD vol_p60: 2 files, range(60), seg-based, v>0 filter
all_vols = []
for pf in parquet_files[:2]:
df = pd.read_parquet(pf)
if 'BTCUSDT' in df.columns:
pr = df['BTCUSDT'].values
for i in range(60, len(pr)):
seg = pr[max(0, i-50):i]
if len(seg) < 10: continue
v = float(np.std(np.diff(seg) / seg[:-1]))
if v > 0: all_vols.append(v)
del df
vol_p60 = float(np.percentile(all_vols, 60)) if all_vols else 0.0002
print(f" Calibrated vol_p60 (gold method): {vol_p60:.8f}")
# GOLD pq_data: float64, all assets, dvol per bar
pq_data = {}
all_assets = set()
for pf in parquet_files:
df = pd.read_parquet(pf)
ac = [c for c in df.columns if c not in META_COLS]
all_assets.update(ac)
bp = df['BTCUSDT'].values if 'BTCUSDT' in df.columns else None
dv = np.full(len(df), np.nan)
if bp is not None:
for i in range(50, len(bp)):
seg = bp[max(0, i-50):i]
if len(seg) < 10: continue
dv[i] = float(np.std(np.diff(seg) / seg[:-1]))
pq_data[pf.stem] = (df, ac, dv)
OB_ASSETS = sorted(list(all_assets))
_mock_ob = MockOBProvider(
imbalance_bias=-0.09, depth_scale=1.0, assets=OB_ASSETS,
imbalance_biases={"BTCUSDT": -0.086, "ETHUSDT": -0.092,
"BNBUSDT": +0.05, "SOLUSDT": +0.05},
)
ob_eng = OBFeatureEngine(_mock_ob)
ob_eng.preload_date("mock", OB_ASSETS)
print(f" OB_ASSETS={len(OB_ASSETS)}, vol_p60={vol_p60:.8f}, days={len(parquet_files)}")
return dict(
parquet_files=parquet_files, date_strings=date_strings,
vol_p60=vol_p60, ob_eng=ob_eng, OB_ASSETS=OB_ASSETS,
pq_data=pq_data,
)
def load_forewarner():
try:
from mc.mc_ml import DolphinForewarner
fw = DolphinForewarner(models_dir=MC_MODELS_DIR)
print(" MC-Forewarner loaded (5 models)")
return fw
except Exception as e:
print(f" MC-Forewarner unavailable: {e}")
return None
def run_backtest(engine_factory, name, forewarner=None, extra_kwargs=None):
"""
Run full 55-day backtest with gold-matching metrics (Lazy loading).
"""
import gc
from nautilus_dolphin.nautilus.adaptive_circuit_breaker import AdaptiveCircuitBreaker
d = load_data()
kw = ENGINE_KWARGS.copy()
if extra_kwargs: kw.update(extra_kwargs)
acb = AdaptiveCircuitBreaker()
acb.preload_w750(d['date_strings'])
eng = engine_factory(kw)
eng.set_ob_engine(d['ob_eng'])
eng.set_acb(acb)
if forewarner is not None:
eng.set_mc_forewarner(forewarner, MC_BASE_CFG)
eng.set_esoteric_hazard_multiplier(0.0)
daily_caps, daily_pnls = [], []
all_vols = []
for i, pf in enumerate(d['parquet_files']):
ds = pf.stem
# Lazy Load and cast to float32 to save RAM
df = pd.read_parquet(pf)
for c in df.columns:
if df[c].dtype == 'float64':
df[c] = df[c].astype('float32')
acols = [c for c in df.columns if c not in META_COLS]
# Per-day OB Preloading (Crucial for 230MB RAM)
if eng.ob_engine is not None:
eng.ob_engine.preload_date(ds, d['OB_ASSETS'])
# Optimized 5s dvol approximation
bp = df['BTCUSDT'].values if 'BTCUSDT' in df.columns else None
dvol = np.zeros(len(df), dtype=np.float32)
if bp is not None:
rets = np.diff(bp.astype('float64')) / (bp[:-1].astype('float64') + 1e-9)
for j in range(50, len(rets)):
v = np.std(rets[j-50:j])
dvol[j+1] = v
if v > 0: all_vols.append(v)
cap_before = eng.capital
vp60 = np.percentile(all_vols, 60) if len(all_vols) > 1000 else d['vol_p60']
vol_ok = np.where(dvol > 0, dvol > vp60, False)
eng.process_day(ds, df, acols, vol_regime_ok=vol_ok)
daily_caps.append(eng.capital)
daily_pnls.append(eng.capital - cap_before)
# CLEAR OB CACHE FOR DAY
if eng.ob_engine is not None:
eng.ob_engine._preloaded_placement.clear()
eng.ob_engine._preloaded_signal.clear()
eng.ob_engine._preloaded_market.clear()
eng.ob_engine._ts_to_idx.clear()
del df
gc.collect()
tr = eng.trade_history
n = len(tr)
roi = (eng.capital - 25000.0) / 25000.0 * 100.0
if n == 0:
return dict(name=name, roi=roi, pf=0, dd=0, wr=0, sharpe=0, trades=0)
def _abs(t): return t.pnl_absolute if hasattr(t,'pnl_absolute') else t.pnl_pct*250.
wins = [t for t in tr if _abs(t) > 0]
losses = [t for t in tr if _abs(t) <= 0]
wr = len(wins) / n * 100.0
pf = sum(_abs(t) for t in wins) / max(abs(sum(_abs(t) for t in losses)), 1e-9)
peak_cap, max_dd = 25000.0, 0.0
for cap in daily_caps:
peak_cap = max(peak_cap, cap)
max_dd = max(max_dd, (peak_cap - cap) / peak_cap * 100.0)
dr = np.array([p/25000.*100. for p in daily_pnls])
sharpe = float(dr.mean()/(dr.std()+1e-9)*math.sqrt(365)) if len(dr)>1 else 0.
# Gather any engine-specific extra stats
extra = {}
for attr in ('gate_suppressed','gate_allowed','early_exits','sizing_scale_mean'):
v = getattr(eng, attr, None)
if v is not None: extra[attr] = v
return dict(name=name, roi=roi, pf=pf, dd=max_dd, wr=wr, sharpe=sharpe,
trades=n, **extra)
def print_table(results, gold=None):
hdr = f"{'Config':<42} {'ROI%':>7} {'PF':>6} {'DD%':>6} {'WR%':>6} {'Sharpe':>7} {'Trades':>7}"
print(hdr); print('-'*83)
if gold:
g = gold
print(f"{'*** GOLD ***':<42} {g['roi']:>7.2f} {g['pf']:>6.4f} {g['dd']:>6.2f} "
f"{g['wr']:>6.2f} {g['sharpe']:>7.3f} {g['trades']:>7d}")
print('-'*83)
for r in results:
extra = ''
if 'suppression_rate' in r: extra += f" gate_supp={r['suppression_rate']:.1f}%"
if 'early_exits' in r: extra += f" early_exits={r['early_exits']}"
if 'sizing_scale_mean' in r: extra += f" scale_mean={r['sizing_scale_mean']:.3f}"
print(f"{r['name']:<42} {r['roi']:>7.2f} {r['pf']:>6.4f} {r['dd']:>6.2f} "
f"{r['wr']:>6.2f} {r['sharpe']:>7.3f} {r['trades']:>7d}{extra}")
def log_results(results, outfile, gold=None, meta=None):
payload = {'gold': gold or GOLD, 'results': results}
if meta: payload['meta'] = meta
outfile = Path(outfile)
outfile.parent.mkdir(parents=True, exist_ok=True)
with open(outfile, 'w', encoding='utf-8') as f:
json.dump(payload, f, indent=2)
print(f"\n Logged → {outfile}")

View File

@@ -0,0 +1,259 @@
"""
Shared infrastructure for proxy-B experiments (exp1exp3, fast sweep).
Provides: data loading, run_backtest() with gold-matching metrics, log_results().
Gold baseline (2026-03-14 confirmed):
ROI=+88.55%, PF=1.215, DD=15.05%, Sharpe=4.38, WR=50.5%, Trades=2155
"""
import sys, time, math, json
sys.stdout.reconfigure(encoding='utf-8', errors='replace')
from pathlib import Path
import numpy as np
import pandas as pd
_HERE = Path(__file__).resolve().parent
_ND_ROOT = _HERE.parent
sys.path.insert(0, str(_ND_ROOT))
# ── Lazy JIT warmup (done once per process) ──────────────────────────────────
_jit_done = False
def ensure_jit():
global _jit_done
if _jit_done: return
print("JIT warmup...")
t0 = time.time()
from nautilus_dolphin.nautilus.alpha_asset_selector import compute_irp_nb, compute_ars_nb, rank_assets_irp_nb
from nautilus_dolphin.nautilus.alpha_bet_sizer import compute_sizing_nb
from nautilus_dolphin.nautilus.alpha_signal_generator import check_dc_nb
from nautilus_dolphin.nautilus.ob_features import (
compute_imbalance_nb, compute_depth_1pct_nb, compute_market_agreement_nb,
compute_cascade_signal_nb,
)
_p = np.array([1.0, 2.0, 3.0], dtype=np.float64)
compute_irp_nb(_p, -1); compute_ars_nb(1.0, 0.5, 0.01)
rank_assets_irp_nb(np.ones((10, 2), dtype=np.float64), 8, -1, 5, 500.0, 20, 0.20)
compute_sizing_nb(-0.03,-0.02,-0.05,3.0,0.5,5.0,0.20,True,True,0.0,
np.zeros(4,dtype=np.int64),np.zeros(4,dtype=np.int64),
np.zeros(5,dtype=np.float64),0,-1,0.01,0.04)
check_dc_nb(_p, 3, 1, 0.75)
_b = np.array([100.,200.,300.,400.,500.], dtype=np.float64)
_a = np.array([110.,190.,310.,390.,510.], dtype=np.float64)
compute_imbalance_nb(_b,_a); compute_depth_1pct_nb(_b,_a)
compute_market_agreement_nb(np.array([0.1,-0.05],dtype=np.float64),2)
compute_cascade_signal_nb(np.array([-0.05,-0.15],dtype=np.float64),2,-0.10)
print(f" JIT: {time.time()-t0:.1f}s")
_jit_done = True
# ── Paths ─────────────────────────────────────────────────────────────────────
VBT_DIR = Path(r"C:\Users\Lenovo\Documents\- DOLPHIN NG HD HCM TSF Predict\vbt_cache")
MC_MODELS_DIR= str(Path(r"C:\Users\Lenovo\Documents\- DOLPHIN NG HD HCM TSF Predict\nautilus_dolphin\mc_results\models"))
META_COLS = {'timestamp','scan_number','v50_lambda_max_velocity','v150_lambda_max_velocity',
'v300_lambda_max_velocity','v750_lambda_max_velocity','vel_div',
'instability_50','instability_150'}
ENGINE_KWARGS = dict(
initial_capital=25000.0, vel_div_threshold=-0.02, vel_div_extreme=-0.05,
min_leverage=0.5, max_leverage=5.0, leverage_convexity=3.0,
fraction=0.20, fixed_tp_pct=0.0095, stop_pct=1.0, max_hold_bars=120,
use_direction_confirm=True, dc_lookback_bars=7, dc_min_magnitude_bps=0.75,
dc_skip_contradicts=True, dc_leverage_boost=1.0, dc_leverage_reduce=0.5,
use_asset_selection=True, min_irp_alignment=0.45,
use_sp_fees=True, use_sp_slippage=True,
sp_maker_entry_rate=0.62, sp_maker_exit_rate=0.50,
use_ob_edge=True, ob_edge_bps=5.0, ob_confirm_rate=0.40,
lookback=100, use_alpha_layers=True, use_dynamic_leverage=True, seed=42,
)
MC_BASE_CFG = {
'trial_id':0, 'vel_div_threshold':-0.020, 'vel_div_extreme':-0.050,
'use_direction_confirm':True, 'dc_lookback_bars':7, 'dc_min_magnitude_bps':0.75,
'dc_skip_contradicts':True, 'dc_leverage_boost':1.00, 'dc_leverage_reduce':0.50,
'vd_trend_lookback':10, 'min_leverage':0.50, 'max_leverage':5.00,
'leverage_convexity':3.00, 'fraction':0.20, 'use_alpha_layers':True,
'use_dynamic_leverage':True, 'fixed_tp_pct':0.0095, 'stop_pct':1.00,
'max_hold_bars':120, 'use_sp_fees':True, 'use_sp_slippage':True,
'sp_maker_entry_rate':0.62, 'sp_maker_exit_rate':0.50, 'use_ob_edge':True,
'ob_edge_bps':5.00, 'ob_confirm_rate':0.40, 'ob_imbalance_bias':-0.09,
'ob_depth_scale':1.00, 'use_asset_selection':True, 'min_irp_alignment':0.45,
'lookback':100, 'acb_beta_high':0.80, 'acb_beta_low':0.20, 'acb_w750_threshold_pct':60,
}
GOLD = dict(roi=88.55, pf=1.215, dd=15.05, sharpe=4.38, wr=50.5, trades=2155)
# ── Data loading (cached per process) ────────────────────────────────────────
_data_cache = {}
def load_data():
"""Returns metadata only; actual data loaded lazily in run_backtest."""
from nautilus_dolphin.nautilus.ob_features import OBFeatureEngine
from nautilus_dolphin.nautilus.ob_provider import MockOBProvider
from nautilus_dolphin.nautilus.adaptive_circuit_breaker import AdaptiveCircuitBreaker
parquet_files = sorted(VBT_DIR.glob("*.parquet"))
parquet_files = [p for p in parquet_files if 'catalog' not in str(p)]
date_strings = [p.stem for p in parquet_files]
# Sample a few files to get vol_p60
all_vols = []
for pf in parquet_files[:3]:
tmp = pd.read_parquet(pf)
if 'BTCUSDT' in tmp.columns:
bp = tmp['BTCUSDT'].values
diffs = np.diff(bp) / bp[:-1]
for i in range(50, len(diffs)):
all_vols.append(np.std(diffs[i-50:i]))
del tmp
vol_p60 = float(np.percentile(all_vols, 60)) if all_vols else 0.0002
print(f" Calibrated vol_p60: {vol_p60:.8f}")
OB_ASSETS = ["BTCUSDT", "ETHUSDT"]
_mock_ob = MockOBProvider(
imbalance_bias=-0.09, depth_scale=1.0, assets=OB_ASSETS,
imbalance_biases={"BTCUSDT":-0.086,"ETHUSDT":-0.092,
"BNBUSDT":+0.05, "SOLUSDT":+0.05},
)
ob_eng = OBFeatureEngine(_mock_ob)
# Preload only the core assets for 56 days (memory-safe now)
ob_eng.preload_date("mock", OB_ASSETS)
return dict(
parquet_files=parquet_files, date_strings=date_strings,
vol_p60=vol_p60, ob_eng=ob_eng, OB_ASSETS=OB_ASSETS,
)
def load_forewarner():
try:
from mc.mc_ml import DolphinForewarner
fw = DolphinForewarner(models_dir=MC_MODELS_DIR)
print(" MC-Forewarner loaded (5 models)")
return fw
except Exception as e:
print(f" MC-Forewarner unavailable: {e}")
return None
def run_backtest(engine_factory, name, forewarner=None, extra_kwargs=None):
"""
Run full 55-day backtest with gold-matching metrics (Lazy loading).
"""
import gc
from nautilus_dolphin.nautilus.adaptive_circuit_breaker import AdaptiveCircuitBreaker
d = load_data()
kw = ENGINE_KWARGS.copy()
if extra_kwargs: kw.update(extra_kwargs)
acb = AdaptiveCircuitBreaker()
acb.preload_w750(d['date_strings'])
eng = engine_factory(kw)
eng.set_ob_engine(d['ob_eng'])
eng.set_acb(acb)
if forewarner is not None:
eng.set_mc_forewarner(forewarner, MC_BASE_CFG)
eng.set_esoteric_hazard_multiplier(0.0)
daily_caps, daily_pnls = [], []
all_vols = []
for i, pf in enumerate(d['parquet_files']):
ds = pf.stem
# Lazy Load and cast to float32 to save RAM
df = pd.read_parquet(pf)
for c in df.columns:
if df[c].dtype == 'float64':
df[c] = df[c].astype('float32')
acols = [c for c in df.columns if c not in META_COLS]
# Per-day OB Preloading (Crucial for 230MB RAM)
if eng.ob_engine is not None:
eng.ob_engine.preload_date(ds, d['OB_ASSETS'])
# Optimized 5s dvol approximation
bp = df['BTCUSDT'].values if 'BTCUSDT' in df.columns else None
dvol = np.zeros(len(df), dtype=np.float32)
if bp is not None:
rets = np.diff(bp.astype('float64')) / (bp[:-1].astype('float64') + 1e-9)
for j in range(50, len(rets)):
v = np.std(rets[j-50:j])
dvol[j+1] = v
if v > 0: all_vols.append(v)
cap_before = eng.capital
vp60 = np.percentile(all_vols, 60) if len(all_vols) > 1000 else d['vol_p60']
vol_ok = np.where(dvol > 0, dvol > vp60, False)
eng.process_day(ds, df, acols, vol_regime_ok=vol_ok)
daily_caps.append(eng.capital)
daily_pnls.append(eng.capital - cap_before)
# CLEAR OB CACHE FOR DAY
if eng.ob_engine is not None:
eng.ob_engine._preloaded_placement.clear()
eng.ob_engine._preloaded_signal.clear()
eng.ob_engine._preloaded_market.clear()
eng.ob_engine._ts_to_idx.clear()
del df
gc.collect()
tr = eng.trade_history
n = len(tr)
roi = (eng.capital - 25000.0) / 25000.0 * 100.0
if n == 0:
return dict(name=name, roi=roi, pf=0, dd=0, wr=0, sharpe=0, trades=0)
def _abs(t): return t.pnl_absolute if hasattr(t,'pnl_absolute') else t.pnl_pct*250.
wins = [t for t in tr if _abs(t) > 0]
losses = [t for t in tr if _abs(t) <= 0]
wr = len(wins) / n * 100.0
pf = sum(_abs(t) for t in wins) / max(abs(sum(_abs(t) for t in losses)), 1e-9)
peak_cap, max_dd = 25000.0, 0.0
for cap in daily_caps:
peak_cap = max(peak_cap, cap)
max_dd = max(max_dd, (peak_cap - cap) / peak_cap * 100.0)
dr = np.array([p/25000.*100. for p in daily_pnls])
sharpe = float(dr.mean()/(dr.std()+1e-9)*math.sqrt(365)) if len(dr)>1 else 0.
# Gather any engine-specific extra stats
extra = {}
for attr in ('gate_suppressed','gate_allowed','early_exits','sizing_scale_mean'):
v = getattr(eng, attr, None)
if v is not None: extra[attr] = v
return dict(name=name, roi=roi, pf=pf, dd=max_dd, wr=wr, sharpe=sharpe,
trades=n, **extra)
def print_table(results, gold=None):
hdr = f"{'Config':<42} {'ROI%':>7} {'PF':>6} {'DD%':>6} {'WR%':>6} {'Sharpe':>7} {'Trades':>7}"
print(hdr); print('-'*83)
if gold:
g = gold
print(f"{'*** GOLD ***':<42} {g['roi']:>7.2f} {g['pf']:>6.4f} {g['dd']:>6.2f} "
f"{g['wr']:>6.2f} {g['sharpe']:>7.3f} {g['trades']:>7d}")
print('-'*83)
for r in results:
extra = ''
if 'suppression_rate' in r: extra += f" gate_supp={r['suppression_rate']:.1f}%"
if 'early_exits' in r: extra += f" early_exits={r['early_exits']}"
if 'sizing_scale_mean' in r: extra += f" scale_mean={r['sizing_scale_mean']:.3f}"
print(f"{r['name']:<42} {r['roi']:>7.2f} {r['pf']:>6.4f} {r['dd']:>6.2f} "
f"{r['wr']:>6.2f} {r['sharpe']:>7.3f} {r['trades']:>7d}{extra}")
def log_results(results, outfile, gold=None, meta=None):
payload = {'gold': gold or GOLD, 'results': results}
if meta: payload['meta'] = meta
outfile = Path(outfile)
outfile.parent.mkdir(parents=True, exist_ok=True)
with open(outfile, 'w', encoding='utf-8') as f:
json.dump(payload, f, indent=2)
print(f"\n Logged → {outfile}")

View File

@@ -0,0 +1,351 @@
"""
flint_dvae_kernel.py
Implementation of a SILOQY-compatible Temporal Disentangled VAE that leverages
the 550-bit arbitrary precision of FLINT via TailPreservingEDAIN_KL.
This script extracts Proxy A, B, C, D, and E from the T1 corpus,
normalizes them using the exact 550-bit TailPreservingEDAIN_KL,
and models the temporal dynamics to predict eigenspace stress precursors.
"""
import sys, os
import numpy as np
from pathlib import Path
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import roc_auc_score
# Ensure the project root is in the path
HERE = Path(__file__).parent
PROJECT_ROOT = r"C:\Users\Lenovo\Documents\- DOLPHIN NG HD HCM TSF Predict"
if PROJECT_ROOT not in sys.path:
sys.path.insert(0, PROJECT_ROOT)
# Import the 550-bit kernel components
from SILOQY_NN_Kernel_COMPLETE6 import MCDAINLayer, FlintTensor, arb_mat, FLINT_AVAILABLE, with_precision, arb, safe_float
from nautilus_dolphin.dvae.corpus_builder import DolphinCorpus, OFF, T1 as T1_DIM
# --- 1. D-VAE Implementation ---
class SiloqyTemporalDVAE:
"""
Temporal Disentangled VAE adapted for the SILOQY framework.
Uses MCDAINLayer internally for robust normalization of highly kurtotic features.
"""
def __init__(self, input_dim=5, hidden_dim=64, latent_dim=8, regime_dim=4, beta=1.0, seq_len=10, precision_bits=550):
self.input_dim = input_dim
self.hidden_dim = hidden_dim
self.latent_dim = latent_dim
self.regime_dim = regime_dim
self.noise_dim = latent_dim - regime_dim
self.beta = beta
self.seq_len = seq_len
self.precision_bits = precision_bits
self.is_trained = False
self._init_weights()
# Instantiate the 550-bit precise normalizer
print(f"Initializing 550-bit MCDAINLayer for dimension {input_dim}...")
self.edain = MCDAINLayer(
input_dim=input_dim,
precision_bits=precision_bits,
use_ball_arithmetic=True
)
def _init_weights(self):
rng = np.random.RandomState(42)
scale = 0.1
self.W_ih = rng.randn(self.input_dim, self.hidden_dim * 4) * scale
self.W_hh = rng.randn(self.hidden_dim, self.hidden_dim * 4) * scale
self.b_h = np.zeros(self.hidden_dim * 4)
self.W_mu = rng.randn(self.hidden_dim, self.latent_dim) * scale
self.W_logvar = rng.randn(self.hidden_dim, self.latent_dim) * scale
self.b_mu = np.zeros(self.latent_dim)
self.b_logvar = np.zeros(self.latent_dim)
self.W_dec = rng.randn(self.latent_dim, self.hidden_dim) * scale
self.W_out = rng.randn(self.hidden_dim, self.input_dim) * scale
self.b_dec = np.zeros(self.hidden_dim)
self.b_out = np.zeros(self.input_dim)
self.regime_centroid = None
self.regime_threshold = None
def _sigmoid(self, x):
return 1.0 / (1.0 + np.exp(-np.clip(x, -500, 500)))
def _lstm_step(self, x, h, c):
gates = x @ self.W_ih + h @ self.W_hh + self.b_h
i, f, g, o = np.split(gates, 4, axis=-1)
i, f, o = self._sigmoid(i), self._sigmoid(f), self._sigmoid(o)
g = np.tanh(g)
c_new = f * c + i * g
h_new = o * np.tanh(c_new)
return h_new, c_new
def _encode_sequence(self, X_seq):
batch = X_seq.shape[0]
h = np.zeros((batch, self.hidden_dim))
c = np.zeros((batch, self.hidden_dim))
for t in range(X_seq.shape[1]):
h, c = self._lstm_step(X_seq[:, t, :], h, c)
mu = h @ self.W_mu + self.b_mu
logvar = h @ self.W_logvar + self.b_logvar
return mu, logvar
def _reparameterize(self, mu, logvar, rng=None):
if rng is None:
rng = np.random.RandomState()
std = np.exp(0.5 * logvar)
eps = rng.randn(*mu.shape)
return mu + eps * std
def _decode(self, z):
h = np.tanh(z @ self.W_dec + self.b_dec)
return h @ self.W_out + self.b_out
def _tc_decomposition(self, mu, logvar):
batch = mu.shape[0]
kl_dim = 0.5 * np.sum(mu**2 + np.exp(logvar) - logvar - 1, axis=1)
var = np.exp(logvar)
cov = np.zeros((self.latent_dim, self.latent_dim))
for i in range(batch):
cov += np.diag(var[i])
cov /= batch
cov += np.outer(mu.mean(0), mu.mean(0))
det = np.linalg.det(cov + 1e-6 * np.eye(self.latent_dim))
tc = 0.5 * (np.sum(np.log(np.diag(cov) + 1e-6)) - np.log(det + 1e-6))
return np.mean(kl_dim), max(0, tc)
def _loss(self, X_seq, X_target, mu, logvar, z):
recon = self._decode(z)
recon_loss = np.mean((recon - X_target) ** 2)
kl_dim, tc = self._tc_decomposition(mu, logvar)
total = recon_loss + kl_dim + self.beta * tc
return total, recon_loss, kl_dim, tc
def _build_sequences(self, X):
n = len(X) - self.seq_len
if n <= 0:
return None, None
seqs = np.array([X[i:i+self.seq_len] for i in range(n)])
targets = X[self.seq_len:]
return seqs, targets
def _convert_arb_to_float(self, matrix_arb) -> np.ndarray:
"""Safely convert FLINT arb_mat to float64 numpy array."""
rows, cols = matrix_arb.nrows(), matrix_arb.ncols()
out = np.zeros((rows, cols), dtype=np.float64)
for i in range(rows):
for j in range(cols):
out[i, j] = safe_float(matrix_arb[i, j])
return out
def _normalize_native(self, X):
"""Native FLINT arbitrary precision MCDAIN logic (bypassing PyTorch)."""
rows, cols = X.shape
X_norm = np.zeros_like(X, dtype=np.float64)
with with_precision(self.precision_bits):
for j in range(cols):
# Calculate vector magnitude for this proxy
sum_sq = arb(0)
for i in range(rows):
sum_sq += arb(str(X[i, j])) ** 2
magnitude = sum_sq.sqrt()
# MCDAIN Analytical params (activation='log')
log_mag = magnitude.log()
mean = magnitude * arb("0.1")
scale = arb("1.0") / (log_mag + arb("1e-8"))
gate = arb("1.0") / (arb("1.0") + (-log_mag).exp())
# Apply normalization
for i in range(rows):
val = arb(str(X[i, j]))
val_centered = val - mean
val_scaled = val_centered * scale
val_gated = val_scaled * gate
X_norm[i, j] = safe_float(val_gated)
# Replace remaining NaNs from float conversion limit if any
X_norm = np.nan_to_num(X_norm, nan=0.0, posinf=5.0, neginf=-5.0)
return X_norm
def fit(self, X, epochs=10, lr=0.001, batch_size=64, verbose=True):
print(f"Normalizing input (shape {X.shape}) natively with MCDAIN Analytical Math (550-bit precision)...")
# 1. Normalize with 550-bit MCDAIN Logic bypassing PyTorch
X_norm = self._normalize_native(X)
# 2. Sequence building
seqs, targets = self._build_sequences(X_norm)
if seqs is None:
return self
n = len(seqs)
rng = np.random.RandomState(42)
losses = []
print(f"Training Temporal D-VAE on normalized sequence space (n={n})...")
for epoch in range(epochs):
idx = rng.permutation(n)
epoch_loss = 0
for start in range(0, n, batch_size):
batch_idx = idx[start:start+batch_size]
X_seq = seqs[batch_idx]
X_tgt = targets[batch_idx]
mu, logvar = self._encode_sequence(X_seq)
z = self._reparameterize(mu, logvar, rng)
loss, rl, kl, tc = self._loss(X_seq, X_tgt, mu, logvar, z)
epoch_loss += loss * len(batch_idx)
grad_scale = lr * 0.1
noise = rng.randn(*self.W_mu.shape) * grad_scale
self.W_mu -= noise * (loss - 1.0)
self.W_logvar -= rng.randn(*self.W_logvar.shape) * grad_scale * (loss - 1.0)
self.W_dec -= rng.randn(*self.W_dec.shape) * grad_scale * rl
self.W_out -= rng.randn(*self.W_out.shape) * grad_scale * rl
epoch_loss /= n
losses.append(epoch_loss)
if verbose and epoch % 2 == 0:
print(f" Epoch {epoch}/{epochs}: loss={epoch_loss:.4f}")
# Finalize
mu_all, _ = self._encode_sequence(seqs)
z_regime = mu_all[:, :self.regime_dim]
self.regime_centroid = z_regime.mean(axis=0)
dists = np.linalg.norm(z_regime - self.regime_centroid, axis=1)
self.regime_threshold = np.mean(dists) + 2.0 * np.std(dists)
self.is_trained = True
return self
def encode(self, X):
if not self.is_trained:
return None, None
X_norm = self._normalize_native(X)
seqs, _ = self._build_sequences(X_norm)
if seqs is None:
return None, None
mu, logvar = self._encode_sequence(seqs)
z_regime = mu[:, :self.regime_dim]
z_noise = mu[:, self.regime_dim:]
return z_regime, z_noise
# --- 2. Main Execution Script ---
def run_analysis():
if not FLINT_AVAILABLE:
print("CRITICAL ERROR: FLINT library is required but not loaded.")
sys.exit(1)
print("Loading corpus...", flush=True)
corpus_path = str(HERE / 'corpus_cache.npz')
if not os.path.exists(corpus_path):
print(f"Corpus not found at {corpus_path}. Make sure to build it.")
sys.exit(1)
corpus = DolphinCorpus.load(corpus_path)
idx = corpus.mask[:, 1] # T1 availability
X_e = corpus.X[idx]
t1 = X_e[:, OFF[1]:OFF[1]+T1_DIM].copy()
# Feature extraction
vel_w50 = t1[:, 1]
vel_w300 = t1[:, 11]
vel_w750 = t1[:, 16]
inst_w50 = t1[:, 3]
inst_w300= t1[:, 13]
gap_w50 = t1[:, 2]
print("\n--- Generating Proxies ---")
proxy_A = -0.674*vel_w750 - 0.357*vel_w300 + 0.421*inst_w50
proxy_B = inst_w50 - vel_w750
proxy_C = vel_w50 - vel_w750
proxy_D = inst_w50 * (-vel_w750)
proxy_E = (inst_w50 - inst_w300) - (vel_w50 - vel_w750)
# Stack proxies into single float array for EDAIN
X_proxies = np.column_stack([proxy_A, proxy_B, proxy_C, proxy_D, proxy_E])
print(f"Proxies Stacked. Shape: {X_proxies.shape}")
for i, name in enumerate(['Proxy A', 'Proxy B', 'Proxy C (kurt=3798)', 'Proxy D', 'Proxy E']):
p = X_proxies[:, i]
kurt = float(((p - p.mean())**4).mean() / (p.std()**4 + 1e-8))
print(f" {name}: skew={float(((p - p.mean())**3).mean() / (p.std()**3 + 1e-8)):.2f}, kurt={kurt:.2f}")
# Initialize SILOQY D-VAE
dvae = SiloqyTemporalDVAE(input_dim=5, hidden_dim=32, latent_dim=8, regime_dim=4, beta=1.0, seq_len=10, precision_bits=550)
print("\n--- Fitting SILOQY D-VAE ---")
# Subsample data to make 550-bit EDAIN training tractable for testing
N_sub = min(2000, len(X_proxies))
sub_idx = np.arange(N_sub)
X_sub = X_proxies[sub_idx]
# Fit the normalizer and temporal D-VAE
dvae.fit(X_sub, epochs=8, batch_size=64, verbose=True)
print("\n--- Evaluating Predictive Power ---")
# Build Precursor Labels
# Goal: Did eigenspace stress follow within N scans?
# We define "stress" as gap_ratio collapse AND instability spike in the FUTURE (T + 2 to T + 12 scans)
# Since we use 10-step sequences, the output of sequence ending at time T corresponds to T.
seqs_len = len(X_sub) - dvae.seq_len
future_horizon = 10
# Labels for the end of sequence `T` looking forward to `T + future_horizon`
labels = np.zeros(seqs_len)
for idx_seq in range(seqs_len):
cur_t = idx_seq + dvae.seq_len
if cur_t + future_horizon >= len(X_sub):
continue
future_inst = inst_w50[cur_t:cur_t+future_horizon]
future_gap = gap_w50[cur_t:cur_t+future_horizon]
# Stress condition
inst_spike = np.any(future_inst > 0.40)
gap_collapse = np.any(future_gap < 0.60)
if inst_spike and gap_collapse:
labels[idx_seq] = 1.0
print(f"Precursor Labels Generated. Positive Class: {labels.mean()*100:.1f}%")
# Encode full validation space
z_regime, z_noise = dvae.encode(X_sub)
if z_regime is not None:
# Align labels
valid_idx = np.arange(len(labels))
valid_z = z_regime[valid_idx]
valid_y = labels[valid_idx]
# Regress
model = LogisticRegression(class_weight='balanced')
model.fit(valid_z, valid_y)
preds = model.predict_proba(valid_z)[:, 1]
auc = roc_auc_score(valid_y, preds)
print("\n--- RESULTS ---")
print(f"Logistic Regression AUC predicting future stress: {auc:.4f}")
print("Coefficients on Latent `z_regime` factors:")
for dim, coef in enumerate(model.coef_[0]):
print(f" z_regime[{dim}]: {coef:+.4f}")
# Correlate transformed proxies with target using normalized arb floats
print("\nDirect predictivity of 550-bit Normalized Proxies:")
X_norm_float = dvae._normalize_native(X_sub)
for k, proxy_name in enumerate(['Proxy A', 'Proxy B', 'Proxy C', 'Proxy D', 'Proxy E']):
px = X_norm_float[dvae.seq_len:, k]
mask = ~np.isnan(px) & ~np.isnan(labels)
if mask.sum() > 0:
corr = np.corrcoef(px[mask], labels[mask])[0, 1]
print(f" {proxy_name}: r = {corr:+.4f}")
else:
print("Failed to encode latent variables.")
if __name__ == '__main__':
run_analysis()

View File

@@ -0,0 +1,274 @@
"""
flint_hd_vae.py
===============
SILOQY-compatible HD-VAE with inverse projection decoder.
Architecture:
Encoder:
T1 (20-dim)
→ MCDAIN 550-bit normalisation (no upstream modification — read-only call)
→ HD random projection W_enc (20×512), ReLU → h (512)
→ Linear bottleneck: W_mu (512×8), W_lv (512×8) → mu, logvar (8)
→ reparameterisation → z (8)
Decoder (inverse projection — THE NEW PIECE):
z (8)
→ Linear W_dec (8×512), ReLU → h_hat (512) *inverse of bottleneck*
→ Linear W_out (512×20) → T1_hat (20) *pseudo-inverse of HD proj*
Loss:
recon = MSE(T1_hat, T1_norm)
KL = -0.5 * sum(1 + logvar - mu^2 - exp(logvar)) [standard VAE KL]
total = recon + beta * KL
No upstream files are modified. All SILOQY calls are read-only.
"""
import sys, os
sys.stdout.reconfigure(encoding='utf-8', errors='replace')
sys.path.insert(0, os.path.dirname(__file__))
sys.path.insert(0, r"C:\Users\Lenovo\Documents\- DOLPHIN NG HD HCM TSF Predict")
import numpy as np
from pathlib import Path
from SILOQY_NN_Kernel_COMPLETE6 import arb, safe_float, FLINT_AVAILABLE, with_precision
EPS = 1e-8
# ── MCDAIN 550-bit normalisation (read-only logic, no upstream changes) ────
def mcdain_550bit(X_raw: np.ndarray) -> np.ndarray:
"""Apply MCDAIN analytical normalisation at 550-bit precision."""
rows, cols = X_raw.shape
X_norm = np.zeros_like(X_raw, dtype=np.float64)
with with_precision(550):
for j in range(cols):
col = X_raw[:, j]
col_abs = np.abs(col[np.isfinite(col)])
if len(col_abs) == 0 or col_abs.mean() < 1e-12:
continue
magnitude = arb(str(float(col_abs.mean())))
log_mag = magnitude.log()
mean_val = magnitude * arb("0.1")
scale_val = arb("1.0") / (log_mag + arb("1e-8"))
gate_val = arb("1.0") / (arb("1.0") + (-log_mag).exp())
m = safe_float(mean_val)
s = safe_float(scale_val)
g = safe_float(gate_val)
X_norm[:, j] = np.clip((X_raw[:, j] - m) * s * g, -10, 10)
return np.nan_to_num(X_norm, nan=0.0, posinf=5.0, neginf=-5.0)
# ── Adam optimiser state ───────────────────────────────────────────────────
class AdamParam:
def __init__(self, shape, seed=0):
rng = np.random.RandomState(seed)
scale = np.sqrt(2.0 / shape[0])
self.W = rng.randn(*shape).astype(np.float64) * scale
self.m = np.zeros_like(self.W)
self.v = np.zeros_like(self.W)
self.t = 0
def step(self, grad, lr=1e-3, b1=0.9, b2=0.999):
self.t += 1
self.m = b1 * self.m + (1 - b1) * grad
self.v = b2 * self.v + (1 - b2) * grad**2
m_hat = self.m / (1 - b1**self.t)
v_hat = self.v / (1 - b2**self.t)
self.W -= lr * m_hat / (np.sqrt(v_hat) + EPS)
# ── FlintHDVAE ────────────────────────────────────────────────────────────
class FlintHDVAE:
"""
HD-VAE with 550-bit MCDAIN encoder normalisation.
Inverse projection decoder: z(8) → Linear+ReLU(512) → Linear(20).
"""
def __init__(self, input_dim=20, hd_dim=512, latent_dim=8,
beta=0.5, seed=42, use_flint_norm=True):
self.input_dim = input_dim
self.hd_dim = hd_dim
self.latent_dim = latent_dim
self.beta = beta
self.use_flint = use_flint_norm and FLINT_AVAILABLE
rng = np.random.RandomState(seed)
# Fixed random HD projection (encoder side, non-trainable)
self.W_hd = rng.randn(input_dim, hd_dim).astype(np.float64) * np.sqrt(2.0/input_dim)
# Trainable parameters — encoder bottleneck
self.P_mu = AdamParam((hd_dim, latent_dim), seed=seed+1)
self.P_lv = AdamParam((hd_dim, latent_dim), seed=seed+2)
# Trainable parameters — DECODER (inverse projection, THE NEW PIECE)
self.P_dec = AdamParam((latent_dim, hd_dim), seed=seed+3) # z→h_hat
self.P_out = AdamParam((hd_dim, input_dim), seed=seed+4) # h_hat→T1_hat
# Normaliser stats (fitted once)
self._norm_fitted = False
self._norm_mu = np.zeros(input_dim)
self._norm_sd = np.ones(input_dim)
self.train_losses = []
# ── Normalisation ──────────────────────────────────────────────────────
def fit_normaliser(self, X: np.ndarray):
"""Fit normaliser stats from the FULL training set (called once).
For MCDAIN: computes global per-column m/s/g and stores them so that
all subsequent _normalise() calls are deterministic (no batch-dependency).
Falls back to z-score if FLINT unavailable."""
self._norm_mu = X.mean(0)
self._norm_sd = X.std(0) + EPS
if self.use_flint:
# Compute MCDAIN params column-wise on full X, store as fixed stats
X_norm_full = mcdain_550bit(X)
# Store the effective per-column shift/scale as z-score of the MCDAIN output
self._mcdain_mu = X_norm_full.mean(0)
self._mcdain_sd = X_norm_full.std(0) + EPS
# Also store the raw MCDAIN params by fitting a passthrough
self._mcdain_fitted = True
self._X_norm_ref = X_norm_full # kept for diagnostics only (not used in loops)
self._norm_fitted = True
def _normalise(self, X: np.ndarray) -> np.ndarray:
if self.use_flint and self._norm_fitted and hasattr(self, '_mcdain_fitted'):
# Apply MCDAIN then standardise using TRAINING statistics
# This makes normalisation deterministic regardless of batch size
raw = mcdain_550bit(X)
return (raw - self._mcdain_mu) / self._mcdain_sd
return (X - self._norm_mu) / self._norm_sd
# ── Forward pass ──────────────────────────────────────────────────────
def _encode(self, X_norm, rng):
"""X_norm (B,20) → h (B,512) → mu,logvar (B,8) → z (B,8)"""
h = np.maximum(0, X_norm @ self.W_hd) # (B, 512) ReLU
mu = h @ self.P_mu.W # (B, 8)
lv = np.clip(h @ self.P_lv.W, -4, 4) # (B, 8)
eps = rng.randn(*mu.shape)
z = mu + np.exp(0.5 * lv) * eps # reparam
return h, mu, lv, z
def _decode(self, z):
"""z (B,8) → h_hat (B,512) → T1_hat (B,20) — INVERSE PROJECTION"""
h_hat = np.maximum(0, z @ self.P_dec.W) # (B, 512) ReLU
T1_hat = h_hat @ self.P_out.W # (B, 20) linear
return h_hat, T1_hat
# ── Loss ──────────────────────────────────────────────────────────────
def _loss(self, T1_norm, T1_hat, mu, lv):
B = len(T1_norm)
recon = np.mean((T1_hat - T1_norm)**2)
kl = -0.5 * np.mean(1 + lv - mu**2 - np.exp(lv))
total = recon + self.beta * kl
return total, recon, kl
# ── Backward (analytical gradients) ───────────────────────────────────
def _backward(self, T1_norm, T1_hat, h, h_hat, mu, lv, z, lr):
B = len(T1_norm)
# ── Decoder gradients ────────────────────────────────────────────
# dL/dT1_hat = 2*(T1_hat - T1_norm) / (B*D)
dT1 = 2.0 * (T1_hat - T1_norm) / (B * self.input_dim)
# W_out: h_hat.T @ dT1
dW_out = h_hat.T @ dT1 # (512, 20)
self.P_out.step(dW_out, lr)
# Back through ReLU of h_hat
dh_hat = (dT1 @ self.P_out.W.T) * (h_hat > 0) # (B, 512)
# W_dec: z.T @ dh_hat
dW_dec = z.T @ dh_hat # (8, 512)
self.P_dec.step(dW_dec, lr)
# dz from decoder
dz_dec = dh_hat @ self.P_dec.W.T # (B, 8)
# ── KL gradients (standard VAE) ──────────────────────────────────
# dKL/dmu = mu/B; dKL/dlv = 0.5*(exp(lv)-1)/B
dmu_kl = self.beta * mu / B
dlv_kl = self.beta * 0.5 * (np.exp(lv) - 1) / B
# ── Reparameterisation: dz flows back to mu and lv ───────────────
# z = mu + exp(0.5*lv)*eps → dmu = dz, dlv = dz*0.5*z (approx)
dmu = dz_dec + dmu_kl
dlv = dz_dec * 0.5 * (z - mu) + dlv_kl # chain rule
# ── Encoder bottleneck gradients ─────────────────────────────────
dW_mu = h.T @ dmu # (512, 8)
dW_lv = h.T @ dlv
self.P_mu.step(dW_mu, lr)
self.P_lv.step(dW_lv, lr)
# (W_hd is fixed, no gradient needed for it)
# ── Training ──────────────────────────────────────────────────────────
def fit(self, X: np.ndarray, epochs=30, lr=1e-3,
batch_size=256, verbose=True, warmup_frac=0.3):
"""
warmup_frac: fraction of epochs over which beta ramps 0 → self.beta.
Prevents KL from dominating before the decoder learns to reconstruct.
"""
rng = np.random.RandomState(42)
self.fit_normaliser(X) # computes global MCDAIN stats once
X_norm = self._normalise(X) # normalise full dataset once; stable across batches
N = len(X_norm)
target_beta = self.beta
warmup_epochs = max(1, int(epochs * warmup_frac))
for epoch in range(1, epochs + 1):
# KL warmup: ramp beta from 0 to target over first warmup_epochs
if epoch <= warmup_epochs:
self.beta = target_beta * (epoch / warmup_epochs)
else:
self.beta = target_beta
idx = rng.permutation(N)
ep_loss = ep_recon = ep_kl = 0.0
n_batches = 0
for start in range(0, N, batch_size):
bi = idx[start:start + batch_size]
Xb = X_norm[bi] # already normalised with global stats
h, mu, lv, z = self._encode(Xb, rng)
h_hat, T1_hat = self._decode(z)
loss, recon, kl = self._loss(Xb, T1_hat, mu, lv)
self._backward(Xb, T1_hat, h, h_hat, mu, lv, z, lr)
ep_loss += loss; ep_recon += recon; ep_kl += kl
n_batches += 1
ep_loss /= n_batches; ep_recon /= n_batches; ep_kl /= n_batches
self.train_losses.append(ep_loss)
if verbose and (epoch % 5 == 0 or epoch == 1):
# Anti-collapse diagnostic: encode a fixed held-out sample
sample_norm = X_norm[:min(1000, N)]
_, mu_s, _, _ = self._encode(sample_norm, rng)
var_per_dim = mu_s.var(0)
print(f" ep{epoch:3d}/{epochs} beta={self.beta:.3f} "
f"loss={ep_loss:.4f} recon={ep_recon:.4f} kl={ep_kl:.4f} "
f"z_var=[{' '.join(f'{v:.3f}' for v in var_per_dim)}]")
self.beta = target_beta # restore after training
return self
# ── Encode for downstream use ─────────────────────────────────────────
def encode(self, X: np.ndarray) -> np.ndarray:
"""Return deterministic mu (B, latent_dim) for all samples.
Normalisation is deterministic (global MCDAIN stats from fit_normaliser)."""
rng = np.random.RandomState(0)
STEP = 512
mus = []
for s in range(0, len(X), STEP):
Xb = self._normalise(X[s:s+STEP])
_, mu, _, _ = self._encode(Xb, rng)
mus.append(mu)
return np.concatenate(mus)
def reconstruct(self, X: np.ndarray) -> np.ndarray:
"""Returns (T1_hat, X_norm) both in the same normalised space.
Normalisation is deterministic (global MCDAIN stats from fit_normaliser)."""
rng = np.random.RandomState(0)
Xn = self._normalise(X)
STEP = 512
hats = []
for s in range(0, len(Xn), STEP):
_, mu, _, _ = self._encode(Xn[s:s+STEP], rng)
_, T1_hat = self._decode(mu)
hats.append(T1_hat)
return np.concatenate(hats), Xn

View File

@@ -0,0 +1,225 @@
"""
SILOQY 550-bit Precursor Sweep — NO MODIFICATIONS TO UPSTREAM CODE.
Runs on full 16K eigen corpus, tests multiple:
- Precursor label thresholds (rare extreme events)
- Horizons (K=5, 10, 20, 50 scans ahead)
- ML approaches: Logistic, Ridge, k-NN, threshold-only baseline
Reports AUC, Precision@TopDecile, and direct proxy predictivity.
"""
import sys, os
sys.stdout.reconfigure(encoding='utf-8', errors='replace')
sys.path.insert(0, os.path.dirname(__file__))
sys.path.insert(0, r"C:\Users\Lenovo\Documents\- DOLPHIN NG HD HCM TSF Predict")
import numpy as np
from pathlib import Path
from sklearn.linear_model import LogisticRegression, Ridge
from sklearn.neighbors import KNeighborsClassifier
from sklearn.metrics import roc_auc_score, average_precision_score
from sklearn.model_selection import TimeSeriesSplit
HERE = Path(__file__).parent
# ── Load corpus ────────────────────────────────────────────────────────────
print("Loading corpus (16K eigen samples)...")
from corpus_builder import DolphinCorpus, OFF, T1 as T1_DIM
corpus = DolphinCorpus.load(str(HERE / 'corpus_cache.npz'))
idx_mask = corpus.mask[:, 1]
X_e = corpus.X[idx_mask]
t1 = X_e[:, OFF[1]:OFF[1]+T1_DIM].copy()
N = len(t1)
print(f"N={N} samples")
# ── Feature extraction ─────────────────────────────────────────────────────
vel_w50 = t1[:, 1]
vel_w150 = t1[:, 6]
vel_w300 = t1[:, 11]
vel_w750 = t1[:, 16]
inst_w50 = t1[:, 3]
inst_w150= t1[:, 8]
inst_w300= t1[:, 13]
gap_w50 = t1[:, 2]
gap_w300 = t1[:, 12]
lmax_w50 = t1[:, 0]
proxy_A = -0.674*vel_w750 - 0.357*vel_w300 + 0.421*inst_w50
proxy_B = inst_w50 - vel_w750
proxy_C = vel_w50 - vel_w750
proxy_D = inst_w50 * (-vel_w750)
proxy_E = (inst_w50 - inst_w300) - (vel_w50 - vel_w750)
X_proxies = np.column_stack([proxy_A, proxy_B, proxy_C, proxy_D, proxy_E])
proxy_names = ['A(linear)', 'B(inst-vel750)', 'C(vel50-vel750,k=3798)', 'D(inst*-vel750)', 'E(dinst-dvel)']
# ── 550-bit MCDAIN normalization (from flint_dvae_kernel.py, read-only) ────
print("\nApplying 550-bit MCDAIN normalization to proxies...")
from SILOQY_NN_Kernel_COMPLETE6 import arb, safe_float, FLINT_AVAILABLE, with_precision
def mcdain_550bit(X_raw):
"""Read-only implementation of MCDAIN analytical logic at 550-bit."""
rows, cols = X_raw.shape
X_norm = np.zeros_like(X_raw, dtype=np.float64)
with with_precision(550):
for j in range(cols):
col = X_raw[:, j]
col_abs = np.abs(col[np.isfinite(col)])
if len(col_abs) == 0 or col_abs.mean() < 1e-12:
continue
magnitude = arb(str(float(col_abs.mean())))
log_mag = magnitude.log()
mean_val = magnitude * arb("0.1")
scale_val = arb("1.0") / (log_mag + arb("1e-8"))
gate_val = arb("1.0") / (arb("1.0") + (-log_mag).exp())
m = safe_float(mean_val)
s = safe_float(scale_val)
g = safe_float(gate_val)
X_norm[:, j] = np.clip((X_raw[:, j] - m) * s * g, -10, 10)
X_norm = np.nan_to_num(X_norm, nan=0.0, posinf=5.0, neginf=-5.0)
return X_norm
X_norm = mcdain_550bit(X_proxies)
print(f" Normalized. std per proxy: {X_norm.std(0).round(4)}")
print(f" Kurtosis after normalization: {[round(float(((X_norm[:,j]-X_norm[:,j].mean())**4).mean()/(X_norm[:,j].std()**4+1e-8)),2) for j in range(5)]}")
# ── Build precursor labels at multiple thresholds and horizons ─────────────
print("\n" + "="*65)
print("PRECURSOR LABEL SWEEP")
print("="*65)
# inst_w50 thresholds (what percentile constitutes "stress"?)
inst_p80 = np.percentile(inst_w50, 80) # lenient
inst_p90 = np.percentile(inst_w50, 90) # moderate
inst_p95 = np.percentile(inst_w50, 95) # strict
gap_p20 = np.percentile(gap_w50, 20) # gap collapse (low = collapse)
gap_p10 = np.percentile(gap_w50, 10) # strict gap collapse
print(f"inst_w50 thresholds: p80={inst_p80:.4f} p90={inst_p90:.4f} p95={inst_p95:.4f}")
print(f"gap_w50 thresholds: p20={gap_p20:.4f} p10={gap_p10:.4f}")
def build_labels(horizon, inst_thresh, gap_thresh):
"""Did eigenspace stress (inst spike AND gap collapse) occur in next K scans?"""
labels = np.zeros(N, dtype=np.float32)
for i in range(N - horizon):
future_inst = inst_w50[i+1:i+1+horizon]
future_gap = gap_w50[i+1:i+1+horizon]
if np.any(future_inst > inst_thresh) and np.any(future_gap < gap_thresh):
labels[i] = 1.0
return labels
configs = [
('K=10 lenient', 10, inst_p80, gap_p20),
('K=10 moderate', 10, inst_p90, gap_p10),
('K=20 moderate', 20, inst_p90, gap_p10),
('K=20 strict', 20, inst_p95, gap_p10),
('K=50 strict', 50, inst_p95, gap_p10),
]
results = []
for cfg_name, K, it, gt in configs:
y = build_labels(K, it, gt)
pos_rate = y.mean()
print(f"\n [{cfg_name}] K={K} inst>{it:.3f} gap<{gt:.3f} pos_rate={pos_rate*100:.1f}%")
# Skip degenerate
if pos_rate < 0.02 or pos_rate > 0.60:
print(f" Skipping (pos_rate out of range)")
continue
# ── Evaluate each proxy directly ─────────────────────────────────────
print(f" Direct proxy AUC (no model):")
best_proxy_auc = 0
for j, pname in enumerate(proxy_names):
px = X_norm[:-K, j] if K > 0 else X_norm[:, j]
yy = y[:-K] if K > 0 else y
valid = np.isfinite(px) & np.isfinite(yy)
if valid.sum() < 100:
continue
try:
auc = roc_auc_score(yy[valid], px[valid])
auc = max(auc, 1-auc) # flip if < 0.5
best_proxy_auc = max(best_proxy_auc, auc)
if auc > 0.52:
print(f" {pname:<30} AUC={auc:.4f} *")
else:
print(f" {pname:<30} AUC={auc:.4f}")
except Exception:
pass
# ── Logistic regression on all proxies ───────────────────────────────
Xf = X_norm[:-K]
yf = y[:-K]
valid = np.isfinite(Xf).all(1) & np.isfinite(yf)
Xf, yf = Xf[valid], yf[valid]
if len(Xf) < 200:
continue
try:
# Chronological 3-fold split
n_val = len(Xf) // 4
X_train, X_val = Xf[:-n_val], Xf[-n_val:]
y_train, y_val = yf[:-n_val], yf[-n_val:]
lr = LogisticRegression(class_weight='balanced', max_iter=500, C=0.1)
lr.fit(X_train, y_train)
preds = lr.predict_proba(X_val)[:, 1]
auc_lr = roc_auc_score(y_val, preds)
auc_lr = max(auc_lr, 1-auc_lr)
ap_lr = average_precision_score(y_val, preds)
print(f" LogReg (OOS): AUC={auc_lr:.4f} AvgPrecision={ap_lr:.4f}")
except Exception as ex:
print(f" LogReg failed: {ex}")
# ── k-NN (captures non-linear manifold structure) ─────────────────────
try:
knn = KNeighborsClassifier(n_neighbors=15, metric='euclidean')
knn.fit(X_train, y_train)
preds_knn = knn.predict_proba(X_val)[:, 1]
auc_knn = roc_auc_score(y_val, preds_knn)
auc_knn = max(auc_knn, 1-auc_knn)
print(f" k-NN (k=15): AUC={auc_knn:.4f}")
except Exception as ex:
print(f" kNN failed: {ex}")
results.append((cfg_name, K, pos_rate, best_proxy_auc,
auc_lr if 'auc_lr' in dir() else 0,
auc_knn if 'auc_knn' in dir() else 0))
# ── Temporal structure: HOW MANY SCANS AHEAD does the signal lead? ─────────
print("\n" + "="*65)
print("TEMPORAL LEAD STRUCTURE: proxy_B vs future inst/gap (by horizon)")
print("="*65)
print(f" {'Horizon':>10} {'AUC(B)':>8} {'AUC(C)':>8} {'pos_rate':>9}")
for K in [1, 2, 5, 10, 20, 30, 50, 100]:
y_k = build_labels(K, inst_p90, gap_p10)
if y_k.mean() < 0.01 or y_k.mean() > 0.80:
continue
pB = X_norm[:-K, 1] # proxy_B normalized
pC = X_norm[:-K, 2] # proxy_C normalized
yy = y_k[:-K]
valid = np.isfinite(pB) & np.isfinite(pC) & np.isfinite(yy)
if valid.sum() < 100:
continue
try:
aB = roc_auc_score(yy[valid], pB[valid])
aB = max(aB, 1-aB)
aC = roc_auc_score(yy[valid], pC[valid])
aC = max(aC, 1-aC)
print(f" K={K:>3} scans ahead: AUC(B)={aB:.4f} AUC(C)={aC:.4f} pos={y_k.mean()*100:.1f}%")
except Exception:
pass
# ── 512-bit DVAE question: variance per proxy before/after normalization ───
print("\n" + "="*65)
print("550-BIT FLINT EFFECT: variance recovery in heavy-tailed proxies")
print("="*65)
for j, pname in enumerate(proxy_names):
raw = X_proxies[:, j]
norm = X_norm[:, j]
kurt_raw = float(((raw-raw.mean())**4).mean() / (raw.std()**4 + 1e-8))
kurt_norm = float(((norm-norm.mean())**4).mean() / (norm.std()**4 + 1e-8))
# Fraction of samples that would be clipped at ±3σ in float64 z-score
z64 = (raw - raw.mean()) / (raw.std() + 1e-8)
clip_pct = (np.abs(z64) > 3).mean() * 100
print(f" {pname:<32} kurt_raw={kurt_raw:8.1f} kurt_norm={kurt_norm:6.2f} "
f"tail_samples={clip_pct:.1f}%_beyond_3sigma")
print("\nDone.")

View File

@@ -0,0 +1,88 @@
"""
flint_vs_float_analysis.py
Differential analysis of 550-bit FLINT vs 64-bit float64 precision
specifically for Proxy C (kurtosis = 3798).
"""
import numpy as np
import sys
from pathlib import Path
PROJECT_ROOT = r"C:\Users\Lenovo\Documents\- DOLPHIN NG HD HCM TSF Predict"
if PROJECT_ROOT not in sys.path:
sys.path.insert(0, PROJECT_ROOT)
from SILOQY_NN_Kernel_COMPLETE6 import arb, with_precision, safe_float, FLINT_AVAILABLE
from nautilus_dolphin.dvae.corpus_builder import DolphinCorpus, OFF, T1 as T1_DIM
def compare_precision():
corpus_path = str(Path(PROJECT_ROOT) / 'nautilus_dolphin' / 'dvae' / 'corpus_cache.npz')
corpus = DolphinCorpus.load(corpus_path)
X_e = corpus.X[corpus.mask[:, 1]]
t1 = X_e[:, OFF[1]:OFF[1]+T1_DIM]
# Proxy C: vel_w50 - vel_w750
proxy_c = t1[:, 1] - t1[:, 16]
# Select extreme tails
tails_idx = np.where(np.abs(proxy_c) > np.percentile(np.abs(proxy_c), 99))[0]
sample_tails = proxy_c[tails_idx]
kurt = float(((proxy_c - proxy_c.mean())**4).mean() / (proxy_c.std()**4 + 1e-8))
print(f"Analyzing {len(sample_tails)} extreme samples from Proxy C (kurt={kurt:.2f})")
# MCDAIN logic: y = (x - mean) * scale * gate
# scale = 1.0 / (log(mag) + eps)
mag_f64 = np.sqrt(np.mean(proxy_c**2))
log_mag_f64 = np.log(mag_f64 + 1e-8)
scale_f64 = 1.0 / (log_mag_f64 + 1e-8)
results = []
with with_precision(550):
# Calc 550-bit magnitude
sum_sq_arb = arb(0)
for val in proxy_c:
sum_sq_arb += arb(str(val))**2
mag_arb = (sum_sq_arb / arb(len(proxy_c))).sqrt()
log_mag_arb = mag_arb.log()
scale_arb = arb(1) / (log_mag_arb + arb("1e-8"))
for x in sample_tails[:10]:
# 64-bit path
y_f64 = x * scale_f64
# 550-bit path
x_arb = arb(str(x))
y_arb = x_arb * scale_arb
y_arb_to_f = safe_float(y_arb)
diff = abs(y_f64 - y_arb_to_f)
results.append((x, y_f64, y_arb_to_f, diff))
print("\n| Input (Extreme) | Float64 Norm | 550-bit Norm | Delta |")
print("|-----------------|--------------|--------------|-------|")
for x, f, a, d in results:
print(f"| {x:15.10f} | {f:12.8f} | {a:12.8f} | {d:.2e} |")
# Gradient Stability Mock
# (x + eps) - (x) / eps
eps_range = [1e-8, 1e-15, 1e-30]
print("\nNumerical Gradient Stability (Finite Difference):")
x_test = sample_tails[0]
for e in eps_range:
# Float64
g_f64 = ((x_test + e) * scale_f64 - (x_test) * scale_f64) / e
# 550 bit
with with_precision(550):
e_arb = arb(str(e))
x_arb = arb(str(x_test))
g_arb = ((x_arb + e_arb) * scale_arb - (x_arb) * scale_arb) / e_arb
g_arb_f = safe_float(g_arb)
print(f" eps={e:.1e}: Float64 Grad={g_f64:.8f}, 550-bit Grad={g_arb_f:.8f}")
if __name__ == "__main__":
compare_precision()

Binary file not shown.

View File

@@ -0,0 +1,559 @@
"""
Hierarchical Disentangled VAE (H-D-VAE) for DOLPHIN
=====================================================
State-of-the-art β-TCVAE with:
- 3-level HIERARCHY of disentangled latent codes
- Real backpropagation (analytical gradients, no random noise)
- Masking for missing tiers (pre-eigenvalue era)
- Curriculum-aware: can freeze/thaw tiers during training
- Spectral features with 512-bit-aware log/ratio encoding
Latent structure:
z0 (dim=4) "macro regime" ← Tier-0 breadth + time
z1 (dim=8) "eigenstructure" ← Tier-1 eigenvalues, conditioned on z0
z2 (dim=8) "cross-section" ← Tier-2 per-asset, conditioned on z0+z1
Total latent dim: 20.
β-TCVAE decomposition (Chen et al. 2018):
KL = MI(x;z) + TC(z) + dim_KL(z)
Loss = Recon + γ·MI + β·TC + λ·dim_KL
Key insight: TC penalises factorial structure violation, encouraging
each dimension of z to capture an INDEPENDENT factor of variation.
With β >> 1 the model is forced to find the minimal sufficient encoding.
"""
import numpy as np
from typing import Tuple, Dict, Optional, List
# ── Constants ──────────────────────────────────────────────────────────────
# Must match corpus_builder.py DIMS = [8, 20, 50, 25, 8]
TIER0_DIM = 8
TIER1_DIM = 20
TIER2_DIM = 50
TIER3_DIM = 25 # ExF macro
TIER4_DIM = 8 # EsoF
# Tier offsets into 111-dim feature vector
T_OFF = [0, 8, 28, 78, 103]
Z0_DIM = 4 # macro regime
Z1_DIM = 8 # eigenstructure
Z2_DIM = 8 # cross-section + ExF
Z3_DIM = 4 # esoteric
Z_TOTAL = Z0_DIM + Z1_DIM + Z2_DIM + Z3_DIM # 24
EPS = 1e-8
# ── Numerics ───────────────────────────────────────────────────────────────
def _relu(x): return np.maximum(0, x)
def _drelu(x): return (x > 0).astype(x.dtype)
def _tanh(x): return np.tanh(x)
def _dtanh(x): return 1.0 - np.tanh(x)**2
def _softplus(x): return np.log1p(np.exp(np.clip(x, -20, 20)))
# ── Linear layer (weights + bias) with gradient ───────────────────────────
class Linear:
"""Affine layer with Adam optimiser state."""
def __init__(self, in_dim: int, out_dim: int, seed: int = 42):
rng = np.random.RandomState(seed)
scale = np.sqrt(2.0 / in_dim)
self.W = rng.randn(in_dim, out_dim).astype(np.float32) * scale
self.b = np.zeros(out_dim, dtype=np.float32)
# Adam state
self.mW = np.zeros_like(self.W)
self.vW = np.zeros_like(self.W)
self.mb = np.zeros_like(self.b)
self.vb = np.zeros_like(self.b)
def forward(self, x: np.ndarray) -> np.ndarray:
self._x = x
return x @ self.W + self.b
def backward(self, dout: np.ndarray) -> np.ndarray:
self.dW = self._x.T @ dout
self.db = dout.sum(axis=0)
return dout @ self.W.T
def step(self, lr: float, t: int, beta1=0.9, beta2=0.999):
bc1 = 1 - beta1**t
bc2 = 1 - beta2**t
for (p, m, v, g) in [(self.W, self.mW, self.vW, self.dW),
(self.b, self.mb, self.vb, self.db)]:
m[:] = beta1 * m + (1 - beta1) * g
v[:] = beta2 * v + (1 - beta2) * g**2
p -= lr * (m / bc1) / (np.sqrt(v / bc2) + 1e-8)
# ── Simple 2-layer MLP with ReLU ──────────────────────────────────────────
class MLP:
def __init__(self, dims: List[int], seed: int = 42):
self.layers = []
for i, (d_in, d_out) in enumerate(zip(dims[:-1], dims[1:])):
self.layers.append(Linear(d_in, d_out, seed=seed + i))
# Activation cache
self._acts = []
def forward(self, x: np.ndarray, final_linear=True) -> np.ndarray:
self._acts = []
h = x
for i, layer in enumerate(self.layers):
h = layer.forward(h)
if i < len(self.layers) - 1 or not final_linear:
self._acts.append(h.copy())
h = _relu(h)
else:
self._acts.append(None)
return h
def backward(self, dout: np.ndarray) -> np.ndarray:
dh = dout
for i in range(len(self.layers) - 1, -1, -1):
if i < len(self.layers) - 1:
dh = dh * _drelu(self._acts[i])
dh = self.layers[i].backward(dh)
return dh
def step(self, lr: float, t: int):
for layer in self.layers:
layer.step(lr, t)
# ── Encoder: MLP → (mu, logvar) ───────────────────────────────────────────
class VAEEncoder:
"""Encodes input (optionally concatenated with conditioning z) to (mu, logvar)."""
def __init__(self, in_dim: int, hidden: int, z_dim: int, seed: int = 0):
self.mlp = MLP([in_dim, hidden, hidden], seed=seed)
self.mu_head = Linear(hidden, z_dim, seed=seed + 100)
self.lv_head = Linear(hidden, z_dim, seed=seed + 200)
self.z_dim = z_dim
def forward(self, x: np.ndarray) -> Tuple[np.ndarray, np.ndarray]:
h = self.mlp.forward(x) # (B, hidden)
h = _relu(h)
self._h = h
mu = self.mu_head.forward(h)
logvar = self.lv_head.forward(h)
logvar = np.clip(logvar, -10, 4) # keep variance sane
return mu, logvar
def backward(self, dmu: np.ndarray, dlv: np.ndarray) -> np.ndarray:
dh_mu = self.mu_head.backward(dmu)
dh_lv = self.lv_head.backward(dlv)
dh = (dh_mu + dh_lv) * _drelu(self._h)
return self.mlp.backward(dh)
def step(self, lr: float, t: int):
self.mlp.step(lr, t)
self.mu_head.step(lr, t)
self.lv_head.step(lr, t)
# ── Decoder: z → x_hat ────────────────────────────────────────────────────
class VAEDecoder:
def __init__(self, z_dim: int, hidden: int, out_dim: int, seed: int = 0):
self.mlp = MLP([z_dim, hidden, hidden, out_dim], seed=seed + 300)
self.out_dim = out_dim
def forward(self, z: np.ndarray) -> np.ndarray:
return self.mlp.forward(z, final_linear=True)
def backward(self, dout: np.ndarray) -> np.ndarray:
return self.mlp.backward(dout)
def step(self, lr: float, t: int):
self.mlp.step(lr, t)
# ── β-TCVAE loss (Chen et al. 2018, proper dataset-size corrected form) ───
def btcvae_kl(mu: np.ndarray, logvar: np.ndarray,
N_dataset: int, beta: float = 4.0,
gamma: float = 1.0, lam: float = 1.0
) -> Tuple[float, np.ndarray, np.ndarray]:
"""
Decompose KL into: MI + beta*TC + lambda*dim_KL
Returns (total_kl, dmu, dlogvar).
MI = E_q[log q(z|x)] - E_q[log q(z)]
TC = E_q[log q(z)] - E_q[sum_j log q(z_j)]
dKL = E_q[sum_j log q(z_j) - log p(z_j)]
Minibatch-weighted estimator — numerically stable.
"""
B, D = mu.shape
var = np.exp(logvar)
# Per-sample KL (closed form vs N(0,1) prior)
kl_per_dim = 0.5 * (mu**2 + var - logvar - 1) # (B, D)
# Log q(z|x_i) for sample z_i ~ q(z|x_i) (diagonal Gaussian)
# We use the mean as the sample point (reparametrization noise ≈ 0 for gradient estimation)
# log q(z_i | x_i) = sum_d -0.5*(log(2π) + logvar + (z-mu)^2/var)
# At z = mu: (z-mu)=0, so log q(z|x) = -0.5*sum(log(2π) + logvar)
log_q_z_given_x = -0.5 * (D * np.log(2 * np.pi) + logvar.sum(axis=1)) # (B,)
# Minibatch estimate of log q(z): E_data[sum_x log q(z|x)] / N
# log q(z_i) ≈ log(1/N * sum_j q(z_i | x_j))
# Using pairwise: log q(z_i) ≈ logsumexp over j of log q(z_i | x_j) - log(N)
# q(z_i | x_j) for each pair: diffs = z_i[none,:] - mu[none,:] shape (B,B,D)
z_sample = mu # (B, D) — use mean as point estimate
# (B_i, B_j, D) pairwise differences
diff = z_sample[:, None, :] - mu[None, :, :] # (B, B, D)
log_q_z_pair = -0.5 * (
D * np.log(2 * np.pi)
+ logvar[None, :, :].sum(axis=2)
+ (diff**2 / (var[None, :, :] + EPS)).sum(axis=2)
) # (B, B)
log_q_z = np.log(np.sum(np.exp(log_q_z_pair - log_q_z_pair.max(axis=1, keepdims=True)), axis=1) + EPS) \
+ log_q_z_pair.max(axis=1) - np.log(N_dataset + EPS) # (B,)
# log q(z_j) marginal — independence assumption for TC
# log q(z_i_j) for each dim j: logsumexp over data points
log_q_z_product = np.zeros(B, dtype=np.float64)
for j in range(D):
diff_j = z_sample[:, j:j+1] - mu[:, j:j+1].T # (B, B)
log_q_zj = -0.5 * (np.log(2 * np.pi) + logvar[None, :, j] + diff_j**2 / (var[None, :, j] + EPS)) # (B,B)
log_q_zj_marginal = np.log(np.sum(np.exp(log_q_zj - log_q_zj.max(axis=1, keepdims=True)), axis=1) + EPS) \
+ log_q_zj.max(axis=1) - np.log(N_dataset + EPS) # (B,)
log_q_z_product += log_q_zj_marginal
# log p(z) = N(0,1)
log_p_z = -0.5 * (D * np.log(2 * np.pi) + (z_sample**2).sum(axis=1)) # (B,)
mi_loss = np.mean(log_q_z_given_x - log_q_z)
tc_loss = np.mean(log_q_z - log_q_z_product)
dkl_loss = np.mean(log_q_z_product - log_p_z)
total_kl = gamma * mi_loss + beta * max(tc_loss, 0) + lam * dkl_loss
# Gradients w.r.t. mu and logvar: use standard closed-form KL gradient
# ∂KL/∂mu = mu, ∂KL/∂logvar = 0.5*(exp(logvar) - 1)
scale = (gamma + beta + lam) / (3.0 * B + EPS)
dmu = scale * mu
dlogvar = scale * 0.5 * (var - 1.0)
return float(total_kl), dmu.astype(np.float32), dlogvar.astype(np.float32)
# ── Reparametrization ─────────────────────────────────────────────────────
def reparametrize(mu, logvar, rng):
std = np.exp(0.5 * logvar)
eps = rng.randn(*mu.shape).astype(np.float32)
return mu + eps * std
# ── Hierarchical D-VAE ────────────────────────────────────────────────────
class HierarchicalDVAE:
"""
4-level Hierarchical Disentangled VAE (111-dim input, 24-dim latent).
Latent hierarchy:
z0 (4) = enc0(T0) macro regime (breadth + time)
z1 (8) = enc1(T1 + T4 + z0) eigenstructure + esoteric, cond on z0
z2 (8) = enc2(T2 + T3 + z0 + z1) cross-section + ExF, cond on z0,z1
z3 (4) = enc3(T3 + T4 + z0) ExF+EsoF regime, cond on z0
Decoding:
T0_hat = dec0(z0)
T1_hat = dec1(z0, z1)
T2_hat = dec2(z0, z1, z2)
T3_hat = dec3(z0, z3)
T4_hat = dec4(z0) (EsoF is deterministic, serves as auxiliary)
Training phases:
0: enc0/dec0 only — full 500K+ corpus (even pre-eigen NG1/NG2)
1: + enc1/dec1 — eigen-tier (NG3+)
2: + enc2/dec2 — pricing + ExF (NG3+ with pricing)
3: + enc3/dec3 — ExF regime
4: joint fine-tune all
"""
def __init__(self, hidden: int = 64, beta: float = 4.0,
gamma: float = 1.0, lam: float = 1.0, seed: int = 42):
self.hidden = hidden
self.beta = beta
self.gamma = gamma
self.lam = lam
self.seed = seed
# Encoders (each conditioned on upstream z)
self.enc0 = VAEEncoder(TIER0_DIM, hidden, Z0_DIM, seed=seed)
self.enc1 = VAEEncoder(TIER1_DIM + TIER4_DIM + Z0_DIM, hidden, Z1_DIM, seed=seed+10)
self.enc2 = VAEEncoder(TIER2_DIM + TIER3_DIM + Z0_DIM + Z1_DIM, hidden, Z2_DIM, seed=seed+20)
self.enc3 = VAEEncoder(TIER3_DIM + TIER4_DIM + Z0_DIM, hidden//2, Z3_DIM, seed=seed+30)
# Decoders
self.dec0 = VAEDecoder(Z0_DIM, hidden, TIER0_DIM, seed=seed)
self.dec1 = VAEDecoder(Z0_DIM + Z1_DIM, hidden, TIER1_DIM, seed=seed+10)
self.dec2 = VAEDecoder(Z0_DIM + Z1_DIM + Z2_DIM, hidden, TIER2_DIM, seed=seed+20)
self.dec3 = VAEDecoder(Z0_DIM + Z3_DIM, hidden//2, TIER3_DIM, seed=seed+30)
self.dec4 = VAEDecoder(Z0_DIM, hidden//2, TIER4_DIM, seed=seed+40)
# Normalisation statistics (fit on training data)
self._mu_t0 = np.zeros(TIER0_DIM, dtype=np.float32)
self._sd_t0 = np.ones(TIER0_DIM, dtype=np.float32)
self._mu_t1 = np.zeros(TIER1_DIM, dtype=np.float32)
self._sd_t1 = np.ones(TIER1_DIM, dtype=np.float32)
self._mu_t2 = np.zeros(TIER2_DIM, dtype=np.float32)
self._sd_t2 = np.ones(TIER2_DIM, dtype=np.float32)
self._mu_t3 = np.zeros(TIER3_DIM, dtype=np.float32)
self._sd_t3 = np.ones(TIER3_DIM, dtype=np.float32)
self._mu_t4 = np.zeros(TIER4_DIM, dtype=np.float32)
self._sd_t4 = np.ones(TIER4_DIM, dtype=np.float32)
self.step_t = 0 # Adam time step
self.train_losses: List[Dict] = []
# ── Normalisation ──────────────────────────────────────────────────────
def fit_normaliser(self, X: np.ndarray, mask: np.ndarray):
"""Fit per-tier normalisation on a representative sample."""
def _fit(rows, dim):
if len(rows) < 2:
return np.zeros(dim, dtype=np.float32), np.ones(dim, dtype=np.float32)
return rows.mean(0).astype(np.float32), (rows.std(0) + EPS).astype(np.float32)
self._mu_t0, self._sd_t0 = _fit(X[:, T_OFF[0]:T_OFF[0]+TIER0_DIM], TIER0_DIM)
idx1 = mask[:, 1]
self._mu_t1, self._sd_t1 = _fit(X[idx1, T_OFF[1]:T_OFF[1]+TIER1_DIM], TIER1_DIM)
idx2 = mask[:, 2]
self._mu_t2, self._sd_t2 = _fit(X[idx2, T_OFF[2]:T_OFF[2]+TIER2_DIM], TIER2_DIM)
idx3 = mask[:, 3]
self._mu_t3, self._sd_t3 = _fit(X[idx3, T_OFF[3]:T_OFF[3]+TIER3_DIM], TIER3_DIM)
self._mu_t4, self._sd_t4 = _fit(X[:, T_OFF[4]:T_OFF[4]+TIER4_DIM], TIER4_DIM)
def _norm(self, x, mu, sd): return (x - mu) / sd
def _norm0(self, x): return self._norm(x, self._mu_t0, self._sd_t0)
def _norm1(self, x): return self._norm(x, self._mu_t1, self._sd_t1)
def _norm2(self, x): return self._norm(x, self._mu_t2, self._sd_t2)
def _norm3(self, x): return self._norm(x, self._mu_t3, self._sd_t3)
def _norm4(self, x): return self._norm(x, self._mu_t4, self._sd_t4)
# ── Forward pass ──────────────────────────────────────────────────────
def _split(self, X):
"""Split 111-dim row into normalised tier vectors."""
t0 = self._norm0(X[:, T_OFF[0]:T_OFF[0]+TIER0_DIM])
t1 = self._norm1(X[:, T_OFF[1]:T_OFF[1]+TIER1_DIM])
t2 = self._norm2(X[:, T_OFF[2]:T_OFF[2]+TIER2_DIM])
t3 = self._norm3(X[:, T_OFF[3]:T_OFF[3]+TIER3_DIM])
t4 = self._norm4(X[:, T_OFF[4]:T_OFF[4]+TIER4_DIM])
return t0, t1, t2, t3, t4
def encode(self, X: np.ndarray, mask: np.ndarray, rng) -> Dict:
t0, t1, t2, t3, t4 = self._split(X)
mu0, lv0 = self.enc0.forward(t0)
z0 = reparametrize(mu0, lv0, rng)
mu1, lv1 = self.enc1.forward(np.concatenate([t1, t4, z0], axis=1))
z1 = reparametrize(mu1, lv1, rng)
mu2, lv2 = self.enc2.forward(np.concatenate([t2, t3, z0, z1], axis=1))
z2 = reparametrize(mu2, lv2, rng)
mu3, lv3 = self.enc3.forward(np.concatenate([t3, t4, z0], axis=1))
z3 = reparametrize(mu3, lv3, rng)
return dict(t0=t0, t1=t1, t2=t2, t3=t3, t4=t4,
mu0=mu0, lv0=lv0, z0=z0,
mu1=mu1, lv1=lv1, z1=z1,
mu2=mu2, lv2=lv2, z2=z2,
mu3=mu3, lv3=lv3, z3=z3)
def decode(self, enc: Dict) -> Dict:
z0, z1, z2, z3 = enc['z0'], enc['z1'], enc['z2'], enc['z3']
x0_hat = self.dec0.forward(z0)
x1_hat = self.dec1.forward(np.concatenate([z0, z1], axis=1))
x2_hat = self.dec2.forward(np.concatenate([z0, z1, z2], axis=1))
x3_hat = self.dec3.forward(np.concatenate([z0, z3], axis=1))
x4_hat = self.dec4.forward(z0) # EsoF decoded from macro only
return dict(x0_hat=x0_hat, x1_hat=x1_hat, x2_hat=x2_hat,
x3_hat=x3_hat, x4_hat=x4_hat)
# ── Loss and gradients ─────────────────────────────────────────────────
def loss_and_grads(self, enc: Dict, dec: Dict, mask: np.ndarray,
N_dataset: int, phase: int) -> Dict:
"""Compute total loss and all gradients via chain rule."""
t0, t1, t2 = enc['t0'], enc['t1'], enc['t2']
x0_hat = dec['x0_hat']
x1_hat = dec['x1_hat']
x2_hat = dec['x2_hat']
B = len(t0)
# Mask weights: per-sample, per-tier
w1 = mask[:, 1].astype(np.float32)[:, None] # (B,1)
w2 = mask[:, 2].astype(np.float32)[:, None]
# Reconstruction losses
recon0 = np.mean((x0_hat - t0)**2)
recon1 = np.mean(w1 * (x1_hat - t1)**2)
recon2 = np.mean(w2 * (x2_hat - t2)**2)
# Reconstruction gradients
dr0 = 2 * (x0_hat - t0) / (B * TIER0_DIM)
dr1 = 2 * w1 * (x1_hat - t1) / (B * TIER1_DIM)
dr2 = 2 * w2 * (x2_hat - t2) / (B * TIER2_DIM)
# KL losses
kl0, dmu0_kl, dlv0_kl = btcvae_kl(enc['mu0'], enc['lv0'], N_dataset, self.beta, self.gamma, self.lam)
kl1, dmu1_kl, dlv1_kl = btcvae_kl(enc['mu1'], enc['lv1'], N_dataset, self.beta, self.gamma, self.lam)
kl2, dmu2_kl, dlv2_kl = btcvae_kl(enc['mu2'], enc['lv2'], N_dataset, self.beta, self.gamma, self.lam)
# Phase-based scaling: only activate eigen/pricing in correct phase
if phase < 1:
kl1 = recon1 = 0.0; dmu1_kl[:] = 0; dlv1_kl[:] = 0; dr1[:] = 0
if phase < 2:
kl2 = recon2 = 0.0; dmu2_kl[:] = 0; dlv2_kl[:] = 0; dr2[:] = 0
total = recon0 + recon1 + recon2 + kl0 + kl1 + kl2
return dict(
total=total, recon0=recon0, recon1=recon1, recon2=recon2,
kl0=kl0, kl1=kl1, kl2=kl2,
dr0=dr0, dr1=dr1, dr2=dr2,
dmu0_kl=dmu0_kl, dlv0_kl=dlv0_kl,
dmu1_kl=dmu1_kl, dlv1_kl=dlv1_kl,
dmu2_kl=dmu2_kl, dlv2_kl=dlv2_kl,
)
def backward(self, enc: Dict, dec: Dict, grads: Dict, phase: int, lr: float):
"""Backpropagate through all encoders and decoders, then Adam step."""
z0, z1, z2 = enc['z0'], enc['z1'], enc['z2']
t = self.step_t
# ── Decoder 2 (z0+z1+z2 → x2) ──────────────────────────────────
if phase >= 2:
dz_all_2 = self.dec2.backward(grads['dr2']) # (B, Z0+Z1+Z2)
dz0_from_d2 = dz_all_2[:, :Z0_DIM]
dz1_from_d2 = dz_all_2[:, Z0_DIM:Z0_DIM + Z1_DIM]
dz2_from_d2 = dz_all_2[:, Z0_DIM + Z1_DIM:]
else:
dz0_from_d2 = np.zeros_like(z0)
dz1_from_d2 = np.zeros_like(z1)
dz2_from_d2 = np.zeros_like(z2)
self.dec2.backward(np.zeros_like(grads['dr2']))
# ── Encoder 2 ─────────────────────────────────────────────────
# enc2 input = concat(T2, T3, z0, z1) dims = TIER2+TIER3+Z0+Z1
if phase >= 2:
dmu2 = dz2_from_d2 + grads['dmu2_kl']
dlv2 = dz2_from_d2 * 0.5 + grads['dlv2_kl']
dinp2 = self.enc2.backward(dmu2, dlv2) # (B, T2+T3+Z0+Z1)
_off = TIER2_DIM + TIER3_DIM
dz0_from_e2 = dinp2[:, _off:_off + Z0_DIM]
dz1_from_e2 = dinp2[:, _off + Z0_DIM:]
self.dec2.step(lr, t)
self.enc2.step(lr, t)
else:
dz0_from_e2 = np.zeros_like(z0)
dz1_from_e2 = np.zeros_like(z1)
# ── Decoder 1 (z0+z1 → x1) ──────────────────────────────────────
if phase >= 1:
dz_all_1 = self.dec1.backward(grads['dr1']) # (B, Z0+Z1)
dz0_from_d1 = dz_all_1[:, :Z0_DIM]
dz1_from_d1 = dz_all_1[:, Z0_DIM:]
else:
dz0_from_d1 = np.zeros_like(z0)
dz1_from_d1 = np.zeros_like(z1)
self.dec1.backward(np.zeros_like(grads['dr1']))
# ── Encoder 1 ─────────────────────────────────────────────────
# enc1 input = concat(T1, T4, z0) dims = TIER1+TIER4+Z0
if phase >= 1:
dmu1 = dz1_from_d1 + dz1_from_d2 + dz1_from_e2 + grads['dmu1_kl']
dlv1 = dz1_from_d1 * 0.5 + grads['dlv1_kl']
dinp1 = self.enc1.backward(dmu1, dlv1) # (B, T1+T4+Z0)
dz0_from_e1 = dinp1[:, TIER1_DIM + TIER4_DIM:] # correct: skip T1+T4
self.dec1.step(lr, t)
self.enc1.step(lr, t)
else:
dz0_from_e1 = np.zeros_like(z0)
# ── Decoder 0 (z0 → x0) ─────────────────────────────────────────
dz0_from_d0 = self.dec0.backward(grads['dr0']) # (B, Z0)
# ── Encoder 0 ────────────────────────────────────────────────────
dmu0 = dz0_from_d0 + dz0_from_d1 + dz0_from_d2 + dz0_from_e1 + dz0_from_e2 + grads['dmu0_kl']
dlv0 = dz0_from_d0 * 0.5 + grads['dlv0_kl']
self.enc0.backward(dmu0, dlv0)
self.enc0.step(lr, t)
self.dec0.step(lr, t)
# ── Training ──────────────────────────────────────────────────────────
def train_epoch(self, X: np.ndarray, mask: np.ndarray,
lr: float, batch_size: int, phase: int, rng) -> Dict:
N = len(X)
idx = rng.permutation(N)
epoch_stats = dict(total=0, recon0=0, recon1=0, recon2=0, kl0=0, kl1=0, kl2=0, n=0)
for start in range(0, N, batch_size):
bi = idx[start:start + batch_size]
Xb = X[bi]
mb = mask[bi]
self.step_t += 1 # Adam global step (cumulative across all epochs)
enc_out = self.encode(Xb, mb, rng)
dec_out = self.decode(enc_out)
grads = self.loss_and_grads(enc_out, dec_out, mb, N, phase)
self.backward(enc_out, dec_out, grads, phase, lr)
for k in ['total', 'recon0', 'recon1', 'recon2', 'kl0', 'kl1', 'kl2']:
epoch_stats[k] += float(grads[k]) * len(bi)
epoch_stats['n'] += len(bi)
return {k: v / max(epoch_stats['n'], 1) for k, v in epoch_stats.items() if k != 'n'}
# ── Inference ─────────────────────────────────────────────────────────
def get_latents(self, X: np.ndarray, mask: np.ndarray) -> Dict[str, np.ndarray]:
"""Return disentangled latent codes for analysis."""
rng = np.random.RandomState(0) # deterministic for inference
enc = self.encode(X, mask, rng)
return {
'z0_macro': enc['mu0'], # (N, 4) deterministic mean
'z1_eigen': enc['mu1'], # (N, 8)
'z2_xsection': enc['mu2'], # (N, 8)
'z_all': np.concatenate([enc['mu0'], enc['mu1'], enc['mu2']], axis=1), # (N, 20)
'sigma0': np.exp(0.5 * enc['lv0']), # uncertainty
'sigma1': np.exp(0.5 * enc['lv1']),
'sigma2': np.exp(0.5 * enc['lv2']),
}
# ── Persistence ───────────────────────────────────────────────────────
def save(self, path: str):
data = {
'norm': dict(mu_t0=self._mu_t0, sd_t0=self._sd_t0,
mu_t1=self._mu_t1, sd_t1=self._sd_t1,
mu_t2=self._mu_t2, sd_t2=self._sd_t2),
'step_t': self.step_t,
'losses': self.train_losses,
}
# Save all weight matrices
for name, enc in [('enc0', self.enc0), ('enc1', self.enc1), ('enc2', self.enc2)]:
for i, layer in enumerate(enc.mlp.layers):
data[f'{name}_mlp{i}_W'] = layer.W
data[f'{name}_mlp{i}_b'] = layer.b
data[f'{name}_mu_W'] = enc.mu_head.W; data[f'{name}_mu_b'] = enc.mu_head.b
data[f'{name}_lv_W'] = enc.lv_head.W; data[f'{name}_lv_b'] = enc.lv_head.b
for name, dec in [('dec0', self.dec0), ('dec1', self.dec1), ('dec2', self.dec2)]:
for i, layer in enumerate(dec.mlp.layers):
data[f'{name}_mlp{i}_W'] = layer.W
data[f'{name}_mlp{i}_b'] = layer.b
np.savez_compressed(path, **data)
print(f"Model saved: {path}.npz")

View File

@@ -0,0 +1,193 @@
"""
proto_v2_query.py — Back-of-envelope z-space probe for convnext_model_v2.json
Queries the current best checkpoint against the 56-day backtest period
(Dec 31 2025 Feb 25 2026) to assess signal quality vs the ep=17 baseline.
Reports:
1. z_active, z_post_std (latent health)
2. proxy_B dim + r (encoding quality)
3. Calibration: is z_proxy_B still always negative for this period?
4. Split test: top-25% vs bottom-25% proxy_B days — does z separate them?
ExF columns (dvol_btc, fng, funding_btc) are zero-filled — same as exp13 fallback.
Safe to run while training is still in progress (read-only, no GPU).
"""
import os, sys, json, glob
import io
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8', errors='replace')
import numpy as np
import pandas as pd
ROOT = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
DVAE_DIR = os.path.join(ROOT, 'nautilus_dolphin', 'dvae')
sys.path.insert(0, DVAE_DIR)
MODEL_V2 = os.path.join(DVAE_DIR, 'convnext_model_v2.json')
MODEL_EP17 = os.path.join(DVAE_DIR, 'convnext_model.json')
KLINES_DIR = os.path.join(ROOT, 'vbt_cache_klines')
FEATURE_COLS = [
'v50_lambda_max_velocity', 'v150_lambda_max_velocity',
'v300_lambda_max_velocity', 'v750_lambda_max_velocity',
'vel_div', 'instability_50', 'instability_150',
]
EXF_COLS = ['dvol_btc', 'fng', 'funding_btc'] # zero-filled
T_WIN = 32
# 56-day backtest window
DATE_START = '2025-12-31'
DATE_END = '2026-02-25'
# ── load model ───────────────────────────────────────────────────────────────
from convnext_dvae import ConvNeXtVAE
def load_model(path):
with open(path) as f:
meta = json.load(f)
arch = meta.get('architecture', {})
m = ConvNeXtVAE(
C_in=arch['C_in'], T_in=arch['T_in'],
z_dim=arch['z_dim'], base_ch=arch['base_ch'],
n_blocks=arch.get('n_blocks', 3), seed=42,
)
m.load(path)
nm = np.array(meta['norm_mean']) if 'norm_mean' in meta else None
ns = np.array(meta['norm_std']) if 'norm_std' in meta else None
return m, nm, ns, meta
print(f"Loading v2 checkpoint...")
model_v2, nm_v2, ns_v2, meta_v2 = load_model(MODEL_V2)
print(f" ep={meta_v2.get('epoch')} val_loss={meta_v2.get('val_loss',0):.5f}")
ep17_exists = os.path.exists(MODEL_EP17)
if ep17_exists:
print(f"Loading ep=17 baseline for comparison...")
model_17, nm_17, ns_17, meta_17 = load_model(MODEL_EP17)
print(f" ep={meta_17.get('epoch')} val_loss={meta_17.get('val_loss',0):.5f}")
# ── build probe set from 56-day window ───────────────────────────────────────
print(f"\nBuilding probe windows from {DATE_START} to {DATE_END}...")
files = sorted(f for f in glob.glob(os.path.join(KLINES_DIR, '*.parquet')))
# filter to date range
period_files = [f for f in files
if DATE_START <= os.path.basename(f)[:10] <= DATE_END]
print(f" {len(period_files)} klines files in period")
rng = np.random.default_rng(42)
probes_raw, proxy_B_vals, file_dates = [], [], []
step = max(1, len(period_files) // 60) # ~60 probes across period
for f in period_files[::step]:
try:
df = pd.read_parquet(f, columns=FEATURE_COLS).dropna()
if len(df) < T_WIN + 10: continue
# sample from middle of each day (avoid open/close noise)
mid = len(df) // 2
pos = int(rng.integers(max(0, mid - 30), min(len(df) - T_WIN, mid + 30)))
arr = df[FEATURE_COLS].values[pos:pos+T_WIN].astype(np.float64) # (T, 7)
proxy_B = (arr[:, 5] - arr[:, 3]).reshape(-1, 1) # instability_50 - v750
exf = np.zeros((T_WIN, 3), dtype=np.float64) # zero-fill ExF
arr11 = np.concatenate([arr, proxy_B, exf], axis=1).T # (11, T)
if not np.isfinite(arr11).all(): continue
probes_raw.append(arr11)
proxy_B_vals.append(float(proxy_B.mean()))
file_dates.append(os.path.basename(f)[:10])
except Exception as e:
pass
probes_raw = np.stack(probes_raw) # (N, 11, T)
proxy_B_arr = np.array(proxy_B_vals)
print(f" Probe set: {probes_raw.shape} ({len(probes_raw)} windows)")
def normalise(probes, nm, ns):
if nm is None: return probes
p = (probes - nm[None, :, None]) / ns[None, :, None]
np.clip(p, -6., 6., out=p)
return p
def run_query(model, nm, ns, label):
probes = normalise(probes_raw, nm, ns)
z_mu, z_logvar = model.encode(probes)
x_recon = model.decode(z_mu)
# 1. Latent health
z_std_per_dim = z_mu.std(0)
z_active = int((z_std_per_dim > 0.01).sum())
z_post_std = float(np.exp(0.5 * z_logvar).mean())
z_mean_all = float(z_mu.mean())
# 2. Reconstruction
recon_err = ((probes - x_recon) ** 2).mean(axis=(-1, -2))
recon_p50 = float(np.median(recon_err))
# 3. proxy_B correlation — find best dim
corrs = []
for d in range(z_mu.shape[1]):
if z_std_per_dim[d] > 0.01:
r = float(np.corrcoef(z_mu[:, d], proxy_B_arr)[0, 1])
if np.isfinite(r): corrs.append((abs(r), r, d))
corrs.sort(reverse=True)
best_abs_r, best_r, best_dim = corrs[0] if corrs else (0, 0, -1)
# 4. Calibration: is best_dim always negative for this period?
z_best = z_mu[:, best_dim]
z_min, z_max = float(z_best.min()), float(z_best.max())
always_neg = z_max < 0
always_pos = z_min > 0
calib = "ALWAYS NEGATIVE" if always_neg else ("ALWAYS POSITIVE" if always_pos else
f"MIXED [{z_min:+.3f}, {z_max:+.3f}]")
# 5. Split test: top-25% vs bottom-25% proxy_B days
q75 = np.percentile(proxy_B_arr, 75)
q25 = np.percentile(proxy_B_arr, 25)
hi_mask = proxy_B_arr >= q75
lo_mask = proxy_B_arr <= q25
z_hi = float(z_best[hi_mask].mean()) if hi_mask.sum() > 2 else float('nan')
z_lo = float(z_best[lo_mask].mean()) if lo_mask.sum() > 2 else float('nan')
sep = abs(z_hi - z_lo)
print(f"\n{'='*60}")
print(f" {label}")
print(f"{'='*60}")
print(f" z_active : {z_active} / {z_mu.shape[1]}")
print(f" z_post_std : {z_post_std:.4f} (healthy: 0.61.2)")
print(f" recon_p50 : {recon_p50:.4f} (ep17 baseline: 0.2999)")
print(f"\n proxy_B dim : z[{best_dim}] r={best_r:+.4f} (ep17 had z[10] r=+0.973)")
print(f" Top-5 z×proxy_B corrs:")
for _, r, d in corrs[:5]:
bar = '#' * int(abs(r) * 30)
print(f" z[{d:2d}] r={r:+.4f} {bar}")
print(f"\n Calibration : {calib}")
print(f" z[{best_dim}] range : [{z_min:+.4f}, {z_max:+.4f}]")
print(f" z[{best_dim}] mean : {z_best.mean():+.4f}")
print(f"\n Split test (proxy_B quartiles):")
print(f" top-25% proxy_B → z[{best_dim}] mean = {z_hi:+.4f}")
print(f" bot-25% proxy_B → z[{best_dim}] mean = {z_lo:+.4f}")
print(f" separation = {sep:.4f} (>0.3 useful, >0.6 good)")
return z_mu, corrs
print("\n" + "="*60)
print(f"proxy_B stats over {len(probes_raw)} probes:")
print(f" mean={proxy_B_arr.mean():+.4f} std={proxy_B_arr.std():.4f} "
f"min={proxy_B_arr.min():+.4f} max={proxy_B_arr.max():+.4f}")
z_v2, corrs_v2 = run_query(model_v2, nm_v2, ns_v2,
f"v2 ep={meta_v2.get('epoch')} val={meta_v2.get('val_loss',0):.5f} [CURRENT BEST]")
if ep17_exists:
z_17, corrs_17 = run_query(model_17, nm_17, ns_17,
f"ep17 val={meta_17.get('val_loss',0):.5f} [PRODUCTION BASELINE]")
# Side-by-side summary
print(f"\n{'='*60}")
print(" COMPARISON SUMMARY")
print(f"{'='*60}")
r_v2 = corrs_v2[0][1] if corrs_v2 else 0
r_17 = corrs_17[0][1] if corrs_17 else 0
print(f" proxy_B r : v2={r_v2:+.4f} vs ep17={r_17:+.4f} "
f"({'BETTER' if abs(r_v2) > abs(r_17) else 'WORSE' if abs(r_v2) < abs(r_17) else 'SAME'})")
print(f"\nDone.")

View File

@@ -0,0 +1,244 @@
"""
Resolution Alignment Check
===========================
GATE TEST: Before training Titan VAE on 1m klines, verify that T0-T4 feature
distributions at 1m resolution are sufficiently aligned with 5s production data.
If they diverge, a 1m-trained model cannot be used at 5s inference — the latent
dims would represent different physics (50-min vs 4.2-min eigenvalue dynamics).
Tests:
1. Per-dim KS test (distribution shape match)
2. Per-dim mean/std ratio (scale match)
3. PCA cosine similarity (covariance structure match)
4. T1 feature correlation matrix alignment (Frobenius distance)
Pass criteria (all must hold):
- KS p > 0.05 for >= 70% of T1+T2 dims (distributions compatible)
- mean ratio within [0.1, 10x] and std ratio within [0.1, 10x] for >= 70% of dims
- PCA cosine(PC1, PC2) >= 0.80 (shared dominant variance axes)
Output: resolution_alignment_report.json
"""
import sys
sys.stdout.reconfigure(encoding='utf-8', errors='replace')
from pathlib import Path
import json
import numpy as np
import pandas as pd
from scipy import stats
ROOT = Path(__file__).parent.parent
VBT5s = Path(r"C:\Users\Lenovo\Documents\- DOLPHIN NG HD HCM TSF Predict\vbt_cache")
VBT1m = Path(r"C:\Users\Lenovo\Documents\- DOLPHIN NG HD HCM TSF Predict\vbt_cache_klines")
OUT = Path(__file__).parent / "resolution_alignment_report.json"
sys.path.insert(0, str(ROOT))
from dvae.titan_sensor import build_feature_vector
META_COLS = {'timestamp','scan_number','v50_lambda_max_velocity','v150_lambda_max_velocity',
'v300_lambda_max_velocity','v750_lambda_max_velocity','vel_div',
'instability_50','instability_150'}
N_5S_DAYS = 10 # use first N days from vbt_cache (5s data)
N_1M_DAYS = 60 # use N days from klines (1m data); pick older period if available
def extract_features(parquet_dir, n_days, label, date_before=None):
"""Build feature matrix from parquet files."""
files = sorted(parquet_dir.glob("*.parquet"))
files = [f for f in files if 'catalog' not in str(f)]
if date_before:
files = [f for f in files if f.stem < date_before]
files = files[:n_days]
if not files:
print(f" [{label}] No files found in {parquet_dir}")
return None, []
print(f" [{label}] {len(files)} days ({files[0].stem} to {files[-1].stem})")
rows = []
for pf in files:
df = pd.read_parquet(pf)
assets = [c for c in df.columns if c not in META_COLS]
for ri in range(50, len(df), 10): # stride=10 to keep runtime low
feat = build_feature_vector(df, ri, assets)
rows.append(feat)
X = np.array(rows, dtype=np.float64)
X = np.nan_to_num(X, nan=0.0, posinf=0.0, neginf=0.0)
print(f" [{label}] Feature matrix: {X.shape}")
return X, [f.stem for f in files]
def ks_test_per_dim(X5, X1, dims):
"""KS test for each dim. Returns fraction with p>0.05."""
results = {}
for d in dims:
a, b = X5[:, d], X1[:, d]
# skip if all-zero in either
if np.std(a) < 1e-10 or np.std(b) < 1e-10:
results[d] = {"p": 1.0, "stat": 0.0, "trivial": True}
continue
stat, p = stats.ks_2samp(a, b)
results[d] = {"p": float(p), "stat": float(stat), "trivial": False}
return results
def scale_check(X5, X1, dims):
"""Check mean/std ratios."""
results = {}
for d in dims:
m5, s5 = float(np.mean(X5[:, d])), float(np.std(X5[:, d]))
m1, s1 = float(np.mean(X1[:, d])), float(np.std(X1[:, d]))
mean_ratio = abs(m5) / (abs(m1) + 1e-10) if abs(m1) > 1e-10 else float('inf')
std_ratio = s5 / (s1 + 1e-10) if s1 > 1e-10 else float('inf')
results[d] = {
"mean_5s": m5, "mean_1m": m1, "mean_ratio": mean_ratio,
"std_5s": s5, "std_1m": s1, "std_ratio": std_ratio,
}
return results
def pca_cosine(X5, X1, n_components=4):
"""PCA on both matrices, return cosine sim of top components."""
from numpy.linalg import svd
def top_pcs(X, k):
Xc = X - X.mean(axis=0)
_, _, Vt = svd(Xc, full_matrices=False)
return Vt[:k] # (k, d)
pcs5 = top_pcs(X5, n_components)
pcs1 = top_pcs(X1, n_components)
cosines = []
for i in range(n_components):
c = abs(float(np.dot(pcs5[i], pcs1[i]) /
(np.linalg.norm(pcs5[i]) * np.linalg.norm(pcs1[i]) + 1e-12)))
cosines.append(c)
return cosines
def corr_frobenius(X5, X1, dims):
"""Frobenius distance between T1 correlation matrices."""
A = np.corrcoef(X5[:, dims].T)
B = np.corrcoef(X1[:, dims].T)
A = np.nan_to_num(A); B = np.nan_to_num(B)
frob = float(np.linalg.norm(A - B, 'fro'))
max_frob = float(np.sqrt(2 * len(dims))) # upper bound (orthogonal corr matrices)
return frob, frob / max_frob # raw and normalised [0,1]
def main():
print("=== Resolution Alignment Check ===")
print(f"5s source: {VBT5s}")
print(f"1m source: {VBT1m}")
# Pick 1m days from OLDER period if possible (true OOS); otherwise use what's there
klines_files = sorted(VBT1m.glob("*.parquet"))
klines_files = [f for f in klines_files if 'catalog' not in str(f)]
# try to use oldest N_1M_DAYS
date_cut = klines_files[0].stem if klines_files else None
print(f" 1m klines available: {len(klines_files)} days ({klines_files[0].stem if klines_files else '?'} to {klines_files[-1].stem if klines_files else '?'})")
print("\nBuilding feature matrices...")
X5, days5 = extract_features(VBT5s, N_5S_DAYS, "5s-prod")
X1, days1 = extract_features(VBT1m, N_1M_DAYS, "1m-klines")
if X5 is None or X1 is None:
print("ERROR: could not load data from one or both sources.")
return
# T1 dims: 8-27 (eigenvalue velocity features)
# T2 dims: 28-77 (per-asset z-scores)
T1_DIMS = list(range(8, 28))
T2_DIMS = list(range(28, 78))
report = {
"n_5s_samples": len(X5),
"n_1m_samples": len(X1),
"5s_days": days5,
"1m_days": days1,
}
print("\n--- T1 eigenvalue velocity features (dims 8-27) ---")
ks_t1 = ks_test_per_dim(X5, X1, T1_DIMS)
sc_t1 = scale_check(X5, X1, T1_DIMS)
frob_t1, frob_t1_norm = corr_frobenius(X5, X1, T1_DIMS)
pval_pass_t1 = sum(1 for v in ks_t1.values() if v['p'] > 0.05 or v.get('trivial'))
scale_pass_t1 = sum(1 for v in sc_t1.values()
if 0.1 <= v['std_ratio'] <= 10 and v['std_ratio'] != float('inf'))
print(f" KS p>0.05: {pval_pass_t1}/{len(T1_DIMS)} dims")
print(f" std_ratio in [0.1,10]: {scale_pass_t1}/{len(T1_DIMS)} dims")
print(f" Correlation matrix Frobenius dist: {frob_t1:.3f} (normalised: {frob_t1_norm:.3f})")
print(" Per-dim std ratios (5s/1m):")
for d in T1_DIMS:
r = sc_t1[d]['std_ratio']
flag = "OK" if 0.1 <= r <= 10 else "MISMATCH"
print(f" dim{d:3d}: std_5s={sc_t1[d]['std_5s']:.4f} std_1m={sc_t1[d]['std_1m']:.4f} ratio={r:.2f} {flag}")
print("\n--- T2 per-asset z-score features (dims 28-77) ---")
ks_t2 = ks_test_per_dim(X5, X1, T2_DIMS)
sc_t2 = scale_check(X5, X1, T2_DIMS)
pval_pass_t2 = sum(1 for v in ks_t2.values() if v['p'] > 0.05 or v.get('trivial'))
scale_pass_t2 = sum(1 for v in sc_t2.values()
if 0.1 <= v['std_ratio'] <= 10 and v['std_ratio'] != float('inf'))
print(f" KS p>0.05: {pval_pass_t2}/{len(T2_DIMS)} dims")
print(f" std_ratio in [0.1,10]: {scale_pass_t2}/{len(T2_DIMS)} dims")
print("\n--- PCA cosine similarity (T1+T2 joint, top-4 components) ---")
joint_dims = T1_DIMS + T2_DIMS
pca_cos = pca_cosine(X5[:, joint_dims], X1[:, joint_dims], n_components=4)
for i, c in enumerate(pca_cos):
flag = "OK" if c >= 0.80 else "DIVERGED"
print(f" PC{i+1}: cosine={c:.3f} {flag}")
# Pass/fail verdict
ks_ok = (pval_pass_t1 / len(T1_DIMS)) >= 0.70
scale_ok = (scale_pass_t1 / len(T1_DIMS)) >= 0.70
pca_ok = pca_cos[0] >= 0.80 and pca_cos[1] >= 0.80
frob_ok = frob_t1_norm < 0.40
verdict = {
"ks_pass": bool(ks_ok),
"scale_pass": bool(scale_ok),
"pca_pass": bool(pca_ok),
"frob_pass": bool(frob_ok),
"PROCEED": bool(ks_ok and scale_ok and pca_ok and frob_ok),
}
print("\n=== VERDICT ===")
print(f" KS distribution match: {'PASS' if ks_ok else 'FAIL'} ({pval_pass_t1}/{len(T1_DIMS)} dims pass)")
print(f" Scale match: {'PASS' if scale_ok else 'FAIL'} ({scale_pass_t1}/{len(T1_DIMS)} dims pass)")
print(f" PCA structure match: {'PASS' if pca_ok else 'FAIL'} (PC1={pca_cos[0]:.3f} PC2={pca_cos[1]:.3f})")
print(f" Corr matrix alignment: {'PASS' if frob_ok else 'FAIL'} (normalised Frobenius={frob_t1_norm:.3f} < 0.40)")
print(f"\n PROCEED with 1m training: {'YES' if verdict['PROCEED'] else 'NO'}")
if not verdict['PROCEED']:
print(" --> Feature distributions diverge across resolutions.")
print(" --> A 1m-trained model would learn different physics than 5s production.")
print(" --> Training on 1m would NOT yield useful representations at 5s inference.")
print(" --> Alternative: use time-normalised features (fixed-time windows, not bar-count windows)")
else:
print(" --> Feature distributions are compatible across resolutions.")
print(" --> 1m training on 2021-2024 data is a valid OOS approach.")
report.update({
"t1_ks": {str(k): v for k,v in ks_t1.items()},
"t1_scale": {str(k): v for k,v in sc_t1.items()},
"t2_ks": {str(k): v for k,v in ks_t2.items()},
"t2_scale": {str(k): v for k,v in sc_t2.items()},
"pca_cosines": pca_cos,
"t1_frob_norm": frob_t1_norm,
"verdict": verdict,
})
with open(OUT, 'w') as f:
json.dump(report, f, indent=2)
print(f"\nReport: {OUT}")
if __name__ == '__main__':
main()

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,287 @@
"""
Task 5: Backtest with proxy_B Flint Gate vs Gold Standard.
Tests three gate configs against the gold baseline:
Gold: ROI~+44.89%, PF~1.123, DD~14.95%, Sharpe~2.50, Trades~2128
(same 55-day NG3 5s dataset, same engine stack)
Gate variants:
A. No gate (baseline reproduction — sanity check)
B. Fixed threshold=0.0 (allow when proxy_B > 0)
C. Adaptive p50 (allow when proxy_B > rolling median)
D. Adaptive p75 (allow when proxy_B > rolling p75)
Measures: ROI, PF, DD, WR, Sharpe, Trades, gate suppression rate.
DOES NOT MODIFY ANY PRODUCTION CODE.
"""
import sys, time, math
sys.stdout.reconfigure(encoding='utf-8', errors='replace')
from pathlib import Path
from datetime import datetime
import numpy as np
import pandas as pd
_HERE = Path(__file__).resolve().parent
_ND_ROOT = _HERE.parent # nautilus_dolphin/ outer dir — contains nautilus_dolphin pkg + mc/
# Insert ND_ROOT at index 0 so it takes priority over any stub nautilus_dolphin at project root
sys.path.insert(0, str(_ND_ROOT))
print("Compiling numba kernels...")
t0c = time.time()
from nautilus_dolphin.nautilus.alpha_asset_selector import compute_irp_nb, compute_ars_nb, rank_assets_irp_nb
from nautilus_dolphin.nautilus.alpha_bet_sizer import compute_sizing_nb
from nautilus_dolphin.nautilus.alpha_signal_generator import check_dc_nb
from nautilus_dolphin.nautilus.ob_features import (
OBFeatureEngine, compute_imbalance_nb, compute_depth_1pct_nb,
compute_depth_quality_nb, compute_fill_probability_nb, compute_spread_proxy_nb,
compute_depth_asymmetry_nb, compute_imbalance_persistence_nb,
compute_withdrawal_velocity_nb, compute_market_agreement_nb, compute_cascade_signal_nb,
)
from nautilus_dolphin.nautilus.ob_provider import MockOBProvider
from nautilus_dolphin.nautilus.esf_alpha_orchestrator import NDAlphaEngine
from nautilus_dolphin.nautilus.adaptive_circuit_breaker import AdaptiveCircuitBreaker
# DolphinForewarner skipped — pickle hangs on sklearn 1.8 vs 1.7.1 mismatch; 0 interventions anyway
from mc.mc_ml import DolphinForewarner
from alpha_signal_generator_flint_gate import FlintGatedEngine
_p = np.array([1.0, 2.0, 3.0], dtype=np.float64)
compute_irp_nb(_p, -1); compute_ars_nb(1.0, 0.5, 0.01)
rank_assets_irp_nb(np.ones((10, 2), dtype=np.float64), 8, -1, 5, 500.0, 20, 0.20)
compute_sizing_nb(-0.03, -0.02, -0.05, 3.0, 0.5, 5.0, 0.20, True, True, 0.0,
np.zeros(4, dtype=np.int64), np.zeros(4, dtype=np.int64),
np.zeros(5, dtype=np.float64), 0, -1, 0.01, 0.04)
check_dc_nb(_p, 3, 1, 0.75)
_b = np.array([100.0, 200.0, 300.0, 400.0, 500.0], dtype=np.float64)
_a = np.array([110.0, 190.0, 310.0, 390.0, 510.0], dtype=np.float64)
compute_imbalance_nb(_b, _a); compute_depth_1pct_nb(_b, _a)
compute_market_agreement_nb(np.array([0.1, -0.05], dtype=np.float64), 2)
compute_cascade_signal_nb(np.array([-0.05, -0.15], dtype=np.float64), 2, -0.10)
print(f" JIT: {time.time() - t0c:.1f}s")
VBT_DIR = Path(r"C:\Users\Lenovo\Documents\- DOLPHIN NG HD HCM TSF Predict\vbt_cache")
META_COLS = {'timestamp', 'scan_number', 'v50_lambda_max_velocity', 'v150_lambda_max_velocity',
'v300_lambda_max_velocity', 'v750_lambda_max_velocity', 'vel_div',
'instability_50', 'instability_150'}
ENGINE_KWARGS = dict(
initial_capital=25000.0, vel_div_threshold=-0.02, vel_div_extreme=-0.05,
min_leverage=0.5, max_leverage=5.0, leverage_convexity=3.0,
fraction=0.20, fixed_tp_pct=0.0095, stop_pct=1.0, max_hold_bars=120,
use_direction_confirm=True, dc_lookback_bars=7, dc_min_magnitude_bps=0.75,
dc_skip_contradicts=True, dc_leverage_boost=1.0, dc_leverage_reduce=0.5,
use_asset_selection=True, min_irp_alignment=0.45,
use_sp_fees=True, use_sp_slippage=True,
sp_maker_entry_rate=0.62, sp_maker_exit_rate=0.50,
use_ob_edge=True, ob_edge_bps=5.0, ob_confirm_rate=0.40,
lookback=100, use_alpha_layers=True, use_dynamic_leverage=True, seed=42,
)
MC_MODELS_DIR = str(Path(r"C:\Users\Lenovo\Documents\- DOLPHIN NG HD HCM TSF Predict\nautilus_dolphin\mc_results\models"))
MC_BASE_CFG = {
'trial_id': 0,
'vel_div_threshold': -0.020, 'vel_div_extreme': -0.050,
'use_direction_confirm': True, 'dc_lookback_bars': 7,
'dc_min_magnitude_bps': 0.75, 'dc_skip_contradicts': True,
'dc_leverage_boost': 1.00, 'dc_leverage_reduce': 0.50,
'vd_trend_lookback': 10, 'min_leverage': 0.50,
'max_leverage': 5.00, 'leverage_convexity': 3.00, 'fraction': 0.20,
'use_alpha_layers': True, 'use_dynamic_leverage': True,
'fixed_tp_pct': 0.0095, 'stop_pct': 1.00, 'max_hold_bars': 120,
'use_sp_fees': True, 'use_sp_slippage': True,
'sp_maker_entry_rate': 0.62, 'sp_maker_exit_rate': 0.50,
'use_ob_edge': True, 'ob_edge_bps': 5.00, 'ob_confirm_rate': 0.40,
'ob_imbalance_bias': -0.09, 'ob_depth_scale': 1.00,
'use_asset_selection': True, 'min_irp_alignment': 0.45, 'lookback': 100,
'acb_beta_high': 0.80, 'acb_beta_low': 0.20, 'acb_w750_threshold_pct': 60,
}
print("\nLoading MC-Forewarner...")
try:
forewarner = DolphinForewarner(models_dir=MC_MODELS_DIR)
print(" MC-Forewarner ready")
except Exception as e:
print(f" [WARN] MC-Forewarner failed to load: {e} — running without it")
forewarner = None
# ── Load data ─────────────────────────────────────────────────────
parquet_files = sorted(VBT_DIR.glob("*.parquet"))
parquet_files = [p for p in parquet_files if 'catalog' not in str(p)]
acb_master = AdaptiveCircuitBreaker()
date_strings = [pf.stem for pf in parquet_files]
acb_master.preload_w750(date_strings)
all_vols = []
for pf in parquet_files[:2]:
df = pd.read_parquet(pf)
if 'BTCUSDT' not in df.columns: continue
pr = df['BTCUSDT'].values
for i in range(60, len(pr)):
seg = pr[max(0,i-50):i]
if len(seg)<10: continue
v = float(np.std(np.diff(seg)/seg[:-1]))
if v > 0: all_vols.append(v)
vol_p60 = float(np.percentile(all_vols, 60))
pq_data = {}
all_assets = set()
for pf in parquet_files:
df = pd.read_parquet(pf)
ac = [c for c in df.columns if c not in META_COLS]
all_assets.update(ac)
bp = df['BTCUSDT'].values if 'BTCUSDT' in df.columns else None
dv = np.full(len(df), np.nan)
if bp is not None:
for i in range(50, len(bp)):
seg = bp[max(0,i-50):i]
if len(seg)<10: continue
dv[i] = float(np.std(np.diff(seg)/seg[:-1]))
pq_data[pf.stem] = (df, ac, dv)
OB_ASSETS = sorted(list(all_assets))
_mock_ob = MockOBProvider(
imbalance_bias=-0.09, depth_scale=1.0, assets=OB_ASSETS,
imbalance_biases={"BTCUSDT": -0.086, "ETHUSDT": -0.092,
"BNBUSDT": +0.05, "SOLUSDT": +0.05},
)
ob_eng = OBFeatureEngine(_mock_ob)
ob_eng.preload_date("mock", OB_ASSETS)
# ── Helpers ───────────────────────────────────────────────────────
def run_backtest(engine_cls, engine_kwargs, name, gate_kwargs=None):
"""Run full 55-day backtest. Returns metrics dict.
Metrics use the SAME methodology as test_pf_dynamic_beta_validate.py:
- PF: pnl_absolute (dollar-weighted, matches gold script)
- DD: day-end capital snapshots (not trade-level curve)
- Sharpe: daily P&L, annualized with sqrt(365)
"""
gate_kwargs = gate_kwargs or {}
acb = AdaptiveCircuitBreaker()
acb.preload_w750(date_strings)
if engine_cls is NDAlphaEngine:
eng = NDAlphaEngine(**engine_kwargs)
else:
eng = FlintGatedEngine(**engine_kwargs, **gate_kwargs)
eng.set_ob_engine(ob_eng)
eng.set_acb(acb)
if forewarner is not None:
eng.set_mc_forewarner(forewarner, MC_BASE_CFG)
eng.set_esoteric_hazard_multiplier(0.0)
daily_caps = []
daily_pnls = []
for pf in parquet_files:
ds = pf.stem
df, acols, dvol = pq_data[ds]
cap_before = eng.capital
vol_ok = np.where(np.isfinite(dvol), dvol > vol_p60, False)
stats = eng.process_day(ds, df, acols, vol_regime_ok=vol_ok)
daily_caps.append(eng.capital)
daily_pnls.append(eng.capital - cap_before)
tr = eng.trade_history
n = len(tr)
if n == 0:
return {'name': name, 'roi': 0, 'pf': 0, 'dd': 0, 'wr': 0, 'sharpe': 0,
'trades': 0, 'suppressed': 0, 'suppression_rate': 0}
roi = (eng.capital - 25000.0) / 25000.0 * 100.0
# PF: dollar-weighted (matches gold script which uses pnl_absolute)
def _abs(t):
return t.pnl_absolute if hasattr(t, 'pnl_absolute') else t.pnl_pct * 250.0
wins = [t for t in tr if _abs(t) > 0]
losses = [t for t in tr if _abs(t) <= 0]
wr = len(wins) / n * 100.0
gross_profit = sum(_abs(t) for t in wins)
gross_loss = abs(sum(_abs(t) for t in losses))
pf = gross_profit / max(gross_loss, 1e-9)
# DD: day-end capital snapshots (matches gold script)
peak_cap = 25000.0
max_dd = 0.0
for cap in daily_caps:
peak_cap = max(peak_cap, cap)
dd = (peak_cap - cap) / peak_cap * 100.0
max_dd = max(max_dd, dd)
# Sharpe: daily P&L annualized (matches gold script)
dr = np.array([p / 25000.0 * 100.0 for p in daily_pnls])
sharpe = float(dr.mean() / (dr.std() + 1e-9) * math.sqrt(365)) if len(dr) > 1 else 0.0
suppressed = getattr(eng, 'gate_suppressed', 0)
allowed = getattr(eng, 'gate_allowed', 0)
sup_rate = suppressed / max(1, suppressed + allowed) * 100.0
return {
'name': name,
'roi': roi, 'pf': pf, 'dd': max_dd, 'wr': wr, 'sharpe': sharpe,
'trades': n, 'suppressed': suppressed, 'suppression_rate': sup_rate,
}
# ── Run all configs ───────────────────────────────────────────────
GOLD = {'name': 'GOLD REFERENCE', 'roi': 88.55, 'pf': 1.215, 'dd': 15.05,
'wr': 50.5, 'sharpe': 4.38, 'trades': 2155}
configs = [
(NDAlphaEngine, {}, 'A. Baseline (no gate)', {}),
(FlintGatedEngine, {}, 'B. Gate: proxy_B > 0.00 (fixed)', {'proxy_b_threshold': 0.00}),
]
results = []
for engine_cls, _, name, gate_kw in configs:
print(f"\n{'='*55}")
print(f"Running: {name}")
t0 = time.time()
r = run_backtest(engine_cls, ENGINE_KWARGS.copy(), name, gate_kw)
r['elapsed'] = time.time() - t0
results.append(r)
print(f" Done in {r['elapsed']:.1f}s Trades={r['trades']} ROI={r['roi']:.2f}% PF={r['pf']:.4f}")
# ── Final comparison ──────────────────────────────────────────────
print("\n" + "="*75)
print("FLINT GATE vs GOLD STANDARD — FINAL COMPARISON")
print("="*75)
hdr = f"{'Config':<35} {'ROI%':>7} {'PF':>6} {'DD%':>6} {'WR%':>6} {'Sharpe':>7} {'Trades':>7} {'Supp%':>7}"
print(hdr)
print("-"*75)
g = GOLD
print(f"{'*** GOLD REFERENCE ***':<35} {g['roi']:>7.2f} {g['pf']:>6.4f} {g['dd']:>6.2f} {'N/A':>6} {g['sharpe']:>7.2f} {g['trades']:>7d} {'N/A':>7}")
print("-"*75)
for r in results:
print(f"{r['name']:<35} {r['roi']:>7.2f} {r['pf']:>6.4f} {r['dd']:>6.2f} "
f"{r['wr']:>6.2f} {r['sharpe']:>7.3f} {r['trades']:>7d} {r['suppression_rate']:>7.1f}%")
print("="*75)
print("\nLEGEND:")
print(" ROI: Return on Initial Capital ($25k)")
print(" PF : Profit Factor (gross profit / gross loss)")
print(" DD : Max Drawdown from equity peak")
print(" WR : Win Rate")
print(" Sharpe: trade-based Sharpe")
print(" Supp%: % of entry attempts suppressed by gate")
print("\n VERDICT:")
base = results[0] if results else None
best_gated = max(results[1:], key=lambda r: r['pf']) if len(results) > 1 else None
if base and best_gated:
pf_delta = best_gated['pf'] - base['pf']
roi_delta = best_gated['roi'] - base['roi']
dd_delta = best_gated['dd'] - base['dd']
print(f" Best gate ({best_gated['name']}):")
print(f" PF: {base['pf']:.4f}{best_gated['pf']:.4f} ({pf_delta:+.4f})")
print(f" ROI: {base['roi']:.2f}% → {best_gated['roi']:.2f}% ({roi_delta:+.2f}pp)")
print(f" DD: {base['dd']:.2f}% → {best_gated['dd']:.2f}% ({dd_delta:+.2f}pp)")
print(f" Trades: {base['trades']}{best_gated['trades']} ({best_gated['suppression_rate']:.1f}% suppressed)")
if best_gated['pf'] > base['pf'] * 1.01:
print(" → GATE IS BENEFICIAL: PF improved >1%")
elif best_gated['pf'] > base['pf']:
print(" → GATE SHOWS MARGINAL IMPROVEMENT")
else:
print(" → GATE IS NEUTRAL/NEGATIVE: no improvement over baseline")

View File

@@ -0,0 +1,468 @@
"""
run_trace_backtest.py — D_LIQ_GOLD full 56-day backtest with painstaking per-tick
and per-trade state logging. Streams to CSV; never accumulates more than one day
of ticks in RAM (~1.2 MB). No PyTorch, no dvae/__init__, no ensemble warmup.
Outputs (dvae/trace/):
tick_trace.csv — one row per valid bar (346k+ rows)
trade_trace.csv — one row per closed trade (~2155 rows)
daily_trace.csv — one row per day (56 rows)
summary.json — ROI / DD / T / Calmar at end
Tick CSV columns:
date, bar_ri, global_bar, btc_price, vel_div, vol_ok,
proxy_b, base_max_lev, sizer_max_lev, regime_size_mult,
capital, pos_open, pos_dir, pos_entry_price, pos_entry_bar,
pos_entry_lev, acb_boost, day_beta, mc_status, dd_halt, event
Trade CSV columns:
trade_id, asset, date_exit, direction, entry_price, exit_price,
entry_bar, exit_bar, notional, leverage, pnl_pct, pnl_abs,
exit_reason, bars_held, proxy_b_at_entry, proxy_b_at_exit,
capital_before_exit, capital_after_exit
Daily CSV columns:
date, cap_start, cap_end, pnl, n_trades, acb_boost, day_beta,
mc_status, max_dd_intraday
Run after freeing RAM. Prints progress every 5 days.
"""
import sys, os, csv, json, gc, math, time
from pathlib import Path
import numpy as np
import pandas as pd
# ------ Path setup (MUST come before any nautilus_dolphin import) ----------
_HERE = Path(__file__).resolve().parent # dvae/
_ND_ROOT = _HERE.parent # nautilus_dolphin/
sys.path.insert(0, str(_ND_ROOT))
# Add dvae/ directly so we can import exp_shared without triggering dvae/__init__
sys.path.insert(0, str(_HERE))
# ------ Imports (NO dvae package, NO torch) --------------------------------
from exp_shared import ENGINE_KWARGS, MC_BASE_CFG, VBT_DIR, MC_MODELS_DIR, META_COLS
from nautilus_dolphin.nautilus.proxy_boost_engine import (
create_d_liq_engine, LiquidationGuardEngine,
)
from nautilus_dolphin.nautilus.adaptive_circuit_breaker import AdaptiveCircuitBreaker
from nautilus_dolphin.nautilus.ob_features import OBFeatureEngine
from nautilus_dolphin.nautilus.ob_provider import MockOBProvider
# ------ Output directory ---------------------------------------------------
TRACE_DIR = _HERE / 'trace'
TRACE_DIR.mkdir(exist_ok=True)
TICK_CSV = TRACE_DIR / 'tick_trace.csv'
TRADE_CSV = TRACE_DIR / 'trade_trace.csv'
DAILY_CSV = TRACE_DIR / 'daily_trace.csv'
SUMMARY = TRACE_DIR / 'summary.json'
TICK_HEADER = [
'date', 'bar_ri', 'global_bar', 'btc_price', 'vel_div', 'vol_ok',
'proxy_b', 'base_max_lev', 'sizer_max_lev', 'regime_size_mult',
'capital', 'pos_open', 'pos_dir', 'pos_entry_price', 'pos_entry_bar',
'pos_entry_lev', 'acb_boost', 'day_beta', 'mc_status', 'dd_halt', 'event',
]
TRADE_HEADER = [
'trade_id', 'asset', 'date_exit', 'direction', 'entry_price', 'exit_price',
'entry_bar', 'exit_bar', 'notional', 'leverage', 'pnl_pct', 'pnl_abs',
'exit_reason', 'bars_held',
'proxy_b_at_entry', 'proxy_b_at_exit',
'capital_before_exit', 'capital_after_exit',
]
DAILY_HEADER = [
'date', 'cap_start', 'cap_end', 'pnl', 'n_trades',
'acb_boost', 'day_beta', 'mc_status', 'max_dd_intraday',
]
# ===========================================================================
# TracingLiquidationEngine — thin subclass that instruments step_bar
# ===========================================================================
class TracingLiquidationEngine(LiquidationGuardEngine):
"""
Wraps LiquidationGuardEngine with per-bar state capture.
Overrides step_bar() to record tick + trade events.
All trace data streamed to CSV via external writers (no RAM accumulation).
"""
def __init__(self, tick_writer, trade_writer, **kwargs):
super().__init__(**kwargs)
self._tw = tick_writer
self._trd = trade_writer
self._cur_date = ''
self._entry_pb_cache: dict = {} # entry_bar → proxy_b at entry
self._tick_buf: list = [] # per-day buffer, flushed at end_day
self._trade_buf: list = [] # per-day buffer, flushed at end_day
self._day_cap_hi = 0.0
self._day_cap_lo = float('inf')
# ── Override begin_day to track date and intraday extremes ───────────────
def begin_day(self, date_str, posture='APEX', direction=None):
super().begin_day(date_str, posture=posture, direction=direction)
self._cur_date = date_str
self._tick_buf = []
self._trade_buf = []
self._day_cap_hi = self.capital
self._day_cap_lo = self.capital
# ── Override step_bar to capture tick state ──────────────────────────────
def step_bar(self, bar_idx, vel_div, prices, vol_regime_ok=True,
price_histories=None, v50_vel=0.0, v750_vel=0.0):
# --- Pre-call state snapshot ---
proxy_b_now = float(self._current_proxy_b)
n_before = len(self.trade_history)
pos_before = self.position # None or NDPosition
cap_before = self.capital
# --- Actual engine work ---
result = super().step_bar(
bar_idx=bar_idx, vel_div=vel_div, prices=prices,
vol_regime_ok=vol_regime_ok, price_histories=price_histories,
v50_vel=v50_vel, v750_vel=v750_vel,
)
# --- Post-call state snapshot ---
n_after = len(self.trade_history)
pos_after = self.position
cap_after = self.capital
# Track intraday capital range
if cap_after > self._day_cap_hi: self._day_cap_hi = cap_after
if cap_after < self._day_cap_lo: self._day_cap_lo = cap_after
# Determine event
new_entry = (pos_before is None and pos_after is not None)
new_exit = (n_after > n_before)
event = 'NONE'
if new_entry and new_exit:
event = 'ENTRY+EXIT' # same-bar close+reopen (rare)
elif new_entry:
event = 'ENTRY'
elif new_exit:
event = 'EXIT'
# Cache proxy_B at entry time
if new_entry and pos_after is not None:
self._entry_pb_cache[getattr(pos_after, 'entry_bar', bar_idx)] = proxy_b_now
# Capture trade rows for completed trades
if new_exit:
for t in self.trade_history[n_before:]:
entry_pb = self._entry_pb_cache.pop(getattr(t, 'entry_bar', bar_idx), proxy_b_now)
self._trade_buf.append((
t.trade_id,
t.asset,
self._cur_date,
t.direction,
round(t.entry_price, 4),
round(t.exit_price, 4),
t.entry_bar,
t.exit_bar,
round(t.notional, 2),
round(t.leverage, 4),
round(t.pnl_pct, 6),
round(t.pnl_absolute,4),
t.exit_reason,
t.bars_held,
round(entry_pb, 6),
round(proxy_b_now, 6),
round(cap_before, 4),
round(cap_after, 4),
))
# BTC close price (primary signal asset)
btc_price = prices.get('BTCUSDT', 0.0)
# Position fields
pos_dir = pos_entry_price = pos_entry_lev = 0.0
pos_entry_bar_i = -1
pos_open = 0
if pos_after is not None:
pos_open = 1
pos_dir = int(getattr(pos_after, 'direction', 0))
pos_entry_price = round(float(getattr(pos_after, 'entry_price', 0.0)), 4)
pos_entry_bar_i = int(getattr(pos_after, 'entry_bar', -1))
pos_entry_lev = round(float(getattr(pos_after, 'leverage', 0.0)), 4)
# global_bar was incremented INSIDE super().step_bar, so -1 gives current
global_bar = self._global_bar_idx - 1
self._tick_buf.append((
self._cur_date,
bar_idx,
global_bar,
round(btc_price, 4),
round(vel_div, 6),
int(vol_regime_ok),
round(proxy_b_now, 6),
round(self.base_max_leverage, 2),
round(self.bet_sizer.max_leverage, 2),
round(getattr(self, 'regime_size_mult', 1.0), 4),
round(cap_after, 4),
pos_open,
pos_dir,
pos_entry_price,
pos_entry_bar_i,
pos_entry_lev,
round(getattr(self, '_day_base_boost', 1.0), 4),
round(getattr(self, '_day_beta', 0.0), 4),
getattr(self, '_day_mc_status', 'OK'),
int(getattr(self, 'regime_dd_halt', False)),
event,
))
return result
# ── Override end_day to flush buffers ────────────────────────────────────
def end_day(self):
result = super().end_day()
# Flush tick buffer
if self._tick_buf:
self._tw.writerows(self._tick_buf)
# Flush trade buffer
if self._trade_buf:
self._trd.writerows(self._trade_buf)
self._tick_buf = []
self._trade_buf = []
return result
# ===========================================================================
# Main
# ===========================================================================
def main():
t_start = time.time()
print("=== D_LIQ_GOLD Trace Backtest ===")
print(f" Outputs → {TRACE_DIR}")
# -- Load data (GOLD method: float64 pq_data, seg-based vol_p60) -----------
print(" Loading data (gold method)...")
parquet_files = sorted(VBT_DIR.glob("*.parquet"))
parquet_files = [p for p in parquet_files if 'catalog' not in str(p)]
date_strings = [p.stem for p in parquet_files]
print(f" {len(parquet_files)} parquet files")
# Gold vol_p60: 2 files, range(60), seg-based, v>0 filter
all_vols = []
for pf in parquet_files[:2]:
df = pd.read_parquet(pf)
if 'BTCUSDT' in df.columns:
pr = df['BTCUSDT'].values
for i in range(60, len(pr)):
seg = pr[max(0, i-50):i]
if len(seg) < 10: continue
v = float(np.std(np.diff(seg) / seg[:-1]))
if v > 0: all_vols.append(v)
del df
vol_p60 = float(np.percentile(all_vols, 60)) if all_vols else 0.0002
print(f" vol_p60 (gold) = {vol_p60:.8f}")
# Get asset list from first file
df0 = pd.read_parquet(parquet_files[0])
OB_ASSETS = sorted([c for c in df0.columns if c not in META_COLS])
del df0
print(f" OB assets: {len(OB_ASSETS)}")
mock_ob = MockOBProvider(
imbalance_bias=-0.09, depth_scale=1.0, assets=OB_ASSETS,
imbalance_biases={"BTCUSDT": -0.086, "ETHUSDT": -0.092,
"BNBUSDT": +0.05, "SOLUSDT": +0.05},
)
ob_eng = OBFeatureEngine(mock_ob)
ob_eng.preload_date("mock", OB_ASSETS)
# -- Load MC-Forewarner (optional) ----------------------------------------
fw = None
try:
from mc.mc_ml import DolphinForewarner
fw = DolphinForewarner(models_dir=MC_MODELS_DIR)
print(" MC-Forewarner: loaded")
except Exception as e:
print(f" MC-Forewarner: unavailable ({e})")
# -- Open output CSVs (streaming) -----------------------------------------
tick_fh = open(TICK_CSV, 'w', newline='', encoding='utf-8')
trade_fh = open(TRADE_CSV, 'w', newline='', encoding='utf-8')
daily_fh = open(DAILY_CSV, 'w', newline='', encoding='utf-8')
tick_w = csv.writer(tick_fh)
trade_w = csv.writer(trade_fh)
daily_w = csv.writer(daily_fh)
tick_w.writerow(TICK_HEADER)
trade_w.writerow(TRADE_HEADER)
daily_w.writerow(DAILY_HEADER)
# -- Build engine ---------------------------------------------------------
print(" Building TracingLiquidationEngine...")
acb = AdaptiveCircuitBreaker()
acb.preload_w750(date_strings)
eng = TracingLiquidationEngine(
tick_writer=tick_w, trade_writer=trade_w,
**ENGINE_KWARGS,
)
eng.set_ob_engine(ob_eng)
eng.set_acb(acb)
if fw is not None:
eng.set_mc_forewarner(fw, MC_BASE_CFG)
eng.set_esoteric_hazard_multiplier(0.0) # fixed function: respects _extended_soft_cap=8.0
print(f" base_max_lev={eng.base_max_leverage} abs_max_lev={eng.abs_max_leverage} "
f"sizer_max_lev={eng.bet_sizer.max_leverage}")
# -- Run 56-day backtest (lazy loading, float32, gc per day) --------------
print(" Running backtest (lazy load, float32 per day)...")
print()
daily_caps = []
running_vols = []
peak_cap = 25000.0
max_dd = 0.0
for i, pf in enumerate(parquet_files):
ds = pf.stem
# Lazy load + cast to float32 to save RAM
df = pd.read_parquet(pf)
for c in df.columns:
if df[c].dtype == 'float64':
df[c] = df[c].astype('float32')
acols = [c for c in df.columns if c not in META_COLS]
# Per-day OB preload
ob_eng.preload_date(ds, OB_ASSETS)
# Compute dvol for this day
bp = df['BTCUSDT'].values if 'BTCUSDT' in df.columns else None
dvol = np.zeros(len(df), dtype=np.float32)
if bp is not None:
rets = np.diff(bp.astype('float64')) / (bp[:-1].astype('float64') + 1e-9)
for j in range(50, len(rets)):
v = np.std(rets[j-50:j])
dvol[j+1] = v
if v > 0:
running_vols.append(v)
vp60 = np.percentile(running_vols, 60) if len(running_vols) > 1000 else vol_p60
vol_ok = np.where(dvol > 0, dvol > vp60, False)
cap_before = eng.capital
n_before = len(eng.trade_history)
# process_day (which internally calls begin_day → step_bar loop → end_day)
eng.process_day(ds, df, acols, vol_regime_ok=vol_ok)
cap_after = eng.capital
n_after = len(eng.trade_history)
daily_caps.append(cap_after)
# Compute intraday DD (rough)
peak_cap = max(peak_cap, cap_after)
max_dd = max(max_dd, (peak_cap - cap_after) / peak_cap * 100.0)
intra_dd_pct = (eng._day_cap_hi - eng._day_cap_lo) / max(eng._day_cap_hi, 1e-9) * 100.0
# Daily row
daily_w.writerow((
ds,
round(cap_before, 4),
round(cap_after, 4),
round(cap_after - cap_before, 4),
n_after - n_before,
round(getattr(eng, '_day_base_boost', 1.0), 4),
round(getattr(eng, '_day_beta', 0.0), 4),
getattr(eng, '_day_mc_status', 'OK'),
round(intra_dd_pct, 4),
))
# Progress every 5 days
if (i + 1) % 5 == 0 or i == len(parquet_files) - 1:
roi_now = (cap_after - 25000.0) / 25000.0 * 100.0
elapsed = time.time() - t_start
print(f" Day {i+1:3d}/{len(parquet_files)} {ds} "
f"cap={cap_after:.0f} ROI={roi_now:+.2f}% T={n_after} "
f"DD={max_dd:.2f}% ({elapsed:.0f}s)")
# Clear OB cache + GC
if eng.ob_engine is not None:
for attr in ('_preloaded_placement', '_preloaded_signal',
'_preloaded_market', '_ts_to_idx'):
try: getattr(eng.ob_engine, attr).clear()
except Exception: pass
del df
gc.collect()
# -- Close CSV files -------------------------------------------------------
tick_fh.flush(); tick_fh.close()
trade_fh.flush(); trade_fh.close()
daily_fh.flush(); daily_fh.close()
# -- Final stats -----------------------------------------------------------
tr = eng.trade_history
n = len(tr)
roi = (eng.capital - 25000.0) / 25000.0 * 100.0
def _abs(t):
return t.pnl_absolute if hasattr(t, 'pnl_absolute') else t.pnl_pct * 250.0
wins = [t for t in tr if _abs(t) > 0]
losses = [t for t in tr if _abs(t) <= 0]
wr = len(wins) / n * 100.0 if n > 0 else 0.0
pf = sum(_abs(t) for t in wins) / max(abs(sum(_abs(t) for t in losses)), 1e-9)
dr = np.array([(c - 25000.0 if i == 0 else daily_caps[i] - daily_caps[i-1]) / 25000.0 * 100.0
for i, c in enumerate(daily_caps)])
sharpe = float(dr.mean() / (dr.std() + 1e-9) * math.sqrt(365)) if len(dr) > 1 else 0.0
calmar = roi / max(max_dd, 1e-9)
liq_stops = getattr(eng, 'liquidation_stops', 0)
summary = dict(
roi=round(roi, 4),
dd=round(max_dd, 4),
calmar=round(calmar, 4),
pf=round(pf, 4),
wr=round(wr, 4),
sharpe=round(sharpe, 4),
trades=n,
liq_stops=liq_stops,
capital_final=round(eng.capital, 4),
elapsed_s=round(time.time() - t_start, 1),
tick_csv=str(TICK_CSV),
trade_csv=str(TRADE_CSV),
daily_csv=str(DAILY_CSV),
gold_roi=181.81,
gold_dd=17.65,
gold_trades=2155,
)
with open(SUMMARY, 'w', encoding='utf-8') as f:
json.dump(summary, f, indent=2)
print()
print("=== RESULTS ===")
print(f" ROI = {roi:+.2f}% (gold 181.81%)")
print(f" DD = {max_dd:.2f}% (gold 17.65%)")
print(f" Calmar = {calmar:.2f} (gold 10.30)")
print(f" PF = {pf:.4f} (gold ~1.55)")
print(f" WR = {wr:.2f}%")
print(f" T = {n} (gold 2155)")
print(f" liq_stops = {liq_stops}")
print(f" Time = {time.time()-t_start:.0f}s")
print()
roi_ok = abs(roi - 181.81) < 1.0
dd_ok = abs(max_dd - 17.65) < 0.5
t_ok = n == 2155
print(f" ROI match: {'✓ PASS' if roi_ok else '✗ FAIL'} (diff={roi-181.81:+.2f}pp)")
print(f" DD match: {'✓ PASS' if dd_ok else '✗ FAIL'} (diff={max_dd-17.65:+.2f}pp)")
print(f" T match: {'✓ PASS' if t_ok else '✗ FAIL'} (got {n})")
print()
print(f" Tick trace → {TICK_CSV} ({TICK_CSV.stat().st_size//1024}KB)")
print(f" Trade trace → {TRADE_CSV}")
print(f" Daily trace → {DAILY_CSV}")
print(f" Summary → {SUMMARY}")
if __name__ == '__main__':
main()

View File

@@ -0,0 +1,104 @@
import json
import re
import os
from pathlib import Path
from datetime import datetime
BASE = Path(r"C:\Users\Lenovo\Documents")
DIRS = {
"NG1": BASE / "- Dolphin NG",
"NG2": BASE / "- Dolphin NG2",
"NG4": BASE / "- DOLPHIN NG4" / "- Results",
"NG5": BASE / "- Dolphin NG5",
"NG3": BASE / "- Dolphin NG HD (NG3)" / "correlation_arb512" / "eigenvalues"
}
def parse_ts(s):
for fmt in ("%Y-%m-%dT%H:%M:%S.%f", "%Y-%m-%dT%H:%M:%S",
"%Y-%m-%d %H:%M:%S.%f", "%Y-%m-%d %H:%M:%S",
"%Y-%m-%dT%H:%M:%SZ"):
try:
return datetime.strptime(str(s)[:26].replace('Z', '').replace('T', ' '), fmt)
except ValueError:
continue
return None
def get_boundary_json(d, pattern):
if not d.exists(): return None, None, 0
files = sorted(list(d.glob(pattern)))
if not files: return None, None, 0
def extract_ts(f):
try:
with open(f, 'r', encoding='utf-8', errors='replace') as fb:
data = json.load(fb)
return parse_ts(data.get('timestamp'))
except: return None
ts_start = None
for f in files:
ts_start = extract_ts(f)
if ts_start: break
ts_end = None
for f in reversed(files):
ts_end = extract_ts(f)
if ts_end: break
return ts_start, ts_end, len(files)
def get_boundary_ng4(d):
if not d.exists(): return None, None, 0
files = sorted(list(d.glob('*.txt')))
if not files: return None, None, 0
log_re = re.compile(r'(\d{4}-\d{2}-\d{2}T[\d:.]+Z)')
def extract_first_last_ts(f):
first = None
last = None
try:
with open(f, 'r', encoding='utf-8', errors='replace') as fb:
for line in fb:
m = log_re.search(line)
if m:
ts = parse_ts(m.group(1))
if not first: first = ts
last = ts
except: pass
return first, last
ts_min = None
ts_max = None
for f in files:
f_min, f_max = extract_first_last_ts(f)
if not ts_min: ts_min = f_min
if f_max: ts_max = f_max
return ts_min, ts_max, len(files)
def get_boundary_ng3(d):
if not d.exists(): return None, None, 0
subdirs = sorted([s for s in d.iterdir() if s.is_dir() and not s.name.endswith('_SKIP')])
if not subdirs: return None, None, 0
ts_min, _, _ = get_boundary_json(subdirs[0], 'scan_*.json')
_, ts_max, _ = get_boundary_json(subdirs[-1], 'scan_*.json')
total_files = sum(len(list(s.glob('scan_*.json'))) for s in subdirs)
return ts_min, ts_max, total_files
print("--- Targeted Data Archaeology Result ---")
for name, d in DIRS.items():
print(f"Checking {name}...")
if name in ["NG1", "NG2", "NG5"]:
ts_start, ts_end, count = get_boundary_json(d, 'regime_result_*.json')
elif name == "NG4":
ts_start, ts_end, count = get_boundary_ng4(d)
elif name == "NG3":
ts_start, ts_end, count = get_boundary_ng3(d)
if ts_start:
print(f" {name}: {ts_start} to {ts_end} ({count} files)")
else:
print(f" {name}: No data found.")

View File

@@ -0,0 +1,71 @@
"""BRONZE baseline with DAILY equity curve DD (same method as test_dliq_fix_verify.py).
Expected: ROI=88.55%, DD=15.05%, T=2155
"""
import sys, time, math
sys.stdout.reconfigure(encoding='utf-8', errors='replace')
from pathlib import Path
import numpy as np
_HERE = Path(__file__).resolve().parent
sys.path.insert(0, str(_HERE.parent))
from exp_shared import ENGINE_KWARGS, MC_BASE_CFG, load_data, load_forewarner
from nautilus_dolphin.nautilus.esf_alpha_orchestrator import NDAlphaEngine
from nautilus_dolphin.nautilus.adaptive_circuit_breaker import AdaptiveCircuitBreaker
def main():
print("Loading data (gold method: float64, 2-file seg-based vol_p60)...")
d = load_data()
fw = load_forewarner()
print()
kw = ENGINE_KWARGS.copy()
eng = NDAlphaEngine(**kw)
acb = AdaptiveCircuitBreaker()
acb.preload_w750(d['date_strings'])
eng.set_ob_engine(d['ob_eng'])
eng.set_acb(acb)
if fw is not None:
eng.set_mc_forewarner(fw, MC_BASE_CFG)
eng.set_esoteric_hazard_multiplier(0.0)
print(f" base_max_leverage = {eng.base_max_leverage}")
t0 = time.time()
daily_caps = []
for pf_file in d['parquet_files']:
ds = pf_file.stem
df, acols, dvol = d['pq_data'][ds]
vol_ok = np.where(np.isfinite(dvol), dvol > d['vol_p60'], False)
eng.process_day(ds, df, acols, vol_regime_ok=vol_ok)
daily_caps.append(eng.capital)
tr = eng.trade_history
n = len(tr)
roi = (eng.capital - 25000.0) / 25000.0 * 100.0
peak, max_dd = 25000.0, 0.0
for cap in daily_caps:
peak = max(peak, cap)
max_dd = max(max_dd, (peak - cap) / peak * 100.0)
def _abs(t):
return t.pnl_absolute if hasattr(t, 'pnl_absolute') else t.pnl_pct * 250.0
wins = [t for t in tr if _abs(t) > 0]
losses = [t for t in tr if _abs(t) <= 0]
pf = sum(_abs(t) for t in wins) / max(abs(sum(_abs(t) for t in losses)), 1e-9)
wr = len(wins) / n * 100.0 if n > 0 else 0.0
print(f" ROI={roi:+.2f}% T={n} DD={max_dd:.2f}% PF={pf:.4f} WR={wr:.1f}% ({time.time()-t0:.0f}s)")
print()
print(f"BRONZE TARGET: ROI=88.55% T=2155 DD=15.05%")
roi_ok = abs(roi - 88.55) < 1.0
dd_ok = abs(max_dd - 15.05) < 0.5
t_ok = n == 2155
print(f"ROI match: {'PASS' if roi_ok else 'FAIL'} (diff={roi-88.55:+.2f}pp)")
print(f"DD match: {'PASS' if dd_ok else 'FAIL'} (diff={max_dd-15.05:+.2f}pp)")
print(f"T match: {'PASS' if t_ok else 'FAIL'} (got {n})")
if __name__ == '__main__':
main()

View File

@@ -0,0 +1,332 @@
"""
Unit tests for ConvNeXt-1D β-TCVAE (pure numpy).
Tests
-----
T1 DWConv1d forward shape correct
T2 DWConv1d backward numerical gradient check
T3 LayerNorm forward/backward numerical gradient check
T4 ConvNeXtBlock1D output shape == input shape (skip preserved)
T5 ConvNeXtBlock1D backward numerical gradient check
T6 ConvNeXtVAE forward: output shapes correct
T7 ConvNeXtVAE forward/backward: loss is finite, grads finite
T8 Loss decreases over 20 steps on small batch (gradient descent works)
T9 Save/load round-trip: weights preserved
T10 β-TCVAE loss backward: numerical gradient check on z_mu/z_logvar
Run: python test_convnext_dvae.py
"""
import sys
import os
import tempfile
import numpy as np
# ensure local import
sys.path.insert(0, os.path.dirname(__file__))
from convnext_dvae import (
ConvNeXtVAE, DWConv1d, LayerNorm, ConvNeXtBlock1D,
btcvae_loss, btcvae_loss_backward
)
RNG = np.random.RandomState(0)
PASS = []
FAIL = []
def check(name: str, cond: bool, detail: str = ''):
if cond:
print(f' PASS {name}')
PASS.append(name)
else:
print(f' FAIL {name} {detail}')
FAIL.append(name)
def num_grad(f, x, eps=1e-5):
"""Numeric gradient of scalar f at x."""
g = np.zeros_like(x)
it = np.nditer(x, flags=['multi_index'])
while not it.finished:
idx = it.multi_index
orig = x[idx]
x[idx] = orig + eps
fp = f(x)
x[idx] = orig - eps
fm = f(x)
x[idx] = orig
g[idx] = (fp - fm) / (2 * eps)
it.iternext()
return g
# ---------------------------------------------------------------------------
def test_t1_dwconv_shape():
print('\nT1: DWConv1d forward shape')
layer = DWConv1d(8, 7, RNG)
x = RNG.randn(4, 8, 32)
y = layer.forward(x)
check('output_shape', y.shape == (4, 8, 32), str(y.shape))
def test_t2_dwconv_grad():
print('\nT2: DWConv1d backward numerical check')
rng2 = np.random.RandomState(1)
layer = DWConv1d(4, 3, rng2)
x = rng2.randn(2, 4, 8).astype(np.float64) * 0.5
def fwd(w_flat):
layer.w.data[:] = w_flat.reshape(layer.w.data.shape)
out = layer.forward(x)
return float(out.sum())
layer.forward(x)
layer.backward(np.ones((2, 4, 8)))
w0 = layer.w.data.copy()
ng = num_grad(lambda w: fwd(w.ravel()), w0.ravel())
ag = layer.w.grad.ravel()
rel = np.abs(ag - ng) / (np.abs(ng) + 1e-8)
check('dw_max_rel_err', rel.max() < 0.01, f'max_rel={rel.max():.4f}')
# also check dx
def fwd_x(xf):
return float(layer.forward(xf.reshape(2, 4, 8)).sum())
ng_x = num_grad(lambda xf: fwd_x(xf), x.ravel().copy())
layer.forward(x)
gx = layer.backward(np.ones((2, 4, 8))).ravel()
rel_x = np.abs(gx - ng_x) / (np.abs(ng_x) + 1e-8)
check('dx_max_rel_err', rel_x.max() < 0.01, f'max_rel_x={rel_x.max():.4f}')
def test_t3_layernorm_grad():
print('\nT3: LayerNorm backward numerical check')
rng2 = np.random.RandomState(2)
layer = LayerNorm(8)
x = rng2.randn(4, 12, 8).astype(np.float64)
def fwd_gamma(gf):
layer.gamma.data[:] = gf
return float(layer.forward(x).sum())
layer.forward(x)
layer.backward(np.ones((4, 12, 8)))
ag = layer.gamma.grad.copy()
ng = num_grad(lambda gf: fwd_gamma(gf), layer.gamma.data.copy())
rel = np.abs(ag - ng) / (np.abs(ng) + 1e-8)
check('dgamma_max_rel_err', rel.max() < 0.01, f'{rel.max():.4f}')
def fwd_x(xf):
return float(layer.forward(xf.reshape(4, 12, 8)).sum())
dx = layer.backward(np.ones((4, 12, 8))).ravel()
ng_x = num_grad(lambda xf: fwd_x(xf), x.ravel().copy())
rel_x = np.abs(dx - ng_x) / (np.abs(ng_x) + 1e-8)
check('dx_max_rel_err', rel_x.max() < 0.01, f'{rel_x.max():.4f}')
def test_t4_block_shape():
print('\nT4: ConvNeXtBlock1D shape preserved')
rng2 = np.random.RandomState(3)
blk = ConvNeXtBlock1D(16, 7, rng2)
x = rng2.randn(4, 16, 32)
y = blk.forward(x)
check('shape_unchanged', y.shape == x.shape, str(y.shape))
check('skip_active', not np.allclose(y, x)) # block changes values
def test_t5_block_grad():
print('\nT5: ConvNeXtBlock1D backward numerical check (small block)')
rng2 = np.random.RandomState(4)
blk = ConvNeXtBlock1D(4, 3, rng2)
x = rng2.randn(2, 4, 8).astype(np.float64) * 0.3
def fwd_x(xf):
return float(blk.forward(xf.reshape(2, 4, 8)).sum())
blk.forward(x)
dx_an = blk.backward(np.ones((2, 4, 8))).ravel()
ng_x = num_grad(lambda xf: fwd_x(xf), x.ravel().copy())
rel = np.abs(dx_an - ng_x) / (np.abs(ng_x) + 1e-8)
check('dx_max_rel_err', rel.max() < 0.02, f'{rel.max():.4f}')
def test_t6_vae_shapes():
print('\nT6: ConvNeXtVAE forward shapes')
rng2 = np.random.RandomState(5)
model = ConvNeXtVAE(C_in=8, T_in=32, z_dim=16, base_ch=16, n_blocks=2, seed=5)
x = rng2.randn(4, 8, 32)
x_recon, z_mu, z_logvar, z = model.forward(x)
check('x_recon_shape', x_recon.shape == (4, 8, 32), str(x_recon.shape))
check('z_mu_shape', z_mu.shape == (4, 16), str(z_mu.shape))
check('z_logvar_shape',z_logvar.shape == (4, 16), str(z_logvar.shape))
check('x_recon_finite',np.all(np.isfinite(x_recon)))
check('z_mu_finite', np.all(np.isfinite(z_mu)))
def test_t7_vae_backward():
print('\nT7: ConvNeXtVAE backward: grads finite, loss finite')
rng2 = np.random.RandomState(6)
model = ConvNeXtVAE(C_in=8, T_in=32, z_dim=16, base_ch=16, n_blocks=2, seed=6)
x = rng2.randn(8, 8, 32)
eps_noise = rng2.randn(8, 16)
# manual forward with stored eps
z_mu, z_logvar = model.encode(x)
z = z_mu + eps_noise * np.exp(0.5 * z_logvar)
x_recon = model.decode(z)
loss, info = btcvae_loss(x, x_recon, z_mu, z_logvar, z, beta_tc=2.0)
check('loss_finite', np.isfinite(loss), f'loss={loss:.4f}')
for k, v in info.items():
check(f'{k}_finite', np.isfinite(v), f'{k}={v:.4f}')
d_recon, d_z_mu, d_z_logvar = btcvae_loss_backward(
x, x_recon, z_mu, z_logvar, z, eps_noise, beta_tc=2.0
)
model.zero_grad()
dz = model.backward_decode(d_recon)
model.backward_encode(d_z_mu + dz, d_z_logvar)
all_grads = [p.grad for p in model.all_params()]
any_nan = any(np.any(np.isnan(g)) for g in all_grads)
any_inf = any(np.any(np.isinf(g)) for g in all_grads)
all_zero = all(np.all(g == 0) for g in all_grads)
check('grads_no_nan', not any_nan)
check('grads_no_inf', not any_inf)
check('grads_not_all_zero', not all_zero)
def test_t8_loss_decreases():
print('\nT8: Loss decreases over 20 gradient steps')
rng2 = np.random.RandomState(7)
model = ConvNeXtVAE(C_in=8, T_in=32, z_dim=16, base_ch=16, n_blocks=2, seed=7)
x = rng2.randn(16, 8, 32).astype(np.float64)
losses = []
lr = 1e-3
for step in range(20):
rng2_eps = np.random.RandomState(step)
eps_noise = rng2_eps.randn(16, 16)
z_mu, z_logvar = model.encode(x)
z = z_mu + eps_noise * np.exp(0.5 * z_logvar)
x_recon = model.decode(z)
loss, _ = btcvae_loss(x, x_recon, z_mu, z_logvar, z, beta_tc=1.0, alpha_mi=1.0)
losses.append(loss)
d_recon, d_z_mu, d_z_logvar = btcvae_loss_backward(
x, x_recon, z_mu, z_logvar, z, eps_noise, beta_tc=1.0, alpha_mi=1.0
)
model.zero_grad()
dz = model.backward_decode(d_recon)
model.backward_encode(d_z_mu + dz, d_z_logvar)
model.adam_step(lr)
first5 = np.mean(losses[:5])
last5 = np.mean(losses[-5:])
print(f' loss[0:5]={first5:.4f} loss[15:20]={last5:.4f}')
check('loss_decreasing', last5 < first5, f'{last5:.4f} vs {first5:.4f}')
def test_t9_save_load():
print('\nT9: Save/load round-trip')
rng2 = np.random.RandomState(8)
model = ConvNeXtVAE(C_in=8, T_in=32, z_dim=16, base_ch=16, n_blocks=2, seed=8)
x = rng2.randn(4, 8, 32)
# deterministic forward: encode then decode with fixed z_mu (no sampling)
z_mu1, _ = model.encode(x)
x_recon1 = model.decode(z_mu1)
nm = rng2.randn(8).astype(np.float64)
ns = np.abs(rng2.randn(8)).astype(np.float64) + 0.1
with tempfile.NamedTemporaryFile(suffix='.json', delete=False) as f:
path = f.name
try:
model.save(path, norm_mean=nm, norm_std=ns)
model2 = ConvNeXtVAE(C_in=8, T_in=32, z_dim=16, base_ch=16, n_blocks=2, seed=0)
extras = model2.load(path)
check('norm_mean_saved', 'norm_mean' in extras)
check('norm_std_saved', 'norm_std' in extras)
z_mu2, _ = model2.encode(x)
x_recon2 = model2.decode(z_mu2)
check('recon_reproduced', np.allclose(x_recon1, x_recon2, atol=1e-10))
check('z_mu_reproduced', np.allclose(z_mu1, z_mu2, atol=1e-10))
finally:
os.unlink(path)
def test_t10_btcvae_grad():
print('\nT10: beta-TCVAE backward numerical gradient check (z_mu, z_logvar)')
rng2 = np.random.RandomState(9)
B, D = 8, 6
C, T = 4, 8
x = rng2.randn(B, C, T).astype(np.float64)
z_mu = rng2.randn(B, D).astype(np.float64) * 0.5
z_lv = rng2.randn(B, D).astype(np.float64) * 0.3 - 1.0 # negative → small var
eps = rng2.randn(B, D).astype(np.float64)
z = z_mu + eps * np.exp(0.5 * z_lv)
# Use a fixed simple decoder output to avoid recomputing model
x_recon = rng2.randn(B, C, T).astype(np.float64) * 0.5
def loss_fn_mu(mu_flat):
mu = mu_flat.reshape(B, D)
z_ = mu + eps * np.exp(0.5 * z_lv)
l, _ = btcvae_loss(x, x_recon, mu, z_lv, z_, beta_tc=2.0, alpha_mi=1.0)
return l
def loss_fn_lv(lv_flat):
lv = lv_flat.reshape(B, D)
z_ = z_mu + eps * np.exp(0.5 * lv)
l, _ = btcvae_loss(x, x_recon, z_mu, lv, z_, beta_tc=2.0, alpha_mi=1.0)
return l
_, d_z_mu, d_z_lv = btcvae_loss_backward(
x, x_recon, z_mu, z_lv, z, eps, beta_tc=2.0, alpha_mi=1.0
)
ng_mu = num_grad(loss_fn_mu, z_mu.ravel().copy()).reshape(B, D)
ng_lv = num_grad(loss_fn_lv, z_lv.ravel().copy()).reshape(B, D)
rel_mu = np.abs(d_z_mu - ng_mu) / (np.abs(ng_mu) + 1e-6)
rel_lv = np.abs(d_z_lv - ng_lv) / (np.abs(ng_lv) + 1e-6)
print(f' d_z_mu max_rel={rel_mu.max():.4f} mean_rel={rel_mu.mean():.4f}')
print(f' d_z_lv max_rel={rel_lv.max():.4f} mean_rel={rel_lv.mean():.4f}')
check('d_z_mu_rel_err', rel_mu.max() < 0.05, f'{rel_mu.max():.4f}')
check('d_z_lv_rel_err', rel_lv.max() < 0.05, f'{rel_lv.max():.4f}')
# ---------------------------------------------------------------------------
if __name__ == '__main__':
print('=' * 60)
print('ConvNeXt-1D beta-TCVAE unit tests')
print('=' * 60)
test_t1_dwconv_shape()
test_t2_dwconv_grad()
test_t3_layernorm_grad()
test_t4_block_shape()
test_t5_block_grad()
test_t6_vae_shapes()
test_t7_vae_backward()
test_t8_loss_decreases()
test_t9_save_load()
test_t10_btcvae_grad()
print()
print('=' * 60)
print(f'Results: {len(PASS)} PASS / {len(FAIL)} FAIL')
if FAIL:
print('FAILED:', FAIL)
sys.exit(1)
else:
print('All tests passed.')

View File

@@ -0,0 +1,86 @@
"""Verify D_LIQ_GOLD ROI=181.81% using exact gold _run_engine() path (float64, gold vol_p60)."""
import sys, time, math
sys.stdout.reconfigure(encoding='utf-8', errors='replace')
from pathlib import Path
import numpy as np
_HERE = Path(__file__).resolve().parent
sys.path.insert(0, str(_HERE.parent)) # nautilus_dolphin root
sys.path.insert(0, str(_HERE)) # dvae/ dir — avoid dvae/__init__ PyTorch load
from exp_shared import ENGINE_KWARGS, MC_BASE_CFG, load_data, load_forewarner
from nautilus_dolphin.nautilus.proxy_boost_engine import create_d_liq_engine
from nautilus_dolphin.nautilus.adaptive_circuit_breaker import AdaptiveCircuitBreaker
def _run_gold(eng, d, fw):
"""Exact gold _run_engine() path: float64 pq_data, gold vol_p60, np.isfinite gate."""
acb = AdaptiveCircuitBreaker()
acb.preload_w750(d['date_strings'])
eng.set_ob_engine(d['ob_eng'])
eng.set_acb(acb)
if fw is not None:
eng.set_mc_forewarner(fw, MC_BASE_CFG)
eng.set_esoteric_hazard_multiplier(0.0)
print(f" base_max_leverage = {eng.base_max_leverage} (expect 8.0 — fix check)")
print(f" abs_max_leverage = {eng.abs_max_leverage}")
print(f" sizer.max_leverage= {eng.bet_sizer.max_leverage}")
assert eng.base_max_leverage == 8.0, f"FIX NOT APPLIED: base_max={eng.base_max_leverage}"
t0 = time.time()
daily_caps = []
for pf_file in d['parquet_files']:
ds = pf_file.stem
df, acols, dvol = d['pq_data'][ds] # float64, pre-computed dvol
cap_before = eng.capital
vol_ok = np.where(np.isfinite(dvol), dvol > d['vol_p60'], False)
eng.process_day(ds, df, acols, vol_regime_ok=vol_ok)
daily_caps.append(eng.capital)
tr = eng.trade_history
n = len(tr)
roi = (eng.capital - 25000.0) / 25000.0 * 100.0
peak, max_dd = 25000.0, 0.0
for cap in daily_caps:
peak = max(peak, cap)
max_dd = max(max_dd, (peak - cap) / peak * 100.0)
def _abs(t):
return t.pnl_absolute if hasattr(t, 'pnl_absolute') else t.pnl_pct * 250.0
wins = [t for t in tr if _abs(t) > 0]
losses = [t for t in tr if _abs(t) <= 0]
pf = sum(_abs(t) for t in wins) / max(abs(sum(_abs(t) for t in losses)), 1e-9)
liq_stops = getattr(eng, 'liquidation_stops', 0)
print(f" ROI={roi:+.2f}% T={n} DD={max_dd:.2f}% PF={pf:.4f} "
f"liq_stops={liq_stops} ({time.time()-t0:.0f}s)")
return roi, n, max_dd, pf
def main():
print("Loading data (gold method: float64, 2-file seg-based vol_p60)...")
d = load_data()
fw = load_forewarner()
print()
print("=== D_LIQ_GOLD with fixed set_esoteric_hazard_multiplier ===")
eng = create_d_liq_engine(**ENGINE_KWARGS)
roi, n, dd, pf = _run_gold(eng, d, fw)
print()
print(f"GOLD TARGET: ROI=181.81% T=2155 DD=17.65%")
roi_ok = abs(roi - 181.81) < 1.0
dd_ok = abs(dd - 17.65) < 0.5
t_ok = n == 2155
print(f"ROI match: {'PASS' if roi_ok else 'FAIL'} (diff={roi-181.81:+.2f}pp)")
print(f"DD match: {'PASS' if dd_ok else 'FAIL'} (diff={dd-17.65:+.2f}pp)")
print(f"T match: {'PASS' if t_ok else 'FAIL'} (got {n})")
if roi_ok and dd_ok and t_ok:
print("\n=== D_LIQ_GOLD RESTORED ===")
else:
print("\n!!! MISMATCH — investigate further !!!")
if __name__ == '__main__':
main()

View File

@@ -0,0 +1,84 @@
"""Quick test: D_LIQ with and without set_esoteric_hazard_multiplier(0.0)
to confirm that's the root cause of 181.81% → 145.84% regression.
"""
import sys, time, math
sys.stdout.reconfigure(encoding='utf-8', errors='replace')
from pathlib import Path
import numpy as np
_HERE = Path(__file__).resolve().parent
sys.path.insert(0, str(_HERE.parent))
from exp_shared import ensure_jit, ENGINE_KWARGS, load_data, load_forewarner, MC_BASE_CFG
from nautilus_dolphin.nautilus.proxy_boost_engine import create_d_liq_engine
from nautilus_dolphin.nautilus.adaptive_circuit_breaker import AdaptiveCircuitBreaker
def _run(eng, d, fw, call_esof):
acb = AdaptiveCircuitBreaker()
acb.preload_w750(d['date_strings'])
eng.set_ob_engine(d['ob_eng'])
eng.set_acb(acb)
if fw is not None:
eng.set_mc_forewarner(fw, MC_BASE_CFG)
if call_esof:
eng.set_esoteric_hazard_multiplier(0.0)
# Print leverage state BEFORE trading
print(f" base_max_leverage = {eng.base_max_leverage}")
print(f" abs_max_leverage = {eng.abs_max_leverage}")
print(f" bet_sizer.max_lev = {eng.bet_sizer.max_leverage}")
t0 = time.time()
daily_caps, daily_pnls = [], []
for pf_file in d['parquet_files']:
ds = pf_file.stem
df, acols, dvol = d['pq_data'][ds]
cap_before = eng.capital
vol_ok = np.where(np.isfinite(dvol), dvol > d['vol_p60'], False)
eng.process_day(ds, df, acols, vol_regime_ok=vol_ok)
daily_caps.append(eng.capital)
daily_pnls.append(eng.capital - cap_before)
tr = eng.trade_history
n = len(tr)
roi = (eng.capital - 25000.0) / 25000.0 * 100.0
peak, max_dd = 25000.0, 0.0
for cap in daily_caps:
peak = max(peak, cap)
max_dd = max(max_dd, (peak - cap) / peak * 100.0)
def _abs(t):
return t.pnl_absolute if hasattr(t, 'pnl_absolute') else t.pnl_pct * 250.0
wins = [t for t in tr if _abs(t) > 0]
losses = [t for t in tr if _abs(t) <= 0]
pf = sum(_abs(t) for t in wins) / max(abs(sum(_abs(t) for t in losses)), 1e-9)
lev_vals = [t.leverage for t in tr if hasattr(t, 'leverage') and t.leverage > 0]
avg_lev = float(np.mean(lev_vals)) if lev_vals else 0.0
liq_stops = getattr(eng, 'liquidation_stops', 0)
print(f" ROI={roi:+.2f}% T={n} DD={max_dd:.2f}% PF={pf:.4f} avg_lev={avg_lev:.2f}x liq_stops={liq_stops} ({time.time()-t0:.0f}s)")
return roi, n, max_dd, avg_lev
def main():
ensure_jit()
d = load_data()
fw = load_forewarner()
print("\n=== WITH set_esoteric_hazard_multiplier(0.0) ===")
eng_a = create_d_liq_engine(**ENGINE_KWARGS)
roi_a, n_a, dd_a, lev_a = _run(eng_a, d, fw, call_esof=True)
print("\n=== WITHOUT set_esoteric_hazard_multiplier(0.0) ===")
eng_b = create_d_liq_engine(**ENGINE_KWARGS)
roi_b, n_b, dd_b, lev_b = _run(eng_b, d, fw, call_esof=False)
print(f"\nGOLD TARGET: ROI=181.81%, T=2155, DD=17.65%, avg_lev=4.09x")
print(f"WITH stomp: ROI={roi_a:+.2f}% T={n_a} DD={dd_a:.2f}% avg_lev={lev_a:.2f}x")
print(f"WITHOUT stomp: ROI={roi_b:+.2f}% T={n_b} DD={dd_b:.2f}% avg_lev={lev_b:.2f}x")
if __name__ == '__main__':
main()

View File

@@ -0,0 +1,110 @@
"""
Unit test for FlintHDVAE inverse projection decoder.
Criteria:
1. No NaN during training
2. z_var per dim > 0.01 after 20 epochs (no posterior collapse)
3. Reconstruction MSE < 1.0 on held-out 20%
"""
import sys, os
sys.stdout.reconfigure(encoding='utf-8', errors='replace')
sys.path.insert(0, os.path.dirname(__file__))
sys.path.insert(0, r"C:\Users\Lenovo\Documents\- DOLPHIN NG HD HCM TSF Predict")
import numpy as np
from pathlib import Path
HERE = Path(__file__).parent
print("=" * 65)
print("UNIT TEST: FlintHDVAE Inverse Projection Decoder")
print("=" * 65)
# ── Load T1 corpus ──────────────────────────────────────────────
print("\nLoading 16K eigen corpus...")
from corpus_builder import DolphinCorpus, OFF, T1 as T1_DIM
corpus = DolphinCorpus.load(str(HERE / 'corpus_cache.npz'))
idx_mask = corpus.mask[:, 1]
X_e = corpus.X[idx_mask]
T1 = X_e[:, OFF[1]:OFF[1] + T1_DIM].copy() # (16607, 20)
N = len(T1)
print(f" T1 shape: {T1.shape} dtype: {T1.dtype}")
print(f" Any NaN in T1: {np.isnan(T1).any()}")
print(f" T1 stats: min={T1.min():.4f} max={T1.max():.4f} std={T1.std():.4f}")
# ── Train/val split (random 80/20 — avoids regime distribution shift) ────
rng_split = np.random.RandomState(42)
idx_all = rng_split.permutation(N)
n_train = int(N * 0.8)
X_train = T1[idx_all[:n_train]]
X_val = T1[idx_all[n_train:]]
print(f"\nTrain: {len(X_train)} Val: {len(X_val)}")
# ── Instantiate model ────────────────────────────────────────────
from flint_hd_vae import FlintHDVAE
print("\nInstantiating FlintHDVAE(beta=0.1, use_flint_norm=False — plain z-score for decoder test)...")
model = FlintHDVAE(input_dim=20, hd_dim=512, latent_dim=8, beta=0.1, seed=42,
use_flint_norm=False)
print(" OK")
# ── Train 40 epochs ──────────────────────────────────────────────
print("\nTraining 40 epochs on 80% T1 data (warmup first 30%)...")
PASS = True
try:
model.fit(X_train, epochs=40, lr=1e-3, batch_size=256, verbose=True, warmup_frac=0.3)
except Exception as ex:
import traceback
traceback.print_exc()
PASS = False
if PASS:
# Check 1: No NaN in losses
losses_arr = np.array(model.train_losses)
nan_losses = np.isnan(losses_arr).sum()
print(f"\n[CHECK 1] NaN in training losses: {nan_losses}")
if nan_losses == 0:
print(" PASS: No NaN detected")
else:
print(" FAIL: NaN losses detected!")
PASS = False
# Check 2: z_var per dim > 0.01 (use encode() which applies global MCDAIN)
mu_s = model.encode(X_train[:1000]) # (1000, 8)
var_per_dim = mu_s.var(axis=0) # (8,)
min_var = var_per_dim.min()
active_dims = (var_per_dim > 0.01).sum()
print(f"\n[CHECK 2] z_var per dim: {var_per_dim.round(4)}")
print(f" Min var: {min_var:.6f} Active dims (>0.01): {active_dims}/8")
if active_dims >= 4: # at least half active
print(f" PASS: {active_dims}/8 dims active (no posterior collapse)")
elif active_dims >= 1:
print(f" PARTIAL: Only {active_dims}/8 dims active — weak but not fully collapsed")
else:
print(f" FAIL: All dims collapsed (posterior collapse)")
PASS = False
# Check 3: Reconstruction MSE < 1.0 on val
# reconstruct() returns (T1_hat, X_val_norm) — both in same normalised space
T1_hat, X_val_norm = model.reconstruct(X_val)
recon_mse = float(np.mean((T1_hat - X_val_norm) ** 2))
print(f"\n[CHECK 3] Val reconstruction MSE: {recon_mse:.4f}")
if recon_mse < 1.0:
print(f" PASS: MSE={recon_mse:.4f} < 1.0")
else:
print(f" FAIL: MSE={recon_mse:.4f} >= 1.0 (decoder not learning)")
PASS = False
# Bonus: check encode output shape and values
z_all = model.encode(X_val[:100])
print(f"\n[BONUS] encode() output shape: {z_all.shape}")
print(f" z range: [{z_all.min():.4f}, {z_all.max():.4f}] std: {z_all.std():.4f}")
if np.isnan(z_all).any():
print(" WARNING: NaN in encode() output!")
print("\n" + "=" * 65)
if PASS:
print("OVERALL: PASS — FlintHDVAE inverse projection decoder functional")
else:
print("OVERALL: FAIL — see issues above")
print("=" * 65)

View File

@@ -0,0 +1,394 @@
"""
Unit tests: LSTM weight save/load fix
======================================
Tests that DisentangledVAEGenerator.save_model() correctly persists W_ih/W_hh/b_h
and that TitanSensor loads them instead of random re-initialising.
Run BEFORE and AFTER retrain to catch regressions.
Usage:
cd nautilus_dolphin
python dvae/test_lstm_weight_fix.py
"""
import sys, json, os, tempfile
sys.stdout.reconfigure(encoding='utf-8', errors='replace')
from pathlib import Path
import numpy as np
ROOT = Path(__file__).parent.parent # nautilus_dolphin/
PROJECT = ROOT.parent # project root (disentangled_vae_joint_generator.py lives here)
sys.path.insert(0, str(ROOT))
sys.path.insert(0, str(PROJECT))
MODEL_PATH = Path(r"C:\Users\Lenovo\Documents\- DOLPHIN NG HD HCM TSF Predict"
r"\dvae_regime_model_TITAN_ULTRA_250_ULTRA261_MCDAIN.json")
MODEL_PATH_GD = Path(r"C:\Users\Lenovo\Documents\- DOLPHIN NG HD HCM TSF Predict"
r"\dvae_regime_model_TITAN_ULTRA_GD.json")
_PASS = "[PASS]"
_FAIL = "[FAIL]"
# ── Helpers ───────────────────────────────────────────────────────────────────
def _make_dummy_generator():
"""
Build a minimal DisentangledVAEGenerator-like object with known numpy weights,
bypassing FLINT _init_weights(). Used to test save/load roundtrip without
requiring FLINT/arb to be present.
"""
import types, importlib
# Import the class but intercept _init_weights so it doesn't call crypto_random_arb
mod = importlib.import_module('disentangled_vae_joint_generator')
cls = mod.DisentangledVAEGenerator
obj = cls.__new__(cls)
obj.input_dim = 8
obj.hidden_dim = 4
obj.latent_dim = 2
obj.regime_dim = 2
obj.prec = 64
obj.beta = 1.0
obj.is_trained = True
obj.edain = None
obj.latent_names = {0: "A", 1: "B"}
rng = np.random.RandomState(7777)
obj.W_ih = rng.randn(8, 16).astype(np.float64) # (input_dim, hidden*4)
obj.W_hh = rng.randn(4, 16).astype(np.float64) # (hidden_dim, hidden*4)
obj.b_h = rng.randn(16).astype(np.float64)
obj.W_mu = rng.randn(4, 2).astype(np.float64)
obj.W_logvar = rng.randn(4, 2).astype(np.float64)
obj.b_mu = rng.randn(2).astype(np.float64)
obj.b_logvar = rng.randn(2).astype(np.float64)
obj.W_dec = rng.randn(2, 4).astype(np.float64)
obj.W_out = rng.randn(4, 8).astype(np.float64)
obj.b_dec = rng.randn(4).astype(np.float64)
obj.b_out = rng.randn(8).astype(np.float64)
return obj
def _load_sensor():
from dvae.titan_sensor import TitanSensor
return TitanSensor(str(MODEL_PATH))
# ── Test 1: save_model() roundtrip (no FLINT needed) ─────────────────────────
def test_save_model_includes_lstm():
print("\n[T1] save_model() includes W_ih / W_hh / b_h ...")
gen = _make_dummy_generator()
with tempfile.NamedTemporaryFile(mode='w', suffix='.json', delete=False) as f:
tmp = f.name
try:
gen.save_model(tmp)
with open(tmp) as f:
m = json.load(f)
for key in ('W_ih', 'W_hh', 'b_h', 'W_mu', 'W_logvar', 'W_dec', 'W_out',
'b_mu', 'b_logvar', 'b_dec', 'b_out'):
assert key in m, f"{_FAIL}: key '{key}' missing from saved JSON"
W_ih_rt = np.array(m['W_ih'])
assert W_ih_rt.shape == gen.W_ih.shape, \
f"{_FAIL}: W_ih shape mismatch {W_ih_rt.shape} vs {gen.W_ih.shape}"
assert np.allclose(W_ih_rt, gen.W_ih, atol=1e-15), \
f"{_FAIL}: W_ih values differ after roundtrip (max={np.max(np.abs(W_ih_rt - gen.W_ih)):.2e})"
W_hh_rt = np.array(m['W_hh'])
assert np.allclose(W_hh_rt, gen.W_hh, atol=1e-15), \
f"{_FAIL}: W_hh values differ after roundtrip"
b_h_rt = np.array(m['b_h'])
assert np.allclose(b_h_rt, gen.b_h, atol=1e-15), \
f"{_FAIL}: b_h values differ after roundtrip"
print(f" {_PASS} W_ih {gen.W_ih.shape} roundtrip exact")
print(f" {_PASS} W_hh {gen.W_hh.shape} roundtrip exact")
print(f" {_PASS} b_h {gen.b_h.shape} roundtrip exact")
print(f" {_PASS} all 11 weight keys present")
finally:
os.unlink(tmp)
# ── Test 2: TitanSensor loads W_ih from JSON, not random seed=42 ─────────────
def test_sensor_loads_from_json_not_random():
print("\n[T2] TitanSensor loads LSTM weights from JSON (not seed=42 random) ...")
assert MODEL_PATH.exists(), f"{_FAIL}: model not found at {MODEL_PATH}"
with open(MODEL_PATH) as f:
m = json.load(f)
assert 'W_ih' in m, \
f"{_FAIL}: W_ih missing from model JSON — model was saved before the fix. Retrain first."
sensor = _load_sensor()
assert sensor.lstm_weights_valid, \
f"{_FAIL}: sensor.lstm_weights_valid=False — W_ih missing from JSON"
W_ih_json = np.array(m['W_ih'], dtype=np.float64)
max_diff = np.max(np.abs(sensor.W_ih - W_ih_json))
assert max_diff < 1e-12, \
f"{_FAIL}: sensor.W_ih != JSON W_ih (max_diff={max_diff:.3e})"
print(f" {_PASS} sensor.W_ih == JSON W_ih (max_diff={max_diff:.2e})")
W_hh_json = np.array(m['W_hh'], dtype=np.float64)
max_diff_hh = np.max(np.abs(sensor.W_hh - W_hh_json))
assert max_diff_hh < 1e-12, \
f"{_FAIL}: sensor.W_hh != JSON W_hh (max_diff={max_diff_hh:.3e})"
print(f" {_PASS} sensor.W_hh == JSON W_hh (max_diff={max_diff_hh:.2e})")
# Confirm it is NOT the seed=42 random initialisation
rng_42 = np.random.RandomState(42)
W_ih_42 = rng_42.randn(261, 512) * 0.1
diff_vs_42 = np.max(np.abs(sensor.W_ih - W_ih_42))
assert diff_vs_42 > 0.01, \
f"{_FAIL}: sensor.W_ih matches seed=42 random (diff={diff_vs_42:.3e}) — LSTM still wrong"
print(f" {_PASS} sensor.W_ih is NOT seed=42 random (diff_vs_42={diff_vs_42:.3f})")
# ── Test 3: recon_err is finite and in plausible range ────────────────────────
def test_recon_err_plausible():
print("\n[T3] recon_err is finite and << 10^6 (was ~10^14 pre-fix) ...")
sensor = _load_sensor()
from dvae.titan_sensor import build_feature_vector
rng = np.random.RandomState(42)
results = {}
for label, x in [
("zeros", np.zeros(261)),
("ones", np.ones(261) * 0.01),
("random_s", rng.randn(261) * 0.05),
("random_l", rng.randn(261) * 2.0),
]:
z_mu, recon_err, z_logvar = sensor.encode(x)
results[label] = recon_err
assert np.isfinite(recon_err), \
f"{_FAIL}: recon_err not finite for '{label}' input: {recon_err}"
# Pre-fix: ~10^14. Post-fix: should be O(1) or O(100) at worst.
assert recon_err < 1e8, \
f"{_FAIL}: recon_err={recon_err:.3e} suspiciously large for '{label}'"
print(f" {_PASS} [{label:10s}] recon_err={recon_err:.4e} z_mu[0:4]={z_mu[:4].round(4)}")
# Distribution check: recon_err should vary with input (not uniform noise)
errs = list(results.values())
assert max(errs) / (min(errs) + 1e-12) > 2.0, \
f"{_FAIL}: recon_err suspiciously uniform across inputs ({errs}) — LSTM may still be wrong"
print(f" {_PASS} recon_err varies meaningfully across inputs (ratio={max(errs)/(min(errs)+1e-12):.1f}x)")
# ── Test 4: Encoding is deterministic ─────────────────────────────────────────
def test_encoding_deterministic():
print("\n[T4] encode() is deterministic across two sensor instances ...")
sensor1 = _load_sensor()
sensor2 = _load_sensor()
x = np.random.RandomState(99).randn(261) * 0.1
z1, e1, _ = sensor1.encode(x)
z2, e2, _ = sensor2.encode(x)
assert np.allclose(z1, z2, atol=1e-14), \
f"{_FAIL}: z_mu differs between two sensors (max={np.max(np.abs(z1-z2)):.2e})"
assert abs(e1 - e2) < 1e-10, \
f"{_FAIL}: recon_err differs between two sensors ({e1:.6e} vs {e2:.6e})"
print(f" {_PASS} z_mu identical (max_diff={np.max(np.abs(z1-z2)):.2e})")
print(f" {_PASS} recon_err identical ({e1:.6e})")
# ── Test 5: Legacy model emits RuntimeWarning, sensor.lstm_weights_valid=False ─
def test_legacy_model_warns():
print("\n[T5] Legacy model (no W_ih in JSON) emits RuntimeWarning ...")
from dvae.titan_sensor import TitanSensor
import warnings
# Build a minimal legacy-style JSON (no W_ih/W_hh)
legacy = {
"W_mu": np.zeros((128, 32)).tolist(),
"W_logvar": np.zeros((128, 32)).tolist(),
"W_dec": np.zeros((32, 128)).tolist(),
"W_out": np.zeros((128, 261)).tolist(),
"latent_names": {},
"precision_bits": 512,
}
with tempfile.NamedTemporaryFile(mode='w', suffix='.json', delete=False) as f:
json.dump(legacy, f)
tmp = f.name
try:
with warnings.catch_warnings(record=True) as caught:
warnings.simplefilter("always")
s = TitanSensor(tmp)
assert not s.lstm_weights_valid, \
f"{_FAIL}: lstm_weights_valid should be False for legacy model"
assert any(issubclass(w.category, RuntimeWarning) for w in caught), \
f"{_FAIL}: no RuntimeWarning emitted for legacy model"
print(f" {_PASS} lstm_weights_valid=False")
print(f" {_PASS} RuntimeWarning emitted")
finally:
os.unlink(tmp)
# ── Test 6: z_mu dimensionality and range ─────────────────────────────────────
def test_latent_dimensionality():
print("\n[T6] z_mu has correct dimensionality and plausible range ...")
sensor = _load_sensor()
x = np.random.RandomState(55).randn(261) * 0.1
z_mu, recon_err, z_logvar = sensor.encode(x)
assert z_mu.shape == (32,), f"{_FAIL}: z_mu shape {z_mu.shape} != (32,)"
assert z_logvar.shape == (32,), f"{_FAIL}: z_logvar shape {z_logvar.shape} != (32,)"
assert np.all(np.isfinite(z_mu)), f"{_FAIL}: z_mu contains non-finite values"
assert np.all(np.isfinite(z_logvar)), f"{_FAIL}: z_logvar contains non-finite values"
assert np.abs(z_mu).max() < 1e3, \
f"{_FAIL}: z_mu values suspiciously large (max={np.abs(z_mu).max():.2e})"
print(f" {_PASS} z_mu.shape=(32,) z_mu range=[{z_mu.min():.3f}, {z_mu.max():.3f}]")
print(f" {_PASS} z_logvar.shape=(32,) range=[{z_logvar.min():.3f}, {z_logvar.max():.3f}]")
# ── Test 7: GD-trained model has correct LSTM weights and sane recon_err ──────
def test_gd_model():
print("\n[T7] GD-trained model: W_ih present, recon_err << MCDAIN model ...")
assert MODEL_PATH_GD.exists(), f"{_FAIL}: GD model not found at {MODEL_PATH_GD}"
from dvae.titan_sensor import TitanSensor
sensor_gd = TitanSensor(str(MODEL_PATH_GD))
assert sensor_gd.lstm_weights_valid, f"{_FAIL}: GD sensor.lstm_weights_valid=False"
print(f" {_PASS} GD model: lstm_weights_valid=True")
# Verify W_ih from GD model matches W_ih from MCDAIN model
# (they should be THE SAME — GD model was initialized from MCDAIN model's W_ih)
sensor_mc = _load_sensor()
diff_wih = np.max(np.abs(sensor_gd.W_ih - sensor_mc.W_ih))
assert diff_wih < 1e-12, \
f"{_FAIL}: GD model W_ih != MCDAIN model W_ih (diff={diff_wih:.3e}) — should be same LSTM basis"
print(f" {_PASS} GD model W_ih == MCDAIN model W_ih (same LSTM basis, diff={diff_wih:.2e})")
# Reconstruction error: generate inputs GUARANTEED to be in-distribution by
# sampling x = norm_mean + norm_std * randn (within ±2σ of training corpus).
# After encode()'s normalization step: v_norm = clip(randn, -2, 2) → perfectly O(1).
# recon_err should be close to the training-set value (p50=0.59).
with open(MODEL_PATH_GD) as fj:
gd_json = json.load(fj)
nm_gd = np.array(gd_json['norm_mean']); ns_gd = np.array(gd_json['norm_std'])
rng = np.random.RandomState(42)
errs_gd = []; errs_mc = []
for _ in range(20):
# Guaranteed in-distribution: raw input = corpus_mean + corpus_std * noise
x_raw = nm_gd + ns_gd * np.clip(rng.randn(261), -2, 2)
x_raw[78:] = 0.0 # T3/T4/T5 are always 0 in corpus
_, e_gd, _ = sensor_gd.encode(x_raw)
_, e_mc, _ = sensor_mc.encode(x_raw)
errs_gd.append(e_gd); errs_mc.append(e_mc)
med_gd = float(np.median(errs_gd)); med_mc = float(np.median(errs_mc))
print(f" GD median recon_err={med_gd:.4e} (in-distribution) MCDAIN median recon_err={med_mc:.4e}")
# GD trained with proper GD — should reconstruct in-distribution inputs well
assert med_gd < 10.0, f"{_FAIL}: GD recon_err too large ({med_gd:.4e}) — model didn't learn"
print(f" {_PASS} GD recon_err < 10.0 for in-distribution inputs (model learned)")
# ── Test 8: GD-v2 normalization stored and applied at inference ───────────────
def test_normalization_stored_and_applied():
print("\n[T8] GD-v2 model: norm_mean/norm_std present and applied by TitanSensor ...")
assert MODEL_PATH_GD.exists(), f"{_FAIL}: GD model not found at {MODEL_PATH_GD}"
from dvae.titan_sensor import TitanSensor
# 8a: JSON must contain norm_mean / norm_std
with open(MODEL_PATH_GD) as f:
m = json.load(f)
assert 'norm_mean' in m, f"{_FAIL}: GD model JSON missing 'norm_mean'"
assert 'norm_std' in m, f"{_FAIL}: GD model JSON missing 'norm_std'"
nm = np.array(m['norm_mean']); ns = np.array(m['norm_std'])
assert nm.shape == (261,), f"{_FAIL}: norm_mean shape {nm.shape} != (261,)"
assert ns.shape == (261,), f"{_FAIL}: norm_std shape {ns.shape} != (261,)"
assert np.all(ns > 0), f"{_FAIL}: norm_std has zero or negative entries"
print(f" {_PASS} norm_mean/norm_std present, shape=(261,), all std>0")
# 8b: TitanSensor must load them (not None)
sensor = TitanSensor(str(MODEL_PATH_GD))
assert sensor.norm_mean is not None, f"{_FAIL}: sensor.norm_mean is None — not loaded"
assert sensor.norm_std is not None, f"{_FAIL}: sensor.norm_std is None — not loaded"
assert np.allclose(sensor.norm_mean, nm, atol=1e-12), \
f"{_FAIL}: sensor.norm_mean != JSON norm_mean"
print(f" {_PASS} sensor.norm_mean loaded from JSON")
# 8c: recon_err on realistic inputs (matching build_feature_vector() output structure)
# Before fix (MCDAIN-trained, no normalization stored): encode(raw) → huge recon_err
# After fix (GD-v2, normalization stored and applied): encode(raw) → O(1-50)
rng = np.random.RandomState(42)
errs = []
for _ in range(30):
x = np.zeros(261)
x[0:8] = rng.randn(8) * 0.7 # T0: time encoding
x[8:28] = rng.randn(20) * 0.02 # T1: eigenvalue velocity scale
x[28:78] = np.clip(rng.randn(50), -5, 5) # T2: return z-scores [-5,5]
# T3/T4/T5 = 0 (Stage 1 contract — matches training corpus)
_, e, _ = sensor.encode(x)
errs.append(e)
med_err = float(np.median(errs))
max_err = float(np.max(errs))
print(f" GD-v2 realistic-input recon_err: median={med_err:.4e} max={max_err:.4e}")
assert med_err < 100.0, \
f"{_FAIL}: recon_err too large ({med_err:.4e}) — normalization may not be applied"
print(f" {_PASS} recon_err O(1-100) for realistic-scale inputs")
# 8d: T5 dims are zero after normalization (non-zero norm_mean would shift them)
x_zeros = np.zeros(261)
_, e_z, _ = sensor.encode(x_zeros)
# The sensor zeros T5 after normalization — this just checks it doesn't crash
assert np.isfinite(e_z), f"{_FAIL}: encode(zeros) returned non-finite recon_err={e_z}"
print(f" {_PASS} encode(zeros) is finite after normalization: recon_err={e_z:.4e}")
# ── Runner ────────────────────────────────────────────────────────────────────
if __name__ == '__main__':
print("=" * 60)
print("TitanSensor LSTM weight fix — unit tests")
print("=" * 60)
n_pass = 0
n_fail = 0
tests = [
("T1: save_model roundtrip", test_save_model_includes_lstm),
("T2: sensor loads JSON weights", test_sensor_loads_from_json_not_random),
("T3: recon_err plausible", test_recon_err_plausible),
("T4: encoding deterministic", test_encoding_deterministic),
("T5: legacy model warns", test_legacy_model_warns),
("T6: latent dimensionality", test_latent_dimensionality),
("T7: GD model quality", test_gd_model),
("T8: normalization stored and applied", test_normalization_stored_and_applied),
]
for name, fn in tests:
try:
fn()
n_pass += 1
except AssertionError as e:
print(f" {_FAIL} ASSERTION: {e}")
n_fail += 1
except Exception as e:
print(f" {_FAIL} EXCEPTION in {name}: {type(e).__name__}: {e}")
n_fail += 1
print()
print("=" * 60)
print(f"Results: {n_pass}/{n_pass+n_fail} PASSED {n_fail} FAILED")
print("=" * 60)
if n_fail > 0:
sys.exit(1)

View File

@@ -0,0 +1,233 @@
"""
TitanSensor — Non-invasive latent state extractor (Stage 1, read-only).
Architecture notes
------------------
The LSTM weights (W_ih, W_hh) are fixed random projections used as the
encoder basis during training. W_mu/W_logvar/W_dec/W_out were trained to
map the output of THESE specific W_ih/W_hh matrices. Loading the correct
W_ih/W_hh is therefore mandatory for valid reconstruction errors.
Models saved before 2026-03-15 did not persist W_ih/W_hh — those fall back
to legacy seed=42 re-init (meaningless recon_err ~10^14) and are flagged.
T5 oracle-leakage fix
---------------------
Training included T5 dims [111:261] = spectral path coefficients derived from
FUTURE prices [t+1, t+51]. At inference we zero those dims unconditionally.
Normalisation (GD-v2 models, training >= 2026-03-15)
------------------------------------------------------
GD-v2 models store per-feature z-score stats (norm_mean, norm_std) computed
from the raw training corpus. encode() applies v = (v - norm_mean)/norm_std
before the LSTM step so that training and inference see the same distribution.
Models without norm_mean (legacy) skip this step — recon_err is in raw-feature
space and remains valid for relative comparison within that model.
"""
import json
import numpy as np
class TitanSensor:
INPUT_DIM = 261
HIDDEN_DIM = 128
LATENT_DIM = 32
T5_START = 111 # dims [111:261] = spectral (oracle) → always zero
def __init__(self, model_path: str, lstm_seed: int = 42):
with open(model_path) as f:
m = json.load(f)
def _arr(key, default_shape):
return np.array(m[key]) if key in m else np.zeros(default_shape)
self.W_mu = _arr('W_mu', (self.HIDDEN_DIM, self.LATENT_DIM))
self.W_logvar = _arr('W_logvar', (self.HIDDEN_DIM, self.LATENT_DIM))
self.W_dec = _arr('W_dec', (self.LATENT_DIM, self.HIDDEN_DIM))
self.W_out = _arr('W_out', (self.HIDDEN_DIM, self.INPUT_DIM))
self.b_mu = _arr('b_mu', (self.LATENT_DIM,))
self.b_logvar = _arr('b_logvar', (self.LATENT_DIM,))
self.b_dec = _arr('b_dec', (self.HIDDEN_DIM,))
self.b_out = _arr('b_out', (self.INPUT_DIM,))
# LSTM weights: load from JSON if present (fixed since 2026-03-15).
# Legacy models (no W_ih in JSON) fall back to seed=42 re-init —
# recon_err will be ~10^14 for those; a warning is emitted.
if 'W_ih' in m:
self.W_ih = np.array(m['W_ih'], dtype=np.float64)
self.W_hh = np.array(m['W_hh'], dtype=np.float64)
self.b_h = np.array(m['b_h'], dtype=np.float64)
self.lstm_weights_valid = True
else:
import warnings
warnings.warn(
f"TitanSensor: model at '{model_path}' does not contain LSTM weights "
"(W_ih/W_hh/b_h). Falling back to seed=42 re-init. "
"recon_err will be ~10^14 (meaningless). Retrain with fixed save_model().",
RuntimeWarning, stacklevel=2,
)
rng = np.random.RandomState(lstm_seed)
s = 0.1
self.W_ih = rng.randn(self.INPUT_DIM, self.HIDDEN_DIM * 4) * s
self.W_hh = rng.randn(self.HIDDEN_DIM, self.HIDDEN_DIM * 4) * s
self.b_h = np.zeros(self.HIDDEN_DIM * 4)
self.lstm_weights_valid = False
self.latent_names = {int(k): v for k, v in m.get('latent_names', {}).items()}
# Per-feature normalisation stats (present in GD-v2 models trained 2026-03-15+).
# If absent (legacy models): no normalization applied — recon_err will be in
# raw-feature space and is still valid for relative comparison within that model.
if 'norm_mean' in m:
self.norm_mean = np.array(m['norm_mean'], dtype=np.float64)
self.norm_std = np.array(m['norm_std'], dtype=np.float64)
else:
self.norm_mean = None
self.norm_std = None
# ------------------------------------------------------------------
def _sigmoid(self, x):
return 1.0 / (1.0 + np.exp(-np.clip(x, -500, 500)))
def _lstm_step(self, x, h, c):
g = x @ self.W_ih + h @ self.W_hh + self.b_h
i_, f_, g_, o_ = np.split(g, 4, axis=-1)
i_ = self._sigmoid(i_); f_ = self._sigmoid(f_); o_ = self._sigmoid(o_)
g_ = np.tanh(g_)
c2 = f_ * c + i_ * g_
h2 = o_ * np.tanh(c2)
return h2, c2
# ------------------------------------------------------------------
def encode(self, x: np.ndarray):
"""
Encode one 261-dim feature row.
Returns
-------
z_mu : np.ndarray (32,) — latent mean
recon_err : float — MSE on T0-T4 reconstruction (in normalised space)
z_logvar : np.ndarray (32,) — per-dim log-variance
"""
v = np.array(x, dtype=np.float64).ravel()
v = np.resize(v, self.INPUT_DIM) # pad/truncate
# Stage-1 zero: T3 (ExF, 78-102), T4 (esoteric, 103-110) and T5 (oracle, 111+)
# are always zero in the training corpus — enforce this here so that
# callers not using build_feature_vector() don't blow up normalization.
v[78:] = 0.0
v[self.T5_START:] = 0.0 # T5 oracle fix (redundant but explicit)
v = np.nan_to_num(v, nan=0.0, posinf=0.0, neginf=0.0)
# Apply stored per-feature normalisation (GD-v2 models only).
# This reproduces the same transform applied during training so that
# the LSTM sees the same input distribution and recon_err is O(1).
if self.norm_mean is not None:
v = (v - self.norm_mean) / self.norm_std
v[78:] = 0.0 # re-zero Stage-1 dims after norm
# Clip to [-50, 50] for safety — training post-norm max was ~37
v = np.clip(v, -50.0, 50.0)
h = np.zeros((1, self.HIDDEN_DIM))
c = np.zeros((1, self.HIDDEN_DIM))
h, c = self._lstm_step(v.reshape(1, -1), h, c)
z_mu = (h @ self.W_mu + self.b_mu)[0]
z_logvar = (h @ self.W_logvar + self.b_logvar)[0]
h_dec = np.tanh(z_mu @ self.W_dec + self.b_dec)
recon = h_dec @ self.W_out + self.b_out
recon_err = float(np.mean((recon[:self.T5_START] - v[:self.T5_START]) ** 2))
return z_mu, recon_err, z_logvar
# ------------------------------------------------------------------
# Feature builder — constructs a 261-dim vector from a parquet row.
# T3 (ExF, 78-102), T4 (esoteric, 103-110), T5 (111-260) are zeroed.
# ------------------------------------------------------------------
_ASSET_CACHE = {} # parquet-path → ordered asset list
def build_feature_vector(df, ri: int, assets: list) -> np.ndarray:
"""
Build 261-dim T0-T4 feature vector (T5 always zero).
T0 (0-7) : time encoding + rolling breadth
T1 (8-27) : eigenvalue velocity features (4 windows × 5 dims)
T2 (28-77) : per-asset return z-scores (up to 50 assets, rolling 50 bars)
T3 (78-102): ExF macro — zeroed (Stage 1)
T4 (103-110): esoteric — zeroed (Stage 1)
T5 (111-260): spectral — zeroed (oracle fix)
"""
x = np.zeros(261, dtype=np.float64)
row = df.iloc[ri]
# --- T0: time encoding ---
try:
ts = row['timestamp']
if hasattr(ts, 'hour'):
h = ts.hour + ts.minute / 60.0
d = ts.dayofweek
else:
import pandas as pd
ts = pd.Timestamp(ts)
h = ts.hour + ts.minute / 60.0
d = ts.dayofweek
x[0] = np.sin(2 * np.pi * h / 24)
x[1] = np.cos(2 * np.pi * h / 24)
x[2] = np.sin(2 * np.pi * d / 7)
x[3] = np.cos(2 * np.pi * d / 7)
except Exception:
pass
x[4] = 1.0 # has_eigen always true for NG3
# rolling BTC breadth (T0 dims 5-7)
if ri >= 20 and 'BTCUSDT' in df.columns:
diffs = np.diff(df['BTCUSDT'].values[ri - 20:ri + 1])
x[5] = float(np.mean(diffs > 0))
x[6] = float(np.mean(diffs < 0))
x[7] = float(np.mean(diffs == 0))
# --- T1: eigenvalue velocity features ---
def _g(col):
v = row.get(col, 0.0)
return float(v) if v is not None and v == v else 0.0
v50 = _g('v50_lambda_max_velocity')
v150 = _g('v150_lambda_max_velocity')
v300 = _g('v300_lambda_max_velocity')
v750 = _g('v750_lambda_max_velocity')
i50 = _g('instability_50')
i150 = _g('instability_150')
vd = _g('vel_div')
# window-0 (8-12): v50 group
x[8] = v50; x[9] = i50; x[10] = v50 - v150
x[11] = v50 - v300; x[12] = v50 - v750
# window-1 (13-17): v150 group
x[13] = v150; x[14] = i150; x[15] = v150 - v300
x[16] = v150 - v750; x[17] = abs(v150)
# window-2 (18-22): v300 group
x[18] = v300; x[19] = abs(v300); x[20] = v300 - v750
x[21] = v50 * v300; x[22] = i50 - v750 # proxy_B equivalent
# window-3 (23-27): v750 + composite
x[23] = v750; x[24] = abs(v750)
x[25] = v50 / (abs(v750) + 1e-8)
x[26] = i50 - i150; x[27] = vd
# --- T2: per-asset return z-scores ---
if ri >= 50:
prices_cache = {a: df[a].values for a in assets if a in df.columns}
for j, asset in enumerate(assets[:50]):
if asset not in prices_cache:
continue
seg = prices_cache[asset][ri - 50:ri + 1]
if np.any(seg <= 0) or np.any(~np.isfinite(seg)):
continue
rets = np.diff(seg) / seg[:-1]
std = np.std(rets)
if std > 0:
x[28 + j] = float(np.clip((rets[-1] - np.mean(rets)) / std, -5.0, 5.0))
return x

View File

@@ -0,0 +1,319 @@
"""
Titan Stage 1 — Silent Sensor Run
==================================
Two-pass approach:
Pass 1: run D_LIQ_GOLD engine (56 days) → assert T=2155 unchanged.
Pass 2: re-scan each parquet, build 261-dim feature vectors, run TitanSensor,
annotate each bar with trade state from pass-1 trade history.
Output: dvae/titan_latent_trail.parquet (all bars × z_mu[0:32] + meta columns)
Zero changes to any production code. Imports from production modules are read-only.
"""
import sys
sys.stdout.reconfigure(encoding='utf-8', errors='replace')
import time
import json
from pathlib import Path
import numpy as np
import pandas as pd
ROOT = Path(__file__).parent.parent
sys.path.insert(0, str(ROOT))
# ── production imports (read-only) ──────────────────────────────────────────
from nautilus_dolphin.nautilus.alpha_asset_selector import compute_irp_nb, compute_ars_nb, rank_assets_irp_nb
from nautilus_dolphin.nautilus.alpha_bet_sizer import compute_sizing_nb
from nautilus_dolphin.nautilus.alpha_signal_generator import check_dc_nb
from nautilus_dolphin.nautilus.ob_features import (
OBFeatureEngine, compute_imbalance_nb, compute_depth_1pct_nb,
compute_depth_quality_nb, compute_fill_probability_nb, compute_spread_proxy_nb,
compute_depth_asymmetry_nb, compute_imbalance_persistence_nb,
compute_withdrawal_velocity_nb, compute_market_agreement_nb, compute_cascade_signal_nb,
)
from nautilus_dolphin.nautilus.ob_provider import MockOBProvider
from nautilus_dolphin.nautilus.esf_alpha_orchestrator import NDAlphaEngine
from nautilus_dolphin.nautilus.adaptive_circuit_breaker import AdaptiveCircuitBreaker
from nautilus_dolphin.nautilus.proxy_boost_engine import create_d_liq_engine
from mc.mc_ml import DolphinForewarner
from dvae.titan_sensor import TitanSensor, build_feature_vector
# ── JIT warmup ───────────────────────────────────────────────────────────────
print("Warming up JIT kernels...")
_p = np.array([1.0, 2.0, 3.0], dtype=np.float64)
compute_irp_nb(_p, -1); compute_ars_nb(1.0, 0.5, 0.01)
rank_assets_irp_nb(np.ones((10, 2), dtype=np.float64), 8, -1, 5, 500.0, 20, 0.20)
compute_sizing_nb(-0.03, -0.02, -0.05, 3.0, 0.5, 5.0, 0.20, True, True, 0.0,
np.zeros(4, dtype=np.int64), np.zeros(4, dtype=np.int64),
np.zeros(5, dtype=np.float64), 0, -1, 0.01, 0.04)
check_dc_nb(_p, 3, 1, 0.75)
_b = np.array([100.0, 200.0, 300.0, 400.0, 500.0], dtype=np.float64)
_a = np.array([110.0, 190.0, 310.0, 390.0, 510.0], dtype=np.float64)
compute_imbalance_nb(_b, _a); compute_depth_1pct_nb(_b, _a)
compute_depth_quality_nb(210.0, 200.0); compute_fill_probability_nb(1.0)
compute_spread_proxy_nb(_b, _a); compute_depth_asymmetry_nb(_b, _a)
compute_imbalance_persistence_nb(np.array([0.1, -0.1], dtype=np.float64), 2)
compute_withdrawal_velocity_nb(np.array([100.0, 110.0], dtype=np.float64), 1)
compute_market_agreement_nb(np.array([0.1, -0.05], dtype=np.float64), 2)
compute_cascade_signal_nb(np.array([-0.05, -0.15], dtype=np.float64), 2, -0.10)
print(" JIT ready.")
# ── paths / config ────────────────────────────────────────────────────────────
VBT_DIR = Path(r"C:\Users\Lenovo\Documents\- DOLPHIN NG HD HCM TSF Predict\vbt_cache")
MODEL_PATH = Path(r"C:\Users\Lenovo\Documents\- DOLPHIN NG HD HCM TSF Predict\dvae_regime_model_TITAN_ULTRA_250_ULTRA261_MCDAIN.json")
MC_MODELS = str(ROOT / "mc_results" / "models")
OUT_FILE = Path(__file__).parent / "titan_latent_trail.parquet"
META_COLS = {'timestamp', 'scan_number', 'v50_lambda_max_velocity', 'v150_lambda_max_velocity',
'v300_lambda_max_velocity', 'v750_lambda_max_velocity', 'vel_div',
'instability_50', 'instability_150'}
ENGINE_KWARGS = dict(
initial_capital=25000.0, vel_div_threshold=-0.02, vel_div_extreme=-0.05,
min_leverage=0.5, max_leverage=5.0, leverage_convexity=3.0,
fraction=0.20, fixed_tp_pct=0.0095, stop_pct=1.0, max_hold_bars=120,
use_direction_confirm=True, dc_lookback_bars=7, dc_min_magnitude_bps=0.75,
dc_skip_contradicts=True, dc_leverage_boost=1.0, dc_leverage_reduce=0.5,
use_asset_selection=True, min_irp_alignment=0.45,
use_sp_fees=True, use_sp_slippage=True,
sp_maker_entry_rate=0.62, sp_maker_exit_rate=0.50,
use_ob_edge=True, ob_edge_bps=5.0, ob_confirm_rate=0.40,
lookback=100, use_alpha_layers=True, use_dynamic_leverage=True, seed=42,
)
MC_BASE_CFG = {
'trial_id': 0, 'vel_div_threshold': -0.020, 'vel_div_extreme': -0.050,
'use_direction_confirm': True, 'dc_lookback_bars': 7,
'dc_min_magnitude_bps': 0.75, 'dc_skip_contradicts': True,
'dc_leverage_boost': 1.00, 'dc_leverage_reduce': 0.50,
'vd_trend_lookback': 10, 'min_leverage': 0.50, 'max_leverage': 5.00,
'leverage_convexity': 3.00, 'fraction': 0.20,
'use_alpha_layers': True, 'use_dynamic_leverage': True,
'fixed_tp_pct': 0.0095, 'stop_pct': 1.00, 'max_hold_bars': 120,
'use_sp_fees': True, 'use_sp_slippage': True,
'sp_maker_entry_rate': 0.62, 'sp_maker_exit_rate': 0.50,
'use_ob_edge': True, 'ob_edge_bps': 5.00, 'ob_confirm_rate': 0.40,
'ob_imbalance_bias': -0.09, 'ob_depth_scale': 1.00,
'use_asset_selection': True, 'min_irp_alignment': 0.45, 'lookback': 100,
'acb_beta_high': 0.80, 'acb_beta_low': 0.20, 'acb_w750_threshold_pct': 60,
}
EXPECTED_TRADES = 2155
# ═══════════════════════════════════════════════════════════════════════════════
# PASS 1 — run D_LIQ_GOLD engine, collect trade timeline + bar log
# ═══════════════════════════════════════════════════════════════════════════════
def run_pass1(parquet_files, pq_data, all_assets):
print("\n=== PASS 1: D_LIQ_GOLD backtest ===")
# OB
OB_ASSETS = sorted(all_assets)
_mock_ob = MockOBProvider(
imbalance_bias=-0.09, depth_scale=1.0, assets=OB_ASSETS,
imbalance_biases={"BTCUSDT": -0.086, "ETHUSDT": -0.092,
"BNBUSDT": +0.05, "SOLUSDT": +0.05},
)
ob_eng = OBFeatureEngine(_mock_ob)
ob_eng.preload_date("mock", OB_ASSETS)
forewarner = DolphinForewarner(models_dir=MC_MODELS)
acb = AdaptiveCircuitBreaker()
acb.preload_w750([pf.stem for pf in parquet_files])
engine = create_d_liq_engine(**ENGINE_KWARGS)
engine.set_ob_engine(ob_eng)
engine.set_acb(acb)
engine.set_mc_forewarner(forewarner, MC_BASE_CFG)
engine.set_esoteric_hazard_multiplier(0.0)
engine._bar_log_enabled = True
# vol threshold (p60, first 2 days)
all_vols = []
for pf in parquet_files[:2]:
df = pd.read_parquet(pf)
if 'BTCUSDT' not in df.columns:
continue
pr = df['BTCUSDT'].values
for i in range(60, len(pr)):
seg = pr[max(0, i-50):i]
if len(seg) >= 10:
v = float(np.std(np.diff(seg) / seg[:-1]))
if v > 0:
all_vols.append(v)
vol_p60 = float(np.percentile(all_vols, 60)) if all_vols else 0.0
bar_logs = {} # date → list[dict]
t0 = time.time()
for pf in parquet_files:
ds = pf.stem
df, acols, dvol = pq_data[ds]
vol_ok = np.where(np.isfinite(dvol), dvol > vol_p60, False)
engine.process_day(ds, df, acols, vol_regime_ok=vol_ok)
bar_logs[ds] = list(engine._bar_log)
elapsed = time.time() - t0
trades = engine.trade_history
roi = (engine.capital - 25000) / 25000 * 100
print(f" Trades: {len(trades)} ROI: {roi:+.2f}% ({elapsed:.0f}s)")
assert len(trades) == EXPECTED_TRADES, \
f"REGRESSION: T={len(trades)}{EXPECTED_TRADES}. Aborting."
print(f" T={len(trades)} ✓ (matches gold standard)")
return trades, bar_logs, vol_p60
# ═══════════════════════════════════════════════════════════════════════════════
# PASS 2 — build feature vectors + run sensor, annotate with trade state
# ═══════════════════════════════════════════════════════════════════════════════
def run_pass2(parquet_files, pq_data, trades, bar_logs, sensor):
print("\n=== PASS 2: TitanSensor latent capture ===")
# Build trade timeline: global_bar_idx → trade metadata
# Each trade record has entry_bar / exit_bar (global_bar_idx values)
active_at = {} # global_bar_idx → {'trade_id', 'asset', 'pnl_pct', 'bars_held', 'outcome'}
for t in trades:
eb, xb = getattr(t, 'entry_bar', None), getattr(t, 'exit_bar', None)
if eb is None or xb is None:
continue
final_pnl = getattr(t, 'pnl_pct', 0.0)
outcome = 'win' if getattr(t, 'pnl_absolute', 0.0) > 0 else 'loss'
for b in range(int(eb), int(xb) + 1):
active_at[b] = {
'trade_id': getattr(t, 'trade_id', ''),
'asset': getattr(t, 'asset', ''),
'trade_outcome': outcome,
'trade_pnl_pct': float(final_pnl),
'bars_in_trade': b - int(eb),
'exit_reason': getattr(t, 'exit_reason', ''),
}
records = []
global_bar = 0
for pf in parquet_files:
ds = pf.stem
df, acols, _ = pq_data[ds]
assets = acols
print(f" {ds} ({len(df)} bars) ...", end=' ', flush=True)
t0 = time.time()
for ri in range(len(df)):
row = df.iloc[ri]
# build feature vector
feat = build_feature_vector(df, ri, assets)
z_mu, recon_err, z_logvar = sensor.encode(feat)
trade_info = active_at.get(global_bar, {})
rec = {
'date': ds,
'bar_idx': global_bar,
'row_idx': ri,
'vel_div': float(row.get('vel_div', 0.0) or 0.0),
'v50': float(row.get('v50_lambda_max_velocity', 0.0) or 0.0),
'v750': float(row.get('v750_lambda_max_velocity', 0.0) or 0.0),
'instab_50': float(row.get('instability_50', 0.0) or 0.0),
'recon_err': recon_err,
'is_in_trade': bool(trade_info),
**{f'z{i}': float(z_mu[i]) for i in range(32)},
**{f'lv{i}': float(z_logvar[i]) for i in range(32)},
**trade_info,
}
records.append(rec)
global_bar += 1
print(f"{time.time() - t0:.1f}s")
return pd.DataFrame(records)
# ═══════════════════════════════════════════════════════════════════════════════
# SUMMARY — print key correlations for immediate insight
# ═══════════════════════════════════════════════════════════════════════════════
def print_summary(df_out):
print("\n=== Stage 1 Summary ===")
trade_rows = df_out[df_out['is_in_trade']].copy()
print(f" Total bars: {len(df_out)} | In-trade bars: {len(trade_rows)}")
if trade_rows.empty:
print(" (no in-trade data)")
return
# recon_err: in-trade vs not
oob = df_out[~df_out['is_in_trade']]['recon_err']
print(f" recon_err — in-trade: {trade_rows['recon_err'].mean():.4f} "
f"out-of-trade: {oob.mean():.4f}")
# per z-dim correlation with recon_err
z_cols = [f'z{i}' for i in range(32)]
corrs = trade_rows[z_cols + ['recon_err']].corr()['recon_err'].drop('recon_err')
top3 = corrs.abs().nlargest(3)
print(f" Top z-dims by |corr| with recon_err: {top3.index.tolist()}")
for c in top3.index:
print(f" {c}: r={corrs[c]:+.3f} (name: {sensor.latent_names.get(int(c[1:]), '?')})")
# win vs loss recon_err (entry bar only)
entry_rows = trade_rows[trade_rows['bars_in_trade'] == 0] if 'bars_in_trade' in trade_rows.columns else trade_rows
if not entry_rows.empty and 'trade_outcome' in entry_rows.columns:
wins = entry_rows[entry_rows['trade_outcome'] == 'win']['recon_err'].mean()
losses= entry_rows[entry_rows['trade_outcome'] == 'loss']['recon_err'].mean()
print(f" recon_err at entry — wins: {wins:.4f} losses: {losses:.4f}")
# ═══════════════════════════════════════════════════════════════════════════════
# MAIN
# ═══════════════════════════════════════════════════════════════════════════════
def main():
assert MODEL_PATH.exists(), f"Model not found: {MODEL_PATH}"
parquet_files = sorted(VBT_DIR.glob("*.parquet"))
parquet_files = [p for p in parquet_files if 'catalog' not in str(p)]
print(f"Dataset: {len(parquet_files)} days ({parquet_files[0].stem} to {parquet_files[-1].stem})")
# preload parquet
print("Loading parquet data...")
pq_data = {}
all_assets = set()
for pf in parquet_files:
df = pd.read_parquet(pf)
ac = [c for c in df.columns if c not in META_COLS]
all_assets.update(ac)
bp = df['BTCUSDT'].values if 'BTCUSDT' in df.columns else None
dv = np.full(len(df), np.nan)
if bp is not None:
for i in range(50, len(bp)):
seg = bp[max(0, i-50):i]
if len(seg) >= 10:
dv[i] = float(np.std(np.diff(seg) / seg[:-1]))
pq_data[pf.stem] = (df, ac, dv)
# load sensor
print(f"\nLoading TitanSensor from {MODEL_PATH.name}...")
sensor = TitanSensor(str(MODEL_PATH))
print(f" Latent dim: {sensor.LATENT_DIM} Hidden: {sensor.HIDDEN_DIM} T5 zeroed: {sensor.T5_START}:261")
# pass 1
trades, bar_logs, vol_p60 = run_pass1(parquet_files, pq_data, all_assets)
# pass 2
df_out = run_pass2(parquet_files, pq_data, trades, bar_logs, sensor)
# save
df_out.to_parquet(str(OUT_FILE), index=False)
print(f"\nLatent trail saved: {OUT_FILE} ({len(df_out):,} rows × {len(df_out.columns)} cols)")
print_summary(df_out)
print("\n=== DONE ===")
print("Next: load titan_latent_trail.parquet and run Stage 2 characterization.")
print(" pd.read_parquet('dvae/titan_latent_trail.parquet')")
if __name__ == '__main__':
main()

View File

@@ -0,0 +1,328 @@
"""
Titan Stage 2 — Latent Trail Characterization
===============================================
Loads titan_latent_trail.parquet and answers:
Q1. Does recon_err discriminate win vs loss at entry bar?
Q2. Which z-dims correlate with trade outcome / recon_err / proxy_B?
Q3. Are DD-driving days in a distinct latent cluster?
Q4. Is z1 (Eigenspace Stress) orthogonal to proxy_B?
Q5. Is recon_err elevated in the N bars before large drawdown events?
Results saved to dvae/titan_stage2_report.json (machine-readable for Stage 3 decisions).
"""
import sys
sys.stdout.reconfigure(encoding='utf-8', errors='replace')
from pathlib import Path
import json
import numpy as np
import pandas as pd
from scipy import stats
TRAIL = Path(__file__).parent / "titan_latent_trail.parquet"
OUT = Path(__file__).parent / "titan_stage2_report.json"
Z_COLS = [f'z{i}' for i in range(32)]
LV_COLS = [f'lv{i}' for i in range(32)]
LATENT_NAMES = {
0: "Trend Consistency",
1: "Eigenspace Stress",
2: "Market Compression",
3: "Volatility Clustering",
}
# ── helpers ────────────────────────────────────────────────────────────────────
def corr_with_pval(a, b):
mask = np.isfinite(a) & np.isfinite(b)
if mask.sum() < 10:
return 0.0, 1.0
r, p = stats.pearsonr(a[mask], b[mask])
return float(r), float(p)
def cohen_d(g1, g2):
n1, n2 = len(g1), len(g2)
if n1 < 2 or n2 < 2:
return 0.0
pooled = np.sqrt(((n1 - 1) * np.var(g1, ddof=1) + (n2 - 1) * np.var(g2, ddof=1)) / (n1 + n2 - 2))
return float((np.mean(g1) - np.mean(g2)) / pooled) if pooled > 0 else 0.0
def mannwhitney(g1, g2):
if len(g1) < 5 or len(g2) < 5:
return 1.0
_, p = stats.mannwhitneyu(g1, g2, alternative='two-sided')
return float(p)
# ── load ───────────────────────────────────────────────────────────────────────
def load_trail():
assert TRAIL.exists(), f"Not found: {TRAIL}\nRun titan_stage1_run.py first."
df = pd.read_parquet(str(TRAIL))
# derive proxy_B (instab_50 - v750)
df['proxy_B'] = df['instab_50'] - df['v750']
return df
# ── Q1: recon_err — win vs loss at entry bar ───────────────────────────────────
def q1_recon_err_win_loss(df):
entry = df[(df['is_in_trade']) & (df['bars_in_trade'] == 0)].copy()
if entry.empty or 'trade_outcome' not in entry.columns:
return {"status": "no_entry_data"}
wins = entry[entry['trade_outcome'] == 'win']['recon_err'].values
losses= entry[entry['trade_outcome'] == 'loss']['recon_err'].values
p_mwu = mannwhitney(wins, losses)
d = cohen_d(wins, losses)
return {
"n_win": int(len(wins)),
"n_loss": int(len(losses)),
"recon_win_mean": float(np.mean(wins)) if len(wins) else None,
"recon_loss_mean": float(np.mean(losses)) if len(losses) else None,
"cohen_d": d,
"p_mannwhitney": p_mwu,
"significant_p05": bool(p_mwu < 0.05),
}
# ── Q2: z-dim correlations (trade outcome, recon_err, proxy_B) ─────────────────
def q2_zdim_correlations(df):
entry = df[(df['is_in_trade']) & (df['bars_in_trade'] == 0)].copy()
if entry.empty:
return {"status": "no_entry_data"}
# outcome as binary
if 'trade_outcome' in entry.columns:
entry['outcome_bin'] = (entry['trade_outcome'] == 'win').astype(float)
else:
entry['outcome_bin'] = np.nan
results = {}
for z in Z_COLS:
r_out, p_out = corr_with_pval(entry[z].values, entry['outcome_bin'].values)
r_rec, p_rec = corr_with_pval(entry[z].values, entry['recon_err'].values)
r_pb, p_pb = corr_with_pval(entry[z].values, entry['proxy_B'].values)
results[z] = {
"r_outcome": r_out, "p_outcome": p_out,
"r_recon": r_rec, "p_recon": p_rec,
"r_proxy_B": r_pb, "p_proxy_B": p_pb,
"name": LATENT_NAMES.get(int(z[1:]), ""),
}
# rank by |r_outcome|
ranked_outcome = sorted(results.items(), key=lambda kv: abs(kv[1]['r_outcome']), reverse=True)
ranked_recon = sorted(results.items(), key=lambda kv: abs(kv[1]['r_recon']), reverse=True)
return {
"top5_by_outcome": [(k, v) for k, v in ranked_outcome[:5]],
"top5_by_recon": [(k, v) for k, v in ranked_recon[:5]],
"z1_eigenspace": results.get("z1", {}),
"all": results,
}
# ── Q3: DD-day cluster analysis ────────────────────────────────────────────────
def q3_dd_cluster(df):
"""
Identify days where in-trade PnL was most negative (bottom 10% days by
mean trade_pnl_pct). Compare z_mu centroids of those days vs the rest.
"""
if 'trade_pnl_pct' not in df.columns:
return {"status": "no_pnl_data"}
# per-day mean pnl among in-trade bars
day_pnl = df[df['is_in_trade']].groupby('date')['trade_pnl_pct'].mean()
if len(day_pnl) < 10:
return {"status": "insufficient_days"}
thr = float(day_pnl.quantile(0.10))
bad_days = set(day_pnl[day_pnl <= thr].index)
good_days = set(day_pnl[day_pnl > thr].index)
bad_df = df[df['date'].isin(bad_days)]
good_df = df[df['date'].isin(good_days)]
diffs = {}
for z in Z_COLS:
g1 = bad_df[z].values
g2 = good_df[z].values
d = cohen_d(g1, g2)
p = mannwhitney(g1, g2)
diffs[z] = {"cohen_d": d, "p_mwu": p}
top5 = sorted(diffs.items(), key=lambda kv: abs(kv[1]['cohen_d']), reverse=True)[:5]
return {
"n_bad_days": len(bad_days),
"n_good_days": len(good_days),
"pnl_thr": thr,
"top5_by_cohend": [(k, v) for k, v in top5],
"all": diffs,
}
# ── Q4: z1 orthogonality to proxy_B ────────────────────────────────────────────
def q4_z1_proxyB(df):
r, p = corr_with_pval(df['z1'].values, df['proxy_B'].values)
return {"r_z1_proxyB": r, "p": p, "orthogonal_p05": bool(p >= 0.05)}
# ── Q5: recon_err pre-drawdown warning ─────────────────────────────────────────
def q5_recon_precursor(df, n_look_ahead=10, dd_thr_pct=-0.015):
"""
For each bar not in a trade, check if any of the next n_look_ahead bars
starts a trade that loses > dd_thr_pct. Does recon_err now predict that?
"""
df2 = df.copy().reset_index(drop=True)
df2['next_loss_flag'] = False
# mark bars N steps before a loss-entry
loss_entry_idxs = set(
df2[(df2['is_in_trade']) &
(df2['bars_in_trade'] == 0) &
(df2['trade_outcome'] == 'loss')].index.tolist()
) if 'trade_outcome' in df2.columns else set()
for idx in loss_entry_idxs:
for lag in range(1, n_look_ahead + 1):
prev = idx - lag
if prev >= 0:
df2.loc[prev, 'next_loss_flag'] = True
flag1 = df2[df2['next_loss_flag']]['recon_err'].values
flag0 = df2[~df2['next_loss_flag'] & ~df2['is_in_trade']]['recon_err'].values
if len(flag1) < 5 or len(flag0) < 5:
return {"status": "insufficient_data"}
p_mwu = mannwhitney(flag1, flag0)
d = cohen_d(flag1, flag0)
return {
"n_pre_loss": int(len(flag1)),
"n_baseline": int(len(flag0)),
"recon_pre_loss_mean": float(np.mean(flag1)),
"recon_baseline_mean": float(np.mean(flag0)),
"cohen_d": d,
"p_mwu": p_mwu,
"significant_p05": bool(p_mwu < 0.05),
}
# ── active z-dim count ─────────────────────────────────────────────────────────
def count_active_dims(df):
variances = {z: float(df[z].var()) for z in Z_COLS}
active = {k: v for k, v in variances.items() if v > 0.01}
return {"n_active": len(active), "top10_var": sorted(variances.items(), key=lambda kv: -kv[1])[:10]}
# ── in-trade vs out-of-trade distributions ────────────────────────────────────
def in_vs_out(df):
it = df[df['is_in_trade']]['recon_err'].values
oot = df[~df['is_in_trade']]['recon_err'].values
return {
"recon_intrade_mean": float(np.mean(it)) if len(it) else None,
"recon_outtrade_mean": float(np.mean(oot)) if len(oot) else None,
"p_mwu": mannwhitney(it, oot),
"cohen_d": cohen_d(it, oot),
}
# ── main ───────────────────────────────────────────────────────────────────────
def main():
print(f"Loading {TRAIL.name}...")
df = load_trail()
print(f" {len(df):,} rows, {len(df.columns)} cols, {df['date'].nunique()} days")
print(f" In-trade bars: {df['is_in_trade'].sum():,} ({df['is_in_trade'].mean()*100:.1f}%)")
report = {}
print("\n[Q0] Active latent dims...")
report['q0_active_dims'] = count_active_dims(df)
r = report['q0_active_dims']
print(f" Active (var>0.01): {r['n_active']}/32")
print(f" Top-5 variance: {r['top10_var'][:5]}")
print("\n[Qin] recon_err in-trade vs out-of-trade...")
report['q_inout'] = in_vs_out(df)
r = report['q_inout']
print(f" in-trade: {r['recon_intrade_mean']:.4f} out-of-trade: {r['recon_outtrade_mean']:.4f}")
print(f" p={r['p_mwu']:.4f} d={r['cohen_d']:.3f}")
print("\n[Q1] recon_err: win vs loss at entry bar...")
report['q1_recon_win_loss'] = q1_recon_err_win_loss(df)
r = report['q1_recon_win_loss']
if 'status' not in r:
print(f" wins: {r['recon_win_mean']:.4f} losses: {r['recon_loss_mean']:.4f}")
print(f" cohen_d={r['cohen_d']:.3f} p={r['p_mannwhitney']:.4f} sig={r['significant_p05']}")
else:
print(f" {r['status']}")
print("\n[Q2] z-dim correlations with outcome/recon/proxy_B...")
report['q2_zdim_corr'] = q2_zdim_correlations(df)
r = report['q2_zdim_corr']
if 'status' not in r:
print(" Top-5 by |r_outcome|:")
for z, v in r['top5_by_outcome']:
nm = LATENT_NAMES.get(int(z[1:]), "")
print(f" {z} {nm:25s}: r_outcome={v['r_outcome']:+.3f}(p={v['p_outcome']:.3f}) "
f"r_proxy_B={v['r_proxy_B']:+.3f}(p={v['p_proxy_B']:.3f})")
print("\n[Q3] DD-day latent cluster...")
report['q3_dd_cluster'] = q3_dd_cluster(df)
r = report['q3_dd_cluster']
if 'status' not in r:
print(f" Bad days (bot-10%): {r['n_bad_days']} threshold pnl: {r['pnl_thr']:.4f}")
print(" Top-5 z-dims separating bad vs good days:")
for z, v in r['top5_by_cohend']:
print(f" {z}: d={v['cohen_d']:+.3f} p={v['p_mwu']:.4f}")
print("\n[Q4] z1 orthogonality to proxy_B...")
report['q4_z1_proxyB'] = q4_z1_proxyB(df)
r = report['q4_z1_proxyB']
print(f" r(z1, proxy_B)={r['r_z1_proxyB']:+.4f} p={r['p']:.4f} orthogonal={r['orthogonal_p05']}")
print("\n[Q5] recon_err pre-drawdown precursor test...")
report['q5_precursor'] = q5_recon_precursor(df)
r = report['q5_precursor']
if 'status' not in r:
print(f" pre-loss: {r['recon_pre_loss_mean']:.4f} baseline: {r['recon_baseline_mean']:.4f}")
print(f" d={r['cohen_d']:.3f} p={r['p_mwu']:.4f} sig={r['significant_p05']}")
else:
print(f" {r['status']}")
# stage 3 verdict
q1 = report['q1_recon_win_loss']
q4 = report['q4_z1_proxyB']
q5 = report['q5_precursor']
q2_top = report['q2_zdim_corr']
verdict = {
"proceed_stage3": False,
"reason": [],
}
if 'cohen_d' in q1 and abs(q1['cohen_d']) > 0.15:
verdict['proceed_stage3'] = True
verdict['reason'].append(f"recon_err win/loss effect d={q1['cohen_d']:.3f}")
if 'status' not in q5 and abs(q5['cohen_d']) > 0.10:
verdict['proceed_stage3'] = True
verdict['reason'].append(f"recon_err pre-drawdown effect d={q5['cohen_d']:.3f}")
if 'top5_by_outcome' in q2_top:
top_r = abs(q2_top['top5_by_outcome'][0][1]['r_outcome']) if q2_top['top5_by_outcome'] else 0
if top_r > 0.10:
verdict['proceed_stage3'] = True
verdict['reason'].append(f"z-dim outcome correlation r={top_r:.3f}")
if not verdict['proceed_stage3']:
verdict['reason'].append("No significant signal found — sensor appears noise-level")
report['verdict'] = verdict
print(f"\n=== VERDICT ===")
print(f" Proceed to Stage 3: {verdict['proceed_stage3']}")
for r_ in verdict['reason']:
print(f" - {r_}")
with open(OUT, 'w') as f:
json.dump(report, f, indent=2, default=str)
print(f"\nReport saved: {OUT}")
if __name__ == '__main__':
main()

View File

@@ -0,0 +1,679 @@
{
"q0_active_dims": {
"n_active": 32,
"top10_var": [
[
"z27",
0.32883926063986446
],
[
"z4",
0.26002923909456854
],
[
"z31",
0.24365596198872513
],
[
"z24",
0.1882054355293537
],
[
"z23",
0.17481377505536136
],
[
"z17",
0.14738766979069604
],
[
"z8",
0.13379813503359
],
[
"z18",
0.12957219683420088
],
[
"z26",
0.12909863581883954
],
[
"z25",
0.12011033328745513
]
]
},
"q_inout": {
"recon_intrade_mean": 490445256765757.3,
"recon_outtrade_mean": 173221457132288.0,
"p_mwu": 4.4477839828301855e-24,
"cohen_d": 0.0016015105028981686
},
"q1_recon_win_loss": {
"n_win": 1092,
"n_loss": 1063,
"recon_win_mean": 21336023959.1057,
"recon_loss_mean": 31830850.76159581,
"cohen_d": 0.047660513750403324,
"p_mannwhitney": 0.5269125857701439,
"significant_p05": false
},
"q2_zdim_corr": {
"top5_by_outcome": [
[
"z17",
{
"r_outcome": -0.06451684559169232,
"p_outcome": 0.002732023167158115,
"r_recon": 0.013235587955279805,
"p_recon": 0.5391541543625472,
"r_proxy_B": 0.04608323921167554,
"p_proxy_B": 0.03282677816142878,
"name": ""
}
],
[
"z2",
{
"r_outcome": -0.056617333675015556,
"p_outcome": 0.008566848922239571,
"r_recon": 0.028238939264785885,
"p_recon": 0.19005780554206572,
"r_proxy_B": 0.09758896193484892,
"p_proxy_B": 5.9565495599157014e-06,
"name": "Market Compression"
}
],
[
"z4",
{
"r_outcome": -0.05277580509930862,
"p_outcome": 0.014275968216344357,
"r_recon": 0.0017309164898371189,
"p_recon": 0.9359938816007572,
"r_proxy_B": 0.055004334527692975,
"p_proxy_B": 0.010836647564480276,
"name": ""
}
],
[
"z16",
{
"r_outcome": 0.050463851611632825,
"p_outcome": 0.01914142102075866,
"r_recon": 0.01739882363324734,
"p_recon": 0.41950502199970113,
"r_proxy_B": 0.033100281203778045,
"p_proxy_B": 0.12538984265986572,
"name": ""
}
],
[
"z27",
{
"r_outcome": -0.049315788487197844,
"p_outcome": 0.02205605389308747,
"r_recon": 0.015075880242927385,
"p_recon": 0.4842484413973795,
"r_proxy_B": 0.03961647894013642,
"p_proxy_B": 0.06658663681187363,
"name": ""
}
]
],
"top5_by_recon": [
[
"z26",
{
"r_outcome": -0.02113199385842844,
"p_outcome": 0.32682493430511894,
"r_recon": 0.04153185508864403,
"p_recon": 0.05389196119312605,
"r_proxy_B": 0.0102788819075515,
"p_proxy_B": 0.6342220744774475,
"name": ""
}
],
[
"z14",
{
"r_outcome": 0.014166671416253942,
"p_outcome": 0.5109904033766511,
"r_recon": -0.038199295429193494,
"p_recon": 0.07624444639169763,
"r_proxy_B": 0.004202317402982868,
"p_proxy_B": 0.8457733204796783,
"name": ""
}
],
[
"z25",
{
"r_outcome": 0.021375298569722354,
"p_outcome": 0.3212847456492331,
"r_recon": -0.03525294504264228,
"p_recon": 0.10182484667695804,
"r_proxy_B": -0.11572879280444771,
"p_proxy_B": 7.665764334161322e-08,
"name": ""
}
],
[
"z29",
{
"r_outcome": 0.01361839088948349,
"p_outcome": 0.5274815415993123,
"r_recon": -0.03387374513006921,
"p_recon": 0.1159447549599887,
"r_proxy_B": -0.15832646672069148,
"p_proxy_B": 1.642920633664296e-13,
"name": ""
}
],
[
"z3",
{
"r_outcome": 0.015700169427212078,
"p_outcome": 0.4663351435517929,
"r_recon": -0.0327767622712368,
"p_recon": 0.1282377588247886,
"r_proxy_B": -0.0021874890904733254,
"p_proxy_B": 0.9193498216976024,
"name": "Volatility Clustering"
}
]
],
"z1_eigenspace": {
"r_outcome": -0.0247992085145205,
"p_outcome": 0.24984031750028884,
"r_recon": -0.0162103194192992,
"p_recon": 0.4519741533201718,
"r_proxy_B": -0.08765969419087043,
"p_proxy_B": 4.796148952864113e-05,
"name": "Eigenspace Stress"
},
"all": {
"z0": {
"r_outcome": -0.0010568750982667657,
"p_outcome": 0.9608923759085856,
"r_recon": 0.009598146203416313,
"p_recon": 0.6560909247329123,
"r_proxy_B": 0.1535392845201724,
"p_proxy_B": 8.720516330731598e-13,
"name": "Trend Consistency"
},
"z1": {
"r_outcome": -0.0247992085145205,
"p_outcome": 0.24984031750028884,
"r_recon": -0.0162103194192992,
"p_recon": 0.4519741533201718,
"r_proxy_B": -0.08765969419087043,
"p_proxy_B": 4.796148952864113e-05,
"name": "Eigenspace Stress"
},
"z2": {
"r_outcome": -0.056617333675015556,
"p_outcome": 0.008566848922239571,
"r_recon": 0.028238939264785885,
"p_recon": 0.19005780554206572,
"r_proxy_B": 0.09758896193484892,
"p_proxy_B": 5.9565495599157014e-06,
"name": "Market Compression"
},
"z3": {
"r_outcome": 0.015700169427212078,
"p_outcome": 0.4663351435517929,
"r_recon": -0.0327767622712368,
"p_recon": 0.1282377588247886,
"r_proxy_B": -0.0021874890904733254,
"p_proxy_B": 0.9193498216976024,
"name": "Volatility Clustering"
},
"z4": {
"r_outcome": -0.05277580509930862,
"p_outcome": 0.014275968216344357,
"r_recon": 0.0017309164898371189,
"p_recon": 0.9359938816007572,
"r_proxy_B": 0.055004334527692975,
"p_proxy_B": 0.010836647564480276,
"name": ""
},
"z5": {
"r_outcome": 0.020000026866032066,
"p_outcome": 0.35341019960525544,
"r_recon": -0.009469967227304968,
"p_recon": 0.6603939006428033,
"r_proxy_B": -0.10834271232265892,
"p_proxy_B": 4.912827716297637e-07,
"name": ""
},
"z6": {
"r_outcome": -0.019569952694745922,
"p_outcome": 0.36385932892636746,
"r_recon": -0.0074057061713787235,
"p_recon": 0.7311525619916945,
"r_proxy_B": -0.020528881091233183,
"p_proxy_B": 0.34194790377129375,
"name": ""
},
"z7": {
"r_outcome": -0.04033988594722973,
"p_outcome": 0.06116056624508808,
"r_recon": 0.0052153687291698414,
"p_recon": 0.8088042580905699,
"r_proxy_B": 0.10085333136306103,
"p_proxy_B": 2.866651889916251e-06,
"name": ""
},
"z8": {
"r_outcome": -0.022628247113126432,
"p_outcome": 0.29373055414136506,
"r_recon": -0.0006728061311814461,
"p_recon": 0.9750981785707314,
"r_proxy_B": -0.0826384980858231,
"p_proxy_B": 0.0001272839253111422,
"name": ""
},
"z9": {
"r_outcome": 0.01690926987281038,
"p_outcome": 0.4327101833651,
"r_recon": 0.02400736945750492,
"p_recon": 0.26528626750917156,
"r_proxy_B": 0.0567844781969885,
"p_proxy_B": 0.008525582602088367,
"name": ""
},
"z10": {
"r_outcome": -0.04334251912075127,
"p_outcome": 0.0442385507477984,
"r_recon": 0.025178248383767365,
"p_recon": 0.24267322161835128,
"r_proxy_B": -0.048852104224130645,
"p_proxy_B": 0.023661870411551635,
"name": ""
},
"z11": {
"r_outcome": -0.0012230351015354048,
"p_outcome": 0.9547500890762326,
"r_recon": 0.019743119140964778,
"p_recon": 0.35962905206258083,
"r_proxy_B": -0.03055337170168354,
"p_proxy_B": 0.15719793615969194,
"name": ""
},
"z12": {
"r_outcome": 0.0041618036839659416,
"p_outcome": 0.8468901295694582,
"r_recon": 0.01520450651259507,
"p_recon": 0.4805274279049127,
"r_proxy_B": 0.041888743860036146,
"p_proxy_B": 0.052408637888979474,
"name": ""
},
"z13": {
"r_outcome": 0.036536901772677266,
"p_outcome": 0.08994321937470057,
"r_recon": -0.014487387452323983,
"p_recon": 0.5014702295391479,
"r_proxy_B": -0.1475724788444472,
"p_proxy_B": 6.495098046309721e-12,
"name": ""
},
"z14": {
"r_outcome": 0.014166671416253942,
"p_outcome": 0.5109904033766511,
"r_recon": -0.038199295429193494,
"p_recon": 0.07624444639169763,
"r_proxy_B": 0.004202317402982868,
"p_proxy_B": 0.8457733204796783,
"name": ""
},
"z15": {
"r_outcome": 0.02499948887983221,
"p_outcome": 0.2460351015023294,
"r_recon": -0.03218413809052064,
"p_recon": 0.13528768330745722,
"r_proxy_B": -0.08823042177601736,
"p_proxy_B": 4.278226406002478e-05,
"name": ""
},
"z16": {
"r_outcome": 0.050463851611632825,
"p_outcome": 0.01914142102075866,
"r_recon": 0.01739882363324734,
"p_recon": 0.41950502199970113,
"r_proxy_B": 0.033100281203778045,
"p_proxy_B": 0.12538984265986572,
"name": ""
},
"z17": {
"r_outcome": -0.06451684559169232,
"p_outcome": 0.002732023167158115,
"r_recon": 0.013235587955279805,
"p_recon": 0.5391541543625472,
"r_proxy_B": 0.04608323921167554,
"p_proxy_B": 0.03282677816142878,
"name": ""
},
"z18": {
"r_outcome": -0.010419742856575296,
"p_outcome": 0.6287850948537157,
"r_recon": -0.003605377207356623,
"p_recon": 0.8671558961288544,
"r_proxy_B": 0.14216278466870444,
"p_proxy_B": 3.742548790512605e-11,
"name": ""
},
"z19": {
"r_outcome": -0.03250126501522874,
"p_outcome": 0.13147877202501235,
"r_recon": 0.021216153102931148,
"p_recon": 0.32490160545451197,
"r_proxy_B": 0.0009477258934566436,
"p_proxy_B": 0.9650099909640183,
"name": ""
},
"z20": {
"r_outcome": -0.03777134930577331,
"p_outcome": 0.07959704172265401,
"r_recon": 0.028740518832845723,
"p_recon": 0.182304453456116,
"r_proxy_B": 0.05137003128880263,
"p_proxy_B": 0.01734351966005348,
"name": ""
},
"z21": {
"r_outcome": 0.013234681648433487,
"p_outcome": 0.5391819426159447,
"r_recon": -0.003171829009950398,
"p_recon": 0.8830076936291017,
"r_proxy_B": -0.10679829002254787,
"p_proxy_B": 7.137941914537842e-07,
"name": ""
},
"z22": {
"r_outcome": -0.02550599748413965,
"p_outcome": 0.2365934622841013,
"r_recon": 0.020307675235927632,
"p_recon": 0.3460530724297239,
"r_proxy_B": 0.10042782658884095,
"p_proxy_B": 3.1574341248678866e-06,
"name": ""
},
"z23": {
"r_outcome": 0.014935198525591557,
"p_outcome": 0.48833601511223057,
"r_recon": 0.027872647621363763,
"p_recon": 0.19587137746125674,
"r_proxy_B": 0.301977799387075,
"p_proxy_B": 1.811461686140159e-46,
"name": ""
},
"z24": {
"r_outcome": -0.0360981091555881,
"p_outcome": 0.09387159323975619,
"r_recon": 0.02857139473657159,
"p_recon": 0.18489211908852077,
"r_proxy_B": 0.06433572953545957,
"p_proxy_B": 0.002873032209764728,
"name": ""
},
"z25": {
"r_outcome": 0.021375298569722354,
"p_outcome": 0.3212847456492331,
"r_recon": -0.03525294504264228,
"p_recon": 0.10182484667695804,
"r_proxy_B": -0.11572879280444771,
"p_proxy_B": 7.665764334161322e-08,
"name": ""
},
"z26": {
"r_outcome": -0.02113199385842844,
"p_outcome": 0.32682493430511894,
"r_recon": 0.04153185508864403,
"p_recon": 0.05389196119312605,
"r_proxy_B": 0.0102788819075515,
"p_proxy_B": 0.6342220744774475,
"name": ""
},
"z27": {
"r_outcome": -0.049315788487197844,
"p_outcome": 0.02205605389308747,
"r_recon": 0.015075880242927385,
"p_recon": 0.4842484413973795,
"r_proxy_B": 0.03961647894013642,
"p_proxy_B": 0.06658663681187363,
"name": ""
},
"z28": {
"r_outcome": 0.0223352138005965,
"p_outcome": 0.3000283991479089,
"r_recon": -0.009249018350925543,
"p_recon": 0.6678375092136699,
"r_proxy_B": -0.20421421742886217,
"p_proxy_B": 1.2558249326308314e-21,
"name": ""
},
"z29": {
"r_outcome": 0.01361839088948349,
"p_outcome": 0.5274815415993123,
"r_recon": -0.03387374513006921,
"p_recon": 0.1159447549599887,
"r_proxy_B": -0.15832646672069148,
"p_proxy_B": 1.642920633664296e-13,
"name": ""
},
"z30": {
"r_outcome": 0.023239956878488108,
"p_outcome": 0.28087114844614264,
"r_recon": -0.03268331324183672,
"p_recon": 0.12933008601923748,
"r_proxy_B": -0.1835909740980365,
"p_proxy_B": 1.0272396398012494e-17,
"name": ""
},
"z31": {
"r_outcome": 0.04289343307123413,
"p_outcome": 0.046485396698545296,
"r_recon": 0.006306882337453737,
"p_recon": 0.7698195124016781,
"r_proxy_B": -0.02802640429729864,
"p_proxy_B": 0.19445329724411342,
"name": ""
}
}
},
"q3_dd_cluster": {
"n_bad_days": 6,
"n_good_days": 50,
"pnl_thr": -0.0018667932119076722,
"top5_by_cohend": [
[
"z2",
{
"cohen_d": 0.1702382007291983,
"p_mwu": 3.0736777951998913e-176
}
],
[
"z30",
{
"cohen_d": -0.13699319599298396,
"p_mwu": 1.6958984078998588e-137
}
],
[
"z14",
{
"cohen_d": -0.13589791488813383,
"p_mwu": 4.785233693695352e-114
}
],
[
"z24",
{
"cohen_d": 0.13343797760574308,
"p_mwu": 1.2927044220421628e-112
}
],
[
"z28",
{
"cohen_d": -0.13101473669555413,
"p_mwu": 2.266661710705797e-108
}
]
],
"all": {
"z0": {
"cohen_d": -0.003607296612985334,
"p_mwu": 0.05448282039381696
},
"z1": {
"cohen_d": 0.002270852314641101,
"p_mwu": 0.18464589699399714
},
"z2": {
"cohen_d": 0.1702382007291983,
"p_mwu": 3.0736777951998913e-176
},
"z3": {
"cohen_d": -0.08606377852354527,
"p_mwu": 3.858966160620741e-31
},
"z4": {
"cohen_d": -0.03930732284705677,
"p_mwu": 3.570418056957964e-08
},
"z5": {
"cohen_d": 0.03767457899823889,
"p_mwu": 0.008304914770217817
},
"z6": {
"cohen_d": -0.0038666876112777304,
"p_mwu": 0.6618622244751171
},
"z7": {
"cohen_d": 0.01694936976956897,
"p_mwu": 0.0008244556186001203
},
"z8": {
"cohen_d": -0.060054157590918734,
"p_mwu": 1.8655724326671896e-11
},
"z9": {
"cohen_d": -0.07312008305740242,
"p_mwu": 1.1770831324668023e-43
},
"z10": {
"cohen_d": 0.10041389587252378,
"p_mwu": 3.0238566750068834e-59
},
"z11": {
"cohen_d": 0.11843170197666235,
"p_mwu": 6.412698538396475e-88
},
"z12": {
"cohen_d": 0.03249783237190171,
"p_mwu": 3.917135177969126e-07
},
"z13": {
"cohen_d": -0.07090955502752205,
"p_mwu": 2.4168037389481122e-24
},
"z14": {
"cohen_d": -0.13589791488813383,
"p_mwu": 4.785233693695352e-114
},
"z15": {
"cohen_d": -0.09101483704799612,
"p_mwu": 4.857812290022197e-53
},
"z16": {
"cohen_d": -0.08306701720666351,
"p_mwu": 1.5693325289612218e-48
},
"z17": {
"cohen_d": 0.03351832104783407,
"p_mwu": 3.418058656690057e-05
},
"z18": {
"cohen_d": -0.0724821119024743,
"p_mwu": 1.841744446899598e-18
},
"z19": {
"cohen_d": 0.09819374372971937,
"p_mwu": 5.5906661745128066e-73
},
"z20": {
"cohen_d": 0.027696992079276395,
"p_mwu": 0.0004392631470794174
},
"z21": {
"cohen_d": -0.09719456361183183,
"p_mwu": 4.2805622117725385e-61
},
"z22": {
"cohen_d": -0.0016563123173948867,
"p_mwu": 0.17419143874597287
},
"z23": {
"cohen_d": 0.07151359964500655,
"p_mwu": 2.385055840415299e-37
},
"z24": {
"cohen_d": 0.13343797760574308,
"p_mwu": 1.2927044220421628e-112
},
"z25": {
"cohen_d": -0.04241011895618605,
"p_mwu": 1.5634881824466994e-06
},
"z26": {
"cohen_d": -0.02937131756252973,
"p_mwu": 0.017666616149218564
},
"z27": {
"cohen_d": 0.0008605492125759038,
"p_mwu": 0.4167828096406764
},
"z28": {
"cohen_d": -0.13101473669555413,
"p_mwu": 2.266661710705797e-108
},
"z29": {
"cohen_d": -0.11529483621765778,
"p_mwu": 4.93695857580849e-86
},
"z30": {
"cohen_d": -0.13699319599298396,
"p_mwu": 1.6958984078998588e-137
},
"z31": {
"cohen_d": 0.04758746969298401,
"p_mwu": 8.924920058240176e-23
}
}
},
"q4_z1_proxyB": {
"r_z1_proxyB": -0.03690845832536047,
"p": 2.0738200311673893e-104,
"orthogonal_p05": false
},
"q5_precursor": {
"n_pre_loss": 10630,
"n_baseline": 99929,
"recon_pre_loss_mean": 1.0888473331169846e+16,
"recon_baseline_mean": 186243987055429.94,
"cohen_d": 0.03051106041997001,
"p_mwu": 4.685046720661133e-06,
"significant_p05": true
},
"verdict": {
"proceed_stage3": false,
"reason": [
"No significant signal found \u2014 sensor appears noise-level"
]
}
}

View File

@@ -0,0 +1,314 @@
"""
DOLPHIN Hierarchical D-VAE — Curriculum Training
==================================================
Run this script to train the H-D-VAE over the full multi-generation corpus.
Usage:
cd nautilus_dolphin/dvae
python train_corpus_dvae.py [--phases 0123] [--epochs 100] [--lr 3e-4]
Training curriculum (4 phases):
Phase 0: Macro encoder only — full 500K+ corpus (breadth signal from ALL gens)
Phase 1: + Eigen encoder — NG3/NG5 subset with eigenvalue data
Phase 2: + Cross-section — NG3/NG5 subset with pricing data
Phase 3: Joint fine-tune — full eigen+pricing corpus together
The "512-bit advantage" is injected via log-ratio features in the corpus builder:
log(lambda_max) — captures the order-of-magnitude accurately
gap_ratio — scale-free condition number proxy
vel_norm — velocity normalised by magnitude
These preserve the mathematical structure even within float64 storage.
Expected discoveries (based on D-VAE theory + DOLPHIN domain knowledge):
1. z0 latent space shows MORE THAN 3 regimes — the simple BULL/BEAR/SIDEWAYS
label is lossy; the VAE will reveal 5-7 meta-states.
2. Regime transition events (high dTC/dt) precede breadth changes by N scans
— eigenvalue instability is LEADING, not coincident.
3. z1_eigen predicts FUTURE z0_macro delta — structural change comes first.
4. z2_xsection reveals which assets (BTC dominance, BNB, stablecoins) are
the primary eigenvectors of regime transitions.
"""
import sys
sys.stdout.reconfigure(encoding='utf-8', errors='replace')
sys.stderr.reconfigure(encoding='utf-8', errors='replace')
import argparse
import json
import time
import numpy as np
from pathlib import Path
from datetime import datetime
from corpus_builder import DolphinCorpus
from hierarchical_dvae import HierarchicalDVAE
# ── Paths ──────────────────────────────────────────────────────────────────
HERE = Path(__file__).parent
CACHE = HERE / 'corpus_cache.npz'
MODEL = HERE / 'hdvae_checkpoint'
LOGS = HERE / 'training_log.jsonl'
# ── Curriculum config ──────────────────────────────────────────────────────
PHASE_CONFIG = {
0: dict(epochs=60, lr=3e-4, batch=256, name="T0: Macro-only (ALL gens, pre-eigen included)"),
1: dict(epochs=80, lr=2e-4, batch=128, name="T1: + Eigenvalue tier (NG3+)"),
2: dict(epochs=80, lr=1e-4, batch=128, name="T2: + Cross-section tier (pricing)"),
3: dict(epochs=60, lr=1e-4, batch=128, name="T3: + ExF macro tier"),
4: dict(epochs=100, lr=5e-5, batch=256, name="T4: Joint fine-tune all tiers"),
}
PRINT_EVERY = 10 # epochs
def build_or_load_corpus(force_rebuild=False) -> DolphinCorpus:
if CACHE.exists() and not force_rebuild:
print(f"Loading corpus from cache: {CACHE}")
return DolphinCorpus.load(str(CACHE))
corpus = DolphinCorpus().build(
ng3_date_from='2025-12-31', # full NG3 history from Dec-31
max_scans_per_day=300, # 300 scans/day × ~70 days = ~21K NG3 rows
max_per_source=50_000, # cap NG1/NG2/NG4 at 50K each (~RAM safe)
max_ng5=2_000, # NG5-local files read slowly (~26/s), cap low
verbose=True,
)
corpus.save(str(HERE / 'corpus_cache'))
return corpus
def get_phase_data(corpus: DolphinCorpus, phase: int):
"""Return (X, mask) appropriate for this training phase."""
if phase == 0:
return corpus.X, corpus.mask # ALL (even pre-eigen)
elif phase == 1:
idx = corpus.mask[:, 1] # has eigenvalues
return corpus.X[idx], corpus.mask[idx]
elif phase == 2:
idx = corpus.mask[:, 1] & corpus.mask[:, 2] # eigen + pricing
return corpus.X[idx], corpus.mask[idx]
elif phase == 3:
idx = corpus.mask[:, 3] # has ExF data
return corpus.X[idx], corpus.mask[idx]
else: # phase 4 joint
idx = corpus.mask[:, 1] # eigen subset, all tiers
return corpus.X[idx], corpus.mask[idx]
def log_stats(epoch: int, phase: int, stats: dict, elapsed: float):
record = dict(epoch=epoch, phase=phase, elapsed_s=round(elapsed, 2), **{k: round(float(v), 6) for k, v in stats.items()})
with open(LOGS, 'a') as f:
f.write(json.dumps(record) + '\n')
return record
def train(phases=(0, 1, 2, 3), force_rebuild=False):
rng = np.random.RandomState(42)
print("=" * 60)
print("DOLPHIN Hierarchical D-VAE — Curriculum Training")
print("=" * 60)
corpus = build_or_load_corpus(force_rebuild)
model = HierarchicalDVAE(hidden=128, beta=0.5, gamma=1.0, lam=1.0, seed=42)
model.fit_normaliser(corpus.X, corpus.mask)
print(f"\nModel: z0=4 z1=8 z2=8 z3=4 total_latent=24 hidden=128 input=111")
print(f"beta-TCVAE: beta=0.5 gamma=1.0 lam=1.0\n")
total_start = time.time()
for phase in phases:
cfg = PHASE_CONFIG[phase]
X, mask = get_phase_data(corpus, phase)
N = len(X)
print(f"\n{''*60}")
print(f"Phase {phase}: {cfg['name']} N={N:,} epochs={cfg['epochs']} lr={cfg['lr']}")
print(f"{''*60}")
best_loss = float('inf')
patience_cnt = 0
# KL warm-up: ramp ALL KL coefficients (gamma, beta, lam) from 0 to target
# over first 30% of epochs. With all=0, pure reconstruction training first.
target_beta = model.beta
target_gamma = model.gamma
target_lam = model.lam
warmup_epochs = max(1, cfg['epochs'] // 3)
for epoch in range(1, cfg['epochs'] + 1):
if epoch <= warmup_epochs:
frac = epoch / warmup_epochs
model.beta = target_beta * frac
model.gamma = target_gamma * frac
model.lam = target_lam * frac
else:
model.beta = target_beta
model.gamma = target_gamma
model.lam = target_lam
t0 = time.time()
stats = model.train_epoch(X, mask, lr=cfg['lr'],
batch_size=cfg['batch'],
phase=phase, rng=rng)
elapsed = time.time() - t0
record = log_stats(epoch + sum(PHASE_CONFIG[p]['epochs'] for p in range(phase)),
phase, stats, elapsed)
if epoch % PRINT_EVERY == 0 or epoch == 1:
print(f" ep {epoch:4d}/{cfg['epochs']} "
f"loss={stats['total']:.4f} "
f"recon=[{stats['recon0']:.4f},{stats['recon1']:.4f},{stats['recon2']:.4f}] "
f"KL=[{stats['kl0']:.4f},{stats['kl1']:.4f},{stats['kl2']:.4f}] "
f"({elapsed:.1f}s/ep)")
# Early stopping
if stats['total'] < best_loss - 1e-5:
best_loss = stats['total']
patience_cnt = 0
model.save(str(MODEL))
else:
patience_cnt += 1
if patience_cnt >= 20 and epoch > cfg['epochs'] // 3:
print(f" Early stop at epoch {epoch} (patience=20)")
break
model.beta = target_beta # restore after warm-up
model.gamma = target_gamma
model.lam = target_lam
print(f" Phase {phase} done. Best loss: {best_loss:.4f}")
total_elapsed = time.time() - total_start
print(f"\n{'='*60}")
print(f"Training complete in {total_elapsed/60:.1f} min")
print(f"Model saved: {MODEL}.npz")
print(f"Log: {LOGS}")
# ── Latent space analysis ──────────────────────────────────────────────
analyse(model, corpus)
return model
def analyse(model: HierarchicalDVAE, corpus: DolphinCorpus):
"""
Post-training analysis — the SURPRISES live here.
Writes results to analysis_results.json.
"""
print("\n" + "=" * 60)
print("LATENT SPACE ANALYSIS")
print("=" * 60)
# Use eigen subset for meaningful analysis
idx_eigen = corpus.mask[:, 1]
X_e = corpus.X[idx_eigen]
mask_e = corpus.mask[idx_eigen]
src_e = corpus.sources[idx_eigen]
latents = model.get_latents(X_e, mask_e)
z0 = latents['z0_macro'] # (N, 4)
z1 = latents['z1_eigen'] # (N, 8)
z2 = latents['z2_xsection'] # (N, 8)
results = {}
# 1. Cluster z0 — how many macro regimes does the model find?
from scipy.cluster.vq import kmeans2
n_clusters_try = [3, 4, 5, 6, 7, 8]
best_sil, best_k = -1, 3
sil_scores = {}
for k in n_clusters_try:
try:
centroids, labels = kmeans2(z0, k, seed=42, minit='points')
# Silhouette (approximate, fast)
intra = np.mean([np.mean(np.linalg.norm(z0[labels == i] - centroids[i], axis=1))
for i in range(k) if (labels == i).sum() > 0])
inter_dists = [np.linalg.norm(centroids[i] - centroids[j])
for i in range(k) for j in range(k) if i != j]
sil = min(inter_dists) / max(intra, 1e-6)
sil_scores[k] = round(float(sil), 4)
if sil > best_sil:
best_sil, best_k = sil, k
except Exception:
pass
print(f"\n1. Macro regime clusters (z0):")
print(f" Silhouette scores: {sil_scores}")
print(f" OPTIMAL k = {best_k} (expected: more than 3 if hypothesis holds)")
results['macro_regime_k'] = best_k
results['macro_regime_silhouettes'] = sil_scores
# 2. Predictive power: does z1 (eigen) predict FUTURE z0?
if len(z0) > 100:
LAGS = [1, 3, 5, 10, 20]
z1_to_z0_pred = {}
for lag in LAGS:
z1_now = z1[:-lag]
z0_future = z0[lag:]
# Linear regression: z1 → z0_future[dim 0] (proxy for regime score)
# Simple correlation
z0_f_norm = z0_future[:, 0]
z1_flat = z1_now.mean(axis=1) # scalar summary
corr = np.corrcoef(z1_flat, z0_f_norm)[0, 1]
z1_to_z0_pred[lag] = round(float(corr), 4)
print(f"\n2. z1_eigen -> future z0_macro correlation:")
for lag, c in z1_to_z0_pred.items():
print(f" lag={lag:2d}: r={c:.4f}")
peak_lag = max(z1_to_z0_pred, key=lambda l: abs(z1_to_z0_pred[l]))
print(f" PEAK at lag={peak_lag} — z1 leads z0 by {peak_lag} scans (~{peak_lag*12}s)")
results['z1_leads_z0'] = z1_to_z0_pred
results['z1_peak_lead_lag'] = peak_lag
# 3. Variance captured per latent dimension
z_all = latents['z_all']
var_per_dim = np.var(z_all, axis=0)
active_dims = (var_per_dim > 0.1).sum()
print(f"\n3. Active latent dimensions (var>0.1): {active_dims}/{z_all.shape[1]}")
print(f" Variance per dim: {np.round(var_per_dim, 3)}")
results['active_dims'] = int(active_dims)
results['var_per_dim'] = var_per_dim.tolist()
# 4. Per-source separation — do NG1 vs NG3 cluster differently in z0?
print(f"\n4. z0 centroids per data generation:")
src_all = corpus.sources[idx_eigen]
src_names = {0: 'NG1/2', 1: 'NG4', 2: 'NG5-local', 3: 'NG3-scan'}
for sid, sname in src_names.items():
sidx = src_all == sid
if sidx.sum() > 5:
c = z0[sidx].mean(axis=0)
print(f" {sname:12s} N={sidx.sum():6,} z0_mean={np.round(c,3)}")
# 5. Reconstruction error as OOD detector
rng_inf = np.random.RandomState(0)
enc_all = model.encode(X_e[:1000], mask_e[:1000], rng_inf)
dec_all = model.decode(enc_all)
t0_recon = dec_all['x0_hat']
recon_err = np.mean((t0_recon - enc_all['t0'])**2, axis=1)
print(f"\n5. Reconstruction error (tier0):")
print(f" Mean: {recon_err.mean():.4f} Std: {recon_err.std():.4f}")
print(f" Top-5% OOD threshold: {np.percentile(recon_err, 95):.4f}")
results['recon_err_mean'] = float(recon_err.mean())
results['recon_err_p95'] = float(np.percentile(recon_err, 95))
# Save
out = HERE / 'analysis_results.json'
with open(out, 'w') as f:
json.dump(results, f, indent=2)
print(f"\nAnalysis saved: {out}")
return results
if __name__ == '__main__':
parser = argparse.ArgumentParser()
parser.add_argument('--phases', type=str, default='0123',
help='Which phases to run e.g. "0123" or "3"')
parser.add_argument('--rebuild', action='store_true', help='Force corpus rebuild')
parser.add_argument('--analyse-only', action='store_true', help='Skip training, just analyse saved model')
args = parser.parse_args()
phases = [int(p) for p in args.phases]
if args.analyse_only:
corpus = build_or_load_corpus(False)
model = HierarchicalDVAE(hidden=128, beta=6.0, gamma=1.0, lam=1.0)
d = np.load(str(MODEL) + '.npz', allow_pickle=True)
# (load weights — simplified; full load_weights() would restore all layers)
print("Note: implement full weight restore for production use.")
analyse(model, corpus)
else:
train(phases=phases, force_rebuild=args.rebuild)

View File

@@ -0,0 +1,309 @@
{"epoch": 1, "phase": 0, "elapsed_s": 22.03, "total": 0.563407, "recon0": 0.821045, "recon1": 0.0, "recon2": 0.0, "kl0": -0.257638, "kl1": 0.0, "kl2": 0.0}
{"epoch": 2, "phase": 0, "elapsed_s": 23.21, "total": 0.011092, "recon0": 0.488985, "recon1": 0.0, "recon2": 0.0, "kl0": -0.477893, "kl1": 0.0, "kl2": 0.0}
{"epoch": 3, "phase": 0, "elapsed_s": 21.05, "total": -0.11968, "recon0": 0.526534, "recon1": 0.0, "recon2": 0.0, "kl0": -0.646214, "kl1": 0.0, "kl2": 0.0}
{"epoch": 4, "phase": 0, "elapsed_s": 19.68, "total": -0.302427, "recon0": 0.566573, "recon1": 0.0, "recon2": 0.0, "kl0": -0.869, "kl1": 0.0, "kl2": 0.0}
{"epoch": 5, "phase": 0, "elapsed_s": 20.36, "total": -0.603467, "recon0": 0.61756, "recon1": 0.0, "recon2": 0.0, "kl0": -1.221026, "kl1": 0.0, "kl2": 0.0}
{"epoch": 6, "phase": 0, "elapsed_s": 19.5, "total": -0.984, "recon0": 0.658019, "recon1": 0.0, "recon2": 0.0, "kl0": -1.642019, "kl1": 0.0, "kl2": 0.0}
{"epoch": 7, "phase": 0, "elapsed_s": 19.66, "total": -1.319487, "recon0": 0.702564, "recon1": 0.0, "recon2": 0.0, "kl0": -2.02205, "kl1": 0.0, "kl2": 0.0}
{"epoch": 8, "phase": 0, "elapsed_s": 18.56, "total": -1.716922, "recon0": 0.752583, "recon1": 0.0, "recon2": 0.0, "kl0": -2.469504, "kl1": 0.0, "kl2": 0.0}
{"epoch": 9, "phase": 0, "elapsed_s": 19.71, "total": -2.202457, "recon0": 0.797613, "recon1": 0.0, "recon2": 0.0, "kl0": -3.000069, "kl1": 0.0, "kl2": 0.0}
{"epoch": 10, "phase": 0, "elapsed_s": 18.81, "total": -2.831215, "recon0": 0.853824, "recon1": 0.0, "recon2": 0.0, "kl0": -3.685039, "kl1": 0.0, "kl2": 0.0}
{"epoch": 11, "phase": 0, "elapsed_s": 18.51, "total": -3.424253, "recon0": 0.90775, "recon1": 0.0, "recon2": 0.0, "kl0": -4.332004, "kl1": 0.0, "kl2": 0.0}
{"epoch": 12, "phase": 0, "elapsed_s": 18.58, "total": -3.981677, "recon0": 0.937774, "recon1": 0.0, "recon2": 0.0, "kl0": -4.919451, "kl1": 0.0, "kl2": 0.0}
{"epoch": 13, "phase": 0, "elapsed_s": 20.87, "total": -4.469276, "recon0": 0.963814, "recon1": 0.0, "recon2": 0.0, "kl0": -5.43309, "kl1": 0.0, "kl2": 0.0}
{"epoch": 14, "phase": 0, "elapsed_s": 20.08, "total": -4.909504, "recon0": 0.977914, "recon1": 0.0, "recon2": 0.0, "kl0": -5.887418, "kl1": 0.0, "kl2": 0.0}
{"epoch": 15, "phase": 0, "elapsed_s": 20.36, "total": -5.342119, "recon0": 0.985359, "recon1": 0.0, "recon2": 0.0, "kl0": -6.327477, "kl1": 0.0, "kl2": 0.0}
{"epoch": 16, "phase": 0, "elapsed_s": 18.58, "total": -5.773167, "recon0": 0.990528, "recon1": 0.0, "recon2": 0.0, "kl0": -6.763696, "kl1": 0.0, "kl2": 0.0}
{"epoch": 17, "phase": 0, "elapsed_s": 18.21, "total": -6.202573, "recon0": 1.001185, "recon1": 0.0, "recon2": 0.0, "kl0": -7.203758, "kl1": 0.0, "kl2": 0.0}
{"epoch": 18, "phase": 0, "elapsed_s": 17.96, "total": -6.631239, "recon0": 1.00317, "recon1": 0.0, "recon2": 0.0, "kl0": -7.63441, "kl1": 0.0, "kl2": 0.0}
{"epoch": 19, "phase": 0, "elapsed_s": 18.56, "total": -7.057077, "recon0": 1.002305, "recon1": 0.0, "recon2": 0.0, "kl0": -8.059382, "kl1": 0.0, "kl2": 0.0}
{"epoch": 20, "phase": 0, "elapsed_s": 18.79, "total": -7.487053, "recon0": 1.002168, "recon1": 0.0, "recon2": 0.0, "kl0": -8.489221, "kl1": 0.0, "kl2": 0.0}
{"epoch": 21, "phase": 0, "elapsed_s": 18.96, "total": -7.486468, "recon0": 1.004711, "recon1": 0.0, "recon2": 0.0, "kl0": -8.49118, "kl1": 0.0, "kl2": 0.0}
{"epoch": 22, "phase": 0, "elapsed_s": 18.62, "total": -7.488365, "recon0": 1.004622, "recon1": 0.0, "recon2": 0.0, "kl0": -8.492987, "kl1": 0.0, "kl2": 0.0}
{"epoch": 23, "phase": 0, "elapsed_s": 18.42, "total": -7.490015, "recon0": 1.00438, "recon1": 0.0, "recon2": 0.0, "kl0": -8.494395, "kl1": 0.0, "kl2": 0.0}
{"epoch": 24, "phase": 0, "elapsed_s": 18.2, "total": -7.489238, "recon0": 1.003929, "recon1": 0.0, "recon2": 0.0, "kl0": -8.493167, "kl1": 0.0, "kl2": 0.0}
{"epoch": 25, "phase": 0, "elapsed_s": 17.27, "total": -7.490874, "recon0": 1.003384, "recon1": 0.0, "recon2": 0.0, "kl0": -8.494258, "kl1": 0.0, "kl2": 0.0}
{"epoch": 26, "phase": 0, "elapsed_s": 17.8, "total": -7.493285, "recon0": 1.003424, "recon1": 0.0, "recon2": 0.0, "kl0": -8.496709, "kl1": 0.0, "kl2": 0.0}
{"epoch": 27, "phase": 0, "elapsed_s": 18.42, "total": -7.492076, "recon0": 1.003325, "recon1": 0.0, "recon2": 0.0, "kl0": -8.495401, "kl1": 0.0, "kl2": 0.0}
{"epoch": 28, "phase": 0, "elapsed_s": 18.65, "total": -7.492305, "recon0": 1.002934, "recon1": 0.0, "recon2": 0.0, "kl0": -8.495239, "kl1": 0.0, "kl2": 0.0}
{"epoch": 29, "phase": 0, "elapsed_s": 20.79, "total": -7.493778, "recon0": 1.003455, "recon1": 0.0, "recon2": 0.0, "kl0": -8.497234, "kl1": 0.0, "kl2": 0.0}
{"epoch": 30, "phase": 0, "elapsed_s": 20.76, "total": -7.492053, "recon0": 1.002286, "recon1": 0.0, "recon2": 0.0, "kl0": -8.494339, "kl1": 0.0, "kl2": 0.0}
{"epoch": 31, "phase": 0, "elapsed_s": 19.65, "total": -7.494672, "recon0": 1.002418, "recon1": 0.0, "recon2": 0.0, "kl0": -8.497089, "kl1": 0.0, "kl2": 0.0}
{"epoch": 32, "phase": 0, "elapsed_s": 21.82, "total": -7.494612, "recon0": 1.002406, "recon1": 0.0, "recon2": 0.0, "kl0": -8.497017, "kl1": 0.0, "kl2": 0.0}
{"epoch": 33, "phase": 0, "elapsed_s": 18.7, "total": -7.494464, "recon0": 1.001947, "recon1": 0.0, "recon2": 0.0, "kl0": -8.496411, "kl1": 0.0, "kl2": 0.0}
{"epoch": 34, "phase": 0, "elapsed_s": 17.75, "total": -7.495878, "recon0": 1.002308, "recon1": 0.0, "recon2": 0.0, "kl0": -8.498185, "kl1": 0.0, "kl2": 0.0}
{"epoch": 35, "phase": 0, "elapsed_s": 18.27, "total": -7.495623, "recon0": 1.002302, "recon1": 0.0, "recon2": 0.0, "kl0": -8.497925, "kl1": 0.0, "kl2": 0.0}
{"epoch": 36, "phase": 0, "elapsed_s": 18.71, "total": -7.495609, "recon0": 1.002202, "recon1": 0.0, "recon2": 0.0, "kl0": -8.49781, "kl1": 0.0, "kl2": 0.0}
{"epoch": 37, "phase": 0, "elapsed_s": 18.01, "total": -7.495415, "recon0": 1.00316, "recon1": 0.0, "recon2": 0.0, "kl0": -8.498574, "kl1": 0.0, "kl2": 0.0}
{"epoch": 38, "phase": 0, "elapsed_s": 18.29, "total": -7.496314, "recon0": 1.002531, "recon1": 0.0, "recon2": 0.0, "kl0": -8.498845, "kl1": 0.0, "kl2": 0.0}
{"epoch": 39, "phase": 0, "elapsed_s": 18.14, "total": -7.497587, "recon0": 1.002706, "recon1": 0.0, "recon2": 0.0, "kl0": -8.500294, "kl1": 0.0, "kl2": 0.0}
{"epoch": 40, "phase": 0, "elapsed_s": 18.0, "total": -7.496429, "recon0": 1.002001, "recon1": 0.0, "recon2": 0.0, "kl0": -8.498431, "kl1": 0.0, "kl2": 0.0}
{"epoch": 41, "phase": 0, "elapsed_s": 17.28, "total": -7.496715, "recon0": 1.001397, "recon1": 0.0, "recon2": 0.0, "kl0": -8.498113, "kl1": 0.0, "kl2": 0.0}
{"epoch": 42, "phase": 0, "elapsed_s": 18.34, "total": -7.496506, "recon0": 1.00252, "recon1": 0.0, "recon2": 0.0, "kl0": -8.499026, "kl1": 0.0, "kl2": 0.0}
{"epoch": 43, "phase": 0, "elapsed_s": 19.07, "total": -7.497662, "recon0": 1.001901, "recon1": 0.0, "recon2": 0.0, "kl0": -8.499563, "kl1": 0.0, "kl2": 0.0}
{"epoch": 44, "phase": 0, "elapsed_s": 19.05, "total": -7.4971, "recon0": 1.002153, "recon1": 0.0, "recon2": 0.0, "kl0": -8.499253, "kl1": 0.0, "kl2": 0.0}
{"epoch": 45, "phase": 0, "elapsed_s": 18.82, "total": -7.497464, "recon0": 1.002783, "recon1": 0.0, "recon2": 0.0, "kl0": -8.500247, "kl1": 0.0, "kl2": 0.0}
{"epoch": 46, "phase": 0, "elapsed_s": 18.34, "total": -7.497747, "recon0": 1.002593, "recon1": 0.0, "recon2": 0.0, "kl0": -8.50034, "kl1": 0.0, "kl2": 0.0}
{"epoch": 47, "phase": 0, "elapsed_s": 17.75, "total": -7.497964, "recon0": 1.002181, "recon1": 0.0, "recon2": 0.0, "kl0": -8.500145, "kl1": 0.0, "kl2": 0.0}
{"epoch": 48, "phase": 0, "elapsed_s": 17.41, "total": -7.498381, "recon0": 1.00227, "recon1": 0.0, "recon2": 0.0, "kl0": -8.500651, "kl1": 0.0, "kl2": 0.0}
{"epoch": 49, "phase": 0, "elapsed_s": 17.94, "total": -7.498628, "recon0": 1.002246, "recon1": 0.0, "recon2": 0.0, "kl0": -8.500874, "kl1": 0.0, "kl2": 0.0}
{"epoch": 50, "phase": 0, "elapsed_s": 18.0, "total": -7.499048, "recon0": 1.002463, "recon1": 0.0, "recon2": 0.0, "kl0": -8.50151, "kl1": 0.0, "kl2": 0.0}
{"epoch": 51, "phase": 0, "elapsed_s": 18.27, "total": -7.498272, "recon0": 1.001012, "recon1": 0.0, "recon2": 0.0, "kl0": -8.499284, "kl1": 0.0, "kl2": 0.0}
{"epoch": 52, "phase": 0, "elapsed_s": 17.93, "total": -7.499382, "recon0": 1.002023, "recon1": 0.0, "recon2": 0.0, "kl0": -8.501405, "kl1": 0.0, "kl2": 0.0}
{"epoch": 53, "phase": 0, "elapsed_s": 17.71, "total": -7.498781, "recon0": 1.001925, "recon1": 0.0, "recon2": 0.0, "kl0": -8.500706, "kl1": 0.0, "kl2": 0.0}
{"epoch": 54, "phase": 0, "elapsed_s": 17.7, "total": -7.499369, "recon0": 1.001958, "recon1": 0.0, "recon2": 0.0, "kl0": -8.501327, "kl1": 0.0, "kl2": 0.0}
{"epoch": 55, "phase": 0, "elapsed_s": 18.18, "total": -7.499192, "recon0": 1.001922, "recon1": 0.0, "recon2": 0.0, "kl0": -8.501114, "kl1": 0.0, "kl2": 0.0}
{"epoch": 56, "phase": 0, "elapsed_s": 17.79, "total": -7.499207, "recon0": 1.001835, "recon1": 0.0, "recon2": 0.0, "kl0": -8.501042, "kl1": 0.0, "kl2": 0.0}
{"epoch": 57, "phase": 0, "elapsed_s": 18.16, "total": -7.499649, "recon0": 1.001424, "recon1": 0.0, "recon2": 0.0, "kl0": -8.501072, "kl1": 0.0, "kl2": 0.0}
{"epoch": 58, "phase": 0, "elapsed_s": 17.85, "total": -7.499302, "recon0": 1.001748, "recon1": 0.0, "recon2": 0.0, "kl0": -8.50105, "kl1": 0.0, "kl2": 0.0}
{"epoch": 59, "phase": 0, "elapsed_s": 17.36, "total": -7.499962, "recon0": 1.002023, "recon1": 0.0, "recon2": 0.0, "kl0": -8.501985, "kl1": 0.0, "kl2": 0.0}
{"epoch": 60, "phase": 0, "elapsed_s": 17.5, "total": -7.499943, "recon0": 1.002261, "recon1": 0.0, "recon2": 0.0, "kl0": -8.502203, "kl1": 0.0, "kl2": 0.0}
{"epoch": 61, "phase": 1, "elapsed_s": 2.68, "total": 1.443433, "recon0": 0.640125, "recon1": 1.404729, "recon2": 0.0, "kl0": -0.112623, "kl1": -0.488798, "kl2": 0.0}
{"epoch": 62, "phase": 1, "elapsed_s": 2.6, "total": 0.124844, "recon0": 0.374956, "recon1": 0.918771, "recon2": 0.0, "kl0": -0.078024, "kl1": -1.09086, "kl2": 0.0}
{"epoch": 63, "phase": 1, "elapsed_s": 2.89, "total": -0.636463, "recon0": 0.369197, "recon1": 0.846841, "recon2": 0.0, "kl0": -0.154775, "kl1": -1.697727, "kl2": 0.0}
{"epoch": 64, "phase": 1, "elapsed_s": 2.7, "total": -1.415433, "recon0": 0.417681, "recon1": 0.834207, "recon2": 0.0, "kl0": -0.34309, "kl1": -2.324232, "kl2": 0.0}
{"epoch": 65, "phase": 1, "elapsed_s": 2.76, "total": -2.300887, "recon0": 0.480138, "recon1": 0.8387, "recon2": 0.0, "kl0": -0.656977, "kl1": -2.962748, "kl2": 0.0}
{"epoch": 66, "phase": 1, "elapsed_s": 2.53, "total": -3.361812, "recon0": 0.541731, "recon1": 0.85261, "recon2": 0.0, "kl0": -1.142483, "kl1": -3.61367, "kl2": 0.0}
{"epoch": 67, "phase": 1, "elapsed_s": 2.75, "total": -4.482302, "recon0": 0.587974, "recon1": 0.869021, "recon2": 0.0, "kl0": -1.646516, "kl1": -4.29278, "kl2": 0.0}
{"epoch": 68, "phase": 1, "elapsed_s": 2.55, "total": -5.526833, "recon0": 0.622729, "recon1": 0.890526, "recon2": 0.0, "kl0": -2.071015, "kl1": -4.969072, "kl2": 0.0}
{"epoch": 69, "phase": 1, "elapsed_s": 3.0, "total": -6.507378, "recon0": 0.645903, "recon1": 0.90855, "recon2": 0.0, "kl0": -2.415256, "kl1": -5.646574, "kl2": 0.0}
{"epoch": 70, "phase": 1, "elapsed_s": 2.86, "total": -7.490256, "recon0": 0.666269, "recon1": 0.920717, "recon2": 0.0, "kl0": -2.738483, "kl1": -6.338758, "kl2": 0.0}
{"epoch": 71, "phase": 1, "elapsed_s": 2.66, "total": -8.44585, "recon0": 0.68369, "recon1": 0.933173, "recon2": 0.0, "kl0": -3.046624, "kl1": -7.016088, "kl2": 0.0}
{"epoch": 72, "phase": 1, "elapsed_s": 2.79, "total": -9.414274, "recon0": 0.690818, "recon1": 0.935083, "recon2": 0.0, "kl0": -3.344225, "kl1": -7.69595, "kl2": 0.0}
{"epoch": 73, "phase": 1, "elapsed_s": 2.51, "total": -10.365102, "recon0": 0.695199, "recon1": 0.936843, "recon2": 0.0, "kl0": -3.63201, "kl1": -8.365133, "kl2": 0.0}
{"epoch": 74, "phase": 1, "elapsed_s": 2.74, "total": -11.306765, "recon0": 0.696917, "recon1": 0.936801, "recon2": 0.0, "kl0": -3.919634, "kl1": -9.02085, "kl2": 0.0}
{"epoch": 75, "phase": 1, "elapsed_s": 2.56, "total": -12.251445, "recon0": 0.699356, "recon1": 0.93311, "recon2": 0.0, "kl0": -4.201334, "kl1": -9.682578, "kl2": 0.0}
{"epoch": 76, "phase": 1, "elapsed_s": 2.68, "total": -13.180547, "recon0": 0.699638, "recon1": 0.933671, "recon2": 0.0, "kl0": -4.485024, "kl1": -10.328832, "kl2": 0.0}
{"epoch": 77, "phase": 1, "elapsed_s": 2.48, "total": -14.12101, "recon0": 0.700406, "recon1": 0.930246, "recon2": 0.0, "kl0": -4.766513, "kl1": -10.985149, "kl2": 0.0}
{"epoch": 78, "phase": 1, "elapsed_s": 2.86, "total": -15.052918, "recon0": 0.699714, "recon1": 0.933221, "recon2": 0.0, "kl0": -5.048321, "kl1": -11.637532, "kl2": 0.0}
{"epoch": 79, "phase": 1, "elapsed_s": 2.48, "total": -15.982993, "recon0": 0.699179, "recon1": 0.932173, "recon2": 0.0, "kl0": -5.329547, "kl1": -12.284798, "kl2": 0.0}
{"epoch": 80, "phase": 1, "elapsed_s": 2.69, "total": -16.89966, "recon0": 0.700024, "recon1": 0.938398, "recon2": 0.0, "kl0": -5.609677, "kl1": -12.928404, "kl2": 0.0}
{"epoch": 81, "phase": 1, "elapsed_s": 2.51, "total": -17.836191, "recon0": 0.699784, "recon1": 0.940595, "recon2": 0.0, "kl0": -5.891752, "kl1": -13.584817, "kl2": 0.0}
{"epoch": 82, "phase": 1, "elapsed_s": 2.77, "total": -18.779568, "recon0": 0.699181, "recon1": 0.939045, "recon2": 0.0, "kl0": -6.172101, "kl1": -14.245693, "kl2": 0.0}
{"epoch": 83, "phase": 1, "elapsed_s": 2.45, "total": -19.710516, "recon0": 0.69958, "recon1": 0.937045, "recon2": 0.0, "kl0": -6.452611, "kl1": -14.89453, "kl2": 0.0}
{"epoch": 84, "phase": 1, "elapsed_s": 2.68, "total": -20.634763, "recon0": 0.699543, "recon1": 0.941639, "recon2": 0.0, "kl0": -6.733226, "kl1": -15.54272, "kl2": 0.0}
{"epoch": 85, "phase": 1, "elapsed_s": 2.61, "total": -21.572125, "recon0": 0.699099, "recon1": 0.941238, "recon2": 0.0, "kl0": -7.015014, "kl1": -16.197448, "kl2": 0.0}
{"epoch": 86, "phase": 1, "elapsed_s": 2.72, "total": -22.516763, "recon0": 0.69965, "recon1": 0.928537, "recon2": 0.0, "kl0": -7.294792, "kl1": -16.850158, "kl2": 0.0}
{"epoch": 87, "phase": 1, "elapsed_s": 2.48, "total": -22.512785, "recon0": 0.699659, "recon1": 0.94048, "recon2": 0.0, "kl0": -7.294768, "kl1": -16.858158, "kl2": 0.0}
{"epoch": 88, "phase": 1, "elapsed_s": 2.71, "total": -22.502439, "recon0": 0.699073, "recon1": 0.924565, "recon2": 0.0, "kl0": -7.295304, "kl1": -16.830774, "kl2": 0.0}
{"epoch": 89, "phase": 1, "elapsed_s": 2.61, "total": -22.495103, "recon0": 0.699292, "recon1": 0.965425, "recon2": 0.0, "kl0": -7.295694, "kl1": -16.864126, "kl2": 0.0}
{"epoch": 90, "phase": 1, "elapsed_s": 2.67, "total": -22.515237, "recon0": 0.699038, "recon1": 0.934505, "recon2": 0.0, "kl0": -7.295051, "kl1": -16.85373, "kl2": 0.0}
{"epoch": 91, "phase": 1, "elapsed_s": 2.62, "total": -22.521241, "recon0": 0.69934, "recon1": 0.947994, "recon2": 0.0, "kl0": -7.295187, "kl1": -16.873387, "kl2": 0.0}
{"epoch": 92, "phase": 1, "elapsed_s": 2.59, "total": -22.508773, "recon0": 0.699033, "recon1": 0.939016, "recon2": 0.0, "kl0": -7.295233, "kl1": -16.85159, "kl2": 0.0}
{"epoch": 93, "phase": 1, "elapsed_s": 2.83, "total": -22.509582, "recon0": 0.698741, "recon1": 0.952616, "recon2": 0.0, "kl0": -7.296545, "kl1": -16.864394, "kl2": 0.0}
{"epoch": 94, "phase": 1, "elapsed_s": 2.68, "total": -22.543631, "recon0": 0.698903, "recon1": 0.934102, "recon2": 0.0, "kl0": -7.295235, "kl1": -16.881401, "kl2": 0.0}
{"epoch": 95, "phase": 1, "elapsed_s": 2.66, "total": -22.528661, "recon0": 0.699042, "recon1": 0.952859, "recon2": 0.0, "kl0": -7.2964, "kl1": -16.884162, "kl2": 0.0}
{"epoch": 96, "phase": 1, "elapsed_s": 2.85, "total": -22.549058, "recon0": 0.69911, "recon1": 0.945919, "recon2": 0.0, "kl0": -7.296002, "kl1": -16.898085, "kl2": 0.0}
{"epoch": 97, "phase": 1, "elapsed_s": 2.79, "total": -22.535128, "recon0": 0.698933, "recon1": 0.947021, "recon2": 0.0, "kl0": -7.295293, "kl1": -16.885789, "kl2": 0.0}
{"epoch": 98, "phase": 1, "elapsed_s": 2.59, "total": -22.564667, "recon0": 0.698953, "recon1": 0.94668, "recon2": 0.0, "kl0": -7.296599, "kl1": -16.9137, "kl2": 0.0}
{"epoch": 99, "phase": 1, "elapsed_s": 2.8, "total": -22.549364, "recon0": 0.698697, "recon1": 0.933321, "recon2": 0.0, "kl0": -7.297326, "kl1": -16.884056, "kl2": 0.0}
{"epoch": 100, "phase": 1, "elapsed_s": 2.77, "total": -22.534828, "recon0": 0.698886, "recon1": 0.935686, "recon2": 0.0, "kl0": -7.294871, "kl1": -16.874529, "kl2": 0.0}
{"epoch": 101, "phase": 1, "elapsed_s": 2.85, "total": -22.517648, "recon0": 0.699037, "recon1": 0.938377, "recon2": 0.0, "kl0": -7.295284, "kl1": -16.859778, "kl2": 0.0}
{"epoch": 102, "phase": 1, "elapsed_s": 2.62, "total": -22.544663, "recon0": 0.698109, "recon1": 0.948144, "recon2": 0.0, "kl0": -7.29522, "kl1": -16.895696, "kl2": 0.0}
{"epoch": 103, "phase": 1, "elapsed_s": 2.93, "total": -22.53423, "recon0": 0.698451, "recon1": 0.973207, "recon2": 0.0, "kl0": -7.295893, "kl1": -16.909996, "kl2": 0.0}
{"epoch": 104, "phase": 1, "elapsed_s": 2.8, "total": -22.547493, "recon0": 0.698021, "recon1": 0.944516, "recon2": 0.0, "kl0": -7.296163, "kl1": -16.893866, "kl2": 0.0}
{"epoch": 105, "phase": 1, "elapsed_s": 2.68, "total": -22.5344, "recon0": 0.69875, "recon1": 0.963687, "recon2": 0.0, "kl0": -7.297439, "kl1": -16.899398, "kl2": 0.0}
{"epoch": 106, "phase": 1, "elapsed_s": 2.58, "total": -22.552142, "recon0": 0.698737, "recon1": 0.930331, "recon2": 0.0, "kl0": -7.295822, "kl1": -16.885388, "kl2": 0.0}
{"epoch": 107, "phase": 1, "elapsed_s": 2.89, "total": -22.576885, "recon0": 0.698854, "recon1": 0.963703, "recon2": 0.0, "kl0": -7.297778, "kl1": -16.941663, "kl2": 0.0}
{"epoch": 108, "phase": 1, "elapsed_s": 2.71, "total": -22.563186, "recon0": 0.698425, "recon1": 0.942986, "recon2": 0.0, "kl0": -7.297396, "kl1": -16.907202, "kl2": 0.0}
{"epoch": 109, "phase": 1, "elapsed_s": 2.64, "total": -22.565159, "recon0": 0.698114, "recon1": 0.951183, "recon2": 0.0, "kl0": -7.297466, "kl1": -16.91699, "kl2": 0.0}
{"epoch": 110, "phase": 1, "elapsed_s": 2.64, "total": -22.536757, "recon0": 0.698828, "recon1": 0.9537, "recon2": 0.0, "kl0": -7.297516, "kl1": -16.891769, "kl2": 0.0}
{"epoch": 111, "phase": 1, "elapsed_s": 2.83, "total": -22.571948, "recon0": 0.697979, "recon1": 0.974188, "recon2": 0.0, "kl0": -7.297035, "kl1": -16.94708, "kl2": 0.0}
{"epoch": 112, "phase": 1, "elapsed_s": 2.76, "total": -22.561356, "recon0": 0.699151, "recon1": 0.949085, "recon2": 0.0, "kl0": -7.297806, "kl1": -16.911785, "kl2": 0.0}
{"epoch": 113, "phase": 1, "elapsed_s": 2.66, "total": -22.551509, "recon0": 0.698769, "recon1": 0.946191, "recon2": 0.0, "kl0": -7.297203, "kl1": -16.899266, "kl2": 0.0}
{"epoch": 114, "phase": 1, "elapsed_s": 2.94, "total": -22.58796, "recon0": 0.698725, "recon1": 0.944108, "recon2": 0.0, "kl0": -7.298027, "kl1": -16.932765, "kl2": 0.0}
{"epoch": 115, "phase": 1, "elapsed_s": 2.69, "total": -22.568928, "recon0": 0.698135, "recon1": 0.94247, "recon2": 0.0, "kl0": -7.296428, "kl1": -16.913105, "kl2": 0.0}
{"epoch": 116, "phase": 1, "elapsed_s": 2.7, "total": -22.577273, "recon0": 0.698659, "recon1": 0.971864, "recon2": 0.0, "kl0": -7.297817, "kl1": -16.949978, "kl2": 0.0}
{"epoch": 117, "phase": 1, "elapsed_s": 2.82, "total": -22.57663, "recon0": 0.697958, "recon1": 0.945848, "recon2": 0.0, "kl0": -7.297284, "kl1": -16.923152, "kl2": 0.0}
{"epoch": 118, "phase": 1, "elapsed_s": 3.16, "total": -22.585756, "recon0": 0.698455, "recon1": 0.962533, "recon2": 0.0, "kl0": -7.297411, "kl1": -16.949333, "kl2": 0.0}
{"epoch": 119, "phase": 1, "elapsed_s": 2.58, "total": -22.541102, "recon0": 0.698211, "recon1": 0.956221, "recon2": 0.0, "kl0": -7.29763, "kl1": -16.897905, "kl2": 0.0}
{"epoch": 120, "phase": 1, "elapsed_s": 2.63, "total": -22.600273, "recon0": 0.698056, "recon1": 0.933736, "recon2": 0.0, "kl0": -7.296003, "kl1": -16.936062, "kl2": 0.0}
{"epoch": 121, "phase": 1, "elapsed_s": 2.54, "total": -22.557611, "recon0": 0.698177, "recon1": 0.921447, "recon2": 0.0, "kl0": -7.294715, "kl1": -16.882519, "kl2": 0.0}
{"epoch": 122, "phase": 1, "elapsed_s": 3.0, "total": -22.554339, "recon0": 0.698562, "recon1": 0.928976, "recon2": 0.0, "kl0": -7.295357, "kl1": -16.886521, "kl2": 0.0}
{"epoch": 123, "phase": 1, "elapsed_s": 2.57, "total": -22.568189, "recon0": 0.698348, "recon1": 0.953124, "recon2": 0.0, "kl0": -7.295016, "kl1": -16.924644, "kl2": 0.0}
{"epoch": 124, "phase": 1, "elapsed_s": 2.63, "total": -22.58484, "recon0": 0.698826, "recon1": 0.940943, "recon2": 0.0, "kl0": -7.296033, "kl1": -16.928577, "kl2": 0.0}
{"epoch": 125, "phase": 1, "elapsed_s": 2.57, "total": -22.575661, "recon0": 0.698054, "recon1": 0.940276, "recon2": 0.0, "kl0": -7.297038, "kl1": -16.916952, "kl2": 0.0}
{"epoch": 126, "phase": 1, "elapsed_s": 2.81, "total": -22.515017, "recon0": 0.698141, "recon1": 0.933903, "recon2": 0.0, "kl0": -7.296651, "kl1": -16.85041, "kl2": 0.0}
{"epoch": 127, "phase": 1, "elapsed_s": 2.53, "total": -22.510401, "recon0": 0.698175, "recon1": 0.941155, "recon2": 0.0, "kl0": -7.296211, "kl1": -16.853521, "kl2": 0.0}
{"epoch": 128, "phase": 1, "elapsed_s": 2.63, "total": -22.539165, "recon0": 0.698273, "recon1": 0.92619, "recon2": 0.0, "kl0": -7.296936, "kl1": -16.866692, "kl2": 0.0}
{"epoch": 129, "phase": 1, "elapsed_s": 2.63, "total": -22.52548, "recon0": 0.697989, "recon1": 0.943638, "recon2": 0.0, "kl0": -7.295674, "kl1": -16.871433, "kl2": 0.0}
{"epoch": 130, "phase": 1, "elapsed_s": 2.6, "total": -22.576777, "recon0": 0.69813, "recon1": 0.96702, "recon2": 0.0, "kl0": -7.296697, "kl1": -16.94523, "kl2": 0.0}
{"epoch": 131, "phase": 1, "elapsed_s": 2.54, "total": -22.542455, "recon0": 0.698431, "recon1": 0.949088, "recon2": 0.0, "kl0": -7.296767, "kl1": -16.893207, "kl2": 0.0}
{"epoch": 132, "phase": 1, "elapsed_s": 2.72, "total": -22.561647, "recon0": 0.698674, "recon1": 0.939834, "recon2": 0.0, "kl0": -7.298199, "kl1": -16.901956, "kl2": 0.0}
{"epoch": 133, "phase": 1, "elapsed_s": 2.69, "total": -22.590657, "recon0": 0.698102, "recon1": 0.973343, "recon2": 0.0, "kl0": -7.297769, "kl1": -16.964333, "kl2": 0.0}
{"epoch": 134, "phase": 1, "elapsed_s": 2.74, "total": -22.595478, "recon0": 0.698237, "recon1": 0.940727, "recon2": 0.0, "kl0": -7.298696, "kl1": -16.935745, "kl2": 0.0}
{"epoch": 135, "phase": 1, "elapsed_s": 2.72, "total": -22.556733, "recon0": 0.698305, "recon1": 0.941065, "recon2": 0.0, "kl0": -7.297486, "kl1": -16.898616, "kl2": 0.0}
{"epoch": 136, "phase": 1, "elapsed_s": 2.5, "total": -22.547829, "recon0": 0.698234, "recon1": 0.980018, "recon2": 0.0, "kl0": -7.297943, "kl1": -16.928137, "kl2": 0.0}
{"epoch": 137, "phase": 1, "elapsed_s": 2.83, "total": -22.540444, "recon0": 0.697993, "recon1": 0.967976, "recon2": 0.0, "kl0": -7.298649, "kl1": -16.907764, "kl2": 0.0}
{"epoch": 138, "phase": 1, "elapsed_s": 2.62, "total": -22.581756, "recon0": 0.698202, "recon1": 0.987401, "recon2": 0.0, "kl0": -7.298696, "kl1": -16.968663, "kl2": 0.0}
{"epoch": 139, "phase": 1, "elapsed_s": 2.64, "total": -22.600094, "recon0": 0.697864, "recon1": 0.97283, "recon2": 0.0, "kl0": -7.297174, "kl1": -16.973614, "kl2": 0.0}
{"epoch": 140, "phase": 1, "elapsed_s": 2.9, "total": -22.611942, "recon0": 0.697902, "recon1": 0.970237, "recon2": 0.0, "kl0": -7.297302, "kl1": -16.982778, "kl2": 0.0}
{"epoch": 141, "phase": 2, "elapsed_s": 3.21, "total": 1.671626, "recon0": 0.655255, "recon1": 0.969567, "recon2": 1.488075, "kl0": -0.258749, "kl1": -0.652416, "kl2": -0.530106}
{"epoch": 142, "phase": 2, "elapsed_s": 2.95, "total": -0.361147, "recon0": 0.540897, "recon1": 0.969224, "recon2": 1.053244, "kl0": -0.45438, "kl1": -1.304523, "kl2": -1.165608}
{"epoch": 143, "phase": 2, "elapsed_s": 3.0, "total": -1.974868, "recon0": 0.500912, "recon1": 0.960665, "recon2": 0.999908, "kl0": -0.65655, "kl1": -1.956123, "kl2": -1.823679}
{"epoch": 144, "phase": 2, "elapsed_s": 3.11, "total": -3.570859, "recon0": 0.50496, "recon1": 0.948123, "recon2": 0.979814, "kl0": -0.90709, "kl1": -2.605298, "kl2": -2.491369}
{"epoch": 145, "phase": 2, "elapsed_s": 2.86, "total": -5.153226, "recon0": 0.527968, "recon1": 0.94134, "recon2": 0.973528, "kl0": -1.186914, "kl1": -3.251588, "kl2": -3.15756}
{"epoch": 146, "phase": 2, "elapsed_s": 2.9, "total": -6.762002, "recon0": 0.556558, "recon1": 0.927196, "recon2": 0.974243, "kl0": -1.486237, "kl1": -3.907897, "kl2": -3.825865}
{"epoch": 147, "phase": 2, "elapsed_s": 2.81, "total": -8.359443, "recon0": 0.58316, "recon1": 0.933016, "recon2": 0.972692, "kl0": -1.802416, "kl1": -4.55222, "kl2": -4.493676}
{"epoch": 148, "phase": 2, "elapsed_s": 3.0, "total": -9.976358, "recon0": 0.608996, "recon1": 0.918249, "recon2": 0.975678, "kl0": -2.115078, "kl1": -5.205395, "kl2": -5.158808}
{"epoch": 149, "phase": 2, "elapsed_s": 2.7, "total": -11.572782, "recon0": 0.630656, "recon1": 0.918623, "recon2": 0.983954, "kl0": -2.425082, "kl1": -5.855749, "kl2": -5.825183}
{"epoch": 150, "phase": 2, "elapsed_s": 2.92, "total": -13.183249, "recon0": 0.651847, "recon1": 0.920311, "recon2": 0.987223, "kl0": -2.735013, "kl1": -6.515043, "kl2": -6.492573}
{"epoch": 151, "phase": 2, "elapsed_s": 2.97, "total": -14.77976, "recon0": 0.669025, "recon1": 0.920059, "recon2": 0.992476, "kl0": -3.04134, "kl1": -7.1624, "kl2": -7.157579}
{"epoch": 152, "phase": 2, "elapsed_s": 2.83, "total": -16.379973, "recon0": 0.681205, "recon1": 0.921924, "recon2": 0.996351, "kl0": -3.339428, "kl1": -7.815764, "kl2": -7.824262}
{"epoch": 153, "phase": 2, "elapsed_s": 2.89, "total": -17.941027, "recon0": 0.688905, "recon1": 0.941461, "recon2": 1.000925, "kl0": -3.633597, "kl1": -8.450716, "kl2": -8.488006}
{"epoch": 154, "phase": 2, "elapsed_s": 3.01, "total": -19.546547, "recon0": 0.692575, "recon1": 0.957137, "recon2": 1.002725, "kl0": -3.919726, "kl1": -9.129161, "kl2": -9.150097}
{"epoch": 155, "phase": 2, "elapsed_s": 3.06, "total": -21.147993, "recon0": 0.697584, "recon1": 0.953546, "recon2": 1.003742, "kl0": -4.204823, "kl1": -9.786724, "kl2": -9.811318}
{"epoch": 156, "phase": 2, "elapsed_s": 2.9, "total": -22.743297, "recon0": 0.698872, "recon1": 0.931535, "recon2": 1.003867, "kl0": -4.490099, "kl1": -10.417056, "kl2": -10.470416}
{"epoch": 157, "phase": 2, "elapsed_s": 3.03, "total": -24.32319, "recon0": 0.699201, "recon1": 0.928163, "recon2": 1.002873, "kl0": -4.77274, "kl1": -11.051614, "kl2": -11.129073}
{"epoch": 158, "phase": 2, "elapsed_s": 2.99, "total": -25.923716, "recon0": 0.698328, "recon1": 0.933273, "recon2": 1.002446, "kl0": -5.051649, "kl1": -11.718859, "kl2": -11.787255}
{"epoch": 159, "phase": 2, "elapsed_s": 2.92, "total": -27.520958, "recon0": 0.698588, "recon1": 0.933459, "recon2": 1.00235, "kl0": -5.33344, "kl1": -12.377499, "kl2": -12.444415}
{"epoch": 160, "phase": 2, "elapsed_s": 3.1, "total": -29.113961, "recon0": 0.697903, "recon1": 0.933099, "recon2": 1.002023, "kl0": -5.613713, "kl1": -13.031933, "kl2": -13.10134}
{"epoch": 161, "phase": 2, "elapsed_s": 2.97, "total": -30.681476, "recon0": 0.697678, "recon1": 0.944307, "recon2": 1.001723, "kl0": -5.895099, "kl1": -13.672867, "kl2": -13.757219}
{"epoch": 162, "phase": 2, "elapsed_s": 3.05, "total": -32.289622, "recon0": 0.697672, "recon1": 0.939692, "recon2": 1.001248, "kl0": -6.17542, "kl1": -14.339734, "kl2": -14.413079}
{"epoch": 163, "phase": 2, "elapsed_s": 3.01, "total": -33.884651, "recon0": 0.697172, "recon1": 0.931534, "recon2": 1.000937, "kl0": -6.455853, "kl1": -14.989775, "kl2": -15.068667}
{"epoch": 164, "phase": 2, "elapsed_s": 3.24, "total": -35.469328, "recon0": 0.698167, "recon1": 0.939489, "recon2": 1.000846, "kl0": -6.737119, "kl1": -15.646385, "kl2": -15.724326}
{"epoch": 165, "phase": 2, "elapsed_s": 3.08, "total": -37.068317, "recon0": 0.697612, "recon1": 0.931558, "recon2": 1.000934, "kl0": -7.017703, "kl1": -16.301263, "kl2": -16.379456}
{"epoch": 166, "phase": 2, "elapsed_s": 2.91, "total": -38.647744, "recon0": 0.697477, "recon1": 0.926427, "recon2": 1.000821, "kl0": -7.29881, "kl1": -16.938834, "kl2": -17.034826}
{"epoch": 167, "phase": 2, "elapsed_s": 3.16, "total": -38.644345, "recon0": 0.697563, "recon1": 0.931897, "recon2": 1.000705, "kl0": -7.298513, "kl1": -16.941034, "kl2": -17.034963}
{"epoch": 168, "phase": 2, "elapsed_s": 2.86, "total": -38.624038, "recon0": 0.697431, "recon1": 0.948439, "recon2": 1.000515, "kl0": -7.298657, "kl1": -16.93685, "kl2": -17.034914}
{"epoch": 169, "phase": 2, "elapsed_s": 2.97, "total": -38.642562, "recon0": 0.697557, "recon1": 0.965059, "recon2": 1.000678, "kl0": -7.298401, "kl1": -16.972417, "kl2": -17.035037}
{"epoch": 170, "phase": 2, "elapsed_s": 3.08, "total": -38.655313, "recon0": 0.697337, "recon1": 0.958772, "recon2": 1.000451, "kl0": -7.298486, "kl1": -16.978215, "kl2": -17.035171}
{"epoch": 171, "phase": 2, "elapsed_s": 2.88, "total": -38.655851, "recon0": 0.697304, "recon1": 0.960978, "recon2": 1.000439, "kl0": -7.29878, "kl1": -16.980742, "kl2": -17.03505}
{"epoch": 172, "phase": 2, "elapsed_s": 3.0, "total": -38.657648, "recon0": 0.697529, "recon1": 0.95035, "recon2": 1.000391, "kl0": -7.299944, "kl1": -16.970768, "kl2": -17.035206}
{"epoch": 173, "phase": 2, "elapsed_s": 2.88, "total": -38.64251, "recon0": 0.697515, "recon1": 0.921252, "recon2": 1.000318, "kl0": -7.299102, "kl1": -16.927297, "kl2": -17.035196}
{"epoch": 174, "phase": 2, "elapsed_s": 3.12, "total": -38.633697, "recon0": 0.697302, "recon1": 0.969825, "recon2": 1.000331, "kl0": -7.299259, "kl1": -16.966744, "kl2": -17.035152}
{"epoch": 175, "phase": 2, "elapsed_s": 2.97, "total": -38.652655, "recon0": 0.697659, "recon1": 0.957246, "recon2": 1.000196, "kl0": -7.29984, "kl1": -16.972754, "kl2": -17.035161}
{"epoch": 176, "phase": 2, "elapsed_s": 2.98, "total": -38.62436, "recon0": 0.697466, "recon1": 0.944832, "recon2": 1.00026, "kl0": -7.29716, "kl1": -16.934562, "kl2": -17.035196}
{"epoch": 177, "phase": 2, "elapsed_s": 3.22, "total": -38.623011, "recon0": 0.697428, "recon1": 0.958133, "recon2": 1.000169, "kl0": -7.298883, "kl1": -16.944562, "kl2": -17.035296}
{"epoch": 178, "phase": 2, "elapsed_s": 2.83, "total": -38.656975, "recon0": 0.697295, "recon1": 0.958404, "recon2": 1.000174, "kl0": -7.299449, "kl1": -16.978192, "kl2": -17.035208}
{"epoch": 179, "phase": 2, "elapsed_s": 3.1, "total": -38.661275, "recon0": 0.697583, "recon1": 0.953954, "recon2": 1.00011, "kl0": -7.298378, "kl1": -16.979299, "kl2": -17.035246}
{"epoch": 180, "phase": 2, "elapsed_s": 2.97, "total": -38.655649, "recon0": 0.697721, "recon1": 0.963923, "recon2": 1.000216, "kl0": -7.299709, "kl1": -16.98253, "kl2": -17.035269}
{"epoch": 181, "phase": 2, "elapsed_s": 2.98, "total": -38.658292, "recon0": 0.697412, "recon1": 0.961175, "recon2": 1.000187, "kl0": -7.299374, "kl1": -16.982415, "kl2": -17.035277}
{"epoch": 182, "phase": 2, "elapsed_s": 2.91, "total": -38.657289, "recon0": 0.697303, "recon1": 0.961094, "recon2": 1.00015, "kl0": -7.299029, "kl1": -16.981467, "kl2": -17.035341}
{"epoch": 183, "phase": 2, "elapsed_s": 3.03, "total": -38.661516, "recon0": 0.697461, "recon1": 0.955227, "recon2": 1.000157, "kl0": -7.298931, "kl1": -16.980124, "kl2": -17.035306}
{"epoch": 184, "phase": 2, "elapsed_s": 3.18, "total": -38.660817, "recon0": 0.697459, "recon1": 0.958033, "recon2": 1.000107, "kl0": -7.299294, "kl1": -16.981724, "kl2": -17.035398}
{"epoch": 185, "phase": 2, "elapsed_s": 2.89, "total": -38.664146, "recon0": 0.6972, "recon1": 0.956741, "recon2": 1.000129, "kl0": -7.298147, "kl1": -16.98482, "kl2": -17.035249}
{"epoch": 186, "phase": 2, "elapsed_s": 3.1, "total": -38.659967, "recon0": 0.69756, "recon1": 0.960808, "recon2": 1.000099, "kl0": -7.299086, "kl1": -16.983979, "kl2": -17.035368}
{"epoch": 187, "phase": 2, "elapsed_s": 3.19, "total": -38.6526, "recon0": 0.697522, "recon1": 0.954463, "recon2": 1.000043, "kl0": -7.297341, "kl1": -16.971918, "kl2": -17.035369}
{"epoch": 188, "phase": 2, "elapsed_s": 2.9, "total": -38.625063, "recon0": 0.697277, "recon1": 0.937186, "recon2": 1.000102, "kl0": -7.299049, "kl1": -16.92523, "kl2": -17.035349}
{"epoch": 189, "phase": 2, "elapsed_s": 3.01, "total": -38.649774, "recon0": 0.697384, "recon1": 0.938209, "recon2": 1.000066, "kl0": -7.299461, "kl1": -16.950623, "kl2": -17.035348}
{"epoch": 190, "phase": 2, "elapsed_s": 3.03, "total": -38.639762, "recon0": 0.697157, "recon1": 0.932993, "recon2": 1.000061, "kl0": -7.298328, "kl1": -16.936229, "kl2": -17.035417}
{"epoch": 191, "phase": 2, "elapsed_s": 3.1, "total": -38.64432, "recon0": 0.697487, "recon1": 0.931817, "recon2": 1.000077, "kl0": -7.297698, "kl1": -16.940711, "kl2": -17.035293}
{"epoch": 192, "phase": 2, "elapsed_s": 2.88, "total": -38.659689, "recon0": 0.696985, "recon1": 0.944537, "recon2": 1.000114, "kl0": -7.299986, "kl1": -16.966024, "kl2": -17.035315}
{"epoch": 193, "phase": 2, "elapsed_s": 2.99, "total": -38.662268, "recon0": 0.696877, "recon1": 0.959826, "recon2": 1.000068, "kl0": -7.298814, "kl1": -16.984894, "kl2": -17.035331}
{"epoch": 194, "phase": 2, "elapsed_s": 3.16, "total": -38.621081, "recon0": 0.697222, "recon1": 0.941506, "recon2": 1.000058, "kl0": -7.297955, "kl1": -16.926558, "kl2": -17.035353}
{"epoch": 195, "phase": 2, "elapsed_s": 3.02, "total": -38.629305, "recon0": 0.69726, "recon1": 0.962823, "recon2": 1.000025, "kl0": -7.298839, "kl1": -16.955208, "kl2": -17.035366}
{"epoch": 196, "phase": 2, "elapsed_s": 3.03, "total": -38.657136, "recon0": 0.697302, "recon1": 0.963072, "recon2": 1.000066, "kl0": -7.298546, "kl1": -16.983712, "kl2": -17.035318}
{"epoch": 197, "phase": 2, "elapsed_s": 3.06, "total": -38.654723, "recon0": 0.697361, "recon1": 0.96786, "recon2": 1.000069, "kl0": -7.298994, "kl1": -16.985678, "kl2": -17.035341}
{"epoch": 198, "phase": 2, "elapsed_s": 2.92, "total": -38.642206, "recon0": 0.697132, "recon1": 0.98112, "recon2": 1.000035, "kl0": -7.299799, "kl1": -16.985293, "kl2": -17.035401}
{"epoch": 199, "phase": 2, "elapsed_s": 2.94, "total": -38.64055, "recon0": 0.697355, "recon1": 0.960747, "recon2": 1.000023, "kl0": -7.299771, "kl1": -16.963534, "kl2": -17.035371}
{"epoch": 200, "phase": 2, "elapsed_s": 3.09, "total": -38.638645, "recon0": 0.697387, "recon1": 0.940058, "recon2": 1.000024, "kl0": -7.298305, "kl1": -16.94246, "kl2": -17.035349}
{"epoch": 201, "phase": 2, "elapsed_s": 3.16, "total": -38.645281, "recon0": 0.697635, "recon1": 0.963598, "recon2": 1.000056, "kl0": -7.299814, "kl1": -16.971408, "kl2": -17.035349}
{"epoch": 202, "phase": 2, "elapsed_s": 2.82, "total": -38.66118, "recon0": 0.697299, "recon1": 0.96029, "recon2": 1.00002, "kl0": -7.29934, "kl1": -16.98409, "kl2": -17.035358}
{"epoch": 203, "phase": 2, "elapsed_s": 3.15, "total": -38.642728, "recon0": 0.696992, "recon1": 0.970511, "recon2": 1.000026, "kl0": -7.297931, "kl1": -16.97695, "kl2": -17.035375}
{"epoch": 204, "phase": 2, "elapsed_s": 2.91, "total": -38.653967, "recon0": 0.69747, "recon1": 0.966204, "recon2": 1.000016, "kl0": -7.298452, "kl1": -16.983853, "kl2": -17.035353}
{"epoch": 205, "phase": 2, "elapsed_s": 2.92, "total": -38.645944, "recon0": 0.697341, "recon1": 0.971349, "recon2": 1.00003, "kl0": -7.299046, "kl1": -16.980246, "kl2": -17.035372}
{"epoch": 221, "phase": 3, "elapsed_s": 2.9, "total": 0.578004, "recon0": 0.683215, "recon1": 0.953823, "recon2": 1.000064, "kl0": -0.358509, "kl1": -0.848828, "kl2": -0.85176}
{"epoch": 222, "phase": 3, "elapsed_s": 3.15, "total": -1.511669, "recon0": 0.583109, "recon1": 0.954665, "recon2": 1.000032, "kl0": -0.650985, "kl1": -1.694999, "kl2": -1.703491}
{"epoch": 223, "phase": 3, "elapsed_s": 3.09, "total": -3.555577, "recon0": 0.543935, "recon1": 0.926833, "recon2": 1.000025, "kl0": -0.94134, "kl1": -2.529749, "kl2": -2.555281}
{"epoch": 224, "phase": 3, "elapsed_s": 2.88, "total": -5.602556, "recon0": 0.558553, "recon1": 0.91122, "recon2": 1.000019, "kl0": -1.282878, "kl1": -3.382386, "kl2": -3.407084}
{"epoch": 225, "phase": 3, "elapsed_s": 3.07, "total": -7.673471, "recon0": 0.587636, "recon1": 0.911463, "recon2": 1.000011, "kl0": -1.67974, "kl1": -4.233979, "kl2": -4.258862}
{"epoch": 226, "phase": 3, "elapsed_s": 2.89, "total": -9.737106, "recon0": 0.619345, "recon1": 0.908999, "recon2": 1.000035, "kl0": -2.082411, "kl1": -5.072475, "kl2": -5.110599}
{"epoch": 227, "phase": 3, "elapsed_s": 2.85, "total": -11.786613, "recon0": 0.64671, "recon1": 0.926756, "recon2": 1.000035, "kl0": -2.484572, "kl1": -5.913173, "kl2": -5.962369}
{"epoch": 228, "phase": 3, "elapsed_s": 2.95, "total": -13.85122, "recon0": 0.667713, "recon1": 0.945754, "recon2": 1.000023, "kl0": -2.873778, "kl1": -6.77681, "kl2": -6.814123}
{"epoch": 229, "phase": 3, "elapsed_s": 2.97, "total": -15.926163, "recon0": 0.678209, "recon1": 0.9446, "recon2": 1.000025, "kl0": -3.255653, "kl1": -7.627426, "kl2": -7.665919}
{"epoch": 230, "phase": 3, "elapsed_s": 3.38, "total": -17.990394, "recon0": 0.688599, "recon1": 0.922017, "recon2": 1.000013, "kl0": -3.631722, "kl1": -8.451639, "kl2": -8.517662}
{"epoch": 231, "phase": 3, "elapsed_s": 2.97, "total": -20.05363, "recon0": 0.692545, "recon1": 0.929011, "recon2": 1.00003, "kl0": -4.004439, "kl1": -9.301344, "kl2": -9.369433}
{"epoch": 232, "phase": 3, "elapsed_s": 3.19, "total": -22.139363, "recon0": 0.696991, "recon1": 0.914097, "recon2": 1.000022, "kl0": -4.375071, "kl1": -10.154198, "kl2": -10.221204}
{"epoch": 233, "phase": 3, "elapsed_s": 2.74, "total": -24.166406, "recon0": 0.697202, "recon1": 0.918354, "recon2": 1.000018, "kl0": -4.742771, "kl1": -10.966177, "kl2": -11.073032}
{"epoch": 234, "phase": 3, "elapsed_s": 2.89, "total": -26.257676, "recon0": 0.697616, "recon1": 0.94674, "recon2": 1.000008, "kl0": -5.108427, "kl1": -11.86887, "kl2": -11.924743}
{"epoch": 235, "phase": 3, "elapsed_s": 3.21, "total": -28.325158, "recon0": 0.697958, "recon1": 0.951478, "recon2": 0.999996, "kl0": -5.473576, "kl1": -12.724432, "kl2": -12.776582}
{"epoch": 236, "phase": 3, "elapsed_s": 2.96, "total": -30.387442, "recon0": 0.697798, "recon1": 0.953353, "recon2": 1.000031, "kl0": -5.838439, "kl1": -13.57188, "kl2": -13.628305}
{"epoch": 237, "phase": 3, "elapsed_s": 2.86, "total": -32.45829, "recon0": 0.697736, "recon1": 0.947966, "recon2": 1.000027, "kl0": -6.203299, "kl1": -14.420664, "kl2": -14.480056}
{"epoch": 238, "phase": 3, "elapsed_s": 3.04, "total": -34.509856, "recon0": 0.69741, "recon1": 0.95943, "recon2": 1.000022, "kl0": -6.567882, "kl1": -15.266967, "kl2": -15.33187}
{"epoch": 239, "phase": 3, "elapsed_s": 2.91, "total": -36.590951, "recon0": 0.697447, "recon1": 0.947785, "recon2": 1.000037, "kl0": -6.934759, "kl1": -16.117864, "kl2": -16.183598}
{"epoch": 240, "phase": 3, "elapsed_s": 2.78, "total": -38.654125, "recon0": 0.697201, "recon1": 0.955148, "recon2": 1.000019, "kl0": -7.298895, "kl1": -16.972213, "kl2": -17.035384}
{"epoch": 241, "phase": 3, "elapsed_s": 2.79, "total": -38.658259, "recon0": 0.697061, "recon1": 0.94497, "recon2": 1.000026, "kl0": -7.298201, "kl1": -16.966735, "kl2": -17.03538}
{"epoch": 242, "phase": 3, "elapsed_s": 3.06, "total": -38.659279, "recon0": 0.697367, "recon1": 0.948459, "recon2": 1.000018, "kl0": -7.299701, "kl1": -16.97002, "kl2": -17.035402}
{"epoch": 243, "phase": 3, "elapsed_s": 2.73, "total": -38.654524, "recon0": 0.697322, "recon1": 0.949876, "recon2": 1.000022, "kl0": -7.299821, "kl1": -16.96653, "kl2": -17.035392}
{"epoch": 244, "phase": 3, "elapsed_s": 2.93, "total": -38.643778, "recon0": 0.697237, "recon1": 0.953351, "recon2": 1.000024, "kl0": -7.299924, "kl1": -16.959078, "kl2": -17.035387}
{"epoch": 245, "phase": 3, "elapsed_s": 3.01, "total": -38.618723, "recon0": 0.697386, "recon1": 0.977086, "recon2": 1.000035, "kl0": -7.299544, "kl1": -16.958299, "kl2": -17.035387}
{"epoch": 246, "phase": 3, "elapsed_s": 3.02, "total": -38.625369, "recon0": 0.697323, "recon1": 0.979142, "recon2": 1.000002, "kl0": -7.298533, "kl1": -16.967917, "kl2": -17.035385}
{"epoch": 247, "phase": 3, "elapsed_s": 2.74, "total": -38.622153, "recon0": 0.697508, "recon1": 0.978482, "recon2": 1.000004, "kl0": -7.299051, "kl1": -16.963708, "kl2": -17.035387}
{"epoch": 248, "phase": 3, "elapsed_s": 2.84, "total": -38.62692, "recon0": 0.697102, "recon1": 0.986603, "recon2": 1.000024, "kl0": -7.299653, "kl1": -16.975606, "kl2": -17.03539}
{"epoch": 249, "phase": 3, "elapsed_s": 2.96, "total": -38.636396, "recon0": 0.697507, "recon1": 0.985464, "recon2": 1.000026, "kl0": -7.299698, "kl1": -16.984327, "kl2": -17.035367}
{"epoch": 250, "phase": 3, "elapsed_s": 2.89, "total": -38.624573, "recon0": 0.697258, "recon1": 0.984266, "recon2": 1.000017, "kl0": -7.299695, "kl1": -16.97103, "kl2": -17.035389}
{"epoch": 251, "phase": 3, "elapsed_s": 2.89, "total": -38.624566, "recon0": 0.697247, "recon1": 0.98811, "recon2": 1.000013, "kl0": -7.299876, "kl1": -16.974685, "kl2": -17.035376}
{"epoch": 252, "phase": 3, "elapsed_s": 3.0, "total": -38.635577, "recon0": 0.697467, "recon1": 0.984038, "recon2": 1.000005, "kl0": -7.299523, "kl1": -16.982165, "kl2": -17.035399}
{"epoch": 253, "phase": 3, "elapsed_s": 2.82, "total": -38.640725, "recon0": 0.697414, "recon1": 0.985166, "recon2": 1.000009, "kl0": -7.299364, "kl1": -16.988548, "kl2": -17.035401}
{"epoch": 254, "phase": 3, "elapsed_s": 2.83, "total": -38.642238, "recon0": 0.697188, "recon1": 0.98575, "recon2": 1.000019, "kl0": -7.299807, "kl1": -16.990005, "kl2": -17.035384}
{"epoch": 255, "phase": 3, "elapsed_s": 2.95, "total": -38.641054, "recon0": 0.697259, "recon1": 0.983436, "recon2": 1.000017, "kl0": -7.299016, "kl1": -16.98737, "kl2": -17.035381}
{"epoch": 256, "phase": 3, "elapsed_s": 2.89, "total": -38.637622, "recon0": 0.697184, "recon1": 0.983699, "recon2": 1.00003, "kl0": -7.299709, "kl1": -16.983436, "kl2": -17.03539}
{"epoch": 257, "phase": 3, "elapsed_s": 2.98, "total": -38.64058, "recon0": 0.697072, "recon1": 0.983793, "recon2": 1.000024, "kl0": -7.299431, "kl1": -16.986666, "kl2": -17.035373}
{"epoch": 258, "phase": 3, "elapsed_s": 3.14, "total": -38.650012, "recon0": 0.697092, "recon1": 0.980382, "recon2": 1.000003, "kl0": -7.299314, "kl1": -16.992774, "kl2": -17.035401}
{"epoch": 259, "phase": 3, "elapsed_s": 3.04, "total": -38.658965, "recon0": 0.697251, "recon1": 0.980628, "recon2": 0.999999, "kl0": -7.300117, "kl1": -17.001324, "kl2": -17.035402}
{"epoch": 260, "phase": 3, "elapsed_s": 2.9, "total": -38.648161, "recon0": 0.697144, "recon1": 0.981451, "recon2": 1.000031, "kl0": -7.299365, "kl1": -16.992029, "kl2": -17.035392}
{"epoch": 261, "phase": 3, "elapsed_s": 2.9, "total": -38.653211, "recon0": 0.697179, "recon1": 0.966256, "recon2": 1.000005, "kl0": -7.299455, "kl1": -16.981796, "kl2": -17.035401}
{"epoch": 262, "phase": 3, "elapsed_s": 2.95, "total": -38.626724, "recon0": 0.697207, "recon1": 0.962443, "recon2": 1.000015, "kl0": -7.299714, "kl1": -16.951284, "kl2": -17.035391}
{"epoch": 281, "phase": 4, "elapsed_s": 3.89, "total": 1.568994, "recon0": 0.693499, "recon1": 0.947326, "recon2": 1.000015, "kl0": -0.188474, "kl1": -0.440658, "kl2": -0.442713}
{"epoch": 282, "phase": 4, "elapsed_s": 3.71, "total": 0.491498, "recon0": 0.658598, "recon1": 0.954929, "recon2": 1.000009, "kl0": -0.356572, "kl1": -0.880035, "kl2": -0.885431}
{"epoch": 283, "phase": 4, "elapsed_s": 4.0, "total": -0.592675, "recon0": 0.594064, "recon1": 0.942773, "recon2": 1.000011, "kl0": -0.481986, "kl1": -1.319369, "kl2": -1.328168}
{"epoch": 284, "phase": 4, "elapsed_s": 4.1, "total": -1.648798, "recon0": 0.581671, "recon1": 0.943071, "recon2": 0.999993, "kl0": -0.642452, "kl1": -1.760196, "kl2": -1.770886}
{"epoch": 285, "phase": 4, "elapsed_s": 4.14, "total": -2.699906, "recon0": 0.588747, "recon1": 0.940156, "recon2": 1.000009, "kl0": -0.81323, "kl1": -2.201979, "kl2": -2.213609}
{"epoch": 286, "phase": 4, "elapsed_s": 3.87, "total": -3.75732, "recon0": 0.593007, "recon1": 0.939351, "recon2": 1.000007, "kl0": -0.990753, "kl1": -2.642587, "kl2": -2.656345}
{"epoch": 287, "phase": 4, "elapsed_s": 3.91, "total": -4.808199, "recon0": 0.602536, "recon1": 0.941, "recon2": 1.000007, "kl0": -1.167965, "kl1": -3.084708, "kl2": -3.099069}
{"epoch": 288, "phase": 4, "elapsed_s": 4.06, "total": -5.87697, "recon0": 0.607726, "recon1": 0.939643, "recon2": 1.000008, "kl0": -1.355443, "kl1": -3.527134, "kl2": -3.541771}
{"epoch": 289, "phase": 4, "elapsed_s": 3.66, "total": -6.947839, "recon0": 0.618321, "recon1": 0.938686, "recon2": 1.000009, "kl0": -1.552108, "kl1": -3.96823, "kl2": -3.984516}
{"epoch": 290, "phase": 4, "elapsed_s": 3.65, "total": -8.02493, "recon0": 0.631577, "recon1": 0.940126, "recon2": 0.999996, "kl0": -1.760842, "kl1": -4.408556, "kl2": -4.42723}
{"epoch": 291, "phase": 4, "elapsed_s": 3.72, "total": -9.111374, "recon0": 0.641746, "recon1": 0.939954, "recon2": 1.000004, "kl0": -1.9735, "kl1": -4.84961, "kl2": -4.869968}
{"epoch": 292, "phase": 4, "elapsed_s": 3.6, "total": -10.19833, "recon0": 0.651906, "recon1": 0.939247, "recon2": 1.000006, "kl0": -2.185887, "kl1": -5.290919, "kl2": -5.312684}
{"epoch": 293, "phase": 4, "elapsed_s": 3.77, "total": -11.275475, "recon0": 0.662185, "recon1": 0.941353, "recon2": 1.000011, "kl0": -2.393798, "kl1": -5.729819, "kl2": -5.755407}
{"epoch": 294, "phase": 4, "elapsed_s": 3.54, "total": -12.366956, "recon0": 0.671937, "recon1": 0.937899, "recon2": 0.999992, "kl0": -2.604576, "kl1": -6.174081, "kl2": -6.198127}
{"epoch": 295, "phase": 4, "elapsed_s": 3.55, "total": -13.466951, "recon0": 0.677389, "recon1": 0.920398, "recon2": 1.00002, "kl0": -2.811491, "kl1": -6.612414, "kl2": -6.640854}
{"epoch": 296, "phase": 4, "elapsed_s": 3.8, "total": -14.558737, "recon0": 0.682467, "recon1": 0.90166, "recon2": 1.000012, "kl0": -3.011368, "kl1": -7.047936, "kl2": -7.083573}
{"epoch": 297, "phase": 4, "elapsed_s": 3.52, "total": -15.627728, "recon0": 0.686995, "recon1": 0.909203, "recon2": 1.000002, "kl0": -3.207996, "kl1": -7.48962, "kl2": -7.526311}
{"epoch": 298, "phase": 4, "elapsed_s": 3.61, "total": -16.699343, "recon0": 0.691447, "recon1": 0.909158, "recon2": 1.000008, "kl0": -3.402371, "kl1": -7.928561, "kl2": -7.969025}
{"epoch": 299, "phase": 4, "elapsed_s": 3.66, "total": -17.775625, "recon0": 0.693403, "recon1": 0.914712, "recon2": 1.00001, "kl0": -3.596156, "kl1": -8.375855, "kl2": -8.41174}
{"epoch": 300, "phase": 4, "elapsed_s": 3.45, "total": -18.843805, "recon0": 0.695437, "recon1": 0.914954, "recon2": 1.000012, "kl0": -3.789835, "kl1": -8.80989, "kl2": -8.854483}
{"epoch": 301, "phase": 4, "elapsed_s": 3.84, "total": -19.923397, "recon0": 0.696971, "recon1": 0.906998, "recon2": 1.000001, "kl0": -3.979459, "kl1": -9.250697, "kl2": -9.297211}
{"epoch": 302, "phase": 4, "elapsed_s": 3.84, "total": -21.001681, "recon0": 0.696564, "recon1": 0.907054, "recon2": 1.000007, "kl0": -4.171324, "kl1": -9.694054, "kl2": -9.739928}
{"epoch": 303, "phase": 4, "elapsed_s": 3.78, "total": -22.071152, "recon0": 0.697238, "recon1": 0.912101, "recon2": 1.000014, "kl0": -4.362586, "kl1": -10.135274, "kl2": -10.182645}
{"epoch": 304, "phase": 4, "elapsed_s": 3.72, "total": -23.136086, "recon0": 0.697386, "recon1": 0.913262, "recon2": 1.000004, "kl0": -4.553108, "kl1": -10.568258, "kl2": -10.625372}
{"epoch": 305, "phase": 4, "elapsed_s": 3.56, "total": -24.214757, "recon0": 0.697235, "recon1": 0.91282, "recon2": 1.000008, "kl0": -4.740853, "kl1": -11.015869, "kl2": -11.068098}
{"epoch": 306, "phase": 4, "elapsed_s": 3.75, "total": -25.294484, "recon0": 0.697401, "recon1": 0.912665, "recon2": 1.000012, "kl0": -4.931934, "kl1": -11.461807, "kl2": -11.510822}
{"epoch": 307, "phase": 4, "elapsed_s": 3.89, "total": -26.362134, "recon0": 0.697572, "recon1": 0.911216, "recon2": 1.000001, "kl0": -5.122238, "kl1": -11.895136, "kl2": -11.953549}
{"epoch": 308, "phase": 4, "elapsed_s": 3.73, "total": -27.440353, "recon0": 0.697543, "recon1": 0.912553, "recon2": 1.000008, "kl0": -5.31231, "kl1": -12.341876, "kl2": -12.396271}
{"epoch": 309, "phase": 4, "elapsed_s": 4.23, "total": -28.511587, "recon0": 0.697549, "recon1": 0.907015, "recon2": 1.000013, "kl0": -5.501276, "kl1": -12.775915, "kl2": -12.838974}
{"epoch": 310, "phase": 4, "elapsed_s": 3.74, "total": -29.581934, "recon0": 0.696742, "recon1": 0.91166, "recon2": 1.000006, "kl0": -5.691119, "kl1": -13.217509, "kl2": -13.281713}
{"epoch": 311, "phase": 4, "elapsed_s": 3.9, "total": -30.657105, "recon0": 0.697226, "recon1": 0.924003, "recon2": 1.000009, "kl0": -5.880981, "kl1": -13.672913, "kl2": -13.724449}
{"epoch": 312, "phase": 4, "elapsed_s": 3.87, "total": -31.724706, "recon0": 0.697059, "recon1": 0.910476, "recon2": 1.000008, "kl0": -6.070221, "kl1": -14.094861, "kl2": -14.167167}
{"epoch": 313, "phase": 4, "elapsed_s": 3.56, "total": -32.801155, "recon0": 0.696998, "recon1": 0.911396, "recon2": 0.999999, "kl0": -6.260247, "kl1": -14.539419, "kl2": -14.609882}
{"epoch": 314, "phase": 4, "elapsed_s": 3.79, "total": -32.79754, "recon0": 0.69681, "recon1": 0.931113, "recon2": 1.000006, "kl0": -6.259699, "kl1": -14.555883, "kl2": -14.609886}
{"epoch": 315, "phase": 4, "elapsed_s": 3.68, "total": -32.789688, "recon0": 0.69697, "recon1": 0.91289, "recon2": 0.999996, "kl0": -6.259897, "kl1": -14.529742, "kl2": -14.609906}
{"epoch": 316, "phase": 4, "elapsed_s": 3.69, "total": -32.799928, "recon0": 0.696995, "recon1": 0.920038, "recon2": 0.999998, "kl0": -6.260204, "kl1": -14.546849, "kl2": -14.609907}
{"epoch": 317, "phase": 4, "elapsed_s": 3.97, "total": -32.808189, "recon0": 0.696813, "recon1": 0.909605, "recon2": 1.000001, "kl0": -6.260183, "kl1": -14.544527, "kl2": -14.609898}
{"epoch": 318, "phase": 4, "elapsed_s": 3.93, "total": -32.809561, "recon0": 0.696884, "recon1": 0.910203, "recon2": 1.000008, "kl0": -6.260279, "kl1": -14.546486, "kl2": -14.609891}
{"epoch": 319, "phase": 4, "elapsed_s": 4.03, "total": -32.806915, "recon0": 0.696695, "recon1": 0.915331, "recon2": 1.000006, "kl0": -6.26038, "kl1": -14.548677, "kl2": -14.609889}
{"epoch": 320, "phase": 4, "elapsed_s": 4.03, "total": -32.809224, "recon0": 0.696971, "recon1": 0.913454, "recon2": 0.999994, "kl0": -6.26115, "kl1": -14.548583, "kl2": -14.609909}
{"epoch": 321, "phase": 4, "elapsed_s": 3.79, "total": -32.80658, "recon0": 0.696921, "recon1": 0.912158, "recon2": 1.000014, "kl0": -6.259892, "kl1": -14.545911, "kl2": -14.60987}
{"epoch": 322, "phase": 4, "elapsed_s": 4.02, "total": -32.816063, "recon0": 0.696816, "recon1": 0.909218, "recon2": 1.000008, "kl0": -6.260476, "kl1": -14.55174, "kl2": -14.609889}
{"epoch": 323, "phase": 4, "elapsed_s": 3.72, "total": -32.796806, "recon0": 0.696905, "recon1": 0.916061, "recon2": 1.000013, "kl0": -6.260144, "kl1": -14.53976, "kl2": -14.609881}
{"epoch": 324, "phase": 4, "elapsed_s": 3.6, "total": -32.791959, "recon0": 0.697228, "recon1": 0.917183, "recon2": 1.000012, "kl0": -6.259073, "kl1": -14.537418, "kl2": -14.609892}
{"epoch": 325, "phase": 4, "elapsed_s": 3.84, "total": -32.801608, "recon0": 0.696749, "recon1": 0.914895, "recon2": 1.000006, "kl0": -6.260871, "kl1": -14.542501, "kl2": -14.609887}
{"epoch": 326, "phase": 4, "elapsed_s": 3.62, "total": -32.80991, "recon0": 0.696849, "recon1": 0.906385, "recon2": 1.000009, "kl0": -6.260348, "kl1": -14.542919, "kl2": -14.609886}
{"epoch": 327, "phase": 4, "elapsed_s": 3.75, "total": -32.783185, "recon0": 0.696993, "recon1": 0.920439, "recon2": 1.000011, "kl0": -6.26005, "kl1": -14.530707, "kl2": -14.609871}
{"epoch": 328, "phase": 4, "elapsed_s": 3.86, "total": -32.780511, "recon0": 0.69678, "recon1": 0.929524, "recon2": 0.999999, "kl0": -6.260888, "kl1": -14.536023, "kl2": -14.609902}
{"epoch": 329, "phase": 4, "elapsed_s": 3.76, "total": -32.800836, "recon0": 0.696711, "recon1": 0.915503, "recon2": 0.999994, "kl0": -6.260517, "kl1": -14.54262, "kl2": -14.609907}
{"epoch": 330, "phase": 4, "elapsed_s": 3.82, "total": -32.800309, "recon0": 0.696824, "recon1": 0.919015, "recon2": 1.000001, "kl0": -6.260781, "kl1": -14.54548, "kl2": -14.609888}
{"epoch": 331, "phase": 4, "elapsed_s": 3.76, "total": -32.80928, "recon0": 0.696806, "recon1": 0.91412, "recon2": 1.000007, "kl0": -6.260476, "kl1": -14.549832, "kl2": -14.609904}
{"epoch": 332, "phase": 4, "elapsed_s": 3.86, "total": -32.800028, "recon0": 0.697042, "recon1": 0.917003, "recon2": 1.000004, "kl0": -6.26014, "kl1": -14.544058, "kl2": -14.60988}
{"epoch": 333, "phase": 4, "elapsed_s": 4.09, "total": -32.805645, "recon0": 0.69698, "recon1": 0.914057, "recon2": 1.000003, "kl0": -6.259438, "kl1": -14.547351, "kl2": -14.609897}
{"epoch": 334, "phase": 4, "elapsed_s": 4.11, "total": -32.803752, "recon0": 0.696743, "recon1": 0.911666, "recon2": 1.000004, "kl0": -6.260028, "kl1": -14.542248, "kl2": -14.609889}
{"epoch": 335, "phase": 4, "elapsed_s": 4.06, "total": -32.79918, "recon0": 0.696952, "recon1": 0.922422, "recon2": 1.000003, "kl0": -6.260643, "kl1": -14.548012, "kl2": -14.609902}
{"epoch": 336, "phase": 4, "elapsed_s": 3.92, "total": -32.806642, "recon0": 0.697, "recon1": 0.910521, "recon2": 1.000005, "kl0": -6.26087, "kl1": -14.543395, "kl2": -14.609903}
{"epoch": 337, "phase": 4, "elapsed_s": 3.79, "total": -32.802425, "recon0": 0.696715, "recon1": 0.911598, "recon2": 0.999997, "kl0": -6.260366, "kl1": -14.540477, "kl2": -14.609892}
{"epoch": 338, "phase": 4, "elapsed_s": 3.86, "total": -32.799268, "recon0": 0.696866, "recon1": 0.911219, "recon2": 1.000009, "kl0": -6.260086, "kl1": -14.53738, "kl2": -14.609895}
{"epoch": 339, "phase": 4, "elapsed_s": 3.64, "total": -32.80133, "recon0": 0.696953, "recon1": 0.914761, "recon2": 1.000007, "kl0": -6.260135, "kl1": -14.543011, "kl2": -14.609903}
{"epoch": 340, "phase": 4, "elapsed_s": 3.81, "total": -32.780337, "recon0": 0.696945, "recon1": 0.924979, "recon2": 1.000001, "kl0": -6.260224, "kl1": -14.532153, "kl2": -14.609886}
{"epoch": 341, "phase": 4, "elapsed_s": 3.89, "total": -32.776223, "recon0": 0.696934, "recon1": 0.945074, "recon2": 1.000013, "kl0": -6.260531, "kl1": -14.547831, "kl2": -14.609882}
{"epoch": 342, "phase": 4, "elapsed_s": 3.48, "total": -32.781524, "recon0": 0.69691, "recon1": 0.946269, "recon2": 1.000009, "kl0": -6.259565, "kl1": -14.555248, "kl2": -14.609898}

View File

@@ -0,0 +1,278 @@
{"epoch": 1, "phase": 0, "elapsed_s": 23.69, "total": -6.79361, "recon0": 1.197567, "recon1": 0.0, "recon2": 0.0, "kl0": -7.991177, "kl1": 0.0, "kl2": 0.0}
{"epoch": 2, "phase": 0, "elapsed_s": 22.68, "total": -7.435481, "recon0": 1.022993, "recon1": 0.0, "recon2": 0.0, "kl0": -8.458473, "kl1": 0.0, "kl2": 0.0}
{"epoch": 3, "phase": 0, "elapsed_s": 22.94, "total": -7.463537, "recon0": 1.014745, "recon1": 0.0, "recon2": 0.0, "kl0": -8.478282, "kl1": 0.0, "kl2": 0.0}
{"epoch": 4, "phase": 0, "elapsed_s": 22.26, "total": -7.474298, "recon0": 1.010887, "recon1": 0.0, "recon2": 0.0, "kl0": -8.485185, "kl1": 0.0, "kl2": 0.0}
{"epoch": 5, "phase": 0, "elapsed_s": 21.82, "total": -7.480975, "recon0": 1.008915, "recon1": 0.0, "recon2": 0.0, "kl0": -8.48989, "kl1": 0.0, "kl2": 0.0}
{"epoch": 6, "phase": 0, "elapsed_s": 21.15, "total": -7.485128, "recon0": 1.006619, "recon1": 0.0, "recon2": 0.0, "kl0": -8.491746, "kl1": 0.0, "kl2": 0.0}
{"epoch": 7, "phase": 0, "elapsed_s": 20.99, "total": -7.487274, "recon0": 1.006728, "recon1": 0.0, "recon2": 0.0, "kl0": -8.494002, "kl1": 0.0, "kl2": 0.0}
{"epoch": 8, "phase": 0, "elapsed_s": 21.6, "total": -7.488429, "recon0": 1.006059, "recon1": 0.0, "recon2": 0.0, "kl0": -8.494488, "kl1": 0.0, "kl2": 0.0}
{"epoch": 9, "phase": 0, "elapsed_s": 21.54, "total": -7.48845, "recon0": 1.00426, "recon1": 0.0, "recon2": 0.0, "kl0": -8.49271, "kl1": 0.0, "kl2": 0.0}
{"epoch": 10, "phase": 0, "elapsed_s": 21.21, "total": -7.491117, "recon0": 1.005311, "recon1": 0.0, "recon2": 0.0, "kl0": -8.496428, "kl1": 0.0, "kl2": 0.0}
{"epoch": 11, "phase": 0, "elapsed_s": 20.93, "total": -7.490992, "recon0": 1.004811, "recon1": 0.0, "recon2": 0.0, "kl0": -8.495802, "kl1": 0.0, "kl2": 0.0}
{"epoch": 12, "phase": 0, "elapsed_s": 20.59, "total": -7.491286, "recon0": 1.005156, "recon1": 0.0, "recon2": 0.0, "kl0": -8.496442, "kl1": 0.0, "kl2": 0.0}
{"epoch": 13, "phase": 0, "elapsed_s": 21.11, "total": -7.492625, "recon0": 1.004928, "recon1": 0.0, "recon2": 0.0, "kl0": -8.497553, "kl1": 0.0, "kl2": 0.0}
{"epoch": 14, "phase": 0, "elapsed_s": 21.7, "total": -7.493261, "recon0": 1.00473, "recon1": 0.0, "recon2": 0.0, "kl0": -8.497991, "kl1": 0.0, "kl2": 0.0}
{"epoch": 15, "phase": 0, "elapsed_s": 20.89, "total": -7.493425, "recon0": 1.004742, "recon1": 0.0, "recon2": 0.0, "kl0": -8.498167, "kl1": 0.0, "kl2": 0.0}
{"epoch": 16, "phase": 0, "elapsed_s": 21.25, "total": -7.493951, "recon0": 1.003882, "recon1": 0.0, "recon2": 0.0, "kl0": -8.497833, "kl1": 0.0, "kl2": 0.0}
{"epoch": 17, "phase": 0, "elapsed_s": 20.54, "total": -7.493448, "recon0": 1.00418, "recon1": 0.0, "recon2": 0.0, "kl0": -8.497628, "kl1": 0.0, "kl2": 0.0}
{"epoch": 18, "phase": 0, "elapsed_s": 21.97, "total": -7.494606, "recon0": 1.003521, "recon1": 0.0, "recon2": 0.0, "kl0": -8.498127, "kl1": 0.0, "kl2": 0.0}
{"epoch": 19, "phase": 0, "elapsed_s": 21.5, "total": -7.495215, "recon0": 1.002139, "recon1": 0.0, "recon2": 0.0, "kl0": -8.497354, "kl1": 0.0, "kl2": 0.0}
{"epoch": 20, "phase": 0, "elapsed_s": 20.57, "total": -7.495079, "recon0": 1.001522, "recon1": 0.0, "recon2": 0.0, "kl0": -8.496601, "kl1": 0.0, "kl2": 0.0}
{"epoch": 21, "phase": 0, "elapsed_s": 20.54, "total": -7.494494, "recon0": 1.00388, "recon1": 0.0, "recon2": 0.0, "kl0": -8.498374, "kl1": 0.0, "kl2": 0.0}
{"epoch": 22, "phase": 0, "elapsed_s": 20.43, "total": -7.495232, "recon0": 1.00332, "recon1": 0.0, "recon2": 0.0, "kl0": -8.498552, "kl1": 0.0, "kl2": 0.0}
{"epoch": 23, "phase": 0, "elapsed_s": 22.07, "total": -7.496413, "recon0": 1.003497, "recon1": 0.0, "recon2": 0.0, "kl0": -8.49991, "kl1": 0.0, "kl2": 0.0}
{"epoch": 24, "phase": 0, "elapsed_s": 21.9, "total": -7.495635, "recon0": 1.003216, "recon1": 0.0, "recon2": 0.0, "kl0": -8.49885, "kl1": 0.0, "kl2": 0.0}
{"epoch": 25, "phase": 0, "elapsed_s": 20.4, "total": -7.496807, "recon0": 1.002496, "recon1": 0.0, "recon2": 0.0, "kl0": -8.499303, "kl1": 0.0, "kl2": 0.0}
{"epoch": 26, "phase": 0, "elapsed_s": 20.37, "total": -7.496199, "recon0": 1.002774, "recon1": 0.0, "recon2": 0.0, "kl0": -8.498973, "kl1": 0.0, "kl2": 0.0}
{"epoch": 27, "phase": 0, "elapsed_s": 21.33, "total": -7.497299, "recon0": 1.002738, "recon1": 0.0, "recon2": 0.0, "kl0": -8.500037, "kl1": 0.0, "kl2": 0.0}
{"epoch": 28, "phase": 0, "elapsed_s": 21.02, "total": -7.496811, "recon0": 1.002596, "recon1": 0.0, "recon2": 0.0, "kl0": -8.499407, "kl1": 0.0, "kl2": 0.0}
{"epoch": 29, "phase": 0, "elapsed_s": 21.11, "total": -7.496809, "recon0": 1.002868, "recon1": 0.0, "recon2": 0.0, "kl0": -8.499677, "kl1": 0.0, "kl2": 0.0}
{"epoch": 30, "phase": 0, "elapsed_s": 22.11, "total": -7.496375, "recon0": 1.001943, "recon1": 0.0, "recon2": 0.0, "kl0": -8.498319, "kl1": 0.0, "kl2": 0.0}
{"epoch": 31, "phase": 0, "elapsed_s": 21.97, "total": -7.49738, "recon0": 1.002466, "recon1": 0.0, "recon2": 0.0, "kl0": -8.499846, "kl1": 0.0, "kl2": 0.0}
{"epoch": 32, "phase": 0, "elapsed_s": 21.22, "total": -7.497343, "recon0": 1.002087, "recon1": 0.0, "recon2": 0.0, "kl0": -8.49943, "kl1": 0.0, "kl2": 0.0}
{"epoch": 33, "phase": 0, "elapsed_s": 20.6, "total": -7.497509, "recon0": 1.001614, "recon1": 0.0, "recon2": 0.0, "kl0": -8.499123, "kl1": 0.0, "kl2": 0.0}
{"epoch": 34, "phase": 0, "elapsed_s": 20.61, "total": -7.498047, "recon0": 1.002, "recon1": 0.0, "recon2": 0.0, "kl0": -8.500047, "kl1": 0.0, "kl2": 0.0}
{"epoch": 35, "phase": 0, "elapsed_s": 20.65, "total": -7.498059, "recon0": 1.001702, "recon1": 0.0, "recon2": 0.0, "kl0": -8.499761, "kl1": 0.0, "kl2": 0.0}
{"epoch": 36, "phase": 0, "elapsed_s": 20.98, "total": -7.49774, "recon0": 1.002183, "recon1": 0.0, "recon2": 0.0, "kl0": -8.499922, "kl1": 0.0, "kl2": 0.0}
{"epoch": 37, "phase": 0, "elapsed_s": 21.7, "total": -7.498474, "recon0": 1.002877, "recon1": 0.0, "recon2": 0.0, "kl0": -8.50135, "kl1": 0.0, "kl2": 0.0}
{"epoch": 38, "phase": 0, "elapsed_s": 21.07, "total": -7.498586, "recon0": 1.002665, "recon1": 0.0, "recon2": 0.0, "kl0": -8.501251, "kl1": 0.0, "kl2": 0.0}
{"epoch": 39, "phase": 0, "elapsed_s": 19.99, "total": -7.498336, "recon0": 1.002378, "recon1": 0.0, "recon2": 0.0, "kl0": -8.500714, "kl1": 0.0, "kl2": 0.0}
{"epoch": 40, "phase": 0, "elapsed_s": 20.77, "total": -7.499933, "recon0": 1.001744, "recon1": 0.0, "recon2": 0.0, "kl0": -8.501677, "kl1": 0.0, "kl2": 0.0}
{"epoch": 41, "phase": 0, "elapsed_s": 20.31, "total": -7.498233, "recon0": 1.00124, "recon1": 0.0, "recon2": 0.0, "kl0": -8.499473, "kl1": 0.0, "kl2": 0.0}
{"epoch": 42, "phase": 0, "elapsed_s": 20.58, "total": -7.499287, "recon0": 1.002397, "recon1": 0.0, "recon2": 0.0, "kl0": -8.501684, "kl1": 0.0, "kl2": 0.0}
{"epoch": 43, "phase": 0, "elapsed_s": 20.63, "total": -7.499364, "recon0": 1.001727, "recon1": 0.0, "recon2": 0.0, "kl0": -8.501091, "kl1": 0.0, "kl2": 0.0}
{"epoch": 44, "phase": 0, "elapsed_s": 21.2, "total": -7.499365, "recon0": 1.002052, "recon1": 0.0, "recon2": 0.0, "kl0": -8.501417, "kl1": 0.0, "kl2": 0.0}
{"epoch": 45, "phase": 0, "elapsed_s": 21.0, "total": -7.499189, "recon0": 1.002568, "recon1": 0.0, "recon2": 0.0, "kl0": -8.501757, "kl1": 0.0, "kl2": 0.0}
{"epoch": 46, "phase": 0, "elapsed_s": 19.55, "total": -7.499561, "recon0": 1.002305, "recon1": 0.0, "recon2": 0.0, "kl0": -8.501866, "kl1": 0.0, "kl2": 0.0}
{"epoch": 47, "phase": 0, "elapsed_s": 19.27, "total": -7.499606, "recon0": 1.002019, "recon1": 0.0, "recon2": 0.0, "kl0": -8.501625, "kl1": 0.0, "kl2": 0.0}
{"epoch": 48, "phase": 0, "elapsed_s": 19.39, "total": -7.50029, "recon0": 1.002188, "recon1": 0.0, "recon2": 0.0, "kl0": -8.502478, "kl1": 0.0, "kl2": 0.0}
{"epoch": 49, "phase": 0, "elapsed_s": 19.47, "total": -7.499886, "recon0": 1.001982, "recon1": 0.0, "recon2": 0.0, "kl0": -8.501869, "kl1": 0.0, "kl2": 0.0}
{"epoch": 50, "phase": 0, "elapsed_s": 19.66, "total": -7.500348, "recon0": 1.002294, "recon1": 0.0, "recon2": 0.0, "kl0": -8.502642, "kl1": 0.0, "kl2": 0.0}
{"epoch": 51, "phase": 0, "elapsed_s": 18.56, "total": -7.500153, "recon0": 1.001241, "recon1": 0.0, "recon2": 0.0, "kl0": -8.501394, "kl1": 0.0, "kl2": 0.0}
{"epoch": 52, "phase": 0, "elapsed_s": 18.16, "total": -7.500405, "recon0": 1.001804, "recon1": 0.0, "recon2": 0.0, "kl0": -8.502209, "kl1": 0.0, "kl2": 0.0}
{"epoch": 53, "phase": 0, "elapsed_s": 18.31, "total": -7.500355, "recon0": 1.002024, "recon1": 0.0, "recon2": 0.0, "kl0": -8.502379, "kl1": 0.0, "kl2": 0.0}
{"epoch": 54, "phase": 0, "elapsed_s": 18.42, "total": -7.500735, "recon0": 1.001755, "recon1": 0.0, "recon2": 0.0, "kl0": -8.50249, "kl1": 0.0, "kl2": 0.0}
{"epoch": 55, "phase": 0, "elapsed_s": 19.32, "total": -7.500336, "recon0": 1.001857, "recon1": 0.0, "recon2": 0.0, "kl0": -8.502193, "kl1": 0.0, "kl2": 0.0}
{"epoch": 56, "phase": 0, "elapsed_s": 18.52, "total": -7.500588, "recon0": 1.001679, "recon1": 0.0, "recon2": 0.0, "kl0": -8.502267, "kl1": 0.0, "kl2": 0.0}
{"epoch": 57, "phase": 0, "elapsed_s": 18.41, "total": -7.500834, "recon0": 1.001549, "recon1": 0.0, "recon2": 0.0, "kl0": -8.502383, "kl1": 0.0, "kl2": 0.0}
{"epoch": 58, "phase": 0, "elapsed_s": 17.97, "total": -7.500644, "recon0": 1.001568, "recon1": 0.0, "recon2": 0.0, "kl0": -8.502212, "kl1": 0.0, "kl2": 0.0}
{"epoch": 59, "phase": 0, "elapsed_s": 18.14, "total": -7.500778, "recon0": 1.001908, "recon1": 0.0, "recon2": 0.0, "kl0": -8.502686, "kl1": 0.0, "kl2": 0.0}
{"epoch": 60, "phase": 0, "elapsed_s": 19.03, "total": -7.500311, "recon0": 1.002093, "recon1": 0.0, "recon2": 0.0, "kl0": -8.502404, "kl1": 0.0, "kl2": 0.0}
{"epoch": 61, "phase": 1, "elapsed_s": 3.01, "total": -20.364208, "recon0": 0.839574, "recon1": 1.469743, "recon2": 0.0, "kl0": -7.247929, "kl1": -15.425595, "kl2": 0.0}
{"epoch": 62, "phase": 1, "elapsed_s": 2.8, "total": -22.054864, "recon0": 0.772972, "recon1": 1.105512, "recon2": 0.0, "kl0": -7.295641, "kl1": -16.637708, "kl2": 0.0}
{"epoch": 63, "phase": 1, "elapsed_s": 3.02, "total": -22.242755, "recon0": 0.756764, "recon1": 1.072717, "recon2": 0.0, "kl0": -7.297455, "kl1": -16.774781, "kl2": 0.0}
{"epoch": 64, "phase": 1, "elapsed_s": 2.98, "total": -22.349453, "recon0": 0.745225, "recon1": 1.054189, "recon2": 0.0, "kl0": -7.296726, "kl1": -16.852141, "kl2": 0.0}
{"epoch": 65, "phase": 1, "elapsed_s": 2.7, "total": -22.41031, "recon0": 0.731803, "recon1": 1.040969, "recon2": 0.0, "kl0": -7.298445, "kl1": -16.884637, "kl2": 0.0}
{"epoch": 66, "phase": 1, "elapsed_s": 2.81, "total": -22.45678, "recon0": 0.718527, "recon1": 1.033292, "recon2": 0.0, "kl0": -7.297256, "kl1": -16.911344, "kl2": 0.0}
{"epoch": 67, "phase": 1, "elapsed_s": 2.89, "total": -22.508489, "recon0": 0.709603, "recon1": 1.025378, "recon2": 0.0, "kl0": -7.298379, "kl1": -16.94509, "kl2": 0.0}
{"epoch": 68, "phase": 1, "elapsed_s": 2.95, "total": -22.52633, "recon0": 0.703889, "recon1": 1.020364, "recon2": 0.0, "kl0": -7.298726, "kl1": -16.951857, "kl2": 0.0}
{"epoch": 69, "phase": 1, "elapsed_s": 2.68, "total": -22.550039, "recon0": 0.701698, "recon1": 1.016157, "recon2": 0.0, "kl0": -7.298223, "kl1": -16.969671, "kl2": 0.0}
{"epoch": 70, "phase": 1, "elapsed_s": 3.01, "total": -22.560013, "recon0": 0.700218, "recon1": 1.012634, "recon2": 0.0, "kl0": -7.299313, "kl1": -16.973552, "kl2": 0.0}
{"epoch": 71, "phase": 1, "elapsed_s": 2.72, "total": -22.575161, "recon0": 0.699513, "recon1": 1.010541, "recon2": 0.0, "kl0": -7.298883, "kl1": -16.986333, "kl2": 0.0}
{"epoch": 72, "phase": 1, "elapsed_s": 2.77, "total": -22.582816, "recon0": 0.69972, "recon1": 1.007565, "recon2": 0.0, "kl0": -7.298496, "kl1": -16.991605, "kl2": 0.0}
{"epoch": 73, "phase": 1, "elapsed_s": 2.84, "total": -22.594111, "recon0": 0.699111, "recon1": 1.006152, "recon2": 0.0, "kl0": -7.299403, "kl1": -16.999971, "kl2": 0.0}
{"epoch": 74, "phase": 1, "elapsed_s": 2.93, "total": -22.600109, "recon0": 0.699142, "recon1": 1.005034, "recon2": 0.0, "kl0": -7.299963, "kl1": -17.004321, "kl2": 0.0}
{"epoch": 75, "phase": 1, "elapsed_s": 2.81, "total": -22.607139, "recon0": 0.699078, "recon1": 1.003796, "recon2": 0.0, "kl0": -7.299051, "kl1": -17.010961, "kl2": 0.0}
{"epoch": 76, "phase": 1, "elapsed_s": 2.7, "total": -22.611322, "recon0": 0.699158, "recon1": 1.002687, "recon2": 0.0, "kl0": -7.299572, "kl1": -17.013593, "kl2": 0.0}
{"epoch": 77, "phase": 1, "elapsed_s": 3.02, "total": -22.616044, "recon0": 0.698892, "recon1": 1.002257, "recon2": 0.0, "kl0": -7.299126, "kl1": -17.018066, "kl2": 0.0}
{"epoch": 78, "phase": 1, "elapsed_s": 2.7, "total": -22.620544, "recon0": 0.69888, "recon1": 1.002004, "recon2": 0.0, "kl0": -7.299446, "kl1": -17.021981, "kl2": 0.0}
{"epoch": 79, "phase": 1, "elapsed_s": 2.78, "total": -22.62354, "recon0": 0.698359, "recon1": 1.00144, "recon2": 0.0, "kl0": -7.299434, "kl1": -17.023905, "kl2": 0.0}
{"epoch": 80, "phase": 1, "elapsed_s": 2.87, "total": -22.624031, "recon0": 0.699373, "recon1": 1.001252, "recon2": 0.0, "kl0": -7.299434, "kl1": -17.025222, "kl2": 0.0}
{"epoch": 81, "phase": 1, "elapsed_s": 3.04, "total": -22.626789, "recon0": 0.698548, "recon1": 1.00094, "recon2": 0.0, "kl0": -7.299155, "kl1": -17.027122, "kl2": 0.0}
{"epoch": 82, "phase": 1, "elapsed_s": 2.79, "total": -22.627569, "recon0": 0.698576, "recon1": 1.001119, "recon2": 0.0, "kl0": -7.299284, "kl1": -17.02798, "kl2": 0.0}
{"epoch": 83, "phase": 1, "elapsed_s": 2.63, "total": -22.630271, "recon0": 0.698666, "recon1": 1.000909, "recon2": 0.0, "kl0": -7.299682, "kl1": -17.030164, "kl2": 0.0}
{"epoch": 84, "phase": 1, "elapsed_s": 3.07, "total": -22.630003, "recon0": 0.698579, "recon1": 1.000564, "recon2": 0.0, "kl0": -7.298856, "kl1": -17.03029, "kl2": 0.0}
{"epoch": 85, "phase": 1, "elapsed_s": 2.74, "total": -22.631745, "recon0": 0.698582, "recon1": 1.000493, "recon2": 0.0, "kl0": -7.299743, "kl1": -17.031077, "kl2": 0.0}
{"epoch": 86, "phase": 1, "elapsed_s": 2.74, "total": -22.630837, "recon0": 0.698722, "recon1": 1.000524, "recon2": 0.0, "kl0": -7.298933, "kl1": -17.031149, "kl2": 0.0}
{"epoch": 87, "phase": 1, "elapsed_s": 2.56, "total": -22.632505, "recon0": 0.698635, "recon1": 1.000349, "recon2": 0.0, "kl0": -7.299598, "kl1": -17.031891, "kl2": 0.0}
{"epoch": 88, "phase": 1, "elapsed_s": 2.99, "total": -22.633299, "recon0": 0.698251, "recon1": 1.000311, "recon2": 0.0, "kl0": -7.300114, "kl1": -17.031746, "kl2": 0.0}
{"epoch": 89, "phase": 1, "elapsed_s": 2.75, "total": -22.63266, "recon0": 0.698295, "recon1": 1.000314, "recon2": 0.0, "kl0": -7.300485, "kl1": -17.030784, "kl2": 0.0}
{"epoch": 90, "phase": 1, "elapsed_s": 2.8, "total": -22.633377, "recon0": 0.698443, "recon1": 1.000196, "recon2": 0.0, "kl0": -7.300037, "kl1": -17.031979, "kl2": 0.0}
{"epoch": 91, "phase": 1, "elapsed_s": 2.85, "total": -22.632813, "recon0": 0.698687, "recon1": 1.000214, "recon2": 0.0, "kl0": -7.299274, "kl1": -17.03244, "kl2": 0.0}
{"epoch": 92, "phase": 1, "elapsed_s": 2.79, "total": -22.634314, "recon0": 0.698674, "recon1": 1.000139, "recon2": 0.0, "kl0": -7.299809, "kl1": -17.033319, "kl2": 0.0}
{"epoch": 93, "phase": 1, "elapsed_s": 2.63, "total": -22.634267, "recon0": 0.698108, "recon1": 1.000236, "recon2": 0.0, "kl0": -7.299279, "kl1": -17.033333, "kl2": 0.0}
{"epoch": 94, "phase": 1, "elapsed_s": 2.84, "total": -22.634642, "recon0": 0.698151, "recon1": 1.000229, "recon2": 0.0, "kl0": -7.299203, "kl1": -17.033819, "kl2": 0.0}
{"epoch": 95, "phase": 1, "elapsed_s": 3.05, "total": -22.635051, "recon0": 0.698184, "recon1": 1.000185, "recon2": 0.0, "kl0": -7.299584, "kl1": -17.033836, "kl2": 0.0}
{"epoch": 96, "phase": 1, "elapsed_s": 2.67, "total": -22.634399, "recon0": 0.6987, "recon1": 1.000158, "recon2": 0.0, "kl0": -7.299652, "kl1": -17.033604, "kl2": 0.0}
{"epoch": 97, "phase": 1, "elapsed_s": 2.77, "total": -22.634712, "recon0": 0.698169, "recon1": 1.000176, "recon2": 0.0, "kl0": -7.299342, "kl1": -17.033715, "kl2": 0.0}
{"epoch": 98, "phase": 1, "elapsed_s": 2.69, "total": -22.635535, "recon0": 0.698447, "recon1": 1.000067, "recon2": 0.0, "kl0": -7.300064, "kl1": -17.033984, "kl2": 0.0}
{"epoch": 99, "phase": 1, "elapsed_s": 2.85, "total": -22.635565, "recon0": 0.698065, "recon1": 1.000134, "recon2": 0.0, "kl0": -7.299802, "kl1": -17.033961, "kl2": 0.0}
{"epoch": 100, "phase": 1, "elapsed_s": 2.53, "total": -22.635214, "recon0": 0.698039, "recon1": 1.000108, "recon2": 0.0, "kl0": -7.298939, "kl1": -17.034422, "kl2": 0.0}
{"epoch": 101, "phase": 1, "elapsed_s": 2.73, "total": -22.635313, "recon0": 0.698443, "recon1": 1.000076, "recon2": 0.0, "kl0": -7.299895, "kl1": -17.033937, "kl2": 0.0}
{"epoch": 102, "phase": 1, "elapsed_s": 2.8, "total": -22.635445, "recon0": 0.697905, "recon1": 1.000099, "recon2": 0.0, "kl0": -7.299004, "kl1": -17.034445, "kl2": 0.0}
{"epoch": 103, "phase": 1, "elapsed_s": 2.74, "total": -22.635967, "recon0": 0.697776, "recon1": 1.000115, "recon2": 0.0, "kl0": -7.299384, "kl1": -17.034474, "kl2": 0.0}
{"epoch": 104, "phase": 1, "elapsed_s": 2.61, "total": -22.635917, "recon0": 0.697951, "recon1": 1.00005, "recon2": 0.0, "kl0": -7.299553, "kl1": -17.034365, "kl2": 0.0}
{"epoch": 105, "phase": 1, "elapsed_s": 2.78, "total": -22.635594, "recon0": 0.698469, "recon1": 1.000082, "recon2": 0.0, "kl0": -7.299519, "kl1": -17.034627, "kl2": 0.0}
{"epoch": 106, "phase": 1, "elapsed_s": 2.8, "total": -22.635632, "recon0": 0.698377, "recon1": 1.000028, "recon2": 0.0, "kl0": -7.299618, "kl1": -17.034419, "kl2": 0.0}
{"epoch": 107, "phase": 1, "elapsed_s": 3.02, "total": -22.635042, "recon0": 0.698203, "recon1": 1.000083, "recon2": 0.0, "kl0": -7.299416, "kl1": -17.033912, "kl2": 0.0}
{"epoch": 108, "phase": 1, "elapsed_s": 2.88, "total": -22.63652, "recon0": 0.697917, "recon1": 1.000033, "recon2": 0.0, "kl0": -7.300002, "kl1": -17.03447, "kl2": 0.0}
{"epoch": 109, "phase": 1, "elapsed_s": 3.01, "total": -22.63586, "recon0": 0.697939, "recon1": 1.00005, "recon2": 0.0, "kl0": -7.299314, "kl1": -17.034535, "kl2": 0.0}
{"epoch": 110, "phase": 1, "elapsed_s": 2.95, "total": -22.637156, "recon0": 0.698122, "recon1": 1.000072, "recon2": 0.0, "kl0": -7.300497, "kl1": -17.034854, "kl2": 0.0}
{"epoch": 111, "phase": 1, "elapsed_s": 2.68, "total": -22.636421, "recon0": 0.697427, "recon1": 1.00006, "recon2": 0.0, "kl0": -7.299167, "kl1": -17.034742, "kl2": 0.0}
{"epoch": 112, "phase": 1, "elapsed_s": 2.93, "total": -22.635576, "recon0": 0.698373, "recon1": 1.000076, "recon2": 0.0, "kl0": -7.299761, "kl1": -17.034264, "kl2": 0.0}
{"epoch": 113, "phase": 1, "elapsed_s": 2.79, "total": -22.635881, "recon0": 0.697973, "recon1": 1.000037, "recon2": 0.0, "kl0": -7.299921, "kl1": -17.03397, "kl2": 0.0}
{"epoch": 114, "phase": 1, "elapsed_s": 2.82, "total": -22.636044, "recon0": 0.698568, "recon1": 1.000058, "recon2": 0.0, "kl0": -7.300304, "kl1": -17.034365, "kl2": 0.0}
{"epoch": 115, "phase": 1, "elapsed_s": 2.73, "total": -22.636887, "recon0": 0.697651, "recon1": 1.00005, "recon2": 0.0, "kl0": -7.300079, "kl1": -17.03451, "kl2": 0.0}
{"epoch": 116, "phase": 1, "elapsed_s": 2.96, "total": -22.635989, "recon0": 0.698411, "recon1": 1.000027, "recon2": 0.0, "kl0": -7.299376, "kl1": -17.03505, "kl2": 0.0}
{"epoch": 117, "phase": 1, "elapsed_s": 2.77, "total": -22.637205, "recon0": 0.697562, "recon1": 1.00003, "recon2": 0.0, "kl0": -7.299761, "kl1": -17.035036, "kl2": 0.0}
{"epoch": 118, "phase": 1, "elapsed_s": 2.66, "total": -22.636829, "recon0": 0.697986, "recon1": 1.000054, "recon2": 0.0, "kl0": -7.299838, "kl1": -17.035032, "kl2": 0.0}
{"epoch": 119, "phase": 1, "elapsed_s": 2.91, "total": -22.637082, "recon0": 0.697886, "recon1": 1.000052, "recon2": 0.0, "kl0": -7.300088, "kl1": -17.034933, "kl2": 0.0}
{"epoch": 120, "phase": 1, "elapsed_s": 2.66, "total": -22.637646, "recon0": 0.697742, "recon1": 1.000016, "recon2": 0.0, "kl0": -7.300269, "kl1": -17.035135, "kl2": 0.0}
{"epoch": 121, "phase": 1, "elapsed_s": 2.89, "total": -22.636979, "recon0": 0.697794, "recon1": 1.00004, "recon2": 0.0, "kl0": -7.299744, "kl1": -17.035068, "kl2": 0.0}
{"epoch": 122, "phase": 1, "elapsed_s": 2.63, "total": -22.636144, "recon0": 0.69824, "recon1": 1.000039, "recon2": 0.0, "kl0": -7.299312, "kl1": -17.035112, "kl2": 0.0}
{"epoch": 123, "phase": 1, "elapsed_s": 2.99, "total": -22.636977, "recon0": 0.697841, "recon1": 1.000013, "recon2": 0.0, "kl0": -7.300311, "kl1": -17.03452, "kl2": 0.0}
{"epoch": 124, "phase": 1, "elapsed_s": 2.67, "total": -22.636172, "recon0": 0.698229, "recon1": 1.000019, "recon2": 0.0, "kl0": -7.300149, "kl1": -17.034271, "kl2": 0.0}
{"epoch": 125, "phase": 1, "elapsed_s": 2.96, "total": -22.637311, "recon0": 0.697641, "recon1": 1.000059, "recon2": 0.0, "kl0": -7.299968, "kl1": -17.035044, "kl2": 0.0}
{"epoch": 126, "phase": 1, "elapsed_s": 2.84, "total": -22.637082, "recon0": 0.697456, "recon1": 1.000035, "recon2": 0.0, "kl0": -7.299392, "kl1": -17.035181, "kl2": 0.0}
{"epoch": 127, "phase": 1, "elapsed_s": 2.9, "total": -22.637048, "recon0": 0.697687, "recon1": 1.000017, "recon2": 0.0, "kl0": -7.299676, "kl1": -17.035076, "kl2": 0.0}
{"epoch": 128, "phase": 1, "elapsed_s": 2.99, "total": -22.637483, "recon0": 0.69754, "recon1": 1.000037, "recon2": 0.0, "kl0": -7.300015, "kl1": -17.035045, "kl2": 0.0}
{"epoch": 129, "phase": 1, "elapsed_s": 2.8, "total": -22.636571, "recon0": 0.697612, "recon1": 1.000051, "recon2": 0.0, "kl0": -7.299664, "kl1": -17.03457, "kl2": 0.0}
{"epoch": 130, "phase": 1, "elapsed_s": 3.01, "total": -22.63704, "recon0": 0.697804, "recon1": 1.000032, "recon2": 0.0, "kl0": -7.300511, "kl1": -17.034366, "kl2": 0.0}
{"epoch": 131, "phase": 1, "elapsed_s": 2.91, "total": -22.63745, "recon0": 0.697984, "recon1": 1.000031, "recon2": 0.0, "kl0": -7.300438, "kl1": -17.035027, "kl2": 0.0}
{"epoch": 132, "phase": 1, "elapsed_s": 3.0, "total": -22.636911, "recon0": 0.698006, "recon1": 1.000025, "recon2": 0.0, "kl0": -7.29979, "kl1": -17.035152, "kl2": 0.0}
{"epoch": 133, "phase": 1, "elapsed_s": 2.76, "total": -22.637178, "recon0": 0.697528, "recon1": 1.000023, "recon2": 0.0, "kl0": -7.299749, "kl1": -17.034979, "kl2": 0.0}
{"epoch": 134, "phase": 1, "elapsed_s": 2.85, "total": -22.636969, "recon0": 0.697772, "recon1": 1.000029, "recon2": 0.0, "kl0": -7.299589, "kl1": -17.035181, "kl2": 0.0}
{"epoch": 135, "phase": 1, "elapsed_s": 2.7, "total": -22.637286, "recon0": 0.697901, "recon1": 1.000023, "recon2": 0.0, "kl0": -7.299821, "kl1": -17.035388, "kl2": 0.0}
{"epoch": 136, "phase": 1, "elapsed_s": 2.73, "total": -22.636587, "recon0": 0.697988, "recon1": 1.000011, "recon2": 0.0, "kl0": -7.299313, "kl1": -17.035272, "kl2": 0.0}
{"epoch": 137, "phase": 1, "elapsed_s": 2.91, "total": -22.636527, "recon0": 0.697762, "recon1": 1.000026, "recon2": 0.0, "kl0": -7.299657, "kl1": -17.034658, "kl2": 0.0}
{"epoch": 138, "phase": 1, "elapsed_s": 2.75, "total": -22.637654, "recon0": 0.697566, "recon1": 1.000035, "recon2": 0.0, "kl0": -7.300295, "kl1": -17.03496, "kl2": 0.0}
{"epoch": 139, "phase": 1, "elapsed_s": 2.82, "total": -22.637698, "recon0": 0.697525, "recon1": 1.000031, "recon2": 0.0, "kl0": -7.299967, "kl1": -17.035286, "kl2": 0.0}
{"epoch": 140, "phase": 1, "elapsed_s": 2.84, "total": -22.636905, "recon0": 0.697643, "recon1": 1.000026, "recon2": 0.0, "kl0": -7.299335, "kl1": -17.035239, "kl2": 0.0}
{"epoch": 141, "phase": 2, "elapsed_s": 3.14, "total": -36.543898, "recon0": 0.697326, "recon1": 1.000006, "recon2": 1.538737, "kl0": -7.274257, "kl1": -17.034862, "kl2": -15.470847}
{"epoch": 142, "phase": 2, "elapsed_s": 2.87, "total": -38.121203, "recon0": 0.697128, "recon1": 1.000005, "recon2": 1.114735, "kl0": -7.29925, "kl1": -17.034726, "kl2": -16.599094}
{"epoch": 143, "phase": 2, "elapsed_s": 2.98, "total": -38.330139, "recon0": 0.697428, "recon1": 1.00002, "recon2": 1.057766, "kl0": -7.299825, "kl1": -17.034867, "kl2": -16.75066}
{"epoch": 144, "phase": 2, "elapsed_s": 3.03, "total": -38.42925, "recon0": 0.696898, "recon1": 1.000011, "recon2": 1.034188, "kl0": -7.299702, "kl1": -17.034977, "kl2": -16.825668}
{"epoch": 145, "phase": 2, "elapsed_s": 2.91, "total": -38.489503, "recon0": 0.697294, "recon1": 1.000026, "recon2": 1.022072, "kl0": -7.300096, "kl1": -17.035174, "kl2": -16.873624}
{"epoch": 146, "phase": 2, "elapsed_s": 3.01, "total": -38.529628, "recon0": 0.697166, "recon1": 1.000016, "recon2": 1.015221, "kl0": -7.300534, "kl1": -17.035242, "kl2": -16.906254}
{"epoch": 147, "phase": 2, "elapsed_s": 2.98, "total": -38.56078, "recon0": 0.69716, "recon1": 1.000011, "recon2": 1.010571, "kl0": -7.299922, "kl1": -17.035287, "kl2": -16.933313}
{"epoch": 148, "phase": 2, "elapsed_s": 3.23, "total": -38.582983, "recon0": 0.697393, "recon1": 1.000031, "recon2": 1.008188, "kl0": -7.300956, "kl1": -17.035278, "kl2": -16.952361}
{"epoch": 149, "phase": 2, "elapsed_s": 2.94, "total": -38.599553, "recon0": 0.697179, "recon1": 1.000018, "recon2": 1.006368, "kl0": -7.300628, "kl1": -17.035154, "kl2": -16.967335}
{"epoch": 150, "phase": 2, "elapsed_s": 3.13, "total": -38.611389, "recon0": 0.697263, "recon1": 1.000012, "recon2": 1.00541, "kl0": -7.299743, "kl1": -17.035303, "kl2": -16.979029}
{"epoch": 151, "phase": 2, "elapsed_s": 3.1, "total": -38.622582, "recon0": 0.697172, "recon1": 1.000025, "recon2": 1.00427, "kl0": -7.300101, "kl1": -17.035318, "kl2": -16.988629}
{"epoch": 152, "phase": 2, "elapsed_s": 2.86, "total": -38.630832, "recon0": 0.696889, "recon1": 1.000012, "recon2": 1.003719, "kl0": -7.300594, "kl1": -17.035286, "kl2": -16.995573}
{"epoch": 153, "phase": 2, "elapsed_s": 3.16, "total": -38.637556, "recon0": 0.697256, "recon1": 1.000025, "recon2": 1.002683, "kl0": -7.300415, "kl1": -17.03535, "kl2": -17.001756}
{"epoch": 154, "phase": 2, "elapsed_s": 3.28, "total": -38.644226, "recon0": 0.697238, "recon1": 1.000012, "recon2": 1.002383, "kl0": -7.300698, "kl1": -17.035387, "kl2": -17.007774}
{"epoch": 155, "phase": 2, "elapsed_s": 2.96, "total": -38.64895, "recon0": 0.697123, "recon1": 1.000015, "recon2": 1.002042, "kl0": -7.299572, "kl1": -17.035322, "kl2": -17.013235}
{"epoch": 156, "phase": 2, "elapsed_s": 3.05, "total": -38.653724, "recon0": 0.697254, "recon1": 1.00002, "recon2": 1.001875, "kl0": -7.300361, "kl1": -17.035297, "kl2": -17.017215}
{"epoch": 157, "phase": 2, "elapsed_s": 3.23, "total": -38.657449, "recon0": 0.697429, "recon1": 1.000023, "recon2": 1.001569, "kl0": -7.300517, "kl1": -17.035301, "kl2": -17.020652}
{"epoch": 158, "phase": 2, "elapsed_s": 3.08, "total": -38.660405, "recon0": 0.697205, "recon1": 1.000013, "recon2": 1.001302, "kl0": -7.300434, "kl1": -17.035034, "kl2": -17.023455}
{"epoch": 159, "phase": 2, "elapsed_s": 2.95, "total": -38.663237, "recon0": 0.697336, "recon1": 1.000001, "recon2": 1.001159, "kl0": -7.300628, "kl1": -17.035176, "kl2": -17.025929}
{"epoch": 160, "phase": 2, "elapsed_s": 3.18, "total": -38.666256, "recon0": 0.696984, "recon1": 1.000031, "recon2": 1.001064, "kl0": -7.300848, "kl1": -17.03539, "kl2": -17.028097}
{"epoch": 161, "phase": 2, "elapsed_s": 2.9, "total": -38.667149, "recon0": 0.697078, "recon1": 1.000021, "recon2": 1.001022, "kl0": -7.30032, "kl1": -17.035269, "kl2": -17.02968}
{"epoch": 162, "phase": 2, "elapsed_s": 2.99, "total": -38.668836, "recon0": 0.697178, "recon1": 1.000018, "recon2": 1.000794, "kl0": -7.300551, "kl1": -17.03533, "kl2": -17.030945}
{"epoch": 163, "phase": 2, "elapsed_s": 3.13, "total": -38.669644, "recon0": 0.696944, "recon1": 1.000013, "recon2": 1.000702, "kl0": -7.300252, "kl1": -17.035204, "kl2": -17.031848}
{"epoch": 164, "phase": 2, "elapsed_s": 2.95, "total": -38.670117, "recon0": 0.697391, "recon1": 1.000008, "recon2": 1.000728, "kl0": -7.300598, "kl1": -17.035308, "kl2": -17.032339}
{"epoch": 165, "phase": 2, "elapsed_s": 2.87, "total": -38.671019, "recon0": 0.697021, "recon1": 1.000028, "recon2": 1.00051, "kl0": -7.299978, "kl1": -17.035307, "kl2": -17.033293}
{"epoch": 166, "phase": 2, "elapsed_s": 2.96, "total": -38.671544, "recon0": 0.697056, "recon1": 1.000018, "recon2": 1.000546, "kl0": -7.3004, "kl1": -17.035193, "kl2": -17.03357}
{"epoch": 167, "phase": 2, "elapsed_s": 3.08, "total": -38.671944, "recon0": 0.697125, "recon1": 1.000012, "recon2": 1.000494, "kl0": -7.300205, "kl1": -17.03526, "kl2": -17.034111}
{"epoch": 168, "phase": 2, "elapsed_s": 2.92, "total": -38.672183, "recon0": 0.697072, "recon1": 1.000009, "recon2": 1.000487, "kl0": -7.300289, "kl1": -17.035323, "kl2": -17.03414}
{"epoch": 169, "phase": 2, "elapsed_s": 2.78, "total": -38.672464, "recon0": 0.697112, "recon1": 1.000018, "recon2": 1.000409, "kl0": -7.300436, "kl1": -17.035296, "kl2": -17.034272}
{"epoch": 170, "phase": 2, "elapsed_s": 3.27, "total": -38.673116, "recon0": 0.696789, "recon1": 1.000007, "recon2": 1.000262, "kl0": -7.300169, "kl1": -17.035254, "kl2": -17.034751}
{"epoch": 171, "phase": 2, "elapsed_s": 3.23, "total": -38.672011, "recon0": 0.697376, "recon1": 1.000023, "recon2": 1.000341, "kl0": -7.299837, "kl1": -17.035228, "kl2": -17.034687}
{"epoch": 172, "phase": 2, "elapsed_s": 3.19, "total": -38.673127, "recon0": 0.69725, "recon1": 1.000022, "recon2": 1.000308, "kl0": -7.300622, "kl1": -17.035194, "kl2": -17.034891}
{"epoch": 173, "phase": 2, "elapsed_s": 3.44, "total": -38.673147, "recon0": 0.697329, "recon1": 1.000019, "recon2": 1.000196, "kl0": -7.300523, "kl1": -17.035184, "kl2": -17.034984}
{"epoch": 174, "phase": 2, "elapsed_s": 3.63, "total": -38.673431, "recon0": 0.697008, "recon1": 1.000017, "recon2": 1.000219, "kl0": -7.300426, "kl1": -17.035198, "kl2": -17.035051}
{"epoch": 175, "phase": 2, "elapsed_s": 3.48, "total": -38.672982, "recon0": 0.697466, "recon1": 1.000003, "recon2": 1.000132, "kl0": -7.300218, "kl1": -17.035357, "kl2": -17.035009}
{"epoch": 176, "phase": 2, "elapsed_s": 3.66, "total": -38.672757, "recon0": 0.697163, "recon1": 1.000016, "recon2": 1.000188, "kl0": -7.299676, "kl1": -17.035396, "kl2": -17.035052}
{"epoch": 177, "phase": 2, "elapsed_s": 3.23, "total": -38.673414, "recon0": 0.696996, "recon1": 1.000013, "recon2": 1.000146, "kl0": -7.300013, "kl1": -17.035373, "kl2": -17.035184}
{"epoch": 178, "phase": 2, "elapsed_s": 3.23, "total": -38.673857, "recon0": 0.697094, "recon1": 1.000008, "recon2": 1.000187, "kl0": -7.300785, "kl1": -17.035217, "kl2": -17.035144}
{"epoch": 179, "phase": 2, "elapsed_s": 3.64, "total": -38.673444, "recon0": 0.697177, "recon1": 1.000019, "recon2": 1.000113, "kl0": -7.300384, "kl1": -17.035256, "kl2": -17.035113}
{"epoch": 180, "phase": 2, "elapsed_s": 3.15, "total": -38.67367, "recon0": 0.697297, "recon1": 1.000018, "recon2": 1.000141, "kl0": -7.30057, "kl1": -17.035379, "kl2": -17.035177}
{"epoch": 181, "phase": 2, "elapsed_s": 3.15, "total": -38.674174, "recon0": 0.697022, "recon1": 1.000015, "recon2": 1.000146, "kl0": -7.300739, "kl1": -17.035345, "kl2": -17.035273}
{"epoch": 182, "phase": 2, "elapsed_s": 3.41, "total": -38.673985, "recon0": 0.696937, "recon1": 1.000015, "recon2": 1.000107, "kl0": -7.300524, "kl1": -17.03535, "kl2": -17.035169}
{"epoch": 183, "phase": 2, "elapsed_s": 3.18, "total": -38.673675, "recon0": 0.697047, "recon1": 1.000015, "recon2": 1.000129, "kl0": -7.300511, "kl1": -17.035123, "kl2": -17.035232}
{"epoch": 184, "phase": 2, "elapsed_s": 3.23, "total": -38.673547, "recon0": 0.69722, "recon1": 1.000013, "recon2": 1.000102, "kl0": -7.300361, "kl1": -17.035239, "kl2": -17.035282}
{"epoch": 185, "phase": 2, "elapsed_s": 3.3, "total": -38.673591, "recon0": 0.697015, "recon1": 1.000016, "recon2": 1.000089, "kl0": -7.300181, "kl1": -17.035323, "kl2": -17.035208}
{"epoch": 186, "phase": 2, "elapsed_s": 3.25, "total": -38.673869, "recon0": 0.697255, "recon1": 1.000012, "recon2": 1.000111, "kl0": -7.3006, "kl1": -17.035351, "kl2": -17.035296}
{"epoch": 187, "phase": 2, "elapsed_s": 3.25, "total": -38.673348, "recon0": 0.697022, "recon1": 1.000008, "recon2": 1.000031, "kl0": -7.299881, "kl1": -17.035226, "kl2": -17.035302}
{"epoch": 188, "phase": 2, "elapsed_s": 3.66, "total": -38.674456, "recon0": 0.696935, "recon1": 1.000013, "recon2": 1.000062, "kl0": -7.300929, "kl1": -17.035238, "kl2": -17.035298}
{"epoch": 189, "phase": 2, "elapsed_s": 3.33, "total": -38.673811, "recon0": 0.697123, "recon1": 1.0, "recon2": 1.000058, "kl0": -7.300363, "kl1": -17.035331, "kl2": -17.035298}
{"epoch": 190, "phase": 2, "elapsed_s": 3.27, "total": -38.674591, "recon0": 0.696925, "recon1": 0.999996, "recon2": 1.000039, "kl0": -7.30086, "kl1": -17.035326, "kl2": -17.035366}
{"epoch": 191, "phase": 2, "elapsed_s": 3.46, "total": -38.673795, "recon0": 0.697175, "recon1": 1.000005, "recon2": 1.000073, "kl0": -7.300497, "kl1": -17.035261, "kl2": -17.03529}
{"epoch": 192, "phase": 2, "elapsed_s": 3.2, "total": -38.674241, "recon0": 0.696667, "recon1": 1.000013, "recon2": 1.000085, "kl0": -7.300423, "kl1": -17.035303, "kl2": -17.035281}
{"epoch": 193, "phase": 2, "elapsed_s": 3.19, "total": -38.674158, "recon0": 0.696903, "recon1": 1.000024, "recon2": 1.000051, "kl0": -7.300454, "kl1": -17.035317, "kl2": -17.035365}
{"epoch": 194, "phase": 2, "elapsed_s": 3.57, "total": -38.673654, "recon0": 0.697021, "recon1": 1.000006, "recon2": 1.000038, "kl0": -7.300003, "kl1": -17.035367, "kl2": -17.035347}
{"epoch": 195, "phase": 2, "elapsed_s": 3.4, "total": -38.674211, "recon0": 0.697071, "recon1": 1.000008, "recon2": 1.000035, "kl0": -7.300688, "kl1": -17.035285, "kl2": -17.035351}
{"epoch": 196, "phase": 2, "elapsed_s": 3.33, "total": -38.673949, "recon0": 0.696984, "recon1": 1.000014, "recon2": 1.000032, "kl0": -7.300345, "kl1": -17.035297, "kl2": -17.035336}
{"epoch": 197, "phase": 2, "elapsed_s": 3.52, "total": -38.673956, "recon0": 0.697176, "recon1": 1.000013, "recon2": 1.000062, "kl0": -7.300623, "kl1": -17.035281, "kl2": -17.035302}
{"epoch": 198, "phase": 2, "elapsed_s": 3.32, "total": -38.674521, "recon0": 0.696722, "recon1": 1.000015, "recon2": 1.00004, "kl0": -7.300587, "kl1": -17.035355, "kl2": -17.035356}
{"epoch": 199, "phase": 2, "elapsed_s": 3.26, "total": -38.674214, "recon0": 0.697188, "recon1": 1.000018, "recon2": 1.000027, "kl0": -7.300772, "kl1": -17.035315, "kl2": -17.03536}
{"epoch": 200, "phase": 2, "elapsed_s": 3.49, "total": -38.674405, "recon0": 0.696944, "recon1": 0.999997, "recon2": 1.000021, "kl0": -7.300623, "kl1": -17.035378, "kl2": -17.035367}
{"epoch": 201, "phase": 2, "elapsed_s": 3.37, "total": -38.67411, "recon0": 0.697145, "recon1": 1.000022, "recon2": 1.000038, "kl0": -7.300582, "kl1": -17.035393, "kl2": -17.03534}
{"epoch": 202, "phase": 2, "elapsed_s": 3.58, "total": -38.673823, "recon0": 0.697276, "recon1": 1.000005, "recon2": 1.000013, "kl0": -7.300408, "kl1": -17.035353, "kl2": -17.035356}
{"epoch": 203, "phase": 2, "elapsed_s": 3.43, "total": -38.673178, "recon0": 0.69705, "recon1": 1.000018, "recon2": 1.000027, "kl0": -7.299643, "kl1": -17.035277, "kl2": -17.035354}
{"epoch": 204, "phase": 2, "elapsed_s": 3.24, "total": -38.673959, "recon0": 0.697046, "recon1": 1.000011, "recon2": 1.000026, "kl0": -7.300329, "kl1": -17.035341, "kl2": -17.035372}
{"epoch": 205, "phase": 2, "elapsed_s": 3.22, "total": -38.674472, "recon0": 0.697073, "recon1": 1.000005, "recon2": 1.000021, "kl0": -7.300873, "kl1": -17.035304, "kl2": -17.035395}
{"epoch": 206, "phase": 2, "elapsed_s": 3.46, "total": -38.673754, "recon0": 0.696857, "recon1": 1.000017, "recon2": 1.000033, "kl0": -7.300015, "kl1": -17.035309, "kl2": -17.035337}
{"epoch": 207, "phase": 2, "elapsed_s": 3.19, "total": -38.673467, "recon0": 0.696934, "recon1": 1.000021, "recon2": 1.000024, "kl0": -7.299862, "kl1": -17.035235, "kl2": -17.035349}
{"epoch": 208, "phase": 2, "elapsed_s": 3.49, "total": -38.673798, "recon0": 0.696967, "recon1": 1.00001, "recon2": 1.000033, "kl0": -7.300083, "kl1": -17.035337, "kl2": -17.035387}
{"epoch": 209, "phase": 2, "elapsed_s": 3.61, "total": -38.673413, "recon0": 0.697096, "recon1": 1.000012, "recon2": 1.000014, "kl0": -7.299872, "kl1": -17.0353, "kl2": -17.035362}
{"epoch": 210, "phase": 2, "elapsed_s": 3.29, "total": -38.674057, "recon0": 0.697171, "recon1": 1.000004, "recon2": 1.000022, "kl0": -7.30069, "kl1": -17.035196, "kl2": -17.035368}
{"epoch": 221, "phase": 3, "elapsed_s": 3.57, "total": -38.674347, "recon0": 0.696889, "recon1": 1.000008, "recon2": 1.00003, "kl0": -7.300621, "kl1": -17.035275, "kl2": -17.035378}
{"epoch": 222, "phase": 3, "elapsed_s": 3.37, "total": -38.673588, "recon0": 0.696957, "recon1": 1.000026, "recon2": 1.000036, "kl0": -7.300135, "kl1": -17.035109, "kl2": -17.035363}
{"epoch": 223, "phase": 3, "elapsed_s": 3.38, "total": -38.673232, "recon0": 0.697183, "recon1": 1.000014, "recon2": 1.00002, "kl0": -7.299703, "kl1": -17.035367, "kl2": -17.035378}
{"epoch": 224, "phase": 3, "elapsed_s": 3.99, "total": -38.674388, "recon0": 0.696861, "recon1": 1.000012, "recon2": 1.000024, "kl0": -7.300596, "kl1": -17.035344, "kl2": -17.035346}
{"epoch": 225, "phase": 3, "elapsed_s": 3.34, "total": -38.673928, "recon0": 0.696897, "recon1": 1.000013, "recon2": 1.000012, "kl0": -7.300149, "kl1": -17.035321, "kl2": -17.035381}
{"epoch": 226, "phase": 3, "elapsed_s": 3.18, "total": -38.67419, "recon0": 0.697145, "recon1": 1.000011, "recon2": 0.999995, "kl0": -7.300583, "kl1": -17.035369, "kl2": -17.035388}
{"epoch": 227, "phase": 3, "elapsed_s": 3.49, "total": -38.674614, "recon0": 0.696862, "recon1": 1.000002, "recon2": 1.000013, "kl0": -7.300753, "kl1": -17.035337, "kl2": -17.035401}
{"epoch": 228, "phase": 3, "elapsed_s": 3.29, "total": -38.674064, "recon0": 0.696961, "recon1": 1.000015, "recon2": 1.000014, "kl0": -7.300496, "kl1": -17.035192, "kl2": -17.035366}
{"epoch": 229, "phase": 3, "elapsed_s": 3.27, "total": -38.674023, "recon0": 0.697038, "recon1": 1.000017, "recon2": 1.000019, "kl0": -7.30042, "kl1": -17.035294, "kl2": -17.035383}
{"epoch": 230, "phase": 3, "elapsed_s": 3.6, "total": -38.674371, "recon0": 0.696943, "recon1": 1.00002, "recon2": 1.000013, "kl0": -7.300633, "kl1": -17.035338, "kl2": -17.035376}
{"epoch": 231, "phase": 3, "elapsed_s": 3.39, "total": -38.673599, "recon0": 0.696955, "recon1": 1.000025, "recon2": 1.000018, "kl0": -7.300192, "kl1": -17.035008, "kl2": -17.035396}
{"epoch": 232, "phase": 3, "elapsed_s": 3.3, "total": -38.674617, "recon0": 0.697004, "recon1": 1.000013, "recon2": 1.000018, "kl0": -7.300836, "kl1": -17.035435, "kl2": -17.035381}
{"epoch": 233, "phase": 3, "elapsed_s": 3.76, "total": -38.674141, "recon0": 0.697063, "recon1": 1.000016, "recon2": 1.000027, "kl0": -7.300584, "kl1": -17.035294, "kl2": -17.035367}
{"epoch": 234, "phase": 3, "elapsed_s": 3.56, "total": -38.674748, "recon0": 0.69684, "recon1": 1.000005, "recon2": 1.000011, "kl0": -7.300946, "kl1": -17.035286, "kl2": -17.035372}
{"epoch": 235, "phase": 3, "elapsed_s": 3.27, "total": -38.673934, "recon0": 0.697008, "recon1": 1.000014, "recon2": 1.000014, "kl0": -7.300278, "kl1": -17.035307, "kl2": -17.035386}
{"epoch": 236, "phase": 3, "elapsed_s": 3.58, "total": -38.674221, "recon0": 0.696883, "recon1": 1.000052, "recon2": 1.000013, "kl0": -7.300362, "kl1": -17.035421, "kl2": -17.035386}
{"epoch": 237, "phase": 3, "elapsed_s": 3.21, "total": -38.674566, "recon0": 0.696997, "recon1": 1.000013, "recon2": 1.000013, "kl0": -7.300865, "kl1": -17.035333, "kl2": -17.03539}
{"epoch": 238, "phase": 3, "elapsed_s": 3.15, "total": -38.674322, "recon0": 0.696882, "recon1": 1.00002, "recon2": 1.000012, "kl0": -7.300493, "kl1": -17.035364, "kl2": -17.035379}
{"epoch": 239, "phase": 3, "elapsed_s": 3.67, "total": -38.673952, "recon0": 0.697018, "recon1": 1.000018, "recon2": 1.000018, "kl0": -7.300445, "kl1": -17.035178, "kl2": -17.035383}
{"epoch": 240, "phase": 3, "elapsed_s": 3.26, "total": -38.673952, "recon0": 0.697255, "recon1": 1.000012, "recon2": 1.000016, "kl0": -7.300654, "kl1": -17.03519, "kl2": -17.035392}
{"epoch": 241, "phase": 3, "elapsed_s": 3.16, "total": -38.674146, "recon0": 0.696803, "recon1": 1.000027, "recon2": 1.000009, "kl0": -7.300309, "kl1": -17.035284, "kl2": -17.035392}
{"epoch": 242, "phase": 3, "elapsed_s": 3.62, "total": -38.674018, "recon0": 0.697059, "recon1": 1.000013, "recon2": 1.000008, "kl0": -7.300407, "kl1": -17.035297, "kl2": -17.035394}
{"epoch": 243, "phase": 3, "elapsed_s": 3.33, "total": -38.673983, "recon0": 0.696909, "recon1": 1.000013, "recon2": 1.000014, "kl0": -7.300219, "kl1": -17.035303, "kl2": -17.035397}
{"epoch": 244, "phase": 3, "elapsed_s": 3.41, "total": -38.674155, "recon0": 0.697312, "recon1": 1.000014, "recon2": 1.000023, "kl0": -7.30077, "kl1": -17.035355, "kl2": -17.035379}
{"epoch": 245, "phase": 3, "elapsed_s": 3.77, "total": -38.674479, "recon0": 0.696863, "recon1": 1.000016, "recon2": 1.000013, "kl0": -7.30063, "kl1": -17.035349, "kl2": -17.035391}
{"epoch": 246, "phase": 3, "elapsed_s": 3.3, "total": -38.674718, "recon0": 0.69698, "recon1": 1.000009, "recon2": 1.000013, "kl0": -7.300975, "kl1": -17.035353, "kl2": -17.035392}
{"epoch": 247, "phase": 3, "elapsed_s": 3.28, "total": -38.673872, "recon0": 0.697185, "recon1": 1.000004, "recon2": 1.000011, "kl0": -7.300353, "kl1": -17.035323, "kl2": -17.035396}
{"epoch": 248, "phase": 3, "elapsed_s": 3.62, "total": -38.673598, "recon0": 0.697002, "recon1": 1.000013, "recon2": 1.000009, "kl0": -7.299961, "kl1": -17.035275, "kl2": -17.035386}
{"epoch": 249, "phase": 3, "elapsed_s": 3.17, "total": -38.674207, "recon0": 0.697069, "recon1": 1.000006, "recon2": 1.000009, "kl0": -7.300616, "kl1": -17.035293, "kl2": -17.035382}
{"epoch": 250, "phase": 3, "elapsed_s": 3.2, "total": -38.673972, "recon0": 0.697071, "recon1": 1.000012, "recon2": 1.00002, "kl0": -7.300334, "kl1": -17.035353, "kl2": -17.035388}
{"epoch": 251, "phase": 3, "elapsed_s": 3.62, "total": -38.673765, "recon0": 0.696969, "recon1": 1.000014, "recon2": 1.000019, "kl0": -7.300072, "kl1": -17.035293, "kl2": -17.035402}
{"epoch": 252, "phase": 3, "elapsed_s": 3.19, "total": -38.674367, "recon0": 0.696939, "recon1": 1.000013, "recon2": 1.000021, "kl0": -7.30061, "kl1": -17.03536, "kl2": -17.035371}
{"epoch": 253, "phase": 3, "elapsed_s": 3.38, "total": -38.67343, "recon0": 0.696841, "recon1": 1.000009, "recon2": 1.000011, "kl0": -7.29953, "kl1": -17.035372, "kl2": -17.03539}
{"epoch": 254, "phase": 3, "elapsed_s": 3.67, "total": -38.674479, "recon0": 0.697112, "recon1": 1.000012, "recon2": 1.000007, "kl0": -7.300824, "kl1": -17.035387, "kl2": -17.035399}
{"epoch": 281, "phase": 4, "elapsed_s": 4.41, "total": -32.784086, "recon0": 0.696698, "recon1": 1.000011, "recon2": 1.000011, "kl0": -6.261057, "kl1": -14.609855, "kl2": -14.609893}
{"epoch": 282, "phase": 4, "elapsed_s": 4.6, "total": -32.784133, "recon0": 0.696612, "recon1": 0.999996, "recon2": 1.000003, "kl0": -6.260942, "kl1": -14.609906, "kl2": -14.609897}
{"epoch": 283, "phase": 4, "elapsed_s": 4.32, "total": -32.784067, "recon0": 0.696555, "recon1": 1.000008, "recon2": 1.000004, "kl0": -6.260874, "kl1": -14.609879, "kl2": -14.609881}
{"epoch": 284, "phase": 4, "elapsed_s": 4.64, "total": -32.784189, "recon0": 0.696596, "recon1": 1.000005, "recon2": 1.000009, "kl0": -6.261046, "kl1": -14.60988, "kl2": -14.609873}
{"epoch": 285, "phase": 4, "elapsed_s": 4.29, "total": -32.78447, "recon0": 0.696698, "recon1": 1.000001, "recon2": 1.00001, "kl0": -6.261363, "kl1": -14.609925, "kl2": -14.60989}
{"epoch": 286, "phase": 4, "elapsed_s": 4.19, "total": -32.78394, "recon0": 0.696777, "recon1": 1.0, "recon2": 1.000008, "kl0": -6.260959, "kl1": -14.609867, "kl2": -14.609899}
{"epoch": 287, "phase": 4, "elapsed_s": 4.43, "total": -32.784069, "recon0": 0.696767, "recon1": 1.000003, "recon2": 1.000002, "kl0": -6.261059, "kl1": -14.609882, "kl2": -14.609899}
{"epoch": 288, "phase": 4, "elapsed_s": 4.5, "total": -32.784277, "recon0": 0.696757, "recon1": 1.000002, "recon2": 1.0, "kl0": -6.261247, "kl1": -14.6099, "kl2": -14.609889}
{"epoch": 289, "phase": 4, "elapsed_s": 4.46, "total": -32.784803, "recon0": 0.696826, "recon1": 1.000001, "recon2": 1.000002, "kl0": -6.261861, "kl1": -14.609879, "kl2": -14.609892}
{"epoch": 290, "phase": 4, "elapsed_s": 4.06, "total": -32.784142, "recon0": 0.696768, "recon1": 1.000006, "recon2": 1.000012, "kl0": -6.26114, "kl1": -14.609896, "kl2": -14.609891}
{"epoch": 291, "phase": 4, "elapsed_s": 4.18, "total": -32.784593, "recon0": 0.696533, "recon1": 0.999998, "recon2": 1.000003, "kl0": -6.261317, "kl1": -14.609915, "kl2": -14.609894}
{"epoch": 292, "phase": 4, "elapsed_s": 4.13, "total": -32.78401, "recon0": 0.69683, "recon1": 1.000003, "recon2": 1.000006, "kl0": -6.261047, "kl1": -14.609903, "kl2": -14.609899}
{"epoch": 293, "phase": 4, "elapsed_s": 5.08, "total": -32.784827, "recon0": 0.696699, "recon1": 1.000007, "recon2": 1.000002, "kl0": -6.261779, "kl1": -14.609864, "kl2": -14.609892}
{"epoch": 294, "phase": 4, "elapsed_s": 4.55, "total": -32.784634, "recon0": 0.696498, "recon1": 0.999999, "recon2": 1.000005, "kl0": -6.261382, "kl1": -14.609863, "kl2": -14.609891}
{"epoch": 295, "phase": 4, "elapsed_s": 4.04, "total": -32.784336, "recon0": 0.696603, "recon1": 1.000008, "recon2": 1.0, "kl0": -6.261158, "kl1": -14.6099, "kl2": -14.609889}
{"epoch": 296, "phase": 4, "elapsed_s": 4.42, "total": -32.783908, "recon0": 0.696655, "recon1": 1.000002, "recon2": 1.000002, "kl0": -6.260758, "kl1": -14.609919, "kl2": -14.60989}
{"epoch": 297, "phase": 4, "elapsed_s": 4.12, "total": -32.784142, "recon0": 0.696555, "recon1": 1.000002, "recon2": 0.999999, "kl0": -6.260925, "kl1": -14.609884, "kl2": -14.609889}
{"epoch": 298, "phase": 4, "elapsed_s": 4.64, "total": -32.783796, "recon0": 0.696658, "recon1": 1.000011, "recon2": 1.000011, "kl0": -6.26067, "kl1": -14.609912, "kl2": -14.609894}
{"epoch": 299, "phase": 4, "elapsed_s": 4.16, "total": -32.784169, "recon0": 0.696622, "recon1": 1.000004, "recon2": 1.000009, "kl0": -6.261036, "kl1": -14.609885, "kl2": -14.609883}
{"epoch": 300, "phase": 4, "elapsed_s": 4.27, "total": -32.784773, "recon0": 0.696595, "recon1": 1.000005, "recon2": 1.000009, "kl0": -6.261594, "kl1": -14.609898, "kl2": -14.60989}
{"epoch": 301, "phase": 4, "elapsed_s": 4.17, "total": -32.784503, "recon0": 0.696744, "recon1": 1.000001, "recon2": 1.000015, "kl0": -6.261502, "kl1": -14.609851, "kl2": -14.60991}
{"epoch": 302, "phase": 4, "elapsed_s": 4.11, "total": -32.784517, "recon0": 0.696613, "recon1": 1.000008, "recon2": 1.000005, "kl0": -6.261355, "kl1": -14.609908, "kl2": -14.609881}
{"epoch": 303, "phase": 4, "elapsed_s": 4.6, "total": -32.783549, "recon0": 0.696622, "recon1": 1.000009, "recon2": 1.000004, "kl0": -6.260391, "kl1": -14.609898, "kl2": -14.609895}
{"epoch": 304, "phase": 4, "elapsed_s": 4.31, "total": -32.784125, "recon0": 0.696576, "recon1": 1.000001, "recon2": 1.000005, "kl0": -6.260926, "kl1": -14.609889, "kl2": -14.609892}
{"epoch": 305, "phase": 4, "elapsed_s": 4.65, "total": -32.784356, "recon0": 0.696656, "recon1": 1.000006, "recon2": 1.000005, "kl0": -6.261234, "kl1": -14.609904, "kl2": -14.609885}
{"epoch": 306, "phase": 4, "elapsed_s": 4.39, "total": -32.784211, "recon0": 0.696681, "recon1": 1.000004, "recon2": 1.000014, "kl0": -6.261133, "kl1": -14.609884, "kl2": -14.609892}
{"epoch": 307, "phase": 4, "elapsed_s": 4.52, "total": -32.783983, "recon0": 0.69654, "recon1": 1.000009, "recon2": 1.000007, "kl0": -6.26077, "kl1": -14.609886, "kl2": -14.609883}
{"epoch": 308, "phase": 4, "elapsed_s": 4.41, "total": -32.784768, "recon0": 0.69664, "recon1": 1.000008, "recon2": 1.000003, "kl0": -6.261649, "kl1": -14.609871, "kl2": -14.609898}
{"epoch": 309, "phase": 4, "elapsed_s": 4.61, "total": -32.784202, "recon0": 0.696524, "recon1": 1.000002, "recon2": 1.000007, "kl0": -6.260946, "kl1": -14.609902, "kl2": -14.609887}
{"epoch": 310, "phase": 4, "elapsed_s": 4.44, "total": -32.784055, "recon0": 0.696745, "recon1": 1.00001, "recon2": 1.000002, "kl0": -6.261045, "kl1": -14.609878, "kl2": -14.609888}
{"epoch": 311, "phase": 4, "elapsed_s": 4.18, "total": -32.784221, "recon0": 0.696624, "recon1": 1.000004, "recon2": 1.000009, "kl0": -6.261076, "kl1": -14.609894, "kl2": -14.609887}
{"epoch": 312, "phase": 4, "elapsed_s": 4.54, "total": -32.784136, "recon0": 0.696652, "recon1": 1.000003, "recon2": 1.000001, "kl0": -6.261022, "kl1": -14.609891, "kl2": -14.609878}
{"epoch": 313, "phase": 4, "elapsed_s": 4.01, "total": -32.784591, "recon0": 0.696464, "recon1": 1.000008, "recon2": 1.000001, "kl0": -6.261299, "kl1": -14.609883, "kl2": -14.609882}
{"epoch": 314, "phase": 4, "elapsed_s": 4.26, "total": -32.78352, "recon0": 0.696619, "recon1": 1.000007, "recon2": 1.000006, "kl0": -6.260384, "kl1": -14.609879, "kl2": -14.609888}

View File

@@ -0,0 +1,310 @@
{"epoch": 1, "phase": 0, "elapsed_s": 21.37, "total": 86.883101, "recon0": 1.209082, "recon1": 0.0, "recon2": 0.0, "kl0": 85.674019, "kl1": 0.0, "kl2": 0.0}
{"epoch": 2, "phase": 0, "elapsed_s": 22.65, "total": 86.105073, "recon0": 1.023753, "recon1": 0.0, "recon2": 0.0, "kl0": 85.08132, "kl1": 0.0, "kl2": 0.0}
{"epoch": 3, "phase": 0, "elapsed_s": 21.13, "total": 86.082626, "recon0": 1.016161, "recon1": 0.0, "recon2": 0.0, "kl0": 85.066465, "kl1": 0.0, "kl2": 0.0}
{"epoch": 4, "phase": 0, "elapsed_s": 20.71, "total": 86.07332, "recon0": 1.012764, "recon1": 0.0, "recon2": 0.0, "kl0": 85.060556, "kl1": 0.0, "kl2": 0.0}
{"epoch": 5, "phase": 0, "elapsed_s": 20.67, "total": 86.067549, "recon0": 1.010427, "recon1": 0.0, "recon2": 0.0, "kl0": 85.057122, "kl1": 0.0, "kl2": 0.0}
{"epoch": 6, "phase": 0, "elapsed_s": 21.83, "total": 86.063378, "recon0": 1.008629, "recon1": 0.0, "recon2": 0.0, "kl0": 85.054749, "kl1": 0.0, "kl2": 0.0}
{"epoch": 7, "phase": 0, "elapsed_s": 20.94, "total": 86.061734, "recon0": 1.008111, "recon1": 0.0, "recon2": 0.0, "kl0": 85.053624, "kl1": 0.0, "kl2": 0.0}
{"epoch": 8, "phase": 0, "elapsed_s": 21.69, "total": 86.060172, "recon0": 1.007566, "recon1": 0.0, "recon2": 0.0, "kl0": 85.052606, "kl1": 0.0, "kl2": 0.0}
{"epoch": 9, "phase": 0, "elapsed_s": 24.39, "total": 86.059015, "recon0": 1.00676, "recon1": 0.0, "recon2": 0.0, "kl0": 85.052255, "kl1": 0.0, "kl2": 0.0}
{"epoch": 10, "phase": 0, "elapsed_s": 21.72, "total": 86.058169, "recon0": 1.006557, "recon1": 0.0, "recon2": 0.0, "kl0": 85.051611, "kl1": 0.0, "kl2": 0.0}
{"epoch": 11, "phase": 0, "elapsed_s": 21.5, "total": 86.057308, "recon0": 1.006546, "recon1": 0.0, "recon2": 0.0, "kl0": 85.050762, "kl1": 0.0, "kl2": 0.0}
{"epoch": 12, "phase": 0, "elapsed_s": 21.15, "total": 86.057449, "recon0": 1.006532, "recon1": 0.0, "recon2": 0.0, "kl0": 85.050917, "kl1": 0.0, "kl2": 0.0}
{"epoch": 13, "phase": 0, "elapsed_s": 20.98, "total": 86.056621, "recon0": 1.006051, "recon1": 0.0, "recon2": 0.0, "kl0": 85.05057, "kl1": 0.0, "kl2": 0.0}
{"epoch": 14, "phase": 0, "elapsed_s": 21.19, "total": 86.056466, "recon0": 1.005925, "recon1": 0.0, "recon2": 0.0, "kl0": 85.050541, "kl1": 0.0, "kl2": 0.0}
{"epoch": 15, "phase": 0, "elapsed_s": 21.56, "total": 86.056324, "recon0": 1.005965, "recon1": 0.0, "recon2": 0.0, "kl0": 85.050359, "kl1": 0.0, "kl2": 0.0}
{"epoch": 16, "phase": 0, "elapsed_s": 21.07, "total": 86.05557, "recon0": 1.005388, "recon1": 0.0, "recon2": 0.0, "kl0": 85.050182, "kl1": 0.0, "kl2": 0.0}
{"epoch": 17, "phase": 0, "elapsed_s": 20.74, "total": 86.055729, "recon0": 1.005701, "recon1": 0.0, "recon2": 0.0, "kl0": 85.050027, "kl1": 0.0, "kl2": 0.0}
{"epoch": 18, "phase": 0, "elapsed_s": 20.58, "total": 86.055101, "recon0": 1.005328, "recon1": 0.0, "recon2": 0.0, "kl0": 85.049773, "kl1": 0.0, "kl2": 0.0}
{"epoch": 19, "phase": 0, "elapsed_s": 21.51, "total": 86.054515, "recon0": 1.004854, "recon1": 0.0, "recon2": 0.0, "kl0": 85.049661, "kl1": 0.0, "kl2": 0.0}
{"epoch": 20, "phase": 0, "elapsed_s": 21.5, "total": 86.054634, "recon0": 1.005132, "recon1": 0.0, "recon2": 0.0, "kl0": 85.049502, "kl1": 0.0, "kl2": 0.0}
{"epoch": 21, "phase": 0, "elapsed_s": 20.8, "total": 86.054984, "recon0": 1.00535, "recon1": 0.0, "recon2": 0.0, "kl0": 85.049634, "kl1": 0.0, "kl2": 0.0}
{"epoch": 22, "phase": 0, "elapsed_s": 21.44, "total": 86.054563, "recon0": 1.004981, "recon1": 0.0, "recon2": 0.0, "kl0": 85.049582, "kl1": 0.0, "kl2": 0.0}
{"epoch": 23, "phase": 0, "elapsed_s": 20.82, "total": 86.054125, "recon0": 1.004879, "recon1": 0.0, "recon2": 0.0, "kl0": 85.049246, "kl1": 0.0, "kl2": 0.0}
{"epoch": 24, "phase": 0, "elapsed_s": 20.26, "total": 86.054195, "recon0": 1.004682, "recon1": 0.0, "recon2": 0.0, "kl0": 85.049513, "kl1": 0.0, "kl2": 0.0}
{"epoch": 25, "phase": 0, "elapsed_s": 21.69, "total": 86.053705, "recon0": 1.00435, "recon1": 0.0, "recon2": 0.0, "kl0": 85.049355, "kl1": 0.0, "kl2": 0.0}
{"epoch": 26, "phase": 0, "elapsed_s": 21.12, "total": 86.053788, "recon0": 1.004346, "recon1": 0.0, "recon2": 0.0, "kl0": 85.049442, "kl1": 0.0, "kl2": 0.0}
{"epoch": 27, "phase": 0, "elapsed_s": 20.49, "total": 86.053552, "recon0": 1.004326, "recon1": 0.0, "recon2": 0.0, "kl0": 85.049226, "kl1": 0.0, "kl2": 0.0}
{"epoch": 28, "phase": 0, "elapsed_s": 21.34, "total": 86.05329, "recon0": 1.004203, "recon1": 0.0, "recon2": 0.0, "kl0": 85.049086, "kl1": 0.0, "kl2": 0.0}
{"epoch": 29, "phase": 0, "elapsed_s": 20.16, "total": 86.053247, "recon0": 1.004118, "recon1": 0.0, "recon2": 0.0, "kl0": 85.049129, "kl1": 0.0, "kl2": 0.0}
{"epoch": 30, "phase": 0, "elapsed_s": 20.36, "total": 86.053359, "recon0": 1.004064, "recon1": 0.0, "recon2": 0.0, "kl0": 85.049294, "kl1": 0.0, "kl2": 0.0}
{"epoch": 31, "phase": 0, "elapsed_s": 20.51, "total": 86.05295, "recon0": 1.004003, "recon1": 0.0, "recon2": 0.0, "kl0": 85.048947, "kl1": 0.0, "kl2": 0.0}
{"epoch": 32, "phase": 0, "elapsed_s": 19.24, "total": 86.052861, "recon0": 1.003819, "recon1": 0.0, "recon2": 0.0, "kl0": 85.049042, "kl1": 0.0, "kl2": 0.0}
{"epoch": 33, "phase": 0, "elapsed_s": 20.48, "total": 86.05269, "recon0": 1.003701, "recon1": 0.0, "recon2": 0.0, "kl0": 85.048989, "kl1": 0.0, "kl2": 0.0}
{"epoch": 34, "phase": 0, "elapsed_s": 21.69, "total": 86.052425, "recon0": 1.003493, "recon1": 0.0, "recon2": 0.0, "kl0": 85.048932, "kl1": 0.0, "kl2": 0.0}
{"epoch": 35, "phase": 0, "elapsed_s": 20.83, "total": 86.052509, "recon0": 1.003599, "recon1": 0.0, "recon2": 0.0, "kl0": 85.04891, "kl1": 0.0, "kl2": 0.0}
{"epoch": 36, "phase": 0, "elapsed_s": 20.88, "total": 86.052773, "recon0": 1.003713, "recon1": 0.0, "recon2": 0.0, "kl0": 85.04906, "kl1": 0.0, "kl2": 0.0}
{"epoch": 37, "phase": 0, "elapsed_s": 21.83, "total": 86.052541, "recon0": 1.003609, "recon1": 0.0, "recon2": 0.0, "kl0": 85.048933, "kl1": 0.0, "kl2": 0.0}
{"epoch": 38, "phase": 0, "elapsed_s": 22.12, "total": 86.052515, "recon0": 1.003521, "recon1": 0.0, "recon2": 0.0, "kl0": 85.048994, "kl1": 0.0, "kl2": 0.0}
{"epoch": 39, "phase": 0, "elapsed_s": 21.51, "total": 86.052543, "recon0": 1.003562, "recon1": 0.0, "recon2": 0.0, "kl0": 85.04898, "kl1": 0.0, "kl2": 0.0}
{"epoch": 40, "phase": 0, "elapsed_s": 21.4, "total": 86.051954, "recon0": 1.003322, "recon1": 0.0, "recon2": 0.0, "kl0": 85.048632, "kl1": 0.0, "kl2": 0.0}
{"epoch": 41, "phase": 0, "elapsed_s": 20.97, "total": 86.05217, "recon0": 1.003313, "recon1": 0.0, "recon2": 0.0, "kl0": 85.048856, "kl1": 0.0, "kl2": 0.0}
{"epoch": 42, "phase": 0, "elapsed_s": 20.93, "total": 86.052204, "recon0": 1.003459, "recon1": 0.0, "recon2": 0.0, "kl0": 85.048745, "kl1": 0.0, "kl2": 0.0}
{"epoch": 43, "phase": 0, "elapsed_s": 21.29, "total": 86.051885, "recon0": 1.003173, "recon1": 0.0, "recon2": 0.0, "kl0": 85.048712, "kl1": 0.0, "kl2": 0.0}
{"epoch": 44, "phase": 0, "elapsed_s": 22.04, "total": 86.051957, "recon0": 1.003255, "recon1": 0.0, "recon2": 0.0, "kl0": 85.048702, "kl1": 0.0, "kl2": 0.0}
{"epoch": 45, "phase": 0, "elapsed_s": 21.58, "total": 86.051914, "recon0": 1.003216, "recon1": 0.0, "recon2": 0.0, "kl0": 85.048699, "kl1": 0.0, "kl2": 0.0}
{"epoch": 46, "phase": 0, "elapsed_s": 20.39, "total": 86.051632, "recon0": 1.002953, "recon1": 0.0, "recon2": 0.0, "kl0": 85.048679, "kl1": 0.0, "kl2": 0.0}
{"epoch": 47, "phase": 0, "elapsed_s": 20.78, "total": 86.051631, "recon0": 1.002924, "recon1": 0.0, "recon2": 0.0, "kl0": 85.048707, "kl1": 0.0, "kl2": 0.0}
{"epoch": 48, "phase": 0, "elapsed_s": 20.23, "total": 86.05159, "recon0": 1.002916, "recon1": 0.0, "recon2": 0.0, "kl0": 85.048674, "kl1": 0.0, "kl2": 0.0}
{"epoch": 49, "phase": 0, "elapsed_s": 21.3, "total": 86.051583, "recon0": 1.002921, "recon1": 0.0, "recon2": 0.0, "kl0": 85.048662, "kl1": 0.0, "kl2": 0.0}
{"epoch": 50, "phase": 0, "elapsed_s": 21.37, "total": 86.051379, "recon0": 1.002779, "recon1": 0.0, "recon2": 0.0, "kl0": 85.0486, "kl1": 0.0, "kl2": 0.0}
{"epoch": 51, "phase": 0, "elapsed_s": 21.07, "total": 86.051596, "recon0": 1.002727, "recon1": 0.0, "recon2": 0.0, "kl0": 85.048869, "kl1": 0.0, "kl2": 0.0}
{"epoch": 52, "phase": 0, "elapsed_s": 19.93, "total": 86.051354, "recon0": 1.002855, "recon1": 0.0, "recon2": 0.0, "kl0": 85.048499, "kl1": 0.0, "kl2": 0.0}
{"epoch": 53, "phase": 0, "elapsed_s": 20.48, "total": 86.051221, "recon0": 1.002693, "recon1": 0.0, "recon2": 0.0, "kl0": 85.048528, "kl1": 0.0, "kl2": 0.0}
{"epoch": 54, "phase": 0, "elapsed_s": 20.25, "total": 86.051298, "recon0": 1.002587, "recon1": 0.0, "recon2": 0.0, "kl0": 85.048711, "kl1": 0.0, "kl2": 0.0}
{"epoch": 55, "phase": 0, "elapsed_s": 20.56, "total": 86.051269, "recon0": 1.002714, "recon1": 0.0, "recon2": 0.0, "kl0": 85.048555, "kl1": 0.0, "kl2": 0.0}
{"epoch": 56, "phase": 0, "elapsed_s": 20.46, "total": 86.051004, "recon0": 1.002401, "recon1": 0.0, "recon2": 0.0, "kl0": 85.048603, "kl1": 0.0, "kl2": 0.0}
{"epoch": 57, "phase": 0, "elapsed_s": 21.53, "total": 86.050967, "recon0": 1.002443, "recon1": 0.0, "recon2": 0.0, "kl0": 85.048524, "kl1": 0.0, "kl2": 0.0}
{"epoch": 58, "phase": 0, "elapsed_s": 19.84, "total": 86.051041, "recon0": 1.002475, "recon1": 0.0, "recon2": 0.0, "kl0": 85.048566, "kl1": 0.0, "kl2": 0.0}
{"epoch": 59, "phase": 0, "elapsed_s": 23.26, "total": 86.050996, "recon0": 1.002409, "recon1": 0.0, "recon2": 0.0, "kl0": 85.048587, "kl1": 0.0, "kl2": 0.0}
{"epoch": 60, "phase": 0, "elapsed_s": 23.53, "total": 86.051132, "recon0": 1.002487, "recon1": 0.0, "recon2": 0.0, "kl0": 85.048645, "kl1": 0.0, "kl2": 0.0}
{"epoch": 61, "phase": 1, "elapsed_s": 3.02, "total": 248.66253, "recon0": 0.83884, "recon1": 1.481662, "recon2": 0.0, "kl0": 73.055095, "kl1": 173.286933, "kl2": 0.0}
{"epoch": 62, "phase": 1, "elapsed_s": 3.03, "total": 245.782191, "recon0": 0.774208, "recon1": 1.108398, "recon2": 0.0, "kl0": 73.011229, "kl1": 170.888356, "kl2": 0.0}
{"epoch": 63, "phase": 1, "elapsed_s": 3.17, "total": 245.5468, "recon0": 0.758408, "recon1": 1.074198, "recon2": 0.0, "kl0": 73.010282, "kl1": 170.703912, "kl2": 0.0}
{"epoch": 64, "phase": 1, "elapsed_s": 3.01, "total": 245.415567, "recon0": 0.747258, "recon1": 1.055708, "recon2": 0.0, "kl0": 73.010096, "kl1": 170.602505, "kl2": 0.0}
{"epoch": 65, "phase": 1, "elapsed_s": 2.89, "total": 245.333834, "recon0": 0.734428, "recon1": 1.041786, "recon2": 0.0, "kl0": 73.010233, "kl1": 170.547386, "kl2": 0.0}
{"epoch": 66, "phase": 1, "elapsed_s": 3.13, "total": 245.279483, "recon0": 0.721529, "recon1": 1.033593, "recon2": 0.0, "kl0": 73.010222, "kl1": 170.51414, "kl2": 0.0}
{"epoch": 67, "phase": 1, "elapsed_s": 2.93, "total": 245.226985, "recon0": 0.712027, "recon1": 1.026275, "recon2": 0.0, "kl0": 73.010072, "kl1": 170.47861, "kl2": 0.0}
{"epoch": 68, "phase": 1, "elapsed_s": 3.06, "total": 245.197208, "recon0": 0.705299, "recon1": 1.021018, "recon2": 0.0, "kl0": 73.009803, "kl1": 170.461088, "kl2": 0.0}
{"epoch": 69, "phase": 1, "elapsed_s": 3.24, "total": 245.170304, "recon0": 0.702581, "recon1": 1.016726, "recon2": 0.0, "kl0": 73.00976, "kl1": 170.441237, "kl2": 0.0}
{"epoch": 70, "phase": 1, "elapsed_s": 2.88, "total": 245.152694, "recon0": 0.700606, "recon1": 1.01305, "recon2": 0.0, "kl0": 73.009622, "kl1": 170.429416, "kl2": 0.0}
{"epoch": 71, "phase": 1, "elapsed_s": 3.38, "total": 245.137568, "recon0": 0.700029, "recon1": 1.010956, "recon2": 0.0, "kl0": 73.009604, "kl1": 170.416978, "kl2": 0.0}
{"epoch": 72, "phase": 1, "elapsed_s": 3.38, "total": 245.123892, "recon0": 0.700032, "recon1": 1.008112, "recon2": 0.0, "kl0": 73.009585, "kl1": 170.406163, "kl2": 0.0}
{"epoch": 73, "phase": 1, "elapsed_s": 3.04, "total": 245.111695, "recon0": 0.699438, "recon1": 1.006333, "recon2": 0.0, "kl0": 73.009235, "kl1": 170.396689, "kl2": 0.0}
{"epoch": 74, "phase": 1, "elapsed_s": 3.08, "total": 245.101468, "recon0": 0.699319, "recon1": 1.005132, "recon2": 0.0, "kl0": 73.009247, "kl1": 170.38777, "kl2": 0.0}
{"epoch": 75, "phase": 1, "elapsed_s": 3.06, "total": 245.09494, "recon0": 0.699216, "recon1": 1.003885, "recon2": 0.0, "kl0": 73.009414, "kl1": 170.382425, "kl2": 0.0}
{"epoch": 76, "phase": 1, "elapsed_s": 3.31, "total": 245.088454, "recon0": 0.699423, "recon1": 1.002792, "recon2": 0.0, "kl0": 73.009426, "kl1": 170.376813, "kl2": 0.0}
{"epoch": 77, "phase": 1, "elapsed_s": 3.41, "total": 245.082945, "recon0": 0.699254, "recon1": 1.002356, "recon2": 0.0, "kl0": 73.009426, "kl1": 170.37191, "kl2": 0.0}
{"epoch": 78, "phase": 1, "elapsed_s": 3.25, "total": 245.078702, "recon0": 0.699014, "recon1": 1.002072, "recon2": 0.0, "kl0": 73.009204, "kl1": 170.368413, "kl2": 0.0}
{"epoch": 79, "phase": 1, "elapsed_s": 3.27, "total": 245.077534, "recon0": 0.69883, "recon1": 1.001512, "recon2": 0.0, "kl0": 73.009275, "kl1": 170.367917, "kl2": 0.0}
{"epoch": 80, "phase": 1, "elapsed_s": 3.27, "total": 245.075582, "recon0": 0.699553, "recon1": 1.001304, "recon2": 0.0, "kl0": 73.009385, "kl1": 170.36534, "kl2": 0.0}
{"epoch": 81, "phase": 1, "elapsed_s": 3.35, "total": 245.074042, "recon0": 0.698861, "recon1": 1.000917, "recon2": 0.0, "kl0": 73.009291, "kl1": 170.364973, "kl2": 0.0}
{"epoch": 82, "phase": 1, "elapsed_s": 3.08, "total": 245.071491, "recon0": 0.698884, "recon1": 1.001143, "recon2": 0.0, "kl0": 73.009318, "kl1": 170.362145, "kl2": 0.0}
{"epoch": 83, "phase": 1, "elapsed_s": 2.89, "total": 245.070205, "recon0": 0.698939, "recon1": 1.000905, "recon2": 0.0, "kl0": 73.009463, "kl1": 170.360898, "kl2": 0.0}
{"epoch": 84, "phase": 1, "elapsed_s": 3.37, "total": 245.069235, "recon0": 0.698986, "recon1": 1.000566, "recon2": 0.0, "kl0": 73.009307, "kl1": 170.360375, "kl2": 0.0}
{"epoch": 85, "phase": 1, "elapsed_s": 3.06, "total": 245.066959, "recon0": 0.698673, "recon1": 1.00048, "recon2": 0.0, "kl0": 73.009114, "kl1": 170.358693, "kl2": 0.0}
{"epoch": 86, "phase": 1, "elapsed_s": 3.16, "total": 245.067654, "recon0": 0.699086, "recon1": 1.000543, "recon2": 0.0, "kl0": 73.009304, "kl1": 170.358721, "kl2": 0.0}
{"epoch": 87, "phase": 1, "elapsed_s": 3.47, "total": 245.066236, "recon0": 0.698813, "recon1": 1.000332, "recon2": 0.0, "kl0": 73.009418, "kl1": 170.357673, "kl2": 0.0}
{"epoch": 88, "phase": 1, "elapsed_s": 3.13, "total": 245.068739, "recon0": 0.698586, "recon1": 1.000304, "recon2": 0.0, "kl0": 73.009374, "kl1": 170.360475, "kl2": 0.0}
{"epoch": 89, "phase": 1, "elapsed_s": 3.17, "total": 245.069731, "recon0": 0.698304, "recon1": 1.000321, "recon2": 0.0, "kl0": 73.009159, "kl1": 170.361947, "kl2": 0.0}
{"epoch": 90, "phase": 1, "elapsed_s": 3.25, "total": 245.065129, "recon0": 0.698564, "recon1": 1.000205, "recon2": 0.0, "kl0": 73.009039, "kl1": 170.357321, "kl2": 0.0}
{"epoch": 91, "phase": 1, "elapsed_s": 2.94, "total": 245.065436, "recon0": 0.69884, "recon1": 1.00025, "recon2": 0.0, "kl0": 73.009381, "kl1": 170.356965, "kl2": 0.0}
{"epoch": 92, "phase": 1, "elapsed_s": 3.08, "total": 245.065126, "recon0": 0.698734, "recon1": 1.00013, "recon2": 0.0, "kl0": 73.009328, "kl1": 170.356934, "kl2": 0.0}
{"epoch": 93, "phase": 1, "elapsed_s": 3.26, "total": 245.065383, "recon0": 0.698294, "recon1": 1.000228, "recon2": 0.0, "kl0": 73.009093, "kl1": 170.357768, "kl2": 0.0}
{"epoch": 94, "phase": 1, "elapsed_s": 3.21, "total": 245.064074, "recon0": 0.69861, "recon1": 1.000234, "recon2": 0.0, "kl0": 73.009337, "kl1": 170.355894, "kl2": 0.0}
{"epoch": 95, "phase": 1, "elapsed_s": 3.41, "total": 245.063219, "recon0": 0.698392, "recon1": 1.000168, "recon2": 0.0, "kl0": 73.009203, "kl1": 170.355455, "kl2": 0.0}
{"epoch": 96, "phase": 1, "elapsed_s": 3.21, "total": 245.064144, "recon0": 0.698896, "recon1": 1.00015, "recon2": 0.0, "kl0": 73.009183, "kl1": 170.355915, "kl2": 0.0}
{"epoch": 97, "phase": 1, "elapsed_s": 3.41, "total": 245.063184, "recon0": 0.698235, "recon1": 1.000166, "recon2": 0.0, "kl0": 73.009361, "kl1": 170.355422, "kl2": 0.0}
{"epoch": 98, "phase": 1, "elapsed_s": 3.46, "total": 245.062962, "recon0": 0.698507, "recon1": 1.000071, "recon2": 0.0, "kl0": 73.009056, "kl1": 170.355328, "kl2": 0.0}
{"epoch": 99, "phase": 1, "elapsed_s": 2.94, "total": 245.063443, "recon0": 0.698436, "recon1": 1.000129, "recon2": 0.0, "kl0": 73.009337, "kl1": 170.355542, "kl2": 0.0}
{"epoch": 100, "phase": 1, "elapsed_s": 3.15, "total": 245.064084, "recon0": 0.698279, "recon1": 1.000102, "recon2": 0.0, "kl0": 73.009177, "kl1": 170.356527, "kl2": 0.0}
{"epoch": 101, "phase": 1, "elapsed_s": 2.87, "total": 245.065917, "recon0": 0.698605, "recon1": 1.000079, "recon2": 0.0, "kl0": 73.009277, "kl1": 170.357956, "kl2": 0.0}
{"epoch": 102, "phase": 1, "elapsed_s": 2.97, "total": 245.062561, "recon0": 0.698225, "recon1": 1.000098, "recon2": 0.0, "kl0": 73.009139, "kl1": 170.3551, "kl2": 0.0}
{"epoch": 103, "phase": 1, "elapsed_s": 3.27, "total": 245.062707, "recon0": 0.698316, "recon1": 1.000145, "recon2": 0.0, "kl0": 73.009187, "kl1": 170.355059, "kl2": 0.0}
{"epoch": 104, "phase": 1, "elapsed_s": 2.99, "total": 245.062482, "recon0": 0.698222, "recon1": 1.000031, "recon2": 0.0, "kl0": 73.009093, "kl1": 170.355135, "kl2": 0.0}
{"epoch": 105, "phase": 1, "elapsed_s": 3.08, "total": 245.063394, "recon0": 0.698644, "recon1": 1.000078, "recon2": 0.0, "kl0": 73.009329, "kl1": 170.355344, "kl2": 0.0}
{"epoch": 106, "phase": 1, "elapsed_s": 3.29, "total": 245.066175, "recon0": 0.698594, "recon1": 1.000032, "recon2": 0.0, "kl0": 73.009279, "kl1": 170.35827, "kl2": 0.0}
{"epoch": 107, "phase": 1, "elapsed_s": 3.14, "total": 245.064538, "recon0": 0.698568, "recon1": 1.000093, "recon2": 0.0, "kl0": 73.009305, "kl1": 170.356572, "kl2": 0.0}
{"epoch": 108, "phase": 1, "elapsed_s": 3.1, "total": 245.062603, "recon0": 0.698303, "recon1": 1.00004, "recon2": 0.0, "kl0": 73.009107, "kl1": 170.355153, "kl2": 0.0}
{"epoch": 109, "phase": 1, "elapsed_s": 3.14, "total": 245.062861, "recon0": 0.698076, "recon1": 1.000048, "recon2": 0.0, "kl0": 73.009279, "kl1": 170.355458, "kl2": 0.0}
{"epoch": 110, "phase": 1, "elapsed_s": 3.17, "total": 245.062319, "recon0": 0.698363, "recon1": 1.000067, "recon2": 0.0, "kl0": 73.008946, "kl1": 170.354943, "kl2": 0.0}
{"epoch": 111, "phase": 1, "elapsed_s": 2.86, "total": 245.06317, "recon0": 0.697733, "recon1": 1.000048, "recon2": 0.0, "kl0": 73.00928, "kl1": 170.356109, "kl2": 0.0}
{"epoch": 112, "phase": 1, "elapsed_s": 3.1, "total": 245.063972, "recon0": 0.698594, "recon1": 1.000056, "recon2": 0.0, "kl0": 73.009176, "kl1": 170.356146, "kl2": 0.0}
{"epoch": 113, "phase": 1, "elapsed_s": 3.08, "total": 245.062496, "recon0": 0.698175, "recon1": 1.000032, "recon2": 0.0, "kl0": 73.009197, "kl1": 170.355092, "kl2": 0.0}
{"epoch": 114, "phase": 1, "elapsed_s": 2.75, "total": 245.062393, "recon0": 0.698546, "recon1": 1.000057, "recon2": 0.0, "kl0": 73.008903, "kl1": 170.354888, "kl2": 0.0}
{"epoch": 115, "phase": 1, "elapsed_s": 2.97, "total": 245.062562, "recon0": 0.69792, "recon1": 1.00003, "recon2": 0.0, "kl0": 73.009382, "kl1": 170.35523, "kl2": 0.0}
{"epoch": 116, "phase": 1, "elapsed_s": 2.99, "total": 245.062535, "recon0": 0.698396, "recon1": 1.00003, "recon2": 0.0, "kl0": 73.009251, "kl1": 170.354857, "kl2": 0.0}
{"epoch": 117, "phase": 1, "elapsed_s": 3.0, "total": 245.06206, "recon0": 0.697923, "recon1": 1.000024, "recon2": 0.0, "kl0": 73.008958, "kl1": 170.355155, "kl2": 0.0}
{"epoch": 118, "phase": 1, "elapsed_s": 2.95, "total": 245.062732, "recon0": 0.698185, "recon1": 1.000067, "recon2": 0.0, "kl0": 73.009075, "kl1": 170.355406, "kl2": 0.0}
{"epoch": 119, "phase": 1, "elapsed_s": 3.37, "total": 245.063905, "recon0": 0.698133, "recon1": 1.000064, "recon2": 0.0, "kl0": 73.009053, "kl1": 170.356654, "kl2": 0.0}
{"epoch": 120, "phase": 1, "elapsed_s": 3.07, "total": 245.061189, "recon0": 0.697946, "recon1": 1.000034, "recon2": 0.0, "kl0": 73.009073, "kl1": 170.354136, "kl2": 0.0}
{"epoch": 121, "phase": 1, "elapsed_s": 2.79, "total": 245.06233, "recon0": 0.697913, "recon1": 1.000029, "recon2": 0.0, "kl0": 73.009293, "kl1": 170.355095, "kl2": 0.0}
{"epoch": 122, "phase": 1, "elapsed_s": 3.18, "total": 245.061638, "recon0": 0.698137, "recon1": 1.000039, "recon2": 0.0, "kl0": 73.009173, "kl1": 170.354288, "kl2": 0.0}
{"epoch": 123, "phase": 1, "elapsed_s": 2.94, "total": 245.062985, "recon0": 0.698249, "recon1": 1.000035, "recon2": 0.0, "kl0": 73.009122, "kl1": 170.355579, "kl2": 0.0}
{"epoch": 124, "phase": 1, "elapsed_s": 3.34, "total": 245.065013, "recon0": 0.698215, "recon1": 1.000013, "recon2": 0.0, "kl0": 73.008975, "kl1": 170.357809, "kl2": 0.0}
{"epoch": 125, "phase": 1, "elapsed_s": 3.12, "total": 245.061624, "recon0": 0.697956, "recon1": 1.000065, "recon2": 0.0, "kl0": 73.009121, "kl1": 170.354482, "kl2": 0.0}
{"epoch": 126, "phase": 1, "elapsed_s": 3.01, "total": 245.060936, "recon0": 0.69769, "recon1": 1.000027, "recon2": 0.0, "kl0": 73.009057, "kl1": 170.354162, "kl2": 0.0}
{"epoch": 127, "phase": 1, "elapsed_s": 2.91, "total": 245.061801, "recon0": 0.698012, "recon1": 1.000037, "recon2": 0.0, "kl0": 73.00914, "kl1": 170.354613, "kl2": 0.0}
{"epoch": 128, "phase": 1, "elapsed_s": 2.81, "total": 245.063489, "recon0": 0.697772, "recon1": 1.00003, "recon2": 0.0, "kl0": 73.00912, "kl1": 170.356566, "kl2": 0.0}
{"epoch": 129, "phase": 1, "elapsed_s": 3.18, "total": 245.064945, "recon0": 0.697916, "recon1": 1.00004, "recon2": 0.0, "kl0": 73.009092, "kl1": 170.357898, "kl2": 0.0}
{"epoch": 130, "phase": 1, "elapsed_s": 2.9, "total": 245.061695, "recon0": 0.697916, "recon1": 1.000021, "recon2": 0.0, "kl0": 73.009018, "kl1": 170.35474, "kl2": 0.0}
{"epoch": 131, "phase": 1, "elapsed_s": 2.95, "total": 245.061505, "recon0": 0.698165, "recon1": 1.000035, "recon2": 0.0, "kl0": 73.008899, "kl1": 170.354405, "kl2": 0.0}
{"epoch": 132, "phase": 1, "elapsed_s": 3.13, "total": 245.061918, "recon0": 0.69824, "recon1": 1.000023, "recon2": 0.0, "kl0": 73.009368, "kl1": 170.354287, "kl2": 0.0}
{"epoch": 133, "phase": 1, "elapsed_s": 3.13, "total": 245.061918, "recon0": 0.697828, "recon1": 1.000028, "recon2": 0.0, "kl0": 73.009069, "kl1": 170.354992, "kl2": 0.0}
{"epoch": 134, "phase": 1, "elapsed_s": 3.01, "total": 245.064172, "recon0": 0.698057, "recon1": 1.00003, "recon2": 0.0, "kl0": 73.009184, "kl1": 170.3569, "kl2": 0.0}
{"epoch": 135, "phase": 1, "elapsed_s": 3.07, "total": 245.061791, "recon0": 0.698137, "recon1": 1.000016, "recon2": 0.0, "kl0": 73.009052, "kl1": 170.354585, "kl2": 0.0}
{"epoch": 136, "phase": 1, "elapsed_s": 3.09, "total": 245.06198, "recon0": 0.69812, "recon1": 0.999998, "recon2": 0.0, "kl0": 73.009132, "kl1": 170.35473, "kl2": 0.0}
{"epoch": 137, "phase": 1, "elapsed_s": 2.86, "total": 245.064893, "recon0": 0.697755, "recon1": 1.000018, "recon2": 0.0, "kl0": 73.009252, "kl1": 170.357868, "kl2": 0.0}
{"epoch": 138, "phase": 1, "elapsed_s": 3.2, "total": 245.062171, "recon0": 0.697811, "recon1": 1.000029, "recon2": 0.0, "kl0": 73.008991, "kl1": 170.355339, "kl2": 0.0}
{"epoch": 139, "phase": 1, "elapsed_s": 3.31, "total": 245.061638, "recon0": 0.697711, "recon1": 1.000027, "recon2": 0.0, "kl0": 73.00917, "kl1": 170.35473, "kl2": 0.0}
{"epoch": 140, "phase": 1, "elapsed_s": 3.0, "total": 245.061362, "recon0": 0.697878, "recon1": 1.000018, "recon2": 0.0, "kl0": 73.00913, "kl1": 170.354335, "kl2": 0.0}
{"epoch": 141, "phase": 2, "elapsed_s": 3.5, "total": 419.459798, "recon0": 0.697499, "recon1": 1.000007, "recon2": 1.547828, "kl0": 73.038778, "kl1": 170.354506, "kl2": 172.821181}
{"epoch": 142, "phase": 2, "elapsed_s": 3.55, "total": 416.948685, "recon0": 0.697238, "recon1": 1.00002, "recon2": 1.117019, "kl0": 73.009815, "kl1": 170.354417, "kl2": 170.770176}
{"epoch": 143, "phase": 2, "elapsed_s": 3.3, "total": 416.73089, "recon0": 0.697466, "recon1": 1.000016, "recon2": 1.058987, "kl0": 73.009417, "kl1": 170.35426, "kl2": 170.610743}
{"epoch": 144, "phase": 2, "elapsed_s": 3.35, "total": 416.6285, "recon0": 0.697056, "recon1": 1.000013, "recon2": 1.034927, "kl0": 73.009292, "kl1": 170.354132, "kl2": 170.533081}
{"epoch": 145, "phase": 2, "elapsed_s": 3.32, "total": 416.57063, "recon0": 0.69739, "recon1": 1.00002, "recon2": 1.022492, "kl0": 73.009285, "kl1": 170.354096, "kl2": 170.487348}
{"epoch": 146, "phase": 2, "elapsed_s": 3.24, "total": 416.529586, "recon0": 0.697334, "recon1": 1.000006, "recon2": 1.015536, "kl0": 73.008931, "kl1": 170.354082, "kl2": 170.453697}
{"epoch": 147, "phase": 2, "elapsed_s": 3.42, "total": 416.503925, "recon0": 0.697302, "recon1": 1.000012, "recon2": 1.010783, "kl0": 73.009043, "kl1": 170.353999, "kl2": 170.432786}
{"epoch": 148, "phase": 2, "elapsed_s": 3.35, "total": 416.483341, "recon0": 0.697367, "recon1": 1.000033, "recon2": 1.008334, "kl0": 73.009005, "kl1": 170.353998, "kl2": 170.414606}
{"epoch": 149, "phase": 2, "elapsed_s": 3.38, "total": 416.470644, "recon0": 0.697335, "recon1": 1.00001, "recon2": 1.006469, "kl0": 73.008867, "kl1": 170.354011, "kl2": 170.403952}
{"epoch": 150, "phase": 2, "elapsed_s": 3.54, "total": 416.46048, "recon0": 0.697264, "recon1": 1.000025, "recon2": 1.005497, "kl0": 73.009136, "kl1": 170.353995, "kl2": 170.394564}
{"epoch": 151, "phase": 2, "elapsed_s": 3.31, "total": 416.452242, "recon0": 0.697281, "recon1": 1.000033, "recon2": 1.004312, "kl0": 73.009159, "kl1": 170.35396, "kl2": 170.387496}
{"epoch": 152, "phase": 2, "elapsed_s": 3.31, "total": 416.44725, "recon0": 0.696984, "recon1": 1.000015, "recon2": 1.003759, "kl0": 73.008808, "kl1": 170.353965, "kl2": 170.383718}
{"epoch": 153, "phase": 2, "elapsed_s": 3.52, "total": 416.442094, "recon0": 0.697271, "recon1": 1.000024, "recon2": 1.00271, "kl0": 73.0089, "kl1": 170.353963, "kl2": 170.379226}
{"epoch": 154, "phase": 2, "elapsed_s": 3.19, "total": 416.437258, "recon0": 0.697454, "recon1": 1.00002, "recon2": 1.002411, "kl0": 73.008803, "kl1": 170.35394, "kl2": 170.374629}
{"epoch": 155, "phase": 2, "elapsed_s": 3.26, "total": 416.433166, "recon0": 0.697317, "recon1": 1.000021, "recon2": 1.00206, "kl0": 73.009312, "kl1": 170.353974, "kl2": 170.370483}
{"epoch": 156, "phase": 2, "elapsed_s": 3.46, "total": 416.430844, "recon0": 0.697491, "recon1": 1.000016, "recon2": 1.001901, "kl0": 73.008935, "kl1": 170.354298, "kl2": 170.368202}
{"epoch": 157, "phase": 2, "elapsed_s": 3.27, "total": 416.42795, "recon0": 0.697417, "recon1": 1.000021, "recon2": 1.001588, "kl0": 73.0089, "kl1": 170.354127, "kl2": 170.365896}
{"epoch": 158, "phase": 2, "elapsed_s": 3.23, "total": 416.425389, "recon0": 0.697349, "recon1": 1.000001, "recon2": 1.001314, "kl0": 73.008961, "kl1": 170.354008, "kl2": 170.363755}
{"epoch": 159, "phase": 2, "elapsed_s": 3.48, "total": 416.423755, "recon0": 0.697305, "recon1": 1.000027, "recon2": 1.001166, "kl0": 73.008999, "kl1": 170.354388, "kl2": 170.361869}
{"epoch": 160, "phase": 2, "elapsed_s": 3.49, "total": 416.421273, "recon0": 0.697173, "recon1": 1.000042, "recon2": 1.001066, "kl0": 73.008869, "kl1": 170.353958, "kl2": 170.360165}
{"epoch": 161, "phase": 2, "elapsed_s": 3.52, "total": 416.419849, "recon0": 0.697089, "recon1": 1.000016, "recon2": 1.001035, "kl0": 73.008964, "kl1": 170.354018, "kl2": 170.358727}
{"epoch": 162, "phase": 2, "elapsed_s": 3.5, "total": 416.418892, "recon0": 0.697406, "recon1": 1.000005, "recon2": 1.000808, "kl0": 73.009006, "kl1": 170.354058, "kl2": 170.35761}
{"epoch": 163, "phase": 2, "elapsed_s": 3.28, "total": 416.417835, "recon0": 0.697041, "recon1": 1.000005, "recon2": 1.000716, "kl0": 73.008931, "kl1": 170.354206, "kl2": 170.356935}
{"epoch": 164, "phase": 2, "elapsed_s": 3.19, "total": 416.417671, "recon0": 0.697478, "recon1": 1.000002, "recon2": 1.00073, "kl0": 73.008778, "kl1": 170.354246, "kl2": 170.356437}
{"epoch": 165, "phase": 2, "elapsed_s": 3.4, "total": 416.416473, "recon0": 0.697042, "recon1": 1.000007, "recon2": 1.000516, "kl0": 73.009237, "kl1": 170.354048, "kl2": 170.355622}
{"epoch": 166, "phase": 2, "elapsed_s": 3.28, "total": 416.416383, "recon0": 0.697208, "recon1": 1.000015, "recon2": 1.000557, "kl0": 73.008953, "kl1": 170.35419, "kl2": 170.35546}
{"epoch": 167, "phase": 2, "elapsed_s": 3.24, "total": 416.415974, "recon0": 0.697206, "recon1": 1.000015, "recon2": 1.000499, "kl0": 73.009172, "kl1": 170.354136, "kl2": 170.354946}
{"epoch": 168, "phase": 2, "elapsed_s": 3.36, "total": 416.415526, "recon0": 0.697085, "recon1": 1.000009, "recon2": 1.000492, "kl0": 73.008963, "kl1": 170.354082, "kl2": 170.354896}
{"epoch": 169, "phase": 2, "elapsed_s": 3.26, "total": 416.415312, "recon0": 0.697175, "recon1": 1.000023, "recon2": 1.000413, "kl0": 73.008891, "kl1": 170.354034, "kl2": 170.354775}
{"epoch": 170, "phase": 2, "elapsed_s": 3.24, "total": 416.414749, "recon0": 0.696923, "recon1": 1.000024, "recon2": 1.000263, "kl0": 73.008857, "kl1": 170.354189, "kl2": 170.354494}
{"epoch": 171, "phase": 2, "elapsed_s": 3.42, "total": 416.415318, "recon0": 0.697313, "recon1": 1.000026, "recon2": 1.000343, "kl0": 73.009014, "kl1": 170.354149, "kl2": 170.354473}
{"epoch": 172, "phase": 2, "elapsed_s": 3.25, "total": 416.414913, "recon0": 0.697233, "recon1": 1.000018, "recon2": 1.000312, "kl0": 73.008845, "kl1": 170.354152, "kl2": 170.354352}
{"epoch": 173, "phase": 2, "elapsed_s": 3.29, "total": 416.414671, "recon0": 0.697382, "recon1": 1.000016, "recon2": 1.000194, "kl0": 73.008938, "kl1": 170.353937, "kl2": 170.354205}
{"epoch": 174, "phase": 2, "elapsed_s": 3.41, "total": 416.414863, "recon0": 0.697109, "recon1": 1.000009, "recon2": 1.000218, "kl0": 73.009087, "kl1": 170.354278, "kl2": 170.354162}
{"epoch": 175, "phase": 2, "elapsed_s": 3.79, "total": 416.41501, "recon0": 0.697472, "recon1": 1.000009, "recon2": 1.000132, "kl0": 73.009061, "kl1": 170.354123, "kl2": 170.354214}
{"epoch": 176, "phase": 2, "elapsed_s": 3.28, "total": 416.41478, "recon0": 0.697134, "recon1": 1.000013, "recon2": 1.000191, "kl0": 73.009267, "kl1": 170.353994, "kl2": 170.35418}
{"epoch": 177, "phase": 2, "elapsed_s": 3.39, "total": 416.414477, "recon0": 0.697107, "recon1": 1.000014, "recon2": 1.000152, "kl0": 73.009069, "kl1": 170.354052, "kl2": 170.354084}
{"epoch": 178, "phase": 2, "elapsed_s": 3.32, "total": 416.41435, "recon0": 0.697227, "recon1": 1.000001, "recon2": 1.000192, "kl0": 73.00884, "kl1": 170.353986, "kl2": 170.354104}
{"epoch": 179, "phase": 2, "elapsed_s": 3.67, "total": 416.41469, "recon0": 0.697377, "recon1": 1.000027, "recon2": 1.000115, "kl0": 73.008979, "kl1": 170.35407, "kl2": 170.354124}
{"epoch": 180, "phase": 2, "elapsed_s": 3.82, "total": 416.414384, "recon0": 0.697445, "recon1": 1.00001, "recon2": 1.000138, "kl0": 73.008802, "kl1": 170.353927, "kl2": 170.354061}
{"epoch": 181, "phase": 2, "elapsed_s": 3.88, "total": 416.414117, "recon0": 0.697102, "recon1": 1.000013, "recon2": 1.000153, "kl0": 73.008846, "kl1": 170.354001, "kl2": 170.354001}
{"epoch": 182, "phase": 2, "elapsed_s": 3.54, "total": 416.414253, "recon0": 0.697038, "recon1": 1.000001, "recon2": 1.000107, "kl0": 73.009018, "kl1": 170.354002, "kl2": 170.354086}
{"epoch": 183, "phase": 2, "elapsed_s": 3.37, "total": 416.415049, "recon0": 0.69711, "recon1": 1.000017, "recon2": 1.000132, "kl0": 73.008877, "kl1": 170.354889, "kl2": 170.354024}
{"epoch": 184, "phase": 2, "elapsed_s": 3.46, "total": 416.41447, "recon0": 0.697414, "recon1": 1.000005, "recon2": 1.000106, "kl0": 73.008895, "kl1": 170.354049, "kl2": 170.354}
{"epoch": 185, "phase": 2, "elapsed_s": 3.5, "total": 416.414607, "recon0": 0.697232, "recon1": 1.000013, "recon2": 1.000091, "kl0": 73.009127, "kl1": 170.354125, "kl2": 170.354019}
{"epoch": 186, "phase": 2, "elapsed_s": 3.34, "total": 416.414269, "recon0": 0.697225, "recon1": 1.000005, "recon2": 1.000117, "kl0": 73.008837, "kl1": 170.354094, "kl2": 170.353991}
{"epoch": 187, "phase": 2, "elapsed_s": 3.38, "total": 416.414399, "recon0": 0.697099, "recon1": 1.000014, "recon2": 1.000033, "kl0": 73.009163, "kl1": 170.354099, "kl2": 170.35399}
{"epoch": 188, "phase": 2, "elapsed_s": 3.63, "total": 416.413924, "recon0": 0.696979, "recon1": 1.000019, "recon2": 1.00006, "kl0": 73.008896, "kl1": 170.353977, "kl2": 170.353992}
{"epoch": 189, "phase": 2, "elapsed_s": 3.32, "total": 416.414135, "recon0": 0.69729, "recon1": 1.000007, "recon2": 1.000056, "kl0": 73.00886, "kl1": 170.353932, "kl2": 170.35399}
{"epoch": 190, "phase": 2, "elapsed_s": 3.4, "total": 416.413896, "recon0": 0.697003, "recon1": 1.000006, "recon2": 1.00004, "kl0": 73.008973, "kl1": 170.353942, "kl2": 170.353932}
{"epoch": 191, "phase": 2, "elapsed_s": 3.63, "total": 416.414689, "recon0": 0.697422, "recon1": 1.000004, "recon2": 1.000073, "kl0": 73.009187, "kl1": 170.354008, "kl2": 170.353996}
{"epoch": 192, "phase": 2, "elapsed_s": 3.26, "total": 416.414086, "recon0": 0.696804, "recon1": 1.000003, "recon2": 1.000084, "kl0": 73.008889, "kl1": 170.354316, "kl2": 170.353991}
{"epoch": 193, "phase": 2, "elapsed_s": 3.22, "total": 416.414284, "recon0": 0.697061, "recon1": 1.000018, "recon2": 1.00005, "kl0": 73.009093, "kl1": 170.354127, "kl2": 170.353937}
{"epoch": 194, "phase": 2, "elapsed_s": 3.5, "total": 416.414123, "recon0": 0.697008, "recon1": 1.000012, "recon2": 1.000041, "kl0": 73.009092, "kl1": 170.354018, "kl2": 170.353953}
{"epoch": 195, "phase": 2, "elapsed_s": 3.37, "total": 416.414318, "recon0": 0.69711, "recon1": 1.000011, "recon2": 1.000039, "kl0": 73.009076, "kl1": 170.354119, "kl2": 170.353962}
{"epoch": 196, "phase": 2, "elapsed_s": 3.29, "total": 416.414093, "recon0": 0.697159, "recon1": 1.0, "recon2": 1.00003, "kl0": 73.009028, "kl1": 170.353924, "kl2": 170.353954}
{"epoch": 197, "phase": 2, "elapsed_s": 3.47, "total": 416.414059, "recon0": 0.697126, "recon1": 1.000015, "recon2": 1.000063, "kl0": 73.00895, "kl1": 170.35394, "kl2": 170.353965}
{"epoch": 198, "phase": 2, "elapsed_s": 3.41, "total": 416.413674, "recon0": 0.696809, "recon1": 1.000007, "recon2": 1.00004, "kl0": 73.008957, "kl1": 170.353923, "kl2": 170.353938}
{"epoch": 199, "phase": 2, "elapsed_s": 3.46, "total": 416.414146, "recon0": 0.697251, "recon1": 1.000027, "recon2": 1.000029, "kl0": 73.008881, "kl1": 170.354015, "kl2": 170.353943}
{"epoch": 200, "phase": 2, "elapsed_s": 3.97, "total": 416.414026, "recon0": 0.697094, "recon1": 1.000003, "recon2": 1.000024, "kl0": 73.008901, "kl1": 170.354077, "kl2": 170.353927}
{"epoch": 201, "phase": 2, "elapsed_s": 3.37, "total": 416.414181, "recon0": 0.69716, "recon1": 1.000028, "recon2": 1.000039, "kl0": 73.008937, "kl1": 170.354063, "kl2": 170.353953}
{"epoch": 202, "phase": 2, "elapsed_s": 3.31, "total": 416.413994, "recon0": 0.6972, "recon1": 1.000007, "recon2": 1.000014, "kl0": 73.0089, "kl1": 170.353935, "kl2": 170.353939}
{"epoch": 203, "phase": 2, "elapsed_s": 3.41, "total": 416.414287, "recon0": 0.697101, "recon1": 1.000004, "recon2": 1.000027, "kl0": 73.009286, "kl1": 170.353925, "kl2": 170.353944}
{"epoch": 204, "phase": 2, "elapsed_s": 3.18, "total": 416.414221, "recon0": 0.697214, "recon1": 1.00001, "recon2": 1.000026, "kl0": 73.009061, "kl1": 170.353978, "kl2": 170.353931}
{"epoch": 205, "phase": 2, "elapsed_s": 3.25, "total": 416.414066, "recon0": 0.697245, "recon1": 1.000014, "recon2": 1.000019, "kl0": 73.008892, "kl1": 170.353966, "kl2": 170.35393}
{"epoch": 206, "phase": 2, "elapsed_s": 3.99, "total": 416.414052, "recon0": 0.696878, "recon1": 1.000029, "recon2": 1.000036, "kl0": 73.009079, "kl1": 170.354075, "kl2": 170.353955}
{"epoch": 207, "phase": 2, "elapsed_s": 3.42, "total": 416.414233, "recon0": 0.697036, "recon1": 1.000011, "recon2": 1.000025, "kl0": 73.009149, "kl1": 170.354065, "kl2": 170.353948}
{"epoch": 208, "phase": 2, "elapsed_s": 3.6, "total": 416.414038, "recon0": 0.697133, "recon1": 1.000009, "recon2": 1.000037, "kl0": 73.0089, "kl1": 170.354036, "kl2": 170.353923}
{"epoch": 209, "phase": 2, "elapsed_s": 3.41, "total": 416.414179, "recon0": 0.69714, "recon1": 1.000015, "recon2": 1.000017, "kl0": 73.00907, "kl1": 170.353994, "kl2": 170.353943}
{"epoch": 210, "phase": 2, "elapsed_s": 3.41, "total": 416.413942, "recon0": 0.697198, "recon1": 1.000003, "recon2": 1.000024, "kl0": 73.009031, "kl1": 170.353753, "kl2": 170.353932}
{"epoch": 211, "phase": 2, "elapsed_s": 3.63, "total": 416.414291, "recon0": 0.697067, "recon1": 1.000007, "recon2": 1.000032, "kl0": 73.008993, "kl1": 170.354262, "kl2": 170.353929}
{"epoch": 212, "phase": 2, "elapsed_s": 3.45, "total": 416.414439, "recon0": 0.697169, "recon1": 1.000017, "recon2": 1.000037, "kl0": 73.009101, "kl1": 170.354181, "kl2": 170.353933}
{"epoch": 213, "phase": 2, "elapsed_s": 3.38, "total": 416.414182, "recon0": 0.697123, "recon1": 1.000021, "recon2": 1.000023, "kl0": 73.009107, "kl1": 170.35398, "kl2": 170.353928}
{"epoch": 214, "phase": 2, "elapsed_s": 3.4, "total": 416.413733, "recon0": 0.696946, "recon1": 1.000013, "recon2": 1.000027, "kl0": 73.008813, "kl1": 170.35399, "kl2": 170.353943}
{"epoch": 215, "phase": 2, "elapsed_s": 3.26, "total": 416.4137, "recon0": 0.696891, "recon1": 1.000012, "recon2": 1.000015, "kl0": 73.008898, "kl1": 170.353954, "kl2": 170.35393}
{"epoch": 216, "phase": 2, "elapsed_s": 3.38, "total": 416.413964, "recon0": 0.697152, "recon1": 0.999999, "recon2": 0.999996, "kl0": 73.008948, "kl1": 170.353943, "kl2": 170.353926}
{"epoch": 217, "phase": 2, "elapsed_s": 3.54, "total": 416.413823, "recon0": 0.697037, "recon1": 1.000016, "recon2": 1.000016, "kl0": 73.008807, "kl1": 170.354025, "kl2": 170.353922}
{"epoch": 218, "phase": 2, "elapsed_s": 3.38, "total": 416.414087, "recon0": 0.69706, "recon1": 1.00002, "recon2": 1.000017, "kl0": 73.008978, "kl1": 170.354074, "kl2": 170.353938}
{"epoch": 221, "phase": 3, "elapsed_s": 3.58, "total": 416.414106, "recon0": 0.697099, "recon1": 1.000004, "recon2": 1.000019, "kl0": 73.008981, "kl1": 170.354079, "kl2": 170.353925}
{"epoch": 222, "phase": 3, "elapsed_s": 3.63, "total": 416.413927, "recon0": 0.697022, "recon1": 1.000023, "recon2": 1.000016, "kl0": 73.008971, "kl1": 170.353967, "kl2": 170.353928}
{"epoch": 223, "phase": 3, "elapsed_s": 3.26, "total": 416.41401, "recon0": 0.697006, "recon1": 1.000004, "recon2": 1.000018, "kl0": 73.008785, "kl1": 170.35427, "kl2": 170.353928}
{"epoch": 224, "phase": 3, "elapsed_s": 3.29, "total": 416.414362, "recon0": 0.697088, "recon1": 1.000009, "recon2": 1.00002, "kl0": 73.009043, "kl1": 170.354283, "kl2": 170.35392}
{"epoch": 225, "phase": 3, "elapsed_s": 3.39, "total": 416.41407, "recon0": 0.697127, "recon1": 1.000017, "recon2": 1.000026, "kl0": 73.009031, "kl1": 170.35394, "kl2": 170.353929}
{"epoch": 226, "phase": 3, "elapsed_s": 3.21, "total": 416.41373, "recon0": 0.696975, "recon1": 1.000002, "recon2": 1.000011, "kl0": 73.008757, "kl1": 170.354054, "kl2": 170.353932}
{"epoch": 227, "phase": 3, "elapsed_s": 3.25, "total": 416.414209, "recon0": 0.697186, "recon1": 1.000016, "recon2": 1.000014, "kl0": 73.009075, "kl1": 170.353997, "kl2": 170.353922}
{"epoch": 228, "phase": 3, "elapsed_s": 3.44, "total": 416.41376, "recon0": 0.696987, "recon1": 1.000008, "recon2": 1.000015, "kl0": 73.008896, "kl1": 170.353926, "kl2": 170.353927}
{"epoch": 229, "phase": 3, "elapsed_s": 3.3, "total": 416.413812, "recon0": 0.697042, "recon1": 1.000016, "recon2": 1.000009, "kl0": 73.008845, "kl1": 170.353977, "kl2": 170.353923}
{"epoch": 230, "phase": 3, "elapsed_s": 3.2, "total": 416.413824, "recon0": 0.697019, "recon1": 1.000011, "recon2": 1.000014, "kl0": 73.008926, "kl1": 170.353931, "kl2": 170.353923}
{"epoch": 231, "phase": 3, "elapsed_s": 3.31, "total": 416.413939, "recon0": 0.697144, "recon1": 1.00001, "recon2": 1.000018, "kl0": 73.008929, "kl1": 170.353913, "kl2": 170.353925}
{"epoch": 232, "phase": 3, "elapsed_s": 3.29, "total": 416.414067, "recon0": 0.697243, "recon1": 1.000013, "recon2": 1.00002, "kl0": 73.008882, "kl1": 170.35399, "kl2": 170.353919}
{"epoch": 233, "phase": 3, "elapsed_s": 3.27, "total": 416.413933, "recon0": 0.696989, "recon1": 1.000016, "recon2": 1.000005, "kl0": 73.008957, "kl1": 170.354044, "kl2": 170.353922}
{"epoch": 234, "phase": 3, "elapsed_s": 3.39, "total": 416.41405, "recon0": 0.697205, "recon1": 1.00001, "recon2": 1.000008, "kl0": 73.00889, "kl1": 170.354017, "kl2": 170.353921}
{"epoch": 235, "phase": 3, "elapsed_s": 3.31, "total": 416.414172, "recon0": 0.69699, "recon1": 1.00001, "recon2": 1.000015, "kl0": 73.009055, "kl1": 170.354183, "kl2": 170.353919}
{"epoch": 236, "phase": 3, "elapsed_s": 3.11, "total": 416.414486, "recon0": 0.69741, "recon1": 1.000009, "recon2": 1.000023, "kl0": 73.009012, "kl1": 170.354106, "kl2": 170.353925}
{"epoch": 237, "phase": 3, "elapsed_s": 3.39, "total": 416.413933, "recon0": 0.696992, "recon1": 1.000018, "recon2": 1.000014, "kl0": 73.008817, "kl1": 170.354171, "kl2": 170.35392}
{"epoch": 238, "phase": 3, "elapsed_s": 3.43, "total": 416.413949, "recon0": 0.696992, "recon1": 1.00001, "recon2": 1.00001, "kl0": 73.008966, "kl1": 170.354046, "kl2": 170.353926}
{"epoch": 239, "phase": 3, "elapsed_s": 3.44, "total": 416.414277, "recon0": 0.697343, "recon1": 1.000014, "recon2": 1.00001, "kl0": 73.008922, "kl1": 170.354068, "kl2": 170.35392}
{"epoch": 240, "phase": 3, "elapsed_s": 3.72, "total": 416.414171, "recon0": 0.697048, "recon1": 1.000011, "recon2": 1.00001, "kl0": 73.009135, "kl1": 170.354041, "kl2": 170.353925}
{"epoch": 241, "phase": 3, "elapsed_s": 3.79, "total": 416.414183, "recon0": 0.697185, "recon1": 1.000006, "recon2": 1.00001, "kl0": 73.00899, "kl1": 170.35407, "kl2": 170.353923}
{"epoch": 242, "phase": 3, "elapsed_s": 3.48, "total": 416.414319, "recon0": 0.697187, "recon1": 1.000014, "recon2": 1.000021, "kl0": 73.009097, "kl1": 170.354076, "kl2": 170.353923}
{"epoch": 243, "phase": 3, "elapsed_s": 3.45, "total": 416.413858, "recon0": 0.696978, "recon1": 1.000014, "recon2": 1.000016, "kl0": 73.00886, "kl1": 170.354074, "kl2": 170.353916}
{"epoch": 244, "phase": 3, "elapsed_s": 3.38, "total": 416.414034, "recon0": 0.697054, "recon1": 1.000009, "recon2": 1.000025, "kl0": 73.009041, "kl1": 170.353977, "kl2": 170.353929}
{"epoch": 245, "phase": 3, "elapsed_s": 3.34, "total": 416.414106, "recon0": 0.697039, "recon1": 1.000009, "recon2": 1.00001, "kl0": 73.009195, "kl1": 170.353929, "kl2": 170.353924}
{"epoch": 246, "phase": 3, "elapsed_s": 3.44, "total": 416.413991, "recon0": 0.697185, "recon1": 1.000011, "recon2": 1.000007, "kl0": 73.008929, "kl1": 170.353938, "kl2": 170.35392}
{"epoch": 281, "phase": 4, "elapsed_s": 4.44, "total": 357.508727, "recon0": 0.696738, "recon1": 1.000006, "recon2": 1.000008, "kl0": 62.614084, "kl1": 146.098958, "kl2": 146.098933}
{"epoch": 282, "phase": 4, "elapsed_s": 4.51, "total": 357.508563, "recon0": 0.696756, "recon1": 1.000005, "recon2": 1.000004, "kl0": 62.61393, "kl1": 146.098932, "kl2": 146.098936}
{"epoch": 283, "phase": 4, "elapsed_s": 4.47, "total": 357.508409, "recon0": 0.696588, "recon1": 1.0, "recon2": 1.000006, "kl0": 62.613935, "kl1": 146.098939, "kl2": 146.09894}
{"epoch": 284, "phase": 4, "elapsed_s": 4.48, "total": 357.508368, "recon0": 0.696541, "recon1": 1.000008, "recon2": 1.000014, "kl0": 62.613894, "kl1": 146.098962, "kl2": 146.098949}
{"epoch": 285, "phase": 4, "elapsed_s": 4.59, "total": 357.508472, "recon0": 0.696699, "recon1": 1.000005, "recon2": 1.00001, "kl0": 62.613899, "kl1": 146.09892, "kl2": 146.098938}
{"epoch": 286, "phase": 4, "elapsed_s": 4.27, "total": 357.50857, "recon0": 0.696757, "recon1": 1.000001, "recon2": 1.000009, "kl0": 62.613913, "kl1": 146.098956, "kl2": 146.098934}
{"epoch": 287, "phase": 4, "elapsed_s": 4.64, "total": 357.508573, "recon0": 0.69673, "recon1": 1.000006, "recon2": 1.000003, "kl0": 62.613953, "kl1": 146.098941, "kl2": 146.098939}
{"epoch": 288, "phase": 4, "elapsed_s": 4.34, "total": 357.508487, "recon0": 0.696724, "recon1": 1.000005, "recon2": 1.000002, "kl0": 62.613881, "kl1": 146.098938, "kl2": 146.098937}
{"epoch": 289, "phase": 4, "elapsed_s": 4.47, "total": 357.508513, "recon0": 0.696767, "recon1": 1.000003, "recon2": 1.000005, "kl0": 62.613871, "kl1": 146.098933, "kl2": 146.098935}
{"epoch": 290, "phase": 4, "elapsed_s": 4.15, "total": 357.508573, "recon0": 0.696788, "recon1": 1.000002, "recon2": 1.000013, "kl0": 62.613888, "kl1": 146.098946, "kl2": 146.098936}
{"epoch": 291, "phase": 4, "elapsed_s": 4.53, "total": 357.508354, "recon0": 0.696605, "recon1": 1.000005, "recon2": 1.000002, "kl0": 62.613871, "kl1": 146.098935, "kl2": 146.098935}
{"epoch": 292, "phase": 4, "elapsed_s": 4.41, "total": 357.508821, "recon0": 0.696871, "recon1": 1.000002, "recon2": 1.000004, "kl0": 62.614074, "kl1": 146.098936, "kl2": 146.098932}
{"epoch": 293, "phase": 4, "elapsed_s": 4.43, "total": 357.508301, "recon0": 0.696768, "recon1": 1.000006, "recon2": 1.000003, "kl0": 62.613648, "kl1": 146.098941, "kl2": 146.098936}
{"epoch": 294, "phase": 4, "elapsed_s": 4.39, "total": 357.508497, "recon0": 0.696589, "recon1": 1.000005, "recon2": 1.000003, "kl0": 62.613997, "kl1": 146.098967, "kl2": 146.098935}
{"epoch": 295, "phase": 4, "elapsed_s": 4.28, "total": 357.508329, "recon0": 0.696556, "recon1": 1.000004, "recon2": 1.0, "kl0": 62.613905, "kl1": 146.098929, "kl2": 146.098936}
{"epoch": 296, "phase": 4, "elapsed_s": 4.45, "total": 357.508607, "recon0": 0.696642, "recon1": 0.999999, "recon2": 1.000002, "kl0": 62.614094, "kl1": 146.098932, "kl2": 146.098937}
{"epoch": 297, "phase": 4, "elapsed_s": 4.43, "total": 357.508523, "recon0": 0.696731, "recon1": 1.000005, "recon2": 0.999999, "kl0": 62.613906, "kl1": 146.098945, "kl2": 146.098936}
{"epoch": 298, "phase": 4, "elapsed_s": 4.77, "total": 357.508516, "recon0": 0.696572, "recon1": 1.000006, "recon2": 1.000012, "kl0": 62.614058, "kl1": 146.098934, "kl2": 146.098934}
{"epoch": 299, "phase": 4, "elapsed_s": 4.45, "total": 357.508349, "recon0": 0.696625, "recon1": 1.000005, "recon2": 1.00001, "kl0": 62.613822, "kl1": 146.098951, "kl2": 146.098936}
{"epoch": 300, "phase": 4, "elapsed_s": 4.56, "total": 357.508386, "recon0": 0.696627, "recon1": 1.000006, "recon2": 1.000009, "kl0": 62.613864, "kl1": 146.098941, "kl2": 146.098939}
{"epoch": 301, "phase": 4, "elapsed_s": 4.6, "total": 357.508455, "recon0": 0.696721, "recon1": 1.000011, "recon2": 1.000016, "kl0": 62.613827, "kl1": 146.098948, "kl2": 146.098931}
{"epoch": 302, "phase": 4, "elapsed_s": 4.68, "total": 357.508406, "recon0": 0.696691, "recon1": 1.000002, "recon2": 1.000005, "kl0": 62.613836, "kl1": 146.098933, "kl2": 146.09894}
{"epoch": 303, "phase": 4, "elapsed_s": 4.67, "total": 357.508657, "recon0": 0.696732, "recon1": 0.999999, "recon2": 1.000006, "kl0": 62.61404, "kl1": 146.098945, "kl2": 146.098935}
{"epoch": 304, "phase": 4, "elapsed_s": 4.82, "total": 357.50847, "recon0": 0.69667, "recon1": 1.000002, "recon2": 1.000004, "kl0": 62.613923, "kl1": 146.098937, "kl2": 146.098933}
{"epoch": 305, "phase": 4, "elapsed_s": 4.4, "total": 357.508318, "recon0": 0.696694, "recon1": 1.000004, "recon2": 1.000003, "kl0": 62.613749, "kl1": 146.098931, "kl2": 146.098936}
{"epoch": 306, "phase": 4, "elapsed_s": 4.14, "total": 357.50834, "recon0": 0.696641, "recon1": 1.000001, "recon2": 1.000013, "kl0": 62.613814, "kl1": 146.098937, "kl2": 146.098935}
{"epoch": 307, "phase": 4, "elapsed_s": 4.87, "total": 357.508649, "recon0": 0.696624, "recon1": 1.000004, "recon2": 1.000008, "kl0": 62.614122, "kl1": 146.098955, "kl2": 146.098936}
{"epoch": 308, "phase": 4, "elapsed_s": 4.49, "total": 357.508615, "recon0": 0.696745, "recon1": 1.000002, "recon2": 1.000004, "kl0": 62.613979, "kl1": 146.09895, "kl2": 146.098935}
{"epoch": 309, "phase": 4, "elapsed_s": 4.42, "total": 357.508191, "recon0": 0.696586, "recon1": 1.000003, "recon2": 1.000005, "kl0": 62.613723, "kl1": 146.098937, "kl2": 146.098936}
{"epoch": 310, "phase": 4, "elapsed_s": 4.43, "total": 357.508561, "recon0": 0.696743, "recon1": 1.000003, "recon2": 1.000004, "kl0": 62.613923, "kl1": 146.098952, "kl2": 146.098936}
{"epoch": 311, "phase": 4, "elapsed_s": 4.49, "total": 357.508497, "recon0": 0.696693, "recon1": 1.000002, "recon2": 1.000012, "kl0": 62.613908, "kl1": 146.098945, "kl2": 146.098937}
{"epoch": 312, "phase": 4, "elapsed_s": 4.45, "total": 357.508555, "recon0": 0.696741, "recon1": 1.000002, "recon2": 1.000003, "kl0": 62.613921, "kl1": 146.098948, "kl2": 146.098939}
{"epoch": 313, "phase": 4, "elapsed_s": 4.68, "total": 357.508473, "recon0": 0.696463, "recon1": 1.000002, "recon2": 1.000002, "kl0": 62.614119, "kl1": 146.098947, "kl2": 146.09894}
{"epoch": 314, "phase": 4, "elapsed_s": 4.36, "total": 357.508525, "recon0": 0.696689, "recon1": 1.000005, "recon2": 1.000006, "kl0": 62.613928, "kl1": 146.098959, "kl2": 146.098938}
{"epoch": 315, "phase": 4, "elapsed_s": 4.2, "total": 357.508567, "recon0": 0.696621, "recon1": 0.999999, "recon2": 1.000006, "kl0": 62.613958, "kl1": 146.099045, "kl2": 146.098937}
{"epoch": 316, "phase": 4, "elapsed_s": 4.42, "total": 357.508457, "recon0": 0.696538, "recon1": 1.000003, "recon2": 1.000003, "kl0": 62.614006, "kl1": 146.098971, "kl2": 146.098936}
{"epoch": 317, "phase": 4, "elapsed_s": 4.1, "total": 357.508585, "recon0": 0.696622, "recon1": 1.000002, "recon2": 1.000005, "kl0": 62.614085, "kl1": 146.098937, "kl2": 146.098933}
{"epoch": 318, "phase": 4, "elapsed_s": 4.49, "total": 357.508534, "recon0": 0.696646, "recon1": 1.0, "recon2": 0.999997, "kl0": 62.614013, "kl1": 146.098942, "kl2": 146.098936}
{"epoch": 319, "phase": 4, "elapsed_s": 4.16, "total": 357.508534, "recon0": 0.696725, "recon1": 1.000006, "recon2": 1.000009, "kl0": 62.613885, "kl1": 146.098977, "kl2": 146.098933}
{"epoch": 320, "phase": 4, "elapsed_s": 4.46, "total": 357.508603, "recon0": 0.696733, "recon1": 1.000003, "recon2": 1.000007, "kl0": 62.613948, "kl1": 146.09898, "kl2": 146.098932}
{"epoch": 321, "phase": 4, "elapsed_s": 4.27, "total": 357.508288, "recon0": 0.696632, "recon1": 1.000006, "recon2": 1.000006, "kl0": 62.613758, "kl1": 146.098947, "kl2": 146.098939}
{"epoch": 322, "phase": 4, "elapsed_s": 4.46, "total": 357.508406, "recon0": 0.696551, "recon1": 1.000005, "recon2": 1.000007, "kl0": 62.613964, "kl1": 146.098946, "kl2": 146.098933}
{"epoch": 323, "phase": 4, "elapsed_s": 4.61, "total": 357.508458, "recon0": 0.696554, "recon1": 1.000002, "recon2": 1.000002, "kl0": 62.613944, "kl1": 146.099022, "kl2": 146.098934}
{"epoch": 324, "phase": 4, "elapsed_s": 4.24, "total": 357.50832, "recon0": 0.696757, "recon1": 1.000002, "recon2": 1.000004, "kl0": 62.613696, "kl1": 146.098924, "kl2": 146.098936}
{"epoch": 325, "phase": 4, "elapsed_s": 4.57, "total": 357.508433, "recon0": 0.696713, "recon1": 1.0, "recon2": 1.000005, "kl0": 62.613782, "kl1": 146.098999, "kl2": 146.098934}
{"epoch": 326, "phase": 4, "elapsed_s": 4.08, "total": 357.50814, "recon0": 0.696502, "recon1": 1.000002, "recon2": 1.000006, "kl0": 62.613743, "kl1": 146.098949, "kl2": 146.098938}
{"epoch": 327, "phase": 4, "elapsed_s": 4.37, "total": 357.508528, "recon0": 0.696822, "recon1": 1.000004, "recon2": 1.000014, "kl0": 62.613728, "kl1": 146.099025, "kl2": 146.098935}
{"epoch": 328, "phase": 4, "elapsed_s": 4.51, "total": 357.508499, "recon0": 0.696682, "recon1": 1.000003, "recon2": 1.000002, "kl0": 62.613899, "kl1": 146.098977, "kl2": 146.098935}
{"epoch": 329, "phase": 4, "elapsed_s": 4.56, "total": 357.508235, "recon0": 0.69663, "recon1": 1.000003, "recon2": 1.000008, "kl0": 62.613703, "kl1": 146.098952, "kl2": 146.098939}
{"epoch": 330, "phase": 4, "elapsed_s": 4.2, "total": 357.508445, "recon0": 0.696646, "recon1": 1.000003, "recon2": 1.00001, "kl0": 62.613887, "kl1": 146.098963, "kl2": 146.098936}
{"epoch": 331, "phase": 4, "elapsed_s": 3.97, "total": 357.508426, "recon0": 0.696552, "recon1": 1.000007, "recon2": 1.000009, "kl0": 62.613976, "kl1": 146.098946, "kl2": 146.098936}
{"epoch": 332, "phase": 4, "elapsed_s": 4.28, "total": 357.508478, "recon0": 0.696749, "recon1": 1.000002, "recon2": 1.000006, "kl0": 62.613837, "kl1": 146.098953, "kl2": 146.098932}
{"epoch": 333, "phase": 4, "elapsed_s": 4.15, "total": 357.508253, "recon0": 0.696554, "recon1": 1.000001, "recon2": 1.000001, "kl0": 62.613824, "kl1": 146.098937, "kl2": 146.098935}
{"epoch": 334, "phase": 4, "elapsed_s": 4.26, "total": 357.508693, "recon0": 0.696684, "recon1": 1.000001, "recon2": 1.000003, "kl0": 62.614064, "kl1": 146.099007, "kl2": 146.098934}
{"epoch": 335, "phase": 4, "elapsed_s": 4.06, "total": 357.508652, "recon0": 0.696877, "recon1": 1.000007, "recon2": 1.000006, "kl0": 62.613908, "kl1": 146.098917, "kl2": 146.098937}
{"epoch": 336, "phase": 4, "elapsed_s": 4.39, "total": 357.508355, "recon0": 0.696603, "recon1": 1.000003, "recon2": 1.0, "kl0": 62.613866, "kl1": 146.098948, "kl2": 146.098935}
{"epoch": 337, "phase": 4, "elapsed_s": 4.78, "total": 357.508362, "recon0": 0.696534, "recon1": 1.000005, "recon2": 1.000003, "kl0": 62.613953, "kl1": 146.098933, "kl2": 146.098934}
{"epoch": 338, "phase": 4, "elapsed_s": 4.55, "total": 357.508468, "recon0": 0.696718, "recon1": 1.000014, "recon2": 1.000005, "kl0": 62.613861, "kl1": 146.098934, "kl2": 146.098936}
{"epoch": 339, "phase": 4, "elapsed_s": 4.34, "total": 357.508602, "recon0": 0.696712, "recon1": 1.000005, "recon2": 1.000005, "kl0": 62.61396, "kl1": 146.098986, "kl2": 146.098935}
{"epoch": 340, "phase": 4, "elapsed_s": 3.92, "total": 357.50841, "recon0": 0.696642, "recon1": 1.000002, "recon2": 1.000002, "kl0": 62.613887, "kl1": 146.098943, "kl2": 146.098935}
{"epoch": 341, "phase": 4, "elapsed_s": 4.25, "total": 357.508368, "recon0": 0.696701, "recon1": 1.000003, "recon2": 1.000008, "kl0": 62.61379, "kl1": 146.098932, "kl2": 146.098935}
{"epoch": 342, "phase": 4, "elapsed_s": 4.57, "total": 357.508829, "recon0": 0.696823, "recon1": 0.999998, "recon2": 1.000002, "kl0": 62.614117, "kl1": 146.098948, "kl2": 146.098941}
{"epoch": 343, "phase": 4, "elapsed_s": 4.38, "total": 357.508626, "recon0": 0.69677, "recon1": 1.000005, "recon2": 1.000001, "kl0": 62.613955, "kl1": 146.098961, "kl2": 146.098934}
{"epoch": 344, "phase": 4, "elapsed_s": 4.23, "total": 357.508445, "recon0": 0.696646, "recon1": 1.000008, "recon2": 1.000008, "kl0": 62.613883, "kl1": 146.098963, "kl2": 146.098937}
{"epoch": 345, "phase": 4, "elapsed_s": 4.08, "total": 357.50854, "recon0": 0.696736, "recon1": 0.999999, "recon2": 1.0, "kl0": 62.613923, "kl1": 146.098949, "kl2": 146.098933}
{"epoch": 346, "phase": 4, "elapsed_s": 4.25, "total": 357.508199, "recon0": 0.696577, "recon1": 1.000002, "recon2": 1.000009, "kl0": 62.613729, "kl1": 146.098944, "kl2": 146.098938}

View File

@@ -0,0 +1,245 @@
"""
z1[7] Mechanical Simplification + FLINT 512-bit amplification.
z1[7] VAE finding:
w750_vel_norm r=-0.674 (long-term velocity reversing)
w300_vel_norm r=-0.357
w50_instability r=+0.421 (short-term chaos spiking)
= "eigenspace stress reversal"
Steps:
1. Mechanical proxy: linear combo from correlations
2. Different activations
3. PCA comparison
4. EDAIN-KL + HD 512-dim projection (FLINT)
"""
import sys, os
sys.stdout.reconfigure(encoding='utf-8', errors='replace')
sys.path.insert(0, os.path.dirname(__file__))
sys.path.insert(0, r"C:\Users\Lenovo\Documents\- DOLPHIN NG HD HCM TSF Predict")
import numpy as np
from pathlib import Path
HERE = Path(__file__).parent
WINDOWS = [50, 150, 300, 750]
T1_NAMES = []
for w in WINDOWS:
for f in ['log_lmax', 'vel_norm', 'gap_ratio', 'instability', 'rtp']:
T1_NAMES.append(f"w{w}_{f}")
T0_NAMES = ['bull_pct', 'bear_pct', 'side_pct', 'sin_hour', 'cos_hour', 'sin_day', 'cos_day', 'has_eigen']
print("Loading corpus...")
from corpus_builder import DolphinCorpus, OFF, T1 as T1_DIM
corpus = DolphinCorpus.load(str(HERE / 'corpus_cache.npz'))
idx = corpus.mask[:, 1]
X_e = corpus.X[idx]
mask_e = corpus.mask[idx]
t1 = X_e[:, OFF[1]:OFF[1]+T1_DIM].copy() # (16607, 20)
t0 = X_e[:, OFF[0]:OFF[0]+8].copy()
print(f"T1 shape: {t1.shape}")
# Feature shortcuts
vel_w50 = t1[:, 1]
vel_w150 = t1[:, 6]
vel_w300 = t1[:, 11]
vel_w750 = t1[:, 16]
inst_w50 = t1[:, 3]
inst_w300= t1[:, 13]
lmax_w50 = t1[:, 0]
lmax_w750= t1[:, 15]
# ================================================================
# 1. MECHANICAL PROXIES
# ================================================================
print("\n" + "="*65)
print("1. MECHANICAL z1[7] PROXIES")
print("="*65)
proxy_A = -0.674*vel_w750 - 0.357*vel_w300 + 0.421*inst_w50
proxy_B = inst_w50 - vel_w750
proxy_C = vel_w50 - vel_w750
proxy_D = inst_w50 * (-vel_w750)
proxy_E = (inst_w50 - inst_w300) - (vel_w50 - vel_w750) # instability delta + vel divergence
for name, p in [
('A: linear VAE weights', proxy_A),
('B: inst_w50 - vel_w750', proxy_B),
('C: vel_w50 - vel_w750 (cross-scale divergence)', proxy_C),
('D: inst_w50 * -vel_w750 (interaction)', proxy_D),
('E: dinst - dvel (delta-delta)', proxy_E),
]:
nans = np.isnan(p).sum()
pv = p[np.isfinite(p)]
print(f"\n{name}:")
print(f" std={pv.std():.4f} p5={np.percentile(pv,5):.4f} "
f"p50={np.percentile(pv,50):.4f} p95={np.percentile(pv,95):.4f} NaN={nans}")
skew = float(((pv - pv.mean())**3).mean() / (pv.std()**3 + 1e-8))
kurt = float(((pv - pv.mean())**4).mean() / (pv.std()**4 + 1e-8))
print(f" skew={skew:.3f} kurt={kurt:.2f} (>3 = heavy tails = signal potential)")
# ================================================================
# 2. WHAT DOES HIGH proxy_B LOOK LIKE IN FULL T1 SPACE?
# ================================================================
print("\n" + "="*65)
print("2. HIGH vs LOW proxy_B = inst_w50 - vel_w750")
print("="*65)
p = proxy_B
lo = p < np.percentile(p, 10)
hi = p > np.percentile(p, 90)
print(f"N low={lo.sum()} N high={hi.sum()}")
print(f"\n{'Feature':<22} {'ALL':>8} {'LOW10%':>8} {'HIGH10%':>8} {'diff':>8}")
for i, name in enumerate(T1_NAMES):
a = t1[:, i].mean()
l = t1[lo, i].mean()
h = t1[hi, i].mean()
d = h - l
if abs(d) > 0.02:
print(f" {name:<20} {a:8.4f} {l:8.4f} {h:8.4f} {d:+8.4f}")
print(f"\n T0 context:")
for i, name in enumerate(T0_NAMES[:3]):
print(f" {name:<20} {t0[:,i].mean():8.3f} {t0[lo,i].mean():8.3f} {t0[hi,i].mean():8.3f} {t0[hi,i].mean()-t0[lo,i].mean():+8.3f}")
# ================================================================
# 3. ACTIVATION FUNCTIONS
# ================================================================
print("\n" + "="*65)
print("3. ACTIVATION FUNCTIONS ON proxy_B")
print("="*65)
def softplus(x):
return np.log1p(np.exp(np.clip(x, -30, 30)))
def sigmoid(x):
return 1.0 / (1.0 + np.exp(-np.clip(x, -30, 30)))
pb_z = (proxy_B - proxy_B.mean()) / (proxy_B.std() + 1e-8)
for name, act in [
('raw z-score', pb_z),
('relu', np.maximum(0, pb_z)),
('tanh', np.tanh(pb_z)),
('softplus', softplus(pb_z)),
('sigmoid', sigmoid(pb_z)),
('sign*log1p(|x|)', np.sign(pb_z) * np.log1p(np.abs(pb_z))),
('x^3 (cubic)', pb_z**3),
]:
if np.isnan(act).any():
print(f" {name:<22} NaN!")
continue
skew = float(((act - act.mean())**3).mean() / (act.std()**3 + 1e-8))
kurt = float(((act - act.mean())**4).mean() / (act.std()**4 + 1e-8))
# High kurtosis = heavy tails = outliers preserved = signal in extremes
print(f" {name:<22} std={act.std():.4f} skew={skew:+.3f} kurt={kurt:6.1f}")
# ================================================================
# 4. PCA vs VAE
# ================================================================
print("\n" + "="*65)
print("4. PCA ON T1: linear basis vs VAE z1 basis")
print("="*65)
t1_z = (t1 - t1.mean(0)) / (t1.std(0) + 1e-8)
U, S, Vt = np.linalg.svd(t1_z, full_matrices=False)
explained = S**2 / (S**2).sum()
print(f"Explained variance: {np.round(100*explained[:8], 1)}%")
for pc_i in range(5):
loadings = Vt[pc_i]
top3 = np.argsort(np.abs(loadings))[-3:][::-1]
pc_scores = t1_z @ Vt[pc_i]
r_pb = np.corrcoef(pc_scores, proxy_B)[0,1]
print(f"PC{pc_i+1} ({100*explained[pc_i]:.1f}%) r(proxy_B)={r_pb:+.3f}: "
+ " ".join(f"{T1_NAMES[j]}={loadings[j]:+.3f}" for j in top3))
# ================================================================
# 5. FLINT EDAIN-KL + HD PROJECTION
# ================================================================
print("\n" + "="*65)
print("5. FLINT: EDAIN-KL + HD PROJECTION 20->512")
print("="*65)
try:
from SILOQY_NN_Kernel_COMPLETE6 import MCDAINLayer
FLINT_OK = True
print("FLINT OK")
except ImportError as e:
FLINT_OK = False
print(f"FLINT import failed: {e}")
if FLINT_OK:
# 5a: MCDAINLayer (no training needed, NaN-safe)
print("\n5a: MCDAINLayer normalization:")
t1_f32 = t1.astype(np.float32)
try:
import torch
t1_torch = torch.from_numpy(t1_f32)
mc = MCDAINLayer(input_dim=20)
with torch.no_grad():
t1_mc = mc(t1_torch).numpy()
mc_std = t1_mc.std(0)
print(f" Per-dim std after MCDAIN: {mc_std.round(3)}")
print(f" Max std: {mc_std.max():.4f} Min std: {mc_std.min():.4f}")
# Proxy_B in MCDAIN space
pb_mc = t1_mc[:, 3] - t1_mc[:, 16] # inst_w50 - vel_w750
print(f" proxy_B (MCDAIN): std={pb_mc.std():.4f} "
f"skew={float(((pb_mc-pb_mc.mean())**3).mean()/(pb_mc.std()**3+1e-8)):.3f} "
f"kurt={float(((pb_mc-pb_mc.mean())**4).mean()/(pb_mc.std()**4+1e-8)):.1f}")
print(f" Variance ratio MCDAIN/raw: {pb_mc.var()/proxy_B.var():.3f}x")
except Exception as ex:
print(f" MCDAINLayer: {ex}")
# 5b: HD projection 20->512 with ReLU
print("\n5b: HD projection (20->512, ReLU):")
try:
np.random.seed(42)
W_hd = np.random.randn(20, 512).astype(np.float32) * np.sqrt(2.0/20)
t1_n = t1_mc if 't1_mc' in dir() else t1_z.astype(np.float32)
H = np.maximum(0, t1_n @ W_hd) # (16607, 512)
hd_var = H.var(0)
active = (hd_var > 0.01).sum()
print(f" Active HD dims (var>0.01): {active}/512")
# Which HD dims correlate most with proxy_B?
pb_n = (proxy_B - proxy_B.mean()) / (proxy_B.std() + 1e-8)
hd_r = np.array([np.corrcoef(pb_n, H[:, d])[0, 1] for d in range(512)])
print(f" |r(HD, proxy_B)| > 0.3: {(np.abs(hd_r)>0.3).sum()} dims")
print(f" |r(HD, proxy_B)| > 0.5: {(np.abs(hd_r)>0.5).sum()} dims")
print(f" max |r|: {np.abs(hd_r).max():.4f}")
top5 = np.argsort(np.abs(hd_r))[-5:][::-1]
print(f" Top HD dims:")
for d in top5:
print(f" HD[{d:3d}]: r={hd_r[d]:+.4f} var={hd_var[d]:.4f}")
# 5c: Can HD space reconstruct proxy_B better than PCA?
print("\n5c: Ridge regression HD->proxy_B (R^2 comparison):")
from numpy.linalg import lstsq
# PCA baseline: PC1 alone
pc1_scores = (t1_z @ Vt[0]).reshape(-1, 1)
coef_pc1, _, _, _ = lstsq(pc1_scores, proxy_B, rcond=None)
pred_pc1 = pc1_scores @ coef_pc1
ss_res = ((proxy_B - pred_pc1)**2).sum()
ss_tot = ((proxy_B - proxy_B.mean())**2).sum()
r2_pc1 = 1 - ss_res/ss_tot
print(f" PC1 alone R2={r2_pc1:.4f}")
# Top 8 PCs
pc8 = (t1_z @ Vt[:8].T)
coef8, _, _, _ = lstsq(pc8, proxy_B, rcond=None)
pred8 = pc8 @ coef8
r2_pc8 = 1 - ((proxy_B - pred8)**2).sum()/ss_tot
print(f" Top 8 PCs R2={r2_pc8:.4f}")
# Top 8 HD dims
top8_hd = np.argsort(np.abs(hd_r))[-8:]
H8 = H[:, top8_hd]
coef_hd8, _, _, _ = lstsq(H8, proxy_B, rcond=None)
pred_hd8 = H8 @ coef_hd8
r2_hd8 = 1 - ((proxy_B - pred_hd8)**2).sum()/ss_tot
print(f" Top 8 HD dims R2={r2_hd8:.4f}")
print(f" HD/PCA improvement: {r2_hd8/max(r2_pc8, 1e-8):.3f}x")
except Exception as ex:
import traceback; traceback.print_exc()
print("\nDone.")