|
@@ -192,6 +192,58 @@ async def get_regime(symbol: str, timeframe: str = "1h", limit: int = 200) -> di
|
|
|
|
|
|
|
|
macd_hist = macd_vals.get("histogram") if isinstance(macd_vals, dict) else None
|
|
macd_hist = macd_vals.get("histogram") if isinstance(macd_vals, dict) else None
|
|
|
|
|
|
|
|
|
|
+ # --- Early reversal score (heuristic) ---
|
|
|
|
|
+ # Goal: flag plausible “pivot attempts” before full trend confirmation.
|
|
|
|
|
+ # Note: without previous-bar indicator deltas, this is a conservative, snapshot-based score.
|
|
|
|
|
+ reversal = {"direction": "none", "score": 0, "triggers": []}
|
|
|
|
|
+ if close_price is not None and vwap_val is not None and isinstance(boll, dict):
|
|
|
|
|
+ upper = boll.get("upper")
|
|
|
|
|
+ lower = boll.get("lower")
|
|
|
|
|
+ if upper and lower and (upper > lower):
|
|
|
|
|
+ dist_to_upper = (upper - close_price) / close_price if close_price else None
|
|
|
|
|
+ dist_to_lower = (close_price - lower) / close_price if close_price else None
|
|
|
|
|
+ else:
|
|
|
|
|
+ dist_to_upper = None
|
|
|
|
|
+ dist_to_lower = None
|
|
|
|
|
+
|
|
|
|
|
+ # Bullish reversal attempt while trend is bearish
|
|
|
|
|
+ if trend_state == "bear" and macd_hist is not None and rsi_val is not None and macd_hist > 0:
|
|
|
|
|
+ score = 0
|
|
|
|
|
+ if rsi_val >= 45:
|
|
|
|
|
+ score += 25
|
|
|
|
|
+ reversal["triggers"].append("RSI rising/near-neutral (>=45)")
|
|
|
|
|
+ if vwap_val is not None and close_price > vwap_val:
|
|
|
|
|
+ score += 25
|
|
|
|
|
+ reversal["triggers"].append("Price reclaimed above VWAP")
|
|
|
|
|
+ if dist_to_lower is not None and dist_to_lower <= 0.01:
|
|
|
|
|
+ score += 20
|
|
|
|
|
+ reversal["triggers"].append("Near lower Bollinger band")
|
|
|
|
|
+ if atr_percent is not None and atr_percent >= 0.8:
|
|
|
|
|
+ score += 10
|
|
|
|
|
+ reversal["triggers"].append("Volatility elevated (ATR% >= 0.8)")
|
|
|
|
|
+
|
|
|
|
|
+ if score >= 40:
|
|
|
|
|
+ reversal.update({"direction": "bullish", "score": min(score, 95)})
|
|
|
|
|
+
|
|
|
|
|
+ # Bearish reversal attempt while trend is bullish
|
|
|
|
|
+ if trend_state == "bull" and macd_hist is not None and rsi_val is not None and macd_hist < 0:
|
|
|
|
|
+ score = 0
|
|
|
|
|
+ if rsi_val <= 55:
|
|
|
|
|
+ score += 25
|
|
|
|
|
+ reversal["triggers"].append("RSI falling/near-neutral (<=55)")
|
|
|
|
|
+ if vwap_val is not None and close_price < vwap_val:
|
|
|
|
|
+ score += 25
|
|
|
|
|
+ reversal["triggers"].append("Price rejected below VWAP")
|
|
|
|
|
+ if dist_to_upper is not None and dist_to_upper <= 0.01:
|
|
|
|
|
+ score += 20
|
|
|
|
|
+ reversal["triggers"].append("Near upper Bollinger band")
|
|
|
|
|
+ if atr_percent is not None and atr_percent >= 0.8:
|
|
|
|
|
+ score += 10
|
|
|
|
|
+ reversal["triggers"].append("Volatility elevated (ATR% >= 0.8)")
|
|
|
|
|
+
|
|
|
|
|
+ if score >= 40:
|
|
|
|
|
+ reversal.update({"direction": "bearish", "score": min(score, 95)})
|
|
|
|
|
+
|
|
|
return {
|
|
return {
|
|
|
"symbol": symbol,
|
|
"symbol": symbol,
|
|
|
"timeframe": timeframe,
|
|
"timeframe": timeframe,
|
|
@@ -216,4 +268,5 @@ async def get_regime(symbol: str, timeframe: str = "1h", limit: int = 200) -> di
|
|
|
"bollinger": boll,
|
|
"bollinger": boll,
|
|
|
},
|
|
},
|
|
|
"vwap": vwap_val,
|
|
"vwap": vwap_val,
|
|
|
|
|
+ "reversal": reversal,
|
|
|
}
|
|
}
|