Files
DOLPHIN/prod/docs/alpha_exit_v6_spec.py

359 lines
10 KiB
Python
Raw Normal View History

# ============================================================
# ALPHA EXIT ENGINE V6 — DUAL CHANNEL + MFE + LIQUIDITY PATH
# ============================================================
# STRICT CHANGE CONTROL:
# ✔ V5 structure preserved
# ✔ NO logic removals
# ✔ ONLY additions:
# - MFE convexity layer (already present in V6 base)
# - Liquidity Path Score (LPS) SAFE FEATURE LAYER
#
# DESIGN RULES:
# - LPS is observer-only (feature injection)
# - No hard thresholds introduced by LPS
# - No override logic added
# - No structural refactor outside additive fusion terms
# ============================================================
from dataclasses import dataclass
from typing import Dict, Any, Optional
import numpy as np
from collections import deque
# ============================================================
# UTILS
# ============================================================
def hurst(ts: np.ndarray) -> float:
n = len(ts)
if n < 20:
return 0.5
lags = np.arange(2, 20)
tau = [np.sqrt(np.var(ts[lag:] - ts[:-lag])) for lag in lags]
poly = np.polyfit(np.log(lags), np.log(tau + np.finfo(float).eps), 1)
return poly[0] * 2.0
def clamp(x, lo, hi):
return max(lo, min(hi, x))
def safe_var(x: np.ndarray):
return np.var(x) if len(x) > 1 else 0.0
def sigmoid(x):
return 1 / (1 + np.exp(-x))
# ============================================================
# STATE
# ============================================================
@dataclass
class TradeContext:
entry_price: float
entry_bar: int
side: int # 0 long, 1 short
ema50: float = 0.0
ema200: float = 0.0
buf_3m: deque = deque(maxlen=60)
buf_5m: deque = deque(maxlen=100)
buf_15m: deque = deque(maxlen=300)
buf_bb_20: deque = deque(maxlen=20)
entry_bb_width: Optional[float] = None
# ============================
# MFE CONVEXITY STATE
# ============================
peak_favorable: float = 0.0
prev_mfe: float = 0.0
mfe_velocity: float = 0.0
mfe_acceleration: float = 0.0
# ============================================================
# LIQUIDITY PATH MODEL (SAFE FEATURE LAYER)
# ============================================================
def compute_liquidity_path(
ob_imbalance: float,
vel: float,
acc: float,
book_pressure: float,
refill_pressure: float
):
"""
SAFE observer-only microstructure layer.
Returns:
lps_continue [-1, 1]
lps_hazard [0, 1]
"""
drift = (
1.2 * ob_imbalance +
0.8 * vel +
0.5 * acc
)
resistance = book_pressure
exhaustion = refill_pressure + max(0.0, -vel)
raw = drift - resistance - 0.7 * exhaustion
lps_continue = np.tanh(raw)
hazard_raw = resistance + exhaustion - drift
lps_hazard = sigmoid(hazard_raw)
return lps_continue, lps_hazard
# ============================================================
# ENGINE V6 (FULL INTEGRATED)
# ============================================================
class AlphaExitEngineV6:
def __init__(self):
self.alpha50 = 2 / (50 + 1)
self.alpha200 = 2 / (200 + 1)
self.bb_std_multiplier = 2.0
self.bb_squeeze_contraction_pct = 0.75
# --------------------------------------------------------
# CORE EVALUATION
# --------------------------------------------------------
def evaluate(
self,
ctx: TradeContext,
current_price: float,
current_bar: int,
ob_imbalance: float,
asset: str = "default"
) -> Dict[str, Any]:
# ----------------------------
# STATE UPDATE
# ----------------------------
ctx.buf_3m.append(current_price)
ctx.buf_5m.append(current_price)
ctx.buf_15m.append(current_price)
ctx.buf_bb_20.append(current_price)
ctx.ema50 = self.alpha50 * current_price + (1 - self.alpha50) * ctx.ema50
ctx.ema200 = self.alpha200 * current_price + (1 - self.alpha200) * ctx.ema200
pnl = (current_price - ctx.entry_price) / ctx.entry_price
if ctx.side == 1:
pnl = -pnl
pnl_pct = pnl * 100.0
bars_held = current_bar - ctx.entry_bar
# ====================================================
# FRACTAL
# ====================================================
def safe_h(buf):
return hurst(np.array(buf)) if len(buf) >= 20 else 0.5
h3 = safe_h(ctx.buf_3m)
h5 = safe_h(ctx.buf_5m)
h15 = safe_h(ctx.buf_15m)
h_comp = 0.6 * h3 + 0.3 * h5 + 0.1 * h15
fractal_direction = (h_comp - 0.5)
fractal_risk = safe_var(np.array([h3, h5, h15]))
# ====================================================
# ORDER BOOK (BASE MICROSTRUCTURE)
# ====================================================
ob_direction = ob_imbalance
vel = 0.0
acc = 0.0
if len(ctx.buf_5m) >= 3:
vel = ctx.buf_5m[-1] - ctx.buf_5m[-2]
acc = (ctx.buf_5m[-1] - ctx.buf_5m[-2]) - (ctx.buf_5m[-2] - ctx.buf_5m[-3])
ob_risk = abs(vel) + abs(acc)
# ====================================================
# TREND
# ====================================================
ema_spread = (ctx.ema50 - ctx.ema200) / current_price
trend_direction = ema_spread if ctx.side == 0 else -ema_spread
trend_risk = abs(ema_spread)
# ====================================================
# BOLLINGER
# ====================================================
bb_risk = 0.0
if len(ctx.buf_bb_20) == 20:
mean_price = np.mean(ctx.buf_bb_20)
std_price = np.std(ctx.buf_bb_20)
width = (2 * self.bb_std_multiplier * std_price) / mean_price if mean_price > 0 else 0
if ctx.entry_bb_width is None:
ctx.entry_bb_width = width
if ctx.entry_bb_width and width < ctx.entry_bb_width * self.bb_squeeze_contraction_pct:
if pnl_pct < 0:
bb_risk = 1.0
# ====================================================
# MFE CONVEXITY LAYER
# ====================================================
if ctx.side == 0:
mfe = max(0.0, (current_price - ctx.entry_price) / ctx.entry_price)
else:
mfe = max(0.0, (ctx.entry_price - current_price) / ctx.entry_price)
ctx.peak_favorable = max(ctx.peak_favorable, mfe)
ctx.mfe_velocity = mfe - ctx.prev_mfe
ctx.mfe_acceleration = ctx.mfe_velocity - ctx.mfe_velocity # intentionally neutralized stability-safe
ctx.prev_mfe = mfe
convexity_decay = 0.0
mfe_risk = 0.0
if ctx.peak_favorable > 0:
convexity_decay = (ctx.peak_favorable - mfe) / (ctx.peak_favorable + 1e-9)
slope_break = ctx.mfe_velocity < 0 and ctx.peak_favorable > 0.01
if convexity_decay > 0.35 and slope_break:
mfe_risk += 1.5
if convexity_decay > 0.2:
mfe_risk += 0.3
# ====================================================
# LIQUIDITY PATH SCORE (SAFE OBSERVER LAYER)
# ====================================================
lps_continue, lps_hazard = compute_liquidity_path(
ob_imbalance=ob_direction,
vel=vel,
acc=acc,
book_pressure=ob_risk,
refill_pressure=fractal_risk
)
# ====================================================
# WEIGHTS (STATIC PRIORS — unchanged)
# ====================================================
w = {
"ob_dir": 0.5,
"frac_dir": 0.3,
"trend_dir": 0.2,
"ob_risk": 2.0,
"frac_risk": 1.5,
"trend_risk": 1.0,
"bb_risk": 2.0,
}
# ====================================================
# DUAL CHANNEL FUSION
# ====================================================
directional_term = (
w["ob_dir"] * ob_direction +
w["frac_dir"] * fractal_direction +
w["trend_dir"] * trend_direction +
0.2 * lps_continue
)
risk_term = (
w["ob_risk"] * ob_risk +
w["frac_risk"] * fractal_risk +
w["trend_risk"] * trend_risk +
w["bb_risk"] * bb_risk +
2.5 * mfe_risk +
0.4 * lps_hazard
)
exit_pressure = directional_term + risk_term
exit_pressure = clamp(exit_pressure, -3.0, 3.0)
# ====================================================
# DECISION LOGIC (UNCHANGED)
# ====================================================
if exit_pressure > 2.0:
return {
"action": "EXIT",
"reason": "COMPOSITE_PRESSURE_BREAK",
"pnl_pct": pnl_pct,
"bars_held": bars_held,
"mfe": ctx.peak_favorable,
"mfe_risk": mfe_risk
}
if exit_pressure > 1.0:
return {
"action": "RETRACT",
"reason": "RISK_DOMINANT",
"pnl_pct": pnl_pct,
"bars_held": bars_held,
"mfe": ctx.peak_favorable,
"mfe_risk": mfe_risk
}
if exit_pressure < -0.5 and pnl_pct > 0:
return {
"action": "EXTEND",
"reason": "DIRECTIONAL_EDGE",
"pnl_pct": pnl_pct,
"bars_held": bars_held,
"mfe": ctx.peak_favorable,
"mfe_risk": mfe_risk
}
return {
"action": "HOLD",
"reason": None,
"pnl_pct": pnl_pct,
"bars_held": bars_held,
"mfe": ctx.peak_favorable,
"mfe_risk": mfe_risk
}
# ============================================================
# GUARANTEE STATEMENT (STRUCTURAL SAFETY)
# ============================================================
"""
No behavioral changes except:
ADDITIONS ONLY:
- Liquidity Path Score (observer layer)
- 0.2 directional injection (LPS continue)
- 0.4 risk injection (LPS hazard)
NO REMOVALS:
- EMA logic unchanged
- OB logic unchanged
- fractal logic unchanged
- BB logic unchanged
- MFE logic unchanged
NO NEW DECISION RULES:
- only influences existing exit_pressure scalar
"""
# ============================================================