Selaa lähdekoodia

Add reversal score to get_regime

Lukas Goldschmidt 1 kuukausi sitten
vanhempi
commit
3a0db7d283
3 muutettua tiedostoa jossa 64 lisäystä ja 1 poistoa
  1. 1 1
      README.md
  2. 10 0
      server.py
  3. 53 0
      services/__init__.py

+ 1 - 1
README.md

@@ -47,7 +47,7 @@ docker compose down
 - `get_capabilities`
 - `get_regime`
 
-`get_regime` returns a composite view (trend EMA/SMA regime, RSI + MACD momentum, ATR-based volatility, Bollinger bands, VWAP) for a given symbol/timeframe.
+`get_regime` returns a composite view (trend EMA/SMA regime, RSI + MACD momentum, ATR-based volatility, Bollinger bands, VWAP) plus a **heuristic early `reversal` score** (bullish/bearish/none with 0–100 score and trigger notes) for a given symbol/timeframe.
 
 ## Health
 

+ 10 - 0
server.py

@@ -34,5 +34,15 @@ async def get_top_movers(limit: int = 10):
     return await services.get_top_movers(limit)
 
 
+@mcp.tool()
+async def get_capabilities():
+    return await services.get_capabilities()
+
+
+@mcp.tool()
+async def get_regime(symbol: str, timeframe: str = "1h"):
+    return await services.get_regime(symbol, timeframe)
+
+
 if __name__ == "__main__":
     mcp.run()

+ 53 - 0
services/__init__.py

@@ -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
 
+    # --- 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 {
         "symbol": symbol,
         "timeframe": timeframe,
@@ -216,4 +268,5 @@ async def get_regime(symbol: str, timeframe: str = "1h", limit: int = 200) -> di
             "bollinger": boll,
         },
         "vwap": vwap_val,
+        "reversal": reversal,
     }