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:
@@ -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, {})
|
||||
|
||||
Reference in New Issue
Block a user