Files
DOLPHIN/nautilus_dolphin/test_pf_ob_putative_roi.py

281 lines
12 KiB
Python
Raw Normal View History

"""Putative ROI estimate: OB engine calibrated to REAL OB distributions.
Real OB data (2025-01-15) showed:
BTC imbalance: mean=-0.086 (mild sell pressure, confirms SHORT)
ETH imbalance: mean=-0.092
DASH imbalance: mean=+0.265 (buy pressure)
LTC imbalance: mean=+0.095
Overall: slight negative bias across majors
Depth quality: ~1.0 (median-normalized)
Fill probability: ~0.86
We calibrate MockOBProvider to these observed distributions and run the
full backtest to estimate putative ROI with realistic OB intelligence.
"""
import sys, time, math
from pathlib import Path
import numpy as np
import pandas as pd
sys.path.insert(0, str(Path(__file__).parent))
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
from nautilus_dolphin.nautilus.ob_provider import MockOBProvider
_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
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,
)
_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(f" JIT: {time.time() - t0c:.1f}s")
from nautilus_dolphin.nautilus.alpha_orchestrator import NDAlphaEngine
from nautilus_dolphin.nautilus.adaptive_circuit_breaker import AdaptiveCircuitBreaker
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.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"))
# Filter out catalog subdirectory parquets
parquet_files = [p for p in parquet_files if 'catalog' not in str(p)]
# Initialize ACB
acb = AdaptiveCircuitBreaker()
date_strings = [pf.stem for pf in parquet_files]
acb.preload_w750(date_strings)
# Pre-load VBT 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))
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):
import gc
gc.collect()
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))
# Cap history to prevent memory growth
if len(ph[ac]) > 500:
ph[ac] = ph[ac][-200:]
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:
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})
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)
mid = len(parquet_files) // 2
h1 = sum(s['pnl'] for s in dstats[:mid])
h2 = sum(s['pnl'] for s in dstats[mid:])
wr = len(w) / len(tr) * 100 if tr else 0.0
avg_win = np.mean([t.pnl_pct for t in w]) * 100 if w else 0.0
avg_loss = np.mean([t.pnl_pct for t in l]) * 100 if l else 0.0
return {
'label': label, 'roi': roi, 'pf': pf_val, 'dd': max_dd,
'sharpe': sharpe, 'trades': len(tr), 'capital': engine.capital,
'h1': h1, 'h2': h2, 'dstats': dstats,
'wr': wr, 'avg_win': avg_win, 'avg_loss': avg_loss,
'n_wins': len(w), 'n_losses': len(l),
}
print(f"\n{'='*75}")
print(f" PUTATIVE ROI WITH REAL-CALIBRATED OB INTELLIGENCE")
print(f"{'='*75}")
print(f" Real OB observations (2025-01-15):")
print(f" BTC imbalance: -0.086 ETH: -0.092 (sell pressure, confirms SHORT)")
print(f" DASH: +0.265 LTC: +0.095 (buy pressure, contradicts SHORT)")
print(f" Depth quality: ~1.0 Fill probability: ~0.86")
print(f" Market agreement: ~55% (rarely >80%)")
print(f" Backtest period: {date_strings[0]} to {date_strings[-1]} ({len(date_strings)} days)")
print(f"{'='*75}")
assets = ["BTCUSDT", "ETHUSDT", "BNBUSDT", "SOLUSDT"]
# Extended configs: per_biases=None uses global imb_bias; dict = per-asset overrides
# Real OB Jan-15: BTC=-0.086, ETH=-0.092 confirm SHORT; DASH=+0.265, LTC=+0.095 contradict.
# Backtest uses BTC/ETH/BNB/SOL - proxying with estimated BNB/SOL biases.
configs = [
# Label, imb_bias, depth_sc, per_biases, rationale
("Baseline (no OB)", None, None, None,
"No OB engine - Monte Carlo edge only"),
("All-confirm SHORT", -0.09, 1.0, None,
"All assets: sell pressure confirms SHORT (uniform -0.09)"),
("Real-calibrated (mixed)", -0.09, 1.0,
{"BTCUSDT": -0.086, "ETHUSDT": -0.092, "BNBUSDT": +0.05, "SOLUSDT": +0.05},
"BTC/ETH confirm (-0.09), BNB/SOL mildly contradict (+0.05) - realistic mixed"),
("Hard-skip test", -0.09, 1.0,
{"BTCUSDT": -0.086, "ETHUSDT": -0.092, "BNBUSDT": +0.20, "SOLUSDT": +0.20},
"BTC/ETH confirm; BNB/SOL strongly contradict (+0.20, persist>0.60) -> OB_SKIP"),
("All-contradict SHORT", +0.20, 1.0, None,
"All assets: buy pressure contradicts SHORT - maximum OB_SKIP filtering"),
]
results = []
for label, imb_bias, depth_sc, per_biases, rationale in configs:
print(f"\n Running: {label}")
print(f" Rationale: {rationale}")
t0 = time.time()
if imb_bias is None:
# Baseline: no OB engine
r = run_engine(label, None)
else:
mock = MockOBProvider(
imbalance_bias=imb_bias, depth_scale=depth_sc, assets=assets,
imbalance_biases=per_biases or {},
)
ob_eng = OBFeatureEngine(mock)
ob_eng.preload_date("mock", assets)
r = run_engine(label, ob_eng)
elapsed = time.time() - t0
r['rationale'] = rationale
results.append(r)
print(f" ROI={r['roi']:+.2f}% PF={r['pf']:.3f} DD={r['dd']:.2f}% "
f"Sharpe={r['sharpe']:.2f} Trades={r['trades']} [{elapsed:.0f}s]")
print(f" WR={r['wr']:.1f}% AvgWin={r['avg_win']:+.3f}% AvgLoss={r['avg_loss']:+.3f}% "
f"W={r['n_wins']} L={r['n_losses']}")
if r['h1'] != 0:
print(f" Capital: ${r['capital']:,.2f} H1=${r['h1']:+,.2f} H2=${r['h2']:+,.2f} "
f"H2/H1={r['h2']/r['h1']:.2f}")
# ============================================================================
# Summary table
# ============================================================================
print(f"\n{'='*75}")
print(f" PUTATIVE ROI SUMMARY")
print(f"{'='*75}")
print(f" {'Config':<25s} {'ROI':>8s} {'PF':>7s} {'WR':>6s} {'AvgW':>7s} {'AvgL':>7s} {'Sharpe':>7s} {'Trades':>7s}")
print(f" {'-'*25} {'-'*8} {'-'*7} {'-'*6} {'-'*7} {'-'*7} {'-'*7} {'-'*7}")
for r in results:
print(f" {r['label']:<25s} {r['roi']:+7.2f}% {r['pf']:6.3f} {r['wr']:5.1f}% "
f"{r['avg_win']:+6.3f}% {r['avg_loss']:+6.3f}% {r['sharpe']:6.2f} {r['trades']:7d}")
# Delta vs baseline
baseline_roi = results[0]['roi']
baseline_wr = results[0]['wr']
print(f"\n {'Config':<25s} {'dROI':>9s} {'dPF':>8s} {'dWR':>7s} {'dAvgWin':>9s} {'dAvgLoss':>9s} {'dTrades':>8s}")
print(f" {'-'*25} {'-'*9} {'-'*8} {'-'*7} {'-'*9} {'-'*9} {'-'*8}")
for r in results[1:]:
print(f" {r['label']:<25s} {r['roi']-baseline_roi:+8.2f}% "
f"{r['pf']-results[0]['pf']:+7.3f} "
f"{r['wr']-baseline_wr:+6.2f}% "
f"{r['avg_win']-results[0]['avg_win']:+8.3f}% "
f"{r['avg_loss']-results[0]['avg_loss']:+8.3f}% "
f"{r['trades']-results[0]['trades']:+7d}")
print(f"\n NOTE: These are PUTATIVE estimates using calibrated MockOBProvider.")
print(f" Real performance requires downloading live OB data for the backtest period.")
print(f"{'='*75}")