171 lines
6.6 KiB
Python
171 lines
6.6 KiB
Python
|
|
import sys, time, math, itertools
|
||
|
|
from pathlib import Path
|
||
|
|
import numpy as np
|
||
|
|
import pandas as pd
|
||
|
|
import gc
|
||
|
|
|
||
|
|
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...")
|
||
|
|
acb = AdaptiveCircuitBreaker()
|
||
|
|
date_strings = [pf.stem for pf in parquet_files]
|
||
|
|
acb.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
|
||
|
|
diffs = np.zeros(len(seg)-1)
|
||
|
|
for j in range(len(seg)-1):
|
||
|
|
if seg[j] > 0: diffs[j] = (seg[j+1]-seg[j])/seg[j]
|
||
|
|
v = float(np.std(diffs))
|
||
|
|
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
|
||
|
|
diffs = np.zeros(len(seg)-1)
|
||
|
|
for j in range(len(seg)-1):
|
||
|
|
if seg[j] > 0: diffs[j] = (seg[j+1]-seg[j])/seg[j]
|
||
|
|
dv[i] = float(np.std(diffs))
|
||
|
|
pq_data[pf.stem] = (df, ac, dv)
|
||
|
|
|
||
|
|
VD_THRESH = -0.02; VD_EXTREME = -0.05; CONVEXITY = 3.0
|
||
|
|
|
||
|
|
def strength_cubic(vel_div, threshold=-0.02):
|
||
|
|
if vel_div >= threshold: return 0.0
|
||
|
|
raw = (threshold - vel_div) / (threshold - VD_EXTREME)
|
||
|
|
return min(1.0, max(0.0, raw)) ** CONVEXITY
|
||
|
|
|
||
|
|
assets = ["BTCUSDT", "ETHUSDT", "BNBUSDT", "SOLUSDT"]
|
||
|
|
|
||
|
|
def evaluate_params(tp_bps, dc_lookback, dc_magnitude, min_irp, max_hold):
|
||
|
|
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=tp_bps/10000.0, stop_pct=1.0, max_hold_bars=max_hold,
|
||
|
|
use_direction_confirm=True, dc_lookback_bars=dc_lookback, dc_min_magnitude_bps=dc_magnitude,
|
||
|
|
dc_skip_contradicts=True, dc_leverage_boost=1.0, dc_leverage_reduce=0.5,
|
||
|
|
use_asset_selection=True, min_irp_alignment=min_irp,
|
||
|
|
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,
|
||
|
|
)
|
||
|
|
|
||
|
|
mock = MockOBProvider(
|
||
|
|
imbalance_bias=-0.09, depth_scale=1.0, assets=assets,
|
||
|
|
imbalance_biases={"BTCUSDT": -0.086, "ETHUSDT": -0.092, "BNBUSDT": +0.20, "SOLUSDT": +0.20},
|
||
|
|
)
|
||
|
|
ob_eng = OBFeatureEngine(mock)
|
||
|
|
ob_eng.preload_date("mock", assets)
|
||
|
|
|
||
|
|
engine = NDAlphaEngine(**engine_kwargs)
|
||
|
|
engine.set_ob_engine(ob_eng)
|
||
|
|
|
||
|
|
bar_idx = 0; ph = {}; dstats = []
|
||
|
|
|
||
|
|
# We evaluate on half the parquet files (about 25 days) to make the sweep fast enough
|
||
|
|
for pf in parquet_files[:25]:
|
||
|
|
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_eng)
|
||
|
|
engine.regime_size_mult = acb_info['boost']
|
||
|
|
|
||
|
|
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 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 acb_info['beta'] > 0:
|
||
|
|
ss = strength_cubic(float(vd))
|
||
|
|
engine.regime_size_mult = acb_info['boost'] * (1.0 + acb_info['beta'] * ss)
|
||
|
|
|
||
|
|
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]
|
||
|
|
wr = len(w) / len(tr) * 100 if tr else 0.0
|
||
|
|
pf = sum(t.pnl_absolute for t in w) / abs(sum(t.pnl_absolute for t in l)) if l else 999
|
||
|
|
roi = (engine.capital - 25000) / 25000 * 100
|
||
|
|
|
||
|
|
del engine
|
||
|
|
gc.collect()
|
||
|
|
return roi, wr, pf, len(tr)
|
||
|
|
|
||
|
|
print("Starting parameter sweep...")
|
||
|
|
params = {
|
||
|
|
'tp_bps': [89, 99, 109],
|
||
|
|
'dc_lookback': [5, 7, 9],
|
||
|
|
'dc_magnitude': [0.5, 0.75],
|
||
|
|
'min_irp': [0.40, 0.45],
|
||
|
|
'max_hold': [120, 150]
|
||
|
|
}
|
||
|
|
|
||
|
|
keys, values = zip(*params.items())
|
||
|
|
permutations = [dict(zip(keys, v)) for v in itertools.product(*values)]
|
||
|
|
print(f"Total combinations: {len(permutations)}")
|
||
|
|
|
||
|
|
best_wr = 0
|
||
|
|
best_roi = 0
|
||
|
|
|
||
|
|
for i, p in enumerate(permutations):
|
||
|
|
try:
|
||
|
|
t0 = time.time()
|
||
|
|
roi, wr, pf, trades = evaluate_params(
|
||
|
|
p['tp_bps'], p['dc_lookback'], p['dc_magnitude'], p['min_irp'], p['max_hold']
|
||
|
|
)
|
||
|
|
print(f"[{i+1}/{len(permutations)}] {p} -> ROI: {roi:+.2f}% | WR: {wr:.2f}% | PF: {pf:.2f} | Trades: {trades} [{time.time()-t0:.1f}s]")
|
||
|
|
|
||
|
|
if wr > best_wr:
|
||
|
|
best_wr = wr
|
||
|
|
print(f" *** NEW BEST WR: {wr:.2f}% ***")
|
||
|
|
|
||
|
|
if roi > best_roi:
|
||
|
|
best_roi = roi
|
||
|
|
print(f" *** NEW BEST ROI: {roi:+.2f}% ***")
|
||
|
|
|
||
|
|
except Exception as e:
|
||
|
|
print(f"Error on {p}: {e}")
|