Spaces:
Running
Running
Update Quasar_axrvi_ranker.py
Browse files- Quasar_axrvi_ranker.py +54 -19
Quasar_axrvi_ranker.py
CHANGED
|
@@ -176,7 +176,7 @@ class PortfolioRiskConfig:
|
|
| 176 |
kelly_fraction: float = 0.5
|
| 177 |
max_portfolio_risk: float = 0.02
|
| 178 |
drawdown_halt_pct: float = 0.10
|
| 179 |
-
halt_duration_secs: float =
|
| 180 |
drawdown_reduce_pct: float = 0.05
|
| 181 |
cvar_floor: float = -0.02
|
| 182 |
min_notional: float = 1.0
|
|
@@ -1744,6 +1744,13 @@ class AXRVINet(nn.Module):
|
|
| 1744 |
mean_quantiles = torch.stack(mc_quantiles, dim=0).mean(dim=0)
|
| 1745 |
mean_regime = torch.stack(mc_regime, dim=0).mean(dim=0)
|
| 1746 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1747 |
return {
|
| 1748 |
"significance_weight": mean_sig,
|
| 1749 |
"epistemic_variance": epistemic_var,
|
|
@@ -1751,6 +1758,10 @@ class AXRVINet(nn.Module):
|
|
| 1751 |
"log_var": mean_log_var.unsqueeze(-1),
|
| 1752 |
"quantiles": mean_quantiles,
|
| 1753 |
"regime_probs": mean_regime,
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1754 |
}
|
| 1755 |
|
| 1756 |
|
|
@@ -2728,12 +2739,28 @@ class PortfolioRiskManager:
|
|
| 2728 |
if current_price <= 0:
|
| 2729 |
return 0.0, "Invalid price"
|
| 2730 |
|
| 2731 |
-
# ββ Layer 3: circuit breaker
|
| 2732 |
-
dd
|
|
|
|
|
|
|
|
|
|
| 2733 |
if dd >= self.cfg.drawdown_halt_pct:
|
| 2734 |
-
|
| 2735 |
-
|
| 2736 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2737 |
|
| 2738 |
# Drawdown reduce (halve sizes)
|
| 2739 |
dd_adj = 0.5 if dd >= self.cfg.drawdown_reduce_pct else 1.0
|
|
@@ -3526,12 +3553,11 @@ class QuasarAXRVIBridge:
|
|
| 3526 |
aleatoric_std_arr = np.full(len(active_ids), 0.1)
|
| 3527 |
|
| 3528 |
# [S1] Extract value-head estimates (VΜ_t = Γ[R_{t+Ο} | F_t] proxy)
|
| 3529 |
-
|
| 3530 |
-
# Prefer the actual value head if available (non-MC path has it)
|
| 3531 |
if "value" in out:
|
| 3532 |
v_arr = out["value"].squeeze(0).squeeze(-1).detach().numpy()
|
| 3533 |
else:
|
| 3534 |
-
v_arr = significance_arr #
|
| 3535 |
|
| 3536 |
significance_map: Dict[str, float] = {}
|
| 3537 |
epistemic_map: Dict[str, float] = {}
|
|
@@ -3635,13 +3661,13 @@ class QuasarAXRVIBridge:
|
|
| 3635 |
logger.debug(f"[{asset_id}] SKIP β hub_confidence=0.00")
|
| 3636 |
continue
|
| 3637 |
|
| 3638 |
-
# Gate C β
|
| 3639 |
-
|
| 3640 |
-
|
| 3641 |
-
|
| 3642 |
-
|
| 3643 |
-
|
| 3644 |
-
|
| 3645 |
|
| 3646 |
# Gate D + E β DynamicExecutionGate (includes martingale null-hypothesis [S7])
|
| 3647 |
buf = self.asset_buffers.get(asset_id)
|
|
@@ -3661,8 +3687,8 @@ class QuasarAXRVIBridge:
|
|
| 3661 |
martingale_deviation = mart_dev_raw, # [S7]
|
| 3662 |
)
|
| 3663 |
if not execute:
|
| 3664 |
-
logger.info(f"[{asset_id}] Gate D/E
|
| 3665 |
-
|
| 3666 |
|
| 3667 |
logger.info(
|
| 3668 |
f"[{asset_id}] EXECUTE {snap.dominant_signal} | "
|
|
@@ -3672,7 +3698,12 @@ class QuasarAXRVIBridge:
|
|
| 3672 |
await self.process_axrvi_signal(
|
| 3673 |
asset = asset_id,
|
| 3674 |
action = snap.dominant_signal,
|
| 3675 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3676 |
realized_vol = realized_vol,
|
| 3677 |
significance = sig_w,
|
| 3678 |
cvar_05 = cvar_map.get(asset_id, 0.0),
|
|
@@ -4533,6 +4564,8 @@ async def run_live_trading_system(
|
|
| 4533 |
hub_ws_url: str = "ws://localhost:7860/ws/subscribe",
|
| 4534 |
enable_logging: bool = True,
|
| 4535 |
shreve_config: Optional[ShreveConfig] = None,
|
|
|
|
|
|
|
| 4536 |
) -> None:
|
| 4537 |
config = AssetRankerConfig(
|
| 4538 |
asset_symbols = asset_symbols or list(ASSET_REGISTRY.keys()),
|
|
@@ -4546,6 +4579,8 @@ async def run_live_trading_system(
|
|
| 4546 |
reward_strategy = reward_strategy,
|
| 4547 |
hub_ws_url = hub_ws_url,
|
| 4548 |
enable_logging = enable_logging,
|
|
|
|
|
|
|
| 4549 |
)
|
| 4550 |
await bridge.run()
|
| 4551 |
|
|
|
|
| 176 |
kelly_fraction: float = 0.5
|
| 177 |
max_portfolio_risk: float = 0.02
|
| 178 |
drawdown_halt_pct: float = 0.10
|
| 179 |
+
halt_duration_secs: float = 5.0 # 3-minute automatic cooldown
|
| 180 |
drawdown_reduce_pct: float = 0.05
|
| 181 |
cvar_floor: float = -0.02
|
| 182 |
min_notional: float = 1.0
|
|
|
|
| 1744 |
mean_quantiles = torch.stack(mc_quantiles, dim=0).mean(dim=0)
|
| 1745 |
mean_regime = torch.stack(mc_regime, dim=0).mean(dim=0)
|
| 1746 |
|
| 1747 |
+
# Recompute value and cvar_05 from mean quantiles so downstream
|
| 1748 |
+
# Kelly sizing and optimal-stopping always have a proper value estimate.
|
| 1749 |
+
mid = mean_quantiles.shape[-1] // 2
|
| 1750 |
+
n_tail = max(1, mean_quantiles.shape[-1] // 5)
|
| 1751 |
+
mean_value = mean_quantiles[..., mid].unsqueeze(-1) # (1, N, 1)
|
| 1752 |
+
mean_cvar05 = mean_quantiles[..., :n_tail].mean(dim=-1) # (1, N)
|
| 1753 |
+
|
| 1754 |
return {
|
| 1755 |
"significance_weight": mean_sig,
|
| 1756 |
"epistemic_variance": epistemic_var,
|
|
|
|
| 1758 |
"log_var": mean_log_var.unsqueeze(-1),
|
| 1759 |
"quantiles": mean_quantiles,
|
| 1760 |
"regime_probs": mean_regime,
|
| 1761 |
+
# Added: required by rank_and_gate for Kelly sizing [S1/S6] and
|
| 1762 |
+
# optimal-stopping continuation value C_t [S8].
|
| 1763 |
+
"value": mean_value,
|
| 1764 |
+
"cvar_05": mean_cvar05,
|
| 1765 |
}
|
| 1766 |
|
| 1767 |
|
|
|
|
| 2739 |
if current_price <= 0:
|
| 2740 |
return 0.0, "Invalid price"
|
| 2741 |
|
| 2742 |
+
# ββ Layer 3: circuit breaker ββββββββββββββββββββββββββββββββββββββββββ
|
| 2743 |
+
dd = self._current_drawdown()
|
| 2744 |
+
now = time.time()
|
| 2745 |
+
|
| 2746 |
+
# Auto-expiring halt: set _halt_until when drawdown first breaches threshold.
|
| 2747 |
if dd >= self.cfg.drawdown_halt_pct:
|
| 2748 |
+
if self._halt_until is None:
|
| 2749 |
+
self._halt_until = now + self.cfg.halt_duration_secs
|
| 2750 |
+
logger.warning(
|
| 2751 |
+
f"[PortfolioRiskManager] π Circuit breaker TRIGGERED "
|
| 2752 |
+
f"drawdown={dd:.1%} β halting for {self.cfg.halt_duration_secs:.0f}s"
|
| 2753 |
+
)
|
| 2754 |
+
if now < self._halt_until:
|
| 2755 |
+
remaining = self._halt_until - now
|
| 2756 |
+
return 0.0, (
|
| 2757 |
+
f"Drawdown halt active: dd={dd:.1%} β₯ halt={self.cfg.drawdown_halt_pct:.1%}. "
|
| 2758 |
+
f"Auto-resumes in {remaining:.0f}s"
|
| 2759 |
+
)
|
| 2760 |
+
else:
|
| 2761 |
+
# Halt expired β reset so the breaker can re-trigger next time
|
| 2762 |
+
self._halt_until = None
|
| 2763 |
+
logger.info("[PortfolioRiskManager] β
Circuit breaker auto-resumed")
|
| 2764 |
|
| 2765 |
# Drawdown reduce (halve sizes)
|
| 2766 |
dd_adj = 0.5 if dd >= self.cfg.drawdown_reduce_pct else 1.0
|
|
|
|
| 3553 |
aleatoric_std_arr = np.full(len(active_ids), 0.1)
|
| 3554 |
|
| 3555 |
# [S1] Extract value-head estimates (VΜ_t = Γ[R_{t+Ο} | F_t] proxy)
|
| 3556 |
+
# "value" is now always present β MC path computes it from mean_quantiles.
|
|
|
|
| 3557 |
if "value" in out:
|
| 3558 |
v_arr = out["value"].squeeze(0).squeeze(-1).detach().numpy()
|
| 3559 |
else:
|
| 3560 |
+
v_arr = significance_arr # last-resort fallback
|
| 3561 |
|
| 3562 |
significance_map: Dict[str, float] = {}
|
| 3563 |
epistemic_map: Dict[str, float] = {}
|
|
|
|
| 3661 |
logger.debug(f"[{asset_id}] SKIP β hub_confidence=0.00")
|
| 3662 |
continue
|
| 3663 |
|
| 3664 |
+
# Gate C β significance threshold
|
| 3665 |
+
if sig_w < self.config.score_threshold:
|
| 3666 |
+
logger.info(
|
| 3667 |
+
f"[{asset_id}] SKIP Gate C β significance={sig_w:.3f} "
|
| 3668 |
+
f"< threshold={self.config.score_threshold:.3f}"
|
| 3669 |
+
)
|
| 3670 |
+
continue
|
| 3671 |
|
| 3672 |
# Gate D + E β DynamicExecutionGate (includes martingale null-hypothesis [S7])
|
| 3673 |
buf = self.asset_buffers.get(asset_id)
|
|
|
|
| 3687 |
martingale_deviation = mart_dev_raw, # [S7]
|
| 3688 |
)
|
| 3689 |
if not execute:
|
| 3690 |
+
logger.info(f"[{asset_id}] Gate D/E BLOCKED: {reason}")
|
| 3691 |
+
continue
|
| 3692 |
|
| 3693 |
logger.info(
|
| 3694 |
f"[{asset_id}] EXECUTE {snap.dominant_signal} | "
|
|
|
|
| 3698 |
await self.process_axrvi_signal(
|
| 3699 |
asset = asset_id,
|
| 3700 |
action = snap.dominant_signal,
|
| 3701 |
+
# Floor at 1e-4: an untrained distributional head can produce
|
| 3702 |
+
# negative median quantiles; passing a negative value_estimate
|
| 3703 |
+
# into Kelly sizing immediately triggers "No positive edge" and
|
| 3704 |
+
# vetoes every trade. The 1e-4 floor lets the system collect
|
| 3705 |
+
# experience while the model is still learning. [S1/Kelly]
|
| 3706 |
+
value_estimate = max(value_map.get(asset_id, 0.001), 1e-4),
|
| 3707 |
realized_vol = realized_vol,
|
| 3708 |
significance = sig_w,
|
| 3709 |
cvar_05 = cvar_map.get(asset_id, 0.0),
|
|
|
|
| 4564 |
hub_ws_url: str = "ws://localhost:7860/ws/subscribe",
|
| 4565 |
enable_logging: bool = True,
|
| 4566 |
shreve_config: Optional[ShreveConfig] = None,
|
| 4567 |
+
checkpoint_dir: str = "./Ranker",
|
| 4568 |
+
resume: bool = True,
|
| 4569 |
) -> None:
|
| 4570 |
config = AssetRankerConfig(
|
| 4571 |
asset_symbols = asset_symbols or list(ASSET_REGISTRY.keys()),
|
|
|
|
| 4579 |
reward_strategy = reward_strategy,
|
| 4580 |
hub_ws_url = hub_ws_url,
|
| 4581 |
enable_logging = enable_logging,
|
| 4582 |
+
checkpoint_dir = checkpoint_dir,
|
| 4583 |
+
resume = resume,
|
| 4584 |
)
|
| 4585 |
await bridge.run()
|
| 4586 |
|