Files
DOLPHIN/nautilus_dolphin/test_pf_ob_intelligence.py

588 lines
26 KiB
Python
Raw Normal View History

"""OB Intelligence — 4-Subsystem Test Suite.
Phase 1: Data validation (CSVOBProvider)
Phase 2: Feature computation (all 4 sub-systems)
Phase 3: Signal quality (forward return correlation)
Phase 4: Per-asset placement quality
Phase 5: Market-wide detection
Phase 6: Macro regime detection
Phase 7: Engine integration (no regression vs baseline)
"""
import sys, time, math
from pathlib import Path
import numpy as np
import pandas as pd
sys.path.insert(0, str(Path(__file__).parent))
OB_DATA_DIR = Path(r"C:\Users\Lenovo\Documents\- DOLPHIN NG HD HCM TSF Predict\ob_data")
VBT_DIR = Path(r"C:\Users\Lenovo\Documents\- DOLPHIN NG HD HCM TSF Predict\vbt_cache")
# ============================================================================
# Phase 0: Compile numba kernels
# ============================================================================
print("=" * 70)
print(" OB INTELLIGENCE 4-SUBSYSTEM TEST SUITE")
print("=" * 70)
print("\nPhase 0: 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 (
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,
OBPlacementFeatures, OBSignalFeatures, OBMarketFeatures, OBMacroFeatures,
NEUTRAL_PLACEMENT, NEUTRAL_SIGNAL, NEUTRAL_MARKET, NEUTRAL_MACRO,
OBFeatureEngine,
)
from nautilus_dolphin.nautilus.ob_provider import CSVOBProvider, MockOBProvider
from nautilus_dolphin.nautilus.ob_placer import OBPlacer
# Warmup JIT
_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)
# Warmup OB kernels
_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, 0.2, 0.3], dtype=np.float64), 4)
compute_withdrawal_velocity_nb(np.array([100.0, 110.0, 105.0], dtype=np.float64), 2)
compute_market_agreement_nb(np.array([0.1, -0.05, 0.2, 0.15], dtype=np.float64), 4)
compute_cascade_signal_nb(np.array([-0.05, -0.15, 0.02, -0.12], dtype=np.float64), 4, -0.10)
print(f" JIT compile: {time.time() - t0c:.1f}s")
from nautilus_dolphin.nautilus.alpha_orchestrator import NDAlphaEngine
from nautilus_dolphin.nautilus.adaptive_circuit_breaker import AdaptiveCircuitBreaker
# ============================================================================
# Phase 1: Data Validation
# ============================================================================
print(f"\n{'='*70}")
print(" PHASE 1: DATA VALIDATION (CSVOBProvider)")
print(f"{'='*70}")
csv_provider = CSVOBProvider(str(OB_DATA_DIR))
csv_assets = csv_provider.get_assets()
print(f"\n Available assets: {csv_assets}")
phase1_ok = True
for asset in csv_assets:
ts = csv_provider.get_all_timestamps(asset)
n = len(ts)
snap0 = csv_provider.get_snapshot(asset, ts[0]) if n > 0 else None
snap_last = csv_provider.get_snapshot(asset, ts[-1]) if n > 0 else None
# Check integrity
nan_count = 0
neg_count = 0
for i in range(min(n, 100)): # Sample first 100
s = csv_provider.get_snapshot(asset, ts[i])
if s is None:
continue
if np.any(np.isnan(s.bid_notional)) or np.any(np.isnan(s.ask_notional)):
nan_count += 1
if np.any(s.bid_notional < 0) or np.any(s.ask_notional < 0):
neg_count += 1
t_range = (ts[-1] - ts[0]) / 3600 if n > 1 else 0
print(f"\n {asset}:")
print(f" Snapshots: {n}")
print(f" Time range: {t_range:.1f} hours")
if snap0:
print(f" Bid depth (1%): ${snap0.bid_notional[0]:,.0f}")
print(f" Ask depth (1%): ${snap0.ask_notional[0]:,.0f}")
print(f" Total 5-level: ${sum(snap0.bid_notional) + sum(snap0.ask_notional):,.0f}")
if nan_count > 0:
print(f" WARNING: {nan_count} snapshots with NaN")
phase1_ok = False
if neg_count > 0:
print(f" WARNING: {neg_count} snapshots with negative notional")
phase1_ok = False
if n == 0:
print(f" WARNING: No data")
phase1_ok = False
print(f"\n Phase 1: {'PASS' if phase1_ok else 'FAIL'}")
# ============================================================================
# Phase 2: Feature Computation (All 4 Sub-systems)
# ============================================================================
print(f"\n{'='*70}")
print(" PHASE 2: FEATURE COMPUTATION (4 SUB-SYSTEMS)")
print(f"{'='*70}")
t2 = time.time()
# Use CSVOBProvider for real data feature computation
ob_engine_csv = OBFeatureEngine(csv_provider)
if csv_assets:
ob_engine_csv.preload_date("2025-01-15", csv_assets)
print(f"\n Preload time: {time.time() - t2:.2f}s")
phase2_ok = True
for asset in csv_assets:
n = csv_provider.get_snapshot_count(asset)
if n == 0:
continue
# Collect features across all snapshots
imbalances = []
imbalances_ma5 = []
persistences = []
depth_qualities = []
fill_probs = []
spreads = []
for snap_idx in range(n):
p = ob_engine_csv._preloaded_placement.get(asset, {}).get(snap_idx)
s = ob_engine_csv._preloaded_signal.get(asset, {}).get(snap_idx)
if p:
depth_qualities.append(p.depth_quality)
fill_probs.append(p.fill_probability)
spreads.append(p.spread_proxy_bps)
if s:
imbalances.append(s.imbalance)
imbalances_ma5.append(s.imbalance_ma5)
persistences.append(s.imbalance_persistence)
print(f"\n {asset} Feature Distributions:")
if imbalances:
imb = np.array(imbalances)
print(f" Sub-2 Imbalance: mean={np.mean(imb):.4f} std={np.std(imb):.4f} "
f"p5={np.percentile(imb,5):.4f} p50={np.percentile(imb,50):.4f} p95={np.percentile(imb,95):.4f}")
# Validate ranges
if np.min(imb) < -1.0 or np.max(imb) > 1.0:
print(f" FAIL: imbalance out of [-1, 1]")
phase2_ok = False
if persistences:
per = np.array(persistences)
print(f" Sub-2 Persistence: mean={np.mean(per):.4f} std={np.std(per):.4f} "
f"p5={np.percentile(per,5):.4f} p50={np.percentile(per,50):.4f} p95={np.percentile(per,95):.4f}")
if np.min(per) < 0 or np.max(per) > 1.0:
print(f" FAIL: persistence out of [0, 1]")
phase2_ok = False
if depth_qualities:
dq = np.array(depth_qualities)
print(f" Sub-1 DepthQual: mean={np.mean(dq):.4f} std={np.std(dq):.4f} "
f"p5={np.percentile(dq,5):.4f} p50={np.percentile(dq,50):.4f} p95={np.percentile(dq,95):.4f}")
if np.min(dq) < 0:
print(f" FAIL: depth_quality < 0")
phase2_ok = False
if fill_probs:
fp = np.array(fill_probs)
print(f" Sub-1 FillProb: mean={np.mean(fp):.4f} std={np.std(fp):.4f} "
f"p5={np.percentile(fp,5):.4f} p50={np.percentile(fp,50):.4f} p95={np.percentile(fp,95):.4f}")
if np.min(fp) < 0 or np.max(fp) > 1.0:
print(f" FAIL: fill_probability out of [0, 1]")
phase2_ok = False
# Market-wide features (Sub-3)
print(f"\n Sub-3 Market-Wide Features:")
mkt_med_imb = []
mkt_agree = []
n_ref = csv_provider.get_snapshot_count(csv_assets[0]) if csv_assets else 0
for snap_idx in range(n_ref):
m = ob_engine_csv._preloaded_market.get(snap_idx)
if m:
mkt_med_imb.append(m.median_imbalance)
mkt_agree.append(m.agreement_pct)
if mkt_med_imb:
mi = np.array(mkt_med_imb)
ag = np.array(mkt_agree)
print(f" Median imbalance: mean={np.mean(mi):.4f} std={np.std(mi):.4f}")
print(f" Agreement pct: mean={np.mean(ag):.4f} std={np.std(ag):.4f} "
f"p5={np.percentile(ag,5):.4f} p95={np.percentile(ag,95):.4f}")
high_agree = np.sum(ag > 0.8) / len(ag) * 100
print(f" Snapshots with agreement > 0.8: {high_agree:.1f}%")
if np.min(ag) < 0 or np.max(ag) > 1.0:
print(f" FAIL: agreement out of [0, 1]")
phase2_ok = False
# Sub-4 Macro
macro = ob_engine_csv.get_macro()
print(f"\n Sub-4 Macro Regime (final state):")
print(f" Depth velocity: {macro.depth_velocity:.4f}")
print(f" Cascade count: {macro.cascade_count}")
print(f" Acceleration: {macro.acceleration:.6f}")
print(f" Regime signal: {macro.regime_signal} ({['CALM','NEUTRAL','STRESS'][macro.regime_signal + 1]})")
print(f"\n Phase 2: {'PASS' if phase2_ok else 'FAIL'}")
# ============================================================================
# Phase 3: Signal Quality (OB Feature Correlation with Itself)
# ============================================================================
print(f"\n{'='*70}")
print(" PHASE 3: OB FEATURE INTERNAL CONSISTENCY")
print(f"{'='*70}")
# Check that imbalance and imbalance_ma5 are correlated (sanity check)
if csv_assets:
asset = csv_assets[0]
n = csv_provider.get_snapshot_count(asset)
raw_imb = []
ma5_imb = []
for snap_idx in range(n):
s = ob_engine_csv._preloaded_signal.get(asset, {}).get(snap_idx)
if s:
raw_imb.append(s.imbalance)
ma5_imb.append(s.imbalance_ma5)
if len(raw_imb) > 10:
from scipy import stats
corr, pval = stats.spearmanr(raw_imb, ma5_imb)
print(f"\n {asset}: imbalance vs imbalance_ma5 Spearman r={corr:.4f} (p={pval:.2e})")
if corr < 0.5:
print(f" WARNING: Low correlation between raw and smoothed imbalance")
# Check fill_prob vs depth_quality monotonicity
dqs = []
fps = []
for snap_idx in range(n):
p = ob_engine_csv._preloaded_placement.get(asset, {}).get(snap_idx)
if p:
dqs.append(p.depth_quality)
fps.append(p.fill_probability)
if len(dqs) > 10:
corr2, pval2 = stats.spearmanr(dqs, fps)
print(f" {asset}: depth_quality vs fill_prob Spearman r={corr2:.4f} (p={pval2:.2e})")
if corr2 < 0.9:
print(f" WARNING: fill_prob should be monotonically related to depth_quality")
print(f"\n Phase 3: PASS (informational)")
# ============================================================================
# Phase 4: Per-Asset Placement Quality
# ============================================================================
print(f"\n{'='*70}")
print(" PHASE 4: PER-ASSET PLACEMENT QUALITY")
print(f"{'='*70}")
for asset in csv_assets:
n = csv_provider.get_snapshot_count(asset)
if n == 0:
continue
dqs = [ob_engine_csv._preloaded_placement.get(asset, {}).get(i) for i in range(n)]
dqs = [d for d in dqs if d is not None]
mean_dq = np.mean([d.depth_quality for d in dqs])
mean_fp = np.mean([d.fill_probability for d in dqs])
mean_sp = np.mean([d.spread_proxy_bps for d in dqs])
print(f"\n {asset}:")
print(f" Mean depth quality: {mean_dq:.3f}")
print(f" Mean fill prob: {mean_fp:.3f}")
print(f" Mean spread proxy: {mean_sp:.3f} bps")
# SmartPlacer advice test
print(f"\n SmartPlacer Advice (sample):")
placer = OBPlacer()
if csv_assets:
asset = csv_assets[0]
n = csv_provider.get_snapshot_count(asset)
mid = n // 2
p = ob_engine_csv._preloaded_placement.get(asset, {}).get(mid, NEUTRAL_PLACEMENT)
s = ob_engine_csv._preloaded_signal.get(asset, {}).get(mid, NEUTRAL_SIGNAL)
for conf in [0.3, 0.5, 0.7, 0.9]:
advice = placer.advise(p, s, signal_confidence=conf, direction=-1)
print(f" conf={conf:.1f}: {advice.method:15s} offset={advice.offset_bps:.2f}bps "
f"timeout={advice.timeout_s:.0f}s reason={advice.reason}")
print(f"\n Phase 4: PASS (informational)")
# ============================================================================
# Phase 5: Market-Wide Agreement Detection
# ============================================================================
print(f"\n{'='*70}")
print(" PHASE 5: MARKET-WIDE AGREEMENT DETECTION")
print(f"{'='*70}")
if mkt_agree:
ag = np.array(mkt_agree)
mi = np.array(mkt_med_imb)
# Top 10 high-agreement moments
sorted_idx = np.argsort(ag)[::-1]
print(f"\n Top 10 high-agreement snapshots:")
for i in range(min(10, len(sorted_idx))):
idx = sorted_idx[i]
print(f" snap {idx:5d}: agreement={ag[idx]:.3f} median_imb={mi[idx]:+.4f}")
# Distribution of agreement
print(f"\n Agreement distribution:")
for thresh in [0.5, 0.6, 0.7, 0.8, 0.9, 1.0]:
pct = np.sum(ag >= thresh) / len(ag) * 100
print(f" >= {thresh:.1f}: {pct:5.1f}%")
print(f"\n Phase 5: PASS (informational)")
# ============================================================================
# Phase 6: Macro Regime Detection (Sub-4)
# ============================================================================
print(f"\n{'='*70}")
print(" PHASE 6: MACRO REGIME DETECTION")
print(f"{'='*70}")
# Trace macro features through time using the preloaded data
# We can examine the final macro state and trace snapshots for regime transitions
print(f"\n Final macro state:")
print(f" Depth velocity: {macro.depth_velocity:+.4f}")
print(f" Cascade count: {macro.cascade_count}")
print(f" Regime signal: {macro.regime_signal}")
# Simulate macro tracking across snapshots using Mock
mock_stress = MockOBProvider(imbalance_bias=-0.3, depth_scale=0.3, assets=csv_assets[:4] or ["BTCUSDT", "ETHUSDT"])
mock_calm = MockOBProvider(imbalance_bias=0.1, depth_scale=2.0, assets=csv_assets[:4] or ["BTCUSDT", "ETHUSDT"])
mock_neutral = MockOBProvider(imbalance_bias=0.0, depth_scale=1.0, assets=csv_assets[:4] or ["BTCUSDT", "ETHUSDT"])
for label, provider in [("STRESS", mock_stress), ("CALM", mock_calm), ("NEUTRAL", mock_neutral)]:
engine = OBFeatureEngine(provider)
engine.preload_date("test", provider.get_assets())
m = engine.get_macro()
print(f"\n Mock {label}: regime={m.regime_signal} velocity={m.depth_velocity:+.4f} cascade={m.cascade_count}")
print(f"\n Phase 6: PASS (informational)")
# ============================================================================
# Phase 7: Engine Integration (No Regression)
# ============================================================================
print(f"\n{'='*70}")
print(" PHASE 7: ENGINE INTEGRATION (BASELINE REGRESSION CHECK)")
print(f"{'='*70}")
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.0099, 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,
)
VD_THRESH = -0.02; VD_EXTREME = -0.05; CONVEXITY = 3.0
parquet_files = sorted(VBT_DIR.glob("*.parquet"))
if not parquet_files:
print("\n WARNING: No parquet files in VBT_DIR, skipping Phase 7")
else:
# Initialize ACB
acb = AdaptiveCircuitBreaker()
date_strings = [pf.stem for pf in parquet_files]
acb.preload_w750(date_strings)
# Pre-load data
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)) if all_vols else 0.001
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: continue
dv[i] = float(np.std(np.diff(seg)/seg[:-1]))
pq_data[pf.stem] = (df, ac, dv)
def strength_cubic(vel_div):
if vel_div >= VD_THRESH: return 0.0
raw = (VD_THRESH - vel_div) / (VD_THRESH - VD_EXTREME)
return min(1.0, max(0.0, raw)) ** CONVEXITY
def run_engine(label, ob_engine_instance=None):
"""Run full backtest with optional OB engine."""
engine = NDAlphaEngine(**ENGINE_KWARGS)
if ob_engine_instance is not None:
engine.set_ob_engine(ob_engine_instance)
bar_idx = 0; ph = {}; dstats = []
for pf in parquet_files:
ds = pf.stem; cs = engine.capital
engine.regime_direction = -1
engine.regime_dd_halt = False
acb_info = acb.get_dynamic_boost_for_date(ds, ob_engine=ob_engine_instance)
base_boost = acb_info['boost']
beta = acb_info['beta']
df, acols, dvol = pq_data[ds]
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(vd): bar_idx+=1; bid+=1; continue
prices = {}
for ac in acols:
p = row[ac]
if p and p > 0 and np.isfinite(p):
prices[ac] = float(p)
if ac not in ph: ph[ac] = []
ph[ac].append(float(p))
if not prices: bar_idx+=1; bid+=1; continue
vrok = False if bid < 100 else (np.isfinite(dvol[ri]) and dvol[ri] > vol_p60)
if beta > 0 and base_boost > 1.0:
ss = strength_cubic(float(vd))
engine.regime_size_mult = base_boost * (1.0 + beta * ss)
else:
engine.regime_size_mult = base_boost
engine.process_bar(bar_idx=bar_idx, vel_div=float(vd), prices=prices,
vol_regime_ok=vrok, price_histories=ph)
bar_idx+=1; bid+=1
dstats.append({'date': ds, 'pnl': engine.capital - cs, 'cap': engine.capital, 'beta': beta})
tr = engine.trade_history
w = [t for t in tr if t.pnl_absolute > 0]; l = [t for t in tr if t.pnl_absolute <= 0]
gw = sum(t.pnl_absolute for t in w) if w else 0
gl = abs(sum(t.pnl_absolute for t in l)) if l else 0
roi = (engine.capital - 25000) / 25000 * 100
pf_val = gw / gl if gl > 0 else 999
dr = [s['pnl']/25000*100 for s in dstats]
sharpe = np.mean(dr) / np.std(dr) * np.sqrt(365) if np.std(dr) > 0 else 0
peak_cap = 25000.0; max_dd = 0.0
for s in dstats:
peak_cap = max(peak_cap, s['cap'])
dd = (peak_cap - s['cap']) / peak_cap * 100
max_dd = max(max_dd, dd)
return {
'label': label, 'roi': roi, 'pf': pf_val, 'dd': max_dd,
'sharpe': sharpe, 'trades': len(tr), 'capital': engine.capital,
}
# Run A: Baseline (ob_engine=None) — should match PF 1.215, ROI +75.87%
print(f"\n Running baseline (ob_engine=None)...")
t7 = time.time()
result_a = run_engine("A: Baseline (no OB)")
t_a = time.time() - t7
print(f" {result_a['label']}: ROI={result_a['roi']:+.2f}% PF={result_a['pf']:.3f} "
f"DD={result_a['dd']:.2f}% Sharpe={result_a['sharpe']:.2f} Trades={result_a['trades']} [{t_a:.0f}s]")
# Run B: Neutral MockOBProvider (should produce SAME results as baseline)
print(f" Running with neutral MockOBProvider...")
mock_neutral_eng = OBFeatureEngine(MockOBProvider(imbalance_bias=0.0, depth_scale=1.0,
assets=["BTCUSDT", "ETHUSDT", "BNBUSDT", "SOLUSDT"]))
mock_neutral_eng.preload_date("mock", mock_neutral_eng.provider.get_assets())
t7b = time.time()
result_b = run_engine("B: Neutral OB", mock_neutral_eng)
t_b = time.time() - t7b
print(f" {result_b['label']}: ROI={result_b['roi']:+.2f}% PF={result_b['pf']:.3f} "
f"DD={result_b['dd']:.2f}% Sharpe={result_b['sharpe']:.2f} Trades={result_b['trades']} [{t_b:.0f}s]")
# Run C: Favorable MockOBProvider (imbalance confirms SHORT)
print(f" Running with favorable MockOBProvider (SHORT-confirming)...")
mock_fav = OBFeatureEngine(MockOBProvider(imbalance_bias=-0.3, depth_scale=1.5,
assets=["BTCUSDT", "ETHUSDT", "BNBUSDT", "SOLUSDT"]))
mock_fav.preload_date("mock", mock_fav.provider.get_assets())
t7c = time.time()
result_c = run_engine("C: Favorable OB", mock_fav)
t_c = time.time() - t7c
print(f" {result_c['label']}: ROI={result_c['roi']:+.2f}% PF={result_c['pf']:.3f} "
f"DD={result_c['dd']:.2f}% Sharpe={result_c['sharpe']:.2f} Trades={result_c['trades']} [{t_c:.0f}s]")
# Run D: Unfavorable MockOBProvider (imbalance contradicts SHORT)
print(f" Running with unfavorable MockOBProvider (SHORT-contradicting)...")
mock_unfav = OBFeatureEngine(MockOBProvider(imbalance_bias=+0.3, depth_scale=0.5,
assets=["BTCUSDT", "ETHUSDT", "BNBUSDT", "SOLUSDT"]))
mock_unfav.preload_date("mock", mock_unfav.provider.get_assets())
t7d = time.time()
result_d = run_engine("D: Unfavorable OB", mock_unfav)
t_d = time.time() - t7d
print(f" {result_d['label']}: ROI={result_d['roi']:+.2f}% PF={result_d['pf']:.3f} "
f"DD={result_d['dd']:.2f}% Sharpe={result_d['sharpe']:.2f} Trades={result_d['trades']} [{t_d:.0f}s]")
# Verification
print(f"\n {'='*60}")
print(f" INTEGRATION VERIFICATION")
print(f" {'='*60}")
all_ok = True
# Check A matches baseline
if abs(result_a['roi'] - 75.87) > 2.0:
print(f" FAIL: Baseline ROI {result_a['roi']:.2f}% vs expected ~75.87%")
all_ok = False
else:
print(f" PASS: Baseline ROI matches ({result_a['roi']:+.2f}%)")
if abs(result_a['pf'] - 1.215) > 0.02:
print(f" FAIL: Baseline PF {result_a['pf']:.3f} vs expected ~1.215")
all_ok = False
else:
print(f" PASS: Baseline PF matches ({result_a['pf']:.3f})")
# Check B: neutral OB should not change trade count (may change PF slightly due to OB edge replacement)
trade_diff = abs(result_b['trades'] - result_a['trades'])
if trade_diff > result_a['trades'] * 0.15:
print(f" WARN: Neutral OB trade count differs significantly: {result_b['trades']} vs {result_a['trades']}")
else:
print(f" PASS: Neutral OB trade count reasonable ({result_b['trades']} vs baseline {result_a['trades']})")
# Check C: favorable should generally help
if result_c['pf'] >= result_a['pf'] * 0.95:
print(f" PASS: Favorable OB PF ({result_c['pf']:.3f}) >= baseline floor ({result_a['pf']*0.95:.3f})")
else:
print(f" WARN: Favorable OB PF ({result_c['pf']:.3f}) below expected")
# Check D: unfavorable should still be profitable
if result_d['roi'] > 0:
print(f" PASS: Unfavorable OB still profitable ({result_d['roi']:+.2f}%)")
else:
print(f" WARN: Unfavorable OB went negative ({result_d['roi']:+.2f}%)")
print(f"\n Phase 7: {'PASS' if all_ok else 'NEEDS REVIEW'}")
# ============================================================================
# SUMMARY
# ============================================================================
print(f"\n{'='*70}")
print(" OB INTELLIGENCE TEST SUITE COMPLETE")
print(f"{'='*70}")
print(f" Phase 1 (Data Validation): {'PASS' if phase1_ok else 'FAIL'}")
print(f" Phase 2 (Feature Computation): {'PASS' if phase2_ok else 'FAIL'}")
print(f" Phase 3 (Internal Consistency): PASS")
print(f" Phase 4 (Placement Quality): PASS")
print(f" Phase 5 (Market Agreement): PASS")
print(f" Phase 6 (Macro Regime): PASS")
if parquet_files:
print(f" Phase 7 (No Regression): {'PASS' if all_ok else 'NEEDS REVIEW'}")
print(f"{'='*70}")