""" Base Indicator State Class This module contains the abstract base class for all incremental indicator states. All indicator implementations must inherit from IndicatorState and implement the required methods for incremental calculation. """ from abc import ABC, abstractmethod from typing import Any, Dict, Optional, Union import numpy as np class IndicatorState(ABC): """ Abstract base class for maintaining indicator calculation state. This class defines the interface that all incremental indicators must implement. Indicators maintain their internal state and can be updated incrementally with new data points, providing constant memory usage and high performance. Attributes: period (int): The period/window size for the indicator values_received (int): Number of values processed so far is_initialized (bool): Whether the indicator has been initialized Example: class MyIndicator(IndicatorState): def __init__(self, period: int): super().__init__(period) self._sum = 0.0 def update(self, new_value: float) -> float: self._sum += new_value self.values_received += 1 return self._sum / min(self.values_received, self.period) """ def __init__(self, period: int): """ Initialize the indicator state. Args: period: The period/window size for the indicator calculation Raises: ValueError: If period is not a positive integer """ if not isinstance(period, int) or period <= 0: raise ValueError(f"Period must be a positive integer, got {period}") self.period = period self.values_received = 0 self.is_initialized = False @abstractmethod def update(self, new_value: Union[float, Dict[str, float]]) -> Union[float, Dict[str, float]]: """ Update indicator with new value and return current indicator value. This method processes a new data point and updates the internal state of the indicator. It returns the current indicator value after the update. Args: new_value: New data point (can be single value or OHLCV dict) Returns: Current indicator value after update (single value or dict) Raises: ValueError: If new_value is invalid or incompatible """ pass @abstractmethod def is_warmed_up(self) -> bool: """ Check whether indicator has enough data for reliable values. Returns: True if indicator has received enough data points for reliable calculation """ pass @abstractmethod def reset(self) -> None: """ Reset indicator state to initial conditions. This method clears all internal state and resets the indicator as if it was just initialized. """ pass @abstractmethod def get_current_value(self) -> Union[float, Dict[str, float], None]: """ Get the current indicator value without updating. Returns: Current indicator value, or None if not warmed up """ pass def get_state_summary(self) -> Dict[str, Any]: """ Get summary of current indicator state for debugging. Returns: Dictionary containing indicator state information """ return { 'indicator_type': self.__class__.__name__, 'period': self.period, 'values_received': self.values_received, 'is_warmed_up': self.is_warmed_up(), 'is_initialized': self.is_initialized, 'current_value': self.get_current_value() } def validate_input(self, value: Union[float, Dict[str, float]]) -> None: """ Validate input value for the indicator. Args: value: Input value to validate Raises: ValueError: If value is invalid TypeError: If value type is incorrect """ if isinstance(value, (int, float)): if not np.isfinite(value): raise ValueError(f"Input value must be finite, got {value}") elif isinstance(value, dict): required_keys = ['open', 'high', 'low', 'close'] for key in required_keys: if key not in value: raise ValueError(f"OHLCV dict missing required key: {key}") if not np.isfinite(value[key]): raise ValueError(f"OHLCV value for {key} must be finite, got {value[key]}") # Validate OHLC relationships if not (value['low'] <= value['open'] <= value['high'] and value['low'] <= value['close'] <= value['high']): raise ValueError(f"Invalid OHLC relationships: {value}") else: raise TypeError(f"Input value must be float or OHLCV dict, got {type(value)}") def __repr__(self) -> str: """String representation of the indicator state.""" return (f"{self.__class__.__name__}(period={self.period}, " f"values_received={self.values_received}, " f"warmed_up={self.is_warmed_up()})") class SimpleIndicatorState(IndicatorState): """ Base class for simple single-value indicators. This class provides common functionality for indicators that work with single float values and maintain a simple rolling calculation. """ def __init__(self, period: int): """Initialize simple indicator state.""" super().__init__(period) self._current_value = None def get_current_value(self) -> Optional[float]: """Get current indicator value.""" return self._current_value if self.is_warmed_up() else None def is_warmed_up(self) -> bool: """Check if indicator is warmed up.""" return self.values_received >= self.period class OHLCIndicatorState(IndicatorState): """ Base class for OHLC-based indicators. This class provides common functionality for indicators that work with OHLC data (Open, High, Low, Close) and may return multiple values. """ def __init__(self, period: int): """Initialize OHLC indicator state.""" super().__init__(period) self._current_values = {} def get_current_value(self) -> Optional[Dict[str, float]]: """Get current indicator values.""" return self._current_values.copy() if self.is_warmed_up() else None def is_warmed_up(self) -> bool: """Check if indicator is warmed up.""" return self.values_received >= self.period