__init__.py 3.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475
  1. """Technical indicators."""
  2. from errors import InsufficientDataError, UnsupportedIndicatorError
  3. def _closes(candles: list) -> list[float]:
  4. return [float(c[4]) for c in candles]
  5. def _ema_series(values: list[float], period: int) -> list[float]:
  6. if len(values) < period:
  7. raise InsufficientDataError(f"EMA({period}) requires at least {period} candles, got {len(values)}")
  8. k = 2 / (period + 1)
  9. ema_vals = [sum(values[:period]) / period]
  10. for v in values[period:]:
  11. ema_vals.append(v * k + ema_vals[-1] * (1 - k))
  12. return ema_vals
  13. def ema(candles: list, period: int = 20) -> float:
  14. return round(_ema_series(_closes(candles), period)[-1], 6)
  15. def rsi(candles: list, period: int = 14) -> float:
  16. closes = _closes(candles)
  17. if len(closes) < period + 1:
  18. raise InsufficientDataError(f"RSI({period}) requires at least {period + 1} candles, got {len(closes)}")
  19. deltas = [closes[i] - closes[i - 1] for i in range(1, len(closes))]
  20. gains = [max(d, 0.0) for d in deltas]
  21. losses = [abs(min(d, 0.0)) for d in deltas]
  22. avg_gain = sum(gains[:period]) / period
  23. avg_loss = sum(losses[:period]) / period
  24. for i in range(period, len(deltas)):
  25. avg_gain = (avg_gain * (period - 1) + gains[i]) / period
  26. avg_loss = (avg_loss * (period - 1) + losses[i]) / period
  27. if avg_loss == 0:
  28. return 100.0
  29. rs = avg_gain / avg_loss
  30. return round(100 - (100 / (1 + rs)), 2)
  31. def macd(candles: list, fast_period: int = 12, slow_period: int = 26, signal_period: int = 9) -> dict:
  32. closes = _closes(candles)
  33. if len(closes) < slow_period + signal_period:
  34. raise InsufficientDataError("MACD requires more candles")
  35. fast_ema = _ema_series(closes, fast_period)
  36. slow_ema = _ema_series(closes, slow_period)
  37. offset = len(fast_ema) - len(slow_ema)
  38. macd_line = [fast_ema[i + offset] - slow_ema[i] for i in range(len(slow_ema))]
  39. signal_line = _ema_series(macd_line, signal_period)
  40. macd_val = round(macd_line[-1], 6)
  41. signal_val = round(signal_line[-1], 6)
  42. return {"macd": macd_val, "signal": signal_val, "histogram": round(macd_val - signal_val, 6)}
  43. def sma(candles: list, period: int = 20) -> float:
  44. closes = _closes(candles)
  45. if len(closes) < period:
  46. raise InsufficientDataError(f"SMA({period}) requires at least {period} candles, got {len(closes)}")
  47. return round(sum(closes[-period:]) / period, 6)
  48. def compute_indicator(candles: list, indicator: str, params: dict) -> dict:
  49. ind = indicator.lower()
  50. if ind == "rsi":
  51. value = rsi(candles, period=int(params.get("period", 14)))
  52. elif ind == "ema":
  53. value = ema(candles, period=int(params.get("period", 20)))
  54. elif ind == "macd":
  55. value = macd(candles, fast_period=int(params.get("fast_period", 12)), slow_period=int(params.get("slow_period", 26)), signal_period=int(params.get("signal_period", 9)))
  56. elif ind == "sma":
  57. value = sma(candles, period=int(params.get("period", 20)))
  58. else:
  59. raise UnsupportedIndicatorError(f"Unsupported indicator: {indicator}")
  60. return {"indicator": ind, "value": value}