364 lines
11 KiB
Markdown
Raw Normal View History

2025-05-28 22:37:53 +08:00
# Base Indicator Classes
## Overview
All indicators in IncrementalTrader are built on a foundation of base classes that provide common functionality for incremental computation. These base classes ensure consistent behavior, memory efficiency, and real-time capability across all indicators.
## Available indicators
- [Moving Averages](moving_averages.md)
- [Volatility](volatility.md) - ATR
- [Trend](trend.md) - Supertrend
- [Oscillators](oscillators.md) - RSI
- [Bollinger Bands](bollinger_bands.md) - Bollinger Bands
## IndicatorState
The foundation class for all indicators in the framework.
### Features
- **Incremental Computation**: O(1) time complexity per update
- **Constant Memory**: O(1) space complexity regardless of data history
- **State Management**: Maintains internal state efficiently
- **Ready State Tracking**: Indicates when indicator has sufficient data
### Class Definition
```python
from IncrementalTrader.strategies.indicators import IndicatorState
class IndicatorState:
def __init__(self, period: int):
self.period = period
self.data_count = 0
def update(self, value: float):
"""Update indicator with new value."""
raise NotImplementedError("Subclasses must implement update method")
def get_value(self) -> float:
"""Get current indicator value."""
raise NotImplementedError("Subclasses must implement get_value method")
def is_ready(self) -> bool:
"""Check if indicator has enough data."""
return self.data_count >= self.period
def reset(self):
"""Reset indicator state."""
self.data_count = 0
```
### Methods
| Method | Description | Returns |
|--------|-------------|---------|
| `update(value: float)` | Update indicator with new value | None |
| `get_value() -> float` | Get current indicator value | float |
| `is_ready() -> bool` | Check if indicator has enough data | bool |
| `reset()` | Reset indicator state | None |
### Usage Example
```python
class MyCustomIndicator(IndicatorState):
def __init__(self, period: int):
super().__init__(period)
self.sum = 0.0
self.values = []
def update(self, value: float):
self.values.append(value)
self.sum += value
if len(self.values) > self.period:
old_value = self.values.pop(0)
self.sum -= old_value
self.data_count += 1
def get_value(self) -> float:
if not self.is_ready():
return 0.0
return self.sum / min(len(self.values), self.period)
# Usage
indicator = MyCustomIndicator(period=10)
for price in [100, 101, 99, 102, 98]:
indicator.update(price)
if indicator.is_ready():
print(f"Value: {indicator.get_value():.2f}")
```
## SimpleIndicatorState
For indicators that only need the current value and don't require a period.
### Features
- **Immediate Ready**: Always ready after first update
- **No Period Requirement**: Doesn't need historical data
- **Minimal State**: Stores only current value
### Class Definition
```python
class SimpleIndicatorState(IndicatorState):
def __init__(self):
super().__init__(period=1)
self.current_value = 0.0
def update(self, value: float):
self.current_value = value
self.data_count = 1 # Always ready
def get_value(self) -> float:
return self.current_value
```
### Usage Example
```python
# Simple price tracker
price_tracker = SimpleIndicatorState()
for price in [100, 101, 99, 102]:
price_tracker.update(price)
print(f"Current price: {price_tracker.get_value():.2f}")
```
## OHLCIndicatorState
For indicators that require OHLC (Open, High, Low, Close) data instead of just a single price value.
### Features
- **OHLC Data Support**: Handles high, low, close data
- **Flexible Updates**: Can update with individual OHLC components
- **Typical Price Calculation**: Built-in typical price (HLC/3) calculation
### Class Definition
```python
class OHLCIndicatorState(IndicatorState):
def __init__(self, period: int):
super().__init__(period)
self.current_high = 0.0
self.current_low = 0.0
self.current_close = 0.0
def update_ohlc(self, high: float, low: float, close: float):
"""Update with OHLC data."""
self.current_high = high
self.current_low = low
self.current_close = close
self._process_ohlc_data(high, low, close)
self.data_count += 1
def _process_ohlc_data(self, high: float, low: float, close: float):
"""Process OHLC data - to be implemented by subclasses."""
raise NotImplementedError("Subclasses must implement _process_ohlc_data")
def get_typical_price(self) -> float:
"""Calculate typical price (HLC/3)."""
return (self.current_high + self.current_low + self.current_close) / 3.0
def get_true_range(self, prev_close: float = None) -> float:
"""Calculate True Range."""
if prev_close is None:
return self.current_high - self.current_low
return max(
self.current_high - self.current_low,
abs(self.current_high - prev_close),
abs(self.current_low - prev_close)
)
```
### Methods
| Method | Description | Returns |
|--------|-------------|---------|
| `update_ohlc(high, low, close)` | Update with OHLC data | None |
| `get_typical_price()` | Get typical price (HLC/3) | float |
| `get_true_range(prev_close)` | Calculate True Range | float |
### Usage Example
```python
class MyOHLCIndicator(OHLCIndicatorState):
def __init__(self, period: int):
super().__init__(period)
self.hl_sum = 0.0
self.count = 0
def _process_ohlc_data(self, high: float, low: float, close: float):
self.hl_sum += (high - low)
self.count += 1
def get_value(self) -> float:
if self.count == 0:
return 0.0
return self.hl_sum / self.count
# Usage
ohlc_indicator = MyOHLCIndicator(period=10)
ohlc_data = [(105, 95, 100), (108, 98, 102), (110, 100, 105)]
for high, low, close in ohlc_data:
ohlc_indicator.update_ohlc(high, low, close)
if ohlc_indicator.is_ready():
print(f"Average Range: {ohlc_indicator.get_value():.2f}")
print(f"Typical Price: {ohlc_indicator.get_typical_price():.2f}")
```
## Best Practices
### 1. Always Check Ready State
```python
indicator = MovingAverageState(period=20)
for price in price_data:
indicator.update(price)
# Always check if ready before using value
if indicator.is_ready():
value = indicator.get_value()
# Use the value...
```
### 2. Initialize Once, Reuse Many Times
```python
# Good: Initialize once
sma = MovingAverageState(period=20)
# Process many data points
for price in large_dataset:
sma.update(price)
if sma.is_ready():
process_signal(sma.get_value())
# Bad: Don't recreate indicators
for price in large_dataset:
sma = MovingAverageState(period=20) # Wasteful!
sma.update(price)
```
### 3. Handle Edge Cases
```python
def safe_indicator_update(indicator, value):
"""Safely update indicator with error handling."""
try:
if value is not None and not math.isnan(value):
indicator.update(value)
return True
except Exception as e:
logger.error(f"Error updating indicator: {e}")
return False
```
### 4. Batch Updates for Multiple Indicators
```python
# Update all indicators together
indicators = [sma_20, ema_12, rsi_14]
for price in price_stream:
# Update all indicators
for indicator in indicators:
indicator.update(price)
# Check if all are ready
if all(ind.is_ready() for ind in indicators):
# Use all indicator values
values = [ind.get_value() for ind in indicators]
process_signals(values)
```
## Performance Characteristics
### Memory Usage
- **IndicatorState**: O(period) memory usage
- **SimpleIndicatorState**: O(1) memory usage
- **OHLCIndicatorState**: O(period) memory usage
### Processing Speed
- **Update Time**: O(1) per data point for all base classes
- **Value Retrieval**: O(1) for getting current value
- **Ready Check**: O(1) for checking ready state
### Scalability
```python
# Memory usage remains constant regardless of data volume
indicator = MovingAverageState(period=20)
# Process 1 million data points - memory usage stays O(20)
for i in range(1_000_000):
indicator.update(i)
if indicator.is_ready():
value = indicator.get_value() # Always O(1)
```
## Error Handling
### Common Patterns
```python
class RobustIndicator(IndicatorState):
def update(self, value: float):
try:
# Validate input
if value is None or math.isnan(value) or math.isinf(value):
self.logger.warning(f"Invalid value: {value}")
return
# Process value
self._process_value(value)
self.data_count += 1
except Exception as e:
self.logger.error(f"Error in indicator update: {e}")
def get_value(self) -> float:
try:
if not self.is_ready():
return 0.0
return self._calculate_value()
except Exception as e:
self.logger.error(f"Error calculating indicator value: {e}")
return 0.0
```
## Integration with Strategies
### Strategy Usage Pattern
```python
class MyStrategy(IncStrategyBase):
def __init__(self, name: str, params: dict = None):
super().__init__(name, params)
# Initialize indicators
self.sma = MovingAverageState(period=20)
self.rsi = RSIState(period=14)
self.atr = ATRState(period=14)
def _process_aggregated_data(self, timestamp: int, ohlcv: tuple) -> IncStrategySignal:
open_price, high, low, close, volume = ohlcv
# Update all indicators
self.sma.update(close)
self.rsi.update(close)
self.atr.update_ohlc(high, low, close)
# Check if all indicators are ready
if not all([self.sma.is_ready(), self.rsi.is_ready(), self.atr.is_ready()]):
return IncStrategySignal.HOLD()
# Use indicator values for signal generation
sma_value = self.sma.get_value()
rsi_value = self.rsi.get_value()
atr_value = self.atr.get_value()
# Generate signals based on indicator values
return self._generate_signal(close, sma_value, rsi_value, atr_value)
```
---
*The base indicator classes provide a solid foundation for building efficient, real-time indicators that maintain constant memory usage and processing time regardless of data history length.*