__init__.py 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219
  1. """Service layer."""
  2. import time
  3. from config import DEFAULT_OHLCV_LIMIT, MAX_OHLCV_LIMIT, TIMEFRAME_TO_BINANCE
  4. from cache import get_cached_price, set_cached_price, get_cached_ohlcv, set_cached_ohlcv
  5. import providers
  6. import indicators as ind_module
  7. async def get_price(symbol: str) -> dict:
  8. symbol = symbol.upper()
  9. cached = get_cached_price(symbol)
  10. if cached:
  11. return cached
  12. data = await providers.fetch_price(symbol)
  13. set_cached_price(symbol, data)
  14. return data
  15. async def get_ohlcv(symbol: str, timeframe: str, limit: int = DEFAULT_OHLCV_LIMIT) -> dict:
  16. symbol = symbol.upper()
  17. limit = min(max(limit, 1), MAX_OHLCV_LIMIT)
  18. cached = get_cached_ohlcv(symbol, timeframe)
  19. if cached:
  20. result = dict(cached)
  21. result["candles"] = cached["candles"][-limit:]
  22. return result
  23. data = await providers.fetch_ohlcv(symbol, timeframe, limit=MAX_OHLCV_LIMIT)
  24. set_cached_ohlcv(symbol, timeframe, data)
  25. result = dict(data)
  26. result["candles"] = data["candles"][-limit:]
  27. return result
  28. async def get_indicator(symbol: str, indicator: str, timeframe: str = "1h", params: dict = None, limit: int = 200) -> dict:
  29. params = params or {}
  30. symbol = symbol.upper()
  31. ohlcv_data = await get_ohlcv(symbol, timeframe, limit=limit)
  32. result = ind_module.compute_indicator(ohlcv_data["candles"], indicator, params)
  33. return {"symbol": symbol, "indicator": result["indicator"], "timeframe": timeframe, "value": result["value"], "timestamp": int(time.time())}
  34. async def get_market_snapshot(symbol: str) -> dict:
  35. symbol = symbol.upper()
  36. price_data = await get_price(symbol)
  37. ohlcv_data = await get_ohlcv(symbol, "1h", limit=200)
  38. candles = ohlcv_data["candles"]
  39. price = price_data["price"]
  40. snapshot = {
  41. "symbol": symbol,
  42. "price": price,
  43. "rsi_1h": None,
  44. "ema_20_1h": None,
  45. "ema_50_1h": None,
  46. "ema_200_1h": None,
  47. "macd_histogram_1h": None,
  48. "atr_1h": None,
  49. "atr_percent_1h": None,
  50. "bollinger_1h": None,
  51. "vwap_1h": None,
  52. "trend_bias": None,
  53. "timestamp": price_data["timestamp"],
  54. }
  55. def _compute(name: str, params: dict):
  56. return ind_module.compute_indicator(candles, name, params)["value"]
  57. for key, ind, params in [
  58. ("rsi_1h", "rsi", {"period": 14}),
  59. ("ema_20_1h", "ema", {"period": 20}),
  60. ("ema_50_1h", "ema", {"period": 50}),
  61. ("ema_200_1h", "sma", {"period": 200}),
  62. ]:
  63. try:
  64. snapshot[key] = _compute(ind, params)
  65. except Exception:
  66. continue
  67. try:
  68. macd_vals = _compute("macd", {"fast_period": 12, "slow_period": 26, "signal_period": 9})
  69. if isinstance(macd_vals, dict):
  70. snapshot["macd_histogram_1h"] = macd_vals.get("histogram")
  71. except Exception:
  72. pass
  73. try:
  74. atr_val = _compute("atr", {"period": 14})
  75. snapshot["atr_1h"] = atr_val
  76. if atr_val is not None and price:
  77. snapshot["atr_percent_1h"] = round((atr_val / price) * 100, 4)
  78. except Exception:
  79. pass
  80. try:
  81. snapshot["bollinger_1h"] = _compute("bollinger", {"period": 20, "multiplier": 2.0})
  82. except Exception:
  83. pass
  84. try:
  85. snapshot["vwap_1h"] = _compute("vwap", {"period": 48})
  86. except Exception:
  87. pass
  88. ema_fast = snapshot.get("ema_20_1h")
  89. ema_slow = snapshot.get("ema_50_1h")
  90. if ema_fast is not None and ema_slow not in (None, 0):
  91. delta = (ema_fast - ema_slow) / ema_slow
  92. if delta > 0.002:
  93. snapshot["trend_bias"] = "bull"
  94. elif delta < -0.002:
  95. snapshot["trend_bias"] = "bear"
  96. else:
  97. snapshot["trend_bias"] = "range"
  98. return snapshot
  99. async def get_top_movers(limit: int = 10) -> dict:
  100. return await providers.fetch_top_movers(min(max(limit, 1), 50))
  101. async def get_capabilities() -> dict:
  102. timeframe_descriptions = {
  103. "1m": "1-minute candles — ultra-fast scalping / heartbeat data (highest volatility, shortest TTL)",
  104. "5m": "5-minute candles — intraday momentum and micro-structure",
  105. "15m": "15-minute candles — short-term swings, aligns with many bot cycles",
  106. "1h": "1-hour candles — default for balanced trend/momentum reads",
  107. "4h": "4-hour candles — swing/position traders",
  108. "1d": "1-day candles — macro trend / higher timeframe context",
  109. }
  110. timeframes = []
  111. for tf, interval in TIMEFRAME_TO_BINANCE.items():
  112. timeframes.append(
  113. {
  114. "timeframe": tf,
  115. "provider_interval": interval,
  116. "description": timeframe_descriptions.get(tf, ""),
  117. }
  118. )
  119. return {
  120. "description": "Supported technical indicators (with params/defaults) and the timeframes you can request via get_ohlcv / get_indicator / get_regime.",
  121. "indicators": ind_module.get_supported_indicators(),
  122. "timeframes": timeframes,
  123. }
  124. async def get_regime(symbol: str, timeframe: str = "1h", limit: int = 200) -> dict:
  125. symbol = symbol.upper()
  126. ohlcv_data = await get_ohlcv(symbol, timeframe, limit=limit)
  127. candles = ohlcv_data["candles"]
  128. close_price = float(candles[-1][4]) if candles else None
  129. timestamp = int(time.time())
  130. def _compute(name: str, params: dict | None = None):
  131. try:
  132. return ind_module.compute_indicator(candles, name, params or {})["value"]
  133. except Exception:
  134. return None
  135. ema_fast = _compute("ema", {"period": 20})
  136. ema_slow = _compute("ema", {"period": 50})
  137. sma_long = _compute("sma", {"period": 200})
  138. atr_val = _compute("atr", {"period": 14})
  139. boll = _compute("bollinger", {"period": 20, "multiplier": 2.0})
  140. vwap_val = _compute("vwap", {"period": 48})
  141. rsi_val = _compute("rsi", {"period": 14})
  142. macd_vals = _compute("macd", {"fast_period": 12, "slow_period": 26, "signal_period": 9})
  143. regime_delta = None
  144. if ema_fast is not None and ema_slow is not None and ema_slow != 0:
  145. regime_delta = (ema_fast - ema_slow) / ema_slow
  146. if regime_delta is None:
  147. trend_state = "unknown"
  148. elif regime_delta > 0.002:
  149. trend_state = "bull"
  150. elif regime_delta < -0.002:
  151. trend_state = "bear"
  152. else:
  153. trend_state = "range"
  154. if rsi_val is None:
  155. momentum_state = "unknown"
  156. elif rsi_val >= 60:
  157. momentum_state = "bull"
  158. elif rsi_val <= 40:
  159. momentum_state = "bear"
  160. else:
  161. momentum_state = "neutral"
  162. atr_percent = None
  163. if atr_val is not None and close_price:
  164. atr_percent = round((atr_val / close_price) * 100, 4)
  165. macd_hist = macd_vals.get("histogram") if isinstance(macd_vals, dict) else None
  166. return {
  167. "symbol": symbol,
  168. "timeframe": timeframe,
  169. "timestamp": timestamp,
  170. "price": close_price,
  171. "trend": {
  172. "ema_fast": ema_fast,
  173. "ema_slow": ema_slow,
  174. "sma_long": sma_long,
  175. "state": trend_state,
  176. },
  177. "momentum": {
  178. "rsi": rsi_val,
  179. "macd_histogram": macd_hist,
  180. "state": momentum_state,
  181. },
  182. "volatility": {
  183. "atr": atr_val,
  184. "atr_percent": atr_percent,
  185. },
  186. "bands": {
  187. "bollinger": boll,
  188. },
  189. "vwap": vwap_val,
  190. }