136 lines
4.7 KiB
Python
136 lines
4.7 KiB
Python
|
|
import sys
|
||
|
|
import time
|
||
|
|
from pathlib import Path
|
||
|
|
from datetime import datetime, timezone
|
||
|
|
import numpy as np
|
||
|
|
import pandas as pd
|
||
|
|
|
||
|
|
# Ensure we can import from the project root
|
||
|
|
sys.path.insert(0, str(Path(__file__).parent))
|
||
|
|
|
||
|
|
# Import Dolphin components
|
||
|
|
from nautilus_dolphin.nautilus.esf_alpha_orchestrator import NDAlphaEngine
|
||
|
|
|
||
|
|
# Mock configuration
|
||
|
|
CONFIG = {
|
||
|
|
'engine': {
|
||
|
|
'initial_capital': 25000.0,
|
||
|
|
'vel_div_threshold': -0.02,
|
||
|
|
'vel_div_extreme': -0.05,
|
||
|
|
'max_leverage': 5.0,
|
||
|
|
'use_alpha_layers': True,
|
||
|
|
'seed': 42
|
||
|
|
},
|
||
|
|
'live_mode': False,
|
||
|
|
'direction': 'short_only'
|
||
|
|
}
|
||
|
|
|
||
|
|
def test_parity():
|
||
|
|
print("=== DOLPHIN-NAUTILUS PARITY TEST ===")
|
||
|
|
|
||
|
|
# 1. Setup Data (load one sample day)
|
||
|
|
vbt_dir = Path(r"C:\Users\Lenovo\Documents\- DOLPHIN NG HD HCM TSF Predict\vbt_cache")
|
||
|
|
pq_files = sorted(vbt_dir.glob("*.parquet"))
|
||
|
|
if not pq_files:
|
||
|
|
print("Error: No parquet files found in vbt_cache")
|
||
|
|
return
|
||
|
|
|
||
|
|
sample_file = pq_files[0]
|
||
|
|
date_str = sample_file.stem
|
||
|
|
print(f"Testing for date: {date_str} using {sample_file.name}")
|
||
|
|
|
||
|
|
df = pd.read_parquet(sample_file)
|
||
|
|
asset_columns = [c for c in df.columns if c not in {
|
||
|
|
'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'
|
||
|
|
}]
|
||
|
|
|
||
|
|
# Pre-calculate vol_ok (p60 threshold from dynamic_beta_validate)
|
||
|
|
vol_p60 = 0.000099 # Hardcoded champion p60
|
||
|
|
bp = df['BTCUSDT'].values if 'BTCUSDT' in df.columns else None
|
||
|
|
dvol = 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:
|
||
|
|
dvol[i] = float(np.std(np.diff(seg)/seg[:-1]))
|
||
|
|
vol_ok_arr = np.where(np.isfinite(dvol), dvol > vol_p60, False)
|
||
|
|
|
||
|
|
# 2. Path A: Legacy Batch (NDAlphaEngine.process_day)
|
||
|
|
print("\nRunning Path A: Legacy Batch logic...")
|
||
|
|
engine_a = NDAlphaEngine(**CONFIG['engine'])
|
||
|
|
engine_a.set_acb(None)
|
||
|
|
|
||
|
|
batch_stats = engine_a.process_day(
|
||
|
|
date_str=date_str,
|
||
|
|
df=df,
|
||
|
|
asset_columns=asset_columns,
|
||
|
|
vol_regime_ok=vol_ok_arr,
|
||
|
|
direction=-1
|
||
|
|
)
|
||
|
|
print(f" Batch Result: PnL={batch_stats['pnl']:+.4f} Trades={batch_stats['trades']} Capital={batch_stats['capital']:.2f}")
|
||
|
|
|
||
|
|
# 3. Path B: Modern Incremental (Direct NDAlphaEngine methods)
|
||
|
|
print("\nRunning Path B: Modern Incremental logic (direct methods)...")
|
||
|
|
engine_b = NDAlphaEngine(**CONFIG['engine'])
|
||
|
|
engine_b.set_acb(None)
|
||
|
|
|
||
|
|
# Orchestrate calls manually to match DolphinActor logic
|
||
|
|
engine_b.begin_day(date_str, posture='APEX', direction=-1)
|
||
|
|
|
||
|
|
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)):
|
||
|
|
engine_b._global_bar_idx += 1 # MUST increment to maintain parity with process_day logic
|
||
|
|
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
|
||
|
|
|
||
|
|
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:
|
||
|
|
engine_b._global_bar_idx += 1 # MUST increment to maintain parity
|
||
|
|
continue
|
||
|
|
|
||
|
|
vol_ok = bool(vol_ok_arr[ri])
|
||
|
|
|
||
|
|
# Step the bar (internally increments _global_bar_idx)
|
||
|
|
engine_b.step_bar(
|
||
|
|
bar_idx=ri,
|
||
|
|
vel_div=float(vd),
|
||
|
|
prices=prices,
|
||
|
|
vol_regime_ok=vol_ok,
|
||
|
|
v50_vel=v50_val,
|
||
|
|
v750_vel=v750_val
|
||
|
|
)
|
||
|
|
|
||
|
|
incremental_stats = engine_b.end_day()
|
||
|
|
print(f" Incremental Result: PnL={incremental_stats['pnl']:+.4f} Trades={incremental_stats['trades']} Capital={incremental_stats['capital']:.2f}")
|
||
|
|
|
||
|
|
# 4. Parity Check
|
||
|
|
pnl_diff = abs(batch_stats['pnl'] - incremental_stats['pnl'])
|
||
|
|
trade_diff = abs(batch_stats['trades'] - incremental_stats['trades'])
|
||
|
|
|
||
|
|
print("\n=== PARITY ANALYSIS ===")
|
||
|
|
print(f" PnL Difference: {pnl_diff:.8f}")
|
||
|
|
print(f" Trade Difference: {trade_diff}")
|
||
|
|
|
||
|
|
success = pnl_diff < 1e-6 and trade_diff == 0
|
||
|
|
if success:
|
||
|
|
print("\nSUCCESS: Batch and Incremental logic paths are identical.")
|
||
|
|
else:
|
||
|
|
print("\nFAILURE: Logic divergence detected.")
|
||
|
|
sys.exit(1)
|
||
|
|
|
||
|
|
if __name__ == "__main__":
|
||
|
|
test_parity()
|