163 lines
6.8 KiB
Python
163 lines
6.8 KiB
Python
|
|
import sys
|
|||
|
|
|
|||
|
|
with open('nautilus_dolphin/nautilus/alpha_orchestrator.py', 'r', encoding='utf-8') as f:
|
|||
|
|
text = f.read()
|
|||
|
|
|
|||
|
|
addition = """
|
|||
|
|
def set_acb(self, acb) -> None:
|
|||
|
|
\"\"\"Inject AdaptiveCircuitBreaker for per-date boost + dynamic beta.\"\"\"
|
|||
|
|
self._acb = acb
|
|||
|
|
|
|||
|
|
def set_mc_forewarner(self, forewarner, mc_base_cfg: dict) -> None:
|
|||
|
|
\"\"\"Inject DolphinForewarner + frozen champion config for per-date envelope gate.\"\"\"
|
|||
|
|
self._forewarner = forewarner
|
|||
|
|
self._mc_base_cfg = dict(mc_base_cfg)
|
|||
|
|
|
|||
|
|
# ------------------------------------------------------------------
|
|||
|
|
# Internal helpers — ACB 3-scale meta-boost
|
|||
|
|
# ------------------------------------------------------------------
|
|||
|
|
|
|||
|
|
def _strength_cubic(self, vel_div: float) -> float:
|
|||
|
|
\"\"\"Normalised signal strength in [0,1]^convexity — mirrors bet_sizer formula.\"\"\"
|
|||
|
|
if vel_div >= self.signal_gen.threshold:
|
|||
|
|
return 0.0
|
|||
|
|
raw = (self.signal_gen.threshold - vel_div) / (self.signal_gen.threshold - self.signal_gen.extreme)
|
|||
|
|
return min(1.0, max(0.0, raw)) ** self.bet_sizer.convexity
|
|||
|
|
|
|||
|
|
def _update_regime_size_mult(self, vel_div: float) -> None:
|
|||
|
|
\"\"\"Compute regime_size_mult from ACB day state + current vel_div strength.
|
|||
|
|
|
|||
|
|
3-scale formula: base_boost × (1 + β × strength³) × mc_scale
|
|||
|
|
β>0 gate is doctrinal: applies whenever eigenvalue-velocity regime is active,
|
|||
|
|
independent of whether ACB has macro signals (base_boost may still be 1.0).
|
|||
|
|
\"\"\"
|
|||
|
|
if self._day_beta > 0:
|
|||
|
|
ss = self._strength_cubic(vel_div)
|
|||
|
|
self.regime_size_mult = self._day_base_boost * (1.0 + self._day_beta * ss) * self._day_mc_scale
|
|||
|
|
else:
|
|||
|
|
self.regime_size_mult = self._day_base_boost * self._day_mc_scale
|
|||
|
|
|
|||
|
|
# ------------------------------------------------------------------
|
|||
|
|
# process_day — canonical day runner (params live here, not in tests)
|
|||
|
|
# ------------------------------------------------------------------
|
|||
|
|
|
|||
|
|
def process_day(
|
|||
|
|
self,
|
|||
|
|
date_str: str,
|
|||
|
|
df,
|
|||
|
|
asset_columns: list,
|
|||
|
|
vol_regime_ok = None,
|
|||
|
|
) -> dict:
|
|||
|
|
\"\"\"Run one full trading day. All per-date regime setup is internal.
|
|||
|
|
|
|||
|
|
Parameters
|
|||
|
|
----------
|
|||
|
|
date_str : str
|
|||
|
|
Date string matching ACB w750 cache key, e.g. '2026-01-14'.
|
|||
|
|
df : pd.DataFrame
|
|||
|
|
Parquet bar data: must contain 'vel_div' column + asset price columns.
|
|||
|
|
asset_columns : List[str]
|
|||
|
|
Non-meta column names (asset price columns only).
|
|||
|
|
vol_regime_ok : Optional[np.ndarray]
|
|||
|
|
Per-bar boolean array (len == len(df)). If None, bars 0-99 are treated
|
|||
|
|
as warm-up (vol_ok=False); bar 100+ treated as True.
|
|||
|
|
|
|||
|
|
Returns
|
|||
|
|
-------
|
|||
|
|
dict
|
|||
|
|
date, pnl, capital, boost, beta, mc_status, trades
|
|||
|
|
\"\"\"
|
|||
|
|
import numpy as np
|
|||
|
|
cap_start = self.capital
|
|||
|
|
self.regime_direction = -1 # SHORT-biased (invariant of champion)
|
|||
|
|
self.regime_dd_halt = False
|
|||
|
|
self._day_mc_scale = 1.0
|
|||
|
|
self._day_mc_status = 'OK'
|
|||
|
|
|
|||
|
|
# 1. ACB per-date: dynamic beta + base boost (OB Sub-4 modulates beta)
|
|||
|
|
if hasattr(self, '_acb') and self._acb is not None:
|
|||
|
|
acb_info = self._acb.get_dynamic_boost_for_date(date_str, ob_engine=self.ob_engine)
|
|||
|
|
self._day_base_boost = acb_info['boost']
|
|||
|
|
self._day_beta = acb_info['beta']
|
|||
|
|
else:
|
|||
|
|
self._day_base_boost = 1.0
|
|||
|
|
self._day_beta = 0.0
|
|||
|
|
|
|||
|
|
# 2. MC-Forewarner per-date envelope gate
|
|||
|
|
if hasattr(self, '_forewarner') and self._forewarner is not None and self._mc_base_cfg is not None:
|
|||
|
|
mc_cfg = dict(self._mc_base_cfg)
|
|||
|
|
mc_cfg['max_leverage'] = self.base_max_leverage * self._day_base_boost
|
|||
|
|
mc_report = self._forewarner.assess_config_dict(mc_cfg)
|
|||
|
|
mc_red = mc_report.catastrophic_probability > 0.25 or mc_report.envelope_score < -1.0
|
|||
|
|
mc_orange = (not mc_red) and (
|
|||
|
|
mc_report.envelope_score < 0 or mc_report.catastrophic_probability > 0.10
|
|||
|
|
)
|
|||
|
|
self._day_mc_status = 'RED' if mc_red else ('ORANGE' if mc_orange else 'OK')
|
|||
|
|
self._day_mc_scale = 0.5 if mc_orange else 1.0
|
|||
|
|
if mc_red:
|
|||
|
|
self.regime_dd_halt = True
|
|||
|
|
|
|||
|
|
# 3. Bar loop
|
|||
|
|
n_before = len(self.trade_history)
|
|||
|
|
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(float(vd)):
|
|||
|
|
self._global_bar_idx += 1; bid += 1; 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 ac not in self._price_histories:
|
|||
|
|
self._price_histories[ac] = []
|
|||
|
|
self._price_histories[ac].append(float(p))
|
|||
|
|
if len(self._price_histories[ac]) > 500:
|
|||
|
|
self._price_histories[ac] = self._price_histories[ac][-200:]
|
|||
|
|
|
|||
|
|
if not prices:
|
|||
|
|
self._global_bar_idx += 1; bid += 1; continue
|
|||
|
|
|
|||
|
|
vrok = bool(vol_regime_ok[ri]) if vol_regime_ok is not None else (bid >= 100)
|
|||
|
|
|
|||
|
|
# Compute regime_size_mult from ACB day state + current signal strength
|
|||
|
|
self._update_regime_size_mult(float(vd))
|
|||
|
|
|
|||
|
|
self.process_bar(
|
|||
|
|
bar_idx=self._global_bar_idx,
|
|||
|
|
vel_div=float(vd),
|
|||
|
|
prices=prices,
|
|||
|
|
vol_regime_ok=vrok,
|
|||
|
|
price_histories=self._price_histories,
|
|||
|
|
v50_vel=v50_val,
|
|||
|
|
v750_vel=v750_val,
|
|||
|
|
)
|
|||
|
|
self._global_bar_idx += 1
|
|||
|
|
bid += 1
|
|||
|
|
|
|||
|
|
return {
|
|||
|
|
'date': date_str,
|
|||
|
|
'pnl': self.capital - cap_start,
|
|||
|
|
'capital': self.capital,
|
|||
|
|
'boost': self._day_base_boost,
|
|||
|
|
'beta': self._day_beta,
|
|||
|
|
'mc_status': self._day_mc_status,
|
|||
|
|
'trades': len(self.trade_history) - n_before,
|
|||
|
|
}
|
|||
|
|
"""
|
|||
|
|
|
|||
|
|
if "def process_day" not in text:
|
|||
|
|
with open('nautilus_dolphin/nautilus/alpha_orchestrator.py', 'a', encoding='utf-8') as f:
|
|||
|
|
f.write(addition)
|
|||
|
|
print("Patched alpha_orchestrator.py successfully.")
|
|||
|
|
else:
|
|||
|
|
print("Already patched.")
|
|||
|
|
|