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.
225 lines
8.8 KiB
Python
Executable File
225 lines
8.8 KiB
Python
Executable File
import sys, time
|
|
from pathlib import Path
|
|
import numpy as np
|
|
import pandas as pd
|
|
import json
|
|
|
|
sys.path.insert(0, str(Path(__file__).parent))
|
|
|
|
from nautilus_dolphin.nautilus.alpha_orchestrator import NDAlphaEngine
|
|
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
|
|
|
|
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'}
|
|
|
|
parquet_files = sorted(VBT_DIR.glob("*.parquet"))
|
|
parquet_files = [p for p in parquet_files if 'catalog' not in str(p)]
|
|
|
|
print("Loading 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)
|
|
|
|
# Initialize systems
|
|
acb = AdaptiveCircuitBreaker()
|
|
acb.preload_w750([pf.stem for pf in parquet_files])
|
|
|
|
mock = MockOBProvider(imbalance_bias=-0.09, depth_scale=1.0,
|
|
assets=["BTCUSDT", "ETHUSDT", "BNBUSDT", "SOLUSDT"],
|
|
imbalance_biases={"BNBUSDT": 0.20, "SOLUSDT": 0.20})
|
|
ob_engine = OBFeatureEngine(mock)
|
|
ob_engine.preload_date("mock", mock.get_assets())
|
|
|
|
def run_base_backtest(lev_multiplier):
|
|
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 * lev_multiplier, 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,
|
|
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,
|
|
)
|
|
|
|
import gc
|
|
gc.collect()
|
|
|
|
engine = NDAlphaEngine(**ENGINE_KWARGS)
|
|
engine.set_ob_engine(ob_engine)
|
|
|
|
bar_idx = 0; peak_cap = engine.capital; max_dd = 0.0
|
|
|
|
# Store daily returns for MC bootstrapping
|
|
daily_returns = []
|
|
|
|
for pf in parquet_files:
|
|
ds = pf.stem
|
|
cs = engine.capital
|
|
# ACB logic
|
|
acb_info = acb.get_dynamic_boost_for_date(ds, ob_engine=ob_engine)
|
|
base_boost = acb_info['boost']
|
|
beta = acb_info['beta']
|
|
|
|
df, acols, dvol = pq_data[ds]
|
|
ph = {}
|
|
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; 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 len(ph[ac]) > 500: ph[ac] = ph[ac][-200:]
|
|
if not prices: bar_idx+=1; continue
|
|
|
|
vrok = False if ri < 100 else (np.isfinite(dvol[ri]) and dvol[ri] > vol_p60)
|
|
|
|
# Use beta strictly for meta-boost
|
|
if beta > 0:
|
|
ss = 0.0
|
|
if vd < -0.02:
|
|
raw = (-0.02 - float(vd)) / (-0.02 - -0.05)
|
|
ss = min(1.0, max(0.0, raw)) ** 3.0
|
|
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
|
|
|
|
peak_cap = max(peak_cap, engine.capital)
|
|
dd = (peak_cap - engine.capital) / peak_cap
|
|
max_dd = max(max_dd, dd)
|
|
daily_returns.append((engine.capital - cs) / cs if cs > 0 else 0)
|
|
|
|
trades = engine.trade_history
|
|
w = [t for t in trades if t.pnl_absolute > 0]
|
|
l = [t for t in trades 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
|
|
wr = len(w) / len(trades) * 100 if trades else 0
|
|
|
|
return {
|
|
'leverage': 5.0 * lev_multiplier,
|
|
'roi': roi,
|
|
'pf': pf_val,
|
|
'wr': wr,
|
|
'max_dd': max_dd * 100,
|
|
'trades': len(trades),
|
|
'daily_returns': np.array(daily_returns)
|
|
}
|
|
|
|
def run_monte_carlo(base_results, n_simulations=1000, periods=365):
|
|
"""
|
|
Run geometric Monte Carlo bootstrapping using historical daily returns.
|
|
"""
|
|
np.random.seed(42)
|
|
daily_returns = base_results['daily_returns']
|
|
n_days = len(daily_returns)
|
|
|
|
# Bootstrap sampling for n_simulations trajectories of length `periods`
|
|
# Randomly sample historical daily returns with replacement to generate realistic synthetic years
|
|
simulated_returns = np.random.choice(daily_returns, size=(n_simulations, periods), replace=True)
|
|
|
|
# Calculate equity curves (geometric compounding)
|
|
# Adding 1.0 to get multiplier for cumulative product
|
|
equity_curves = np.cumprod(1.0 + simulated_returns, axis=1)
|
|
|
|
# CAGR calculations
|
|
final_multipliers = equity_curves[:, -1]
|
|
# CAGR = (End/Start)^(1/Years) - 1. We simulate 1 year, so exponent is 1.
|
|
cagrs = (final_multipliers - 1.0) * 100
|
|
|
|
median_cagr = np.median(cagrs)
|
|
p05_cagr = np.percentile(cagrs, 5) # 5th percentile worst outcome
|
|
|
|
# Calculate Max Drawdowns for each simulated trajectory
|
|
max_dds = np.zeros(n_simulations)
|
|
recovery_times = np.zeros(n_simulations)
|
|
|
|
for i in range(n_simulations):
|
|
curve = equity_curves[i]
|
|
peaks = np.maximum.accumulate(curve)
|
|
drawdowns = (peaks - curve) / peaks
|
|
max_dd_idx = np.argmax(drawdowns)
|
|
max_dds[i] = drawdowns[max_dd_idx]
|
|
|
|
# Calculate time to recovery from max drawdown
|
|
if drawdowns[max_dd_idx] > 0:
|
|
peak_val = peaks[max_dd_idx]
|
|
# Find first index after max drawdown where equity hits or exceeds the peak
|
|
recovery_idx = -1
|
|
for j in range(max_dd_idx, periods):
|
|
if curve[j] >= peak_val:
|
|
recovery_idx = j
|
|
break
|
|
|
|
if recovery_idx != -1:
|
|
recovery_times[i] = recovery_idx - max_dd_idx
|
|
else:
|
|
recovery_times[i] = periods - max_dd_idx # Did not recover within period
|
|
|
|
median_max_dd = np.median(max_dds) * 100
|
|
median_recovery = np.median(recovery_times[recovery_times > 0]) if np.any(recovery_times > 0) else -1
|
|
|
|
return {
|
|
'median_cagr': median_cagr,
|
|
'p05_cagr': p05_cagr,
|
|
'median_max_dd': median_max_dd,
|
|
'median_recovery_days': median_recovery,
|
|
'prob_ruin_50': np.mean(max_dds >= 0.50) * 100 # Prob of 50% DD
|
|
}
|
|
|
|
print("\n" + "="*80)
|
|
print("GEOMETRIC MONTE CARLO DRAG SIMULATION (1000 Trajectories / 1 Year)")
|
|
print("="*80)
|
|
print(f"{'Lev':<5} | {'Base ROI':<10} | {'Base DD':<10} | {'Base PF':<8} | {'Med CAGR':<10} | {'5th% CAGR':<10} | {'Med MC DD':<10} | {'Recovery':<10} | {'Risk > 50% DD'}")
|
|
print("-" * 80)
|
|
|
|
results = []
|
|
for mult in [1.0, 1.2, 1.4]: # 5x, 6x, 7x
|
|
lev = 5.0 * mult
|
|
|
|
# Get empirical sequence first
|
|
base = run_base_backtest(mult)
|
|
|
|
# Run MC on the empirical sequence
|
|
mc = run_monte_carlo(base, n_simulations=1000, periods=365)
|
|
|
|
print(f"{lev:<4.1f}x | {base['roi']:>+9.2f}% | {base['max_dd']:>9.2f}% | {base['pf']:>7.3f} | " +
|
|
f"{mc['median_cagr']:>+9.2f}% | {mc['p05_cagr']:>+9.2f}% | {mc['median_max_dd']:>9.2f}% | " +
|
|
f"{mc['median_recovery_days']:>7.0f} d | {mc['prob_ruin_50']:>11.1f}%")
|