Pro tier upgrade: quant rearchitect + WebSocket level-breach monitor

- Restore XAU/USD (Pro plan), drop USD/CAD, raise rate limit to 55/min
- Reduce signal engine to 4 uncorrelated pillars (EMA, RSI, BB, Candlestick)
  with regime-adaptive weights (trending boosts EMA, ranging boosts RSI+BB)
- Add structure-based S/R (swing highs/lows) for trade setup targeting
- Fix confidence death spiral: all adjustments now additive, not multiplicative
- Dynamic R:R trade setup: SL behind swing level, TP at next structure level,
  reject if R:R < 1.5
- Fix news engine for all 4 pairs (was hardcoded to USDJPY)
- Add WebSocket monitor: streams live ticks, triggers M15 scan on level breach
  with 5-min cooldown per pair — catches breakouts between candle boundaries
- Add correlation filter (EUR/USD+GBP/USD, XAU/USD+USD/JPY exposure warnings)
This commit is contained in:
harwinder
2026-05-11 16:33:23 -04:00
parent cc66e65f2d
commit 058ef57d8f
8 changed files with 435 additions and 196 deletions
Regular → Executable
+71 -70
View File
@@ -87,16 +87,15 @@ class TechnicalEngine:
if "ema_trend" not in disabled: self._compute_ema_signals(df, result)
if "rsi" not in disabled: self._compute_rsi(df, result)
if "macd" not in disabled: self._compute_macd(df, result)
if "bollinger" not in disabled: self._compute_bollinger(df, result)
if "stochastic" not in disabled: self._compute_stochastic(df, result)
if "adx" not in disabled: self._compute_adx(df, result)
if "atr" not in disabled: self._compute_atr(df, result)
if "ichimoku" not in disabled: self._compute_ichimoku(df, result)
self._compute_macd_internal(df, result)
if "divergence" not in disabled: self._detect_divergences(df, result)
if "structure" not in disabled: self._compute_structure_sr(df, result)
if "candlestick" not in disabled: self._detect_candlestick_patterns(df, result)
if "pivot" not in disabled: self._compute_support_resistance(df, result)
if "volume" not in disabled: self._compute_volume_analysis(df, result)
# ── Classify market regime ─────────────────────────────────
self._classify_regime(df, result)
@@ -222,14 +221,14 @@ class TechnicalEngine:
result.indicators["rsi"] = IndicatorSignal(
name="RSI", value=rsi_val, signal=signal,
confidence=conf, description=desc, weight=1.8,
confidence=conf, description=desc, weight=1.5,
)
# Attach RSI series for divergence detection (dynamic attribute, filtered by _prefix)
result._rsi_series = rsi # type: ignore[attr-defined]
def _compute_macd(self, df: pd.DataFrame, result: TechnicalAnalysis):
"""MACD with histogram momentum analysis."""
def _compute_macd_internal(self, df: pd.DataFrame, result: TechnicalAnalysis):
"""Compute MACD histogram internally for divergence detection (not a signal pillar)."""
c = self.config
close = df["Close"]
@@ -239,62 +238,6 @@ class TechnicalEngine:
signal_line = macd_line.ewm(span=c.macd_signal, adjust=False).mean()
histogram = macd_line - signal_line
macd_val = macd_line.iloc[-1]
signal_val = signal_line.iloc[-1]
hist_val = histogram.iloc[-1]
# Check for crossover
crossover = False
crossover_type = ""
if len(macd_line) >= 2:
prev_macd = macd_line.iloc[-2]
prev_signal = signal_line.iloc[-2]
if prev_macd <= prev_signal and macd_val > signal_val:
crossover = True
crossover_type = "bullish"
elif prev_macd >= prev_signal and macd_val < signal_val:
crossover = True
crossover_type = "bearish"
# Histogram momentum (is it expanding or contracting?)
hist_momentum = ""
if len(histogram) >= 3:
h1, h2, h3 = histogram.iloc[-3], histogram.iloc[-2], histogram.iloc[-1]
if h3 > h2 > h1 and h3 > 0:
hist_momentum = "accelerating bullish"
elif h3 < h2 < h1 and h3 < 0:
hist_momentum = "accelerating bearish"
elif abs(h3) < abs(h2) and h3 * h2 > 0:
hist_momentum = "decelerating"
# Signal
if crossover and crossover_type == "bullish":
signal = SignalStrength.STRONG_BUY
conf = 75
desc = f"MACD bullish crossover | Hist: {hist_val:.6f}"
elif crossover and crossover_type == "bearish":
signal = SignalStrength.STRONG_SELL
conf = 75
desc = f"MACD bearish crossover | Hist: {hist_val:.6f}"
elif macd_val > signal_val and hist_val > 0:
signal = SignalStrength.BUY
conf = 55
desc = f"MACD above signal, positive histogram | {hist_momentum}"
elif macd_val < signal_val and hist_val < 0:
signal = SignalStrength.SELL
conf = 55
desc = f"MACD below signal, negative histogram | {hist_momentum}"
else:
signal = SignalStrength.NEUTRAL
conf = 35
desc = f"MACD mixed | Hist: {hist_val:.6f}"
result.indicators["macd"] = IndicatorSignal(
name="MACD", value=hist_val, signal=signal,
confidence=conf, description=desc, weight=1.2,
)
# Attach histogram for divergence detection
result._macd_histogram = histogram # type: ignore[attr-defined]
def _compute_bollinger(self, df: pd.DataFrame, result: TechnicalAnalysis):
@@ -672,11 +615,39 @@ class TechnicalEngine:
name="Divergence", value=len(divergences),
signal=signal, confidence=70,
description=" | ".join(divergences),
weight=3.5,
weight=1.0,
)
# Note: divergences are NOT added to pattern_signals to avoid
# duplicate reason collection (the indicator entry covers them).
def _compute_structure_sr(self, df: pd.DataFrame, result: TechnicalAnalysis):
"""Detect swing high/low structure levels for S/R and trade setup."""
lookback = min(self.config.swing_sr_lookback, len(df) - 1)
if lookback < 10:
return
price = df["Close"].iloc[-1]
high_window = df["High"].iloc[-lookback:]
low_window = df["Low"].iloc[-lookback:]
swing_highs = self._find_swing_points(high_window, mode="high", window=3)
swing_lows = self._find_swing_points(low_window, mode="low", window=3)
highs_above = sorted([v for _, v in swing_highs if v > price])
highs_below = sorted([v for _, v in swing_highs if v <= price], reverse=True)
lows_below = sorted([v for _, v in swing_lows if v < price], reverse=True)
lows_above = sorted([v for _, v in swing_lows if v >= price])
for i, val in enumerate(highs_above[:3], 1):
result.key_levels[f"swing_high_{i}"] = float(val)
for i, val in enumerate(lows_below[:3], 1):
result.key_levels[f"swing_low_{i}"] = float(val)
if not highs_above and highs_below:
result.key_levels["swing_high_1"] = float(highs_below[0])
if not lows_below and lows_above:
result.key_levels["swing_low_1"] = float(lows_above[0])
def _detect_candlestick_patterns(self, df: pd.DataFrame, result: TechnicalAnalysis):
"""Detect significant candlestick patterns on recent bars."""
if len(df) < 5:
@@ -738,11 +709,33 @@ class TechnicalEngine:
else:
signal = SignalStrength.NEUTRAL
conf = 50
atr_val = result.key_levels.get("atr", 0)
latest = c
proximity = atr_val * 0.5 if atr_val > 0 else 0
if proximity > 0:
swing_lows = sorted(
[v for k, v in result.key_levels.items() if k.startswith("swing_low_")]
)
swing_highs = sorted(
[v for k, v in result.key_levels.items() if k.startswith("swing_high_")]
)
near_support = any(abs(latest - sl) <= proximity for sl in swing_lows)
near_resistance = any(abs(latest - sh) <= proximity for sh in swing_highs)
if bullish > bearish and near_support:
conf += 20
patterns.append("(at support)")
elif bearish > bullish and near_resistance:
conf += 20
patterns.append("(at resistance)")
result.indicators["candlestick"] = IndicatorSignal(
name="Candlestick", value=float(bullish - bearish),
signal=signal, confidence=50,
signal=signal, confidence=conf,
description=" | ".join(patterns),
weight=1.2,
weight=1.5,
)
result.pattern_signals.extend(patterns)
@@ -854,10 +847,18 @@ class TechnicalEngine:
total_weight = 0.0
total_confidence = 0.0
_trending = {
"ema_trend": 2.0, "rsi": 1.5, "bollinger": 0.8,
"candlestick": 1.5, "divergence": 1.0,
}
regime_weights = {
"trending_up": {"divergence": 1.5, "rsi": 1.2, "stochastic": 1.1, "candlestick": 1.2, "ema_trend": 1.0, "macd": 1.0, "adx": 1.0, "ichimoku": 0.8, "bollinger": 0.7, "pivot": 0.8},
"trending_down": {"divergence": 1.5, "rsi": 1.2, "stochastic": 1.1, "candlestick": 1.2, "ema_trend": 1.0, "macd": 1.0, "adx": 1.0, "ichimoku": 0.8, "bollinger": 0.7, "pivot": 0.8},
"ranging": {"divergence": 1.3, "rsi": 1.4, "stochastic": 1.4, "bollinger": 1.5, "candlestick": 1.3, "pivot": 1.3, "ema_trend": 0.5, "macd": 0.6, "adx": 0.4, "ichimoku": 0.2},
"trending_up": _trending,
"trending_down": _trending,
"trending": _trending,
"ranging": {
"ema_trend": 0.5, "rsi": 2.0, "bollinger": 2.0,
"candlestick": 1.5, "divergence": 1.5,
},
}
active_regime_weights = regime_weights.get(result.market_regime, {})