# Volatility Indicators ## Overview Volatility indicators measure the rate of price change and market uncertainty. IncrementalTrader provides Average True Range (ATR) implementations that help assess market volatility and set appropriate stop-loss levels. ## ATRState (Average True Range) Full ATR implementation that maintains a moving average of True Range values. ### Features - **True Range Calculation**: Accounts for gaps between trading sessions - **Volatility Measurement**: Provides absolute volatility measurement - **Stop-Loss Guidance**: Helps set dynamic stop-loss levels - **Trend Strength**: Indicates trend strength through volatility ### Mathematical Formula ``` True Range = max( High - Low, |High - Previous_Close|, |Low - Previous_Close| ) ATR = Moving_Average(True_Range, period) ``` ### Class Definition ```python from IncrementalTrader.strategies.indicators import ATRState class ATRState(OHLCIndicatorState): def __init__(self, period: int): super().__init__(period) self.true_ranges = [] self.tr_sum = 0.0 self.previous_close = None def _process_ohlc_data(self, high: float, low: float, close: float): # Calculate True Range if self.previous_close is not None: tr = max( high - low, abs(high - self.previous_close), abs(low - self.previous_close) ) else: tr = high - low # Update True Range moving average self.true_ranges.append(tr) self.tr_sum += tr if len(self.true_ranges) > self.period: old_tr = self.true_ranges.pop(0) self.tr_sum -= old_tr self.previous_close = close def get_value(self) -> float: if not self.is_ready(): return 0.0 return self.tr_sum / len(self.true_ranges) ``` ### Usage Examples #### Basic ATR Calculation ```python # Create 14-period ATR atr_14 = ATRState(period=14) # OHLC data: (high, low, close) ohlc_data = [ (105.0, 102.0, 104.0), (106.0, 103.0, 105.5), (107.0, 104.0, 106.0), (108.0, 105.0, 107.5) ] for high, low, close in ohlc_data: atr_14.update_ohlc(high, low, close) if atr_14.is_ready(): print(f"ATR(14): {atr_14.get_value():.2f}") ``` #### Dynamic Stop-Loss with ATR ```python class ATRStopLoss: def __init__(self, atr_period: int = 14, atr_multiplier: float = 2.0): self.atr = ATRState(atr_period) self.atr_multiplier = atr_multiplier def update(self, high: float, low: float, close: float): self.atr.update_ohlc(high, low, close) def get_stop_loss(self, entry_price: float, position_type: str) -> float: if not self.atr.is_ready(): return entry_price * 0.95 if position_type == "LONG" else entry_price * 1.05 atr_value = self.atr.get_value() if position_type == "LONG": return entry_price - (atr_value * self.atr_multiplier) else: # SHORT return entry_price + (atr_value * self.atr_multiplier) def get_position_size(self, account_balance: float, risk_percent: float, entry_price: float, position_type: str) -> float: """Calculate position size based on ATR risk.""" if not self.atr.is_ready(): return 0.0 risk_amount = account_balance * (risk_percent / 100) stop_loss = self.get_stop_loss(entry_price, position_type) risk_per_share = abs(entry_price - stop_loss) if risk_per_share == 0: return 0.0 return risk_amount / risk_per_share # Usage atr_stop = ATRStopLoss(atr_period=14, atr_multiplier=2.0) for high, low, close in ohlc_stream: atr_stop.update(high, low, close) # Calculate stop loss for a long position entry_price = close stop_loss = atr_stop.get_stop_loss(entry_price, "LONG") position_size = atr_stop.get_position_size(10000, 2.0, entry_price, "LONG") print(f"Entry: {entry_price:.2f}, Stop: {stop_loss:.2f}, Size: {position_size:.0f}") ``` ### Performance Characteristics - **Time Complexity**: O(1) per update - **Space Complexity**: O(period) - **Memory Usage**: ~8 bytes per period + constant overhead ## SimpleATRState Simplified ATR implementation using exponential smoothing instead of simple moving average. ### Features - **O(1) Memory**: Constant memory usage regardless of period - **Exponential Smoothing**: Uses Wilder's smoothing method - **Faster Computation**: No need to maintain historical True Range values - **Traditional ATR**: Follows Wilder's original ATR calculation ### Mathematical Formula ``` True Range = max( High - Low, |High - Previous_Close|, |Low - Previous_Close| ) ATR = (Previous_ATR × (period - 1) + True_Range) / period ``` ### Class Definition ```python class SimpleATRState(OHLCIndicatorState): def __init__(self, period: int): super().__init__(period) self.atr_value = 0.0 self.previous_close = None self.is_first_value = True def _process_ohlc_data(self, high: float, low: float, close: float): # Calculate True Range if self.previous_close is not None: tr = max( high - low, abs(high - self.previous_close), abs(low - self.previous_close) ) else: tr = high - low # Update ATR using Wilder's smoothing if self.is_first_value: self.atr_value = tr self.is_first_value = False else: self.atr_value = ((self.atr_value * (self.period - 1)) + tr) / self.period self.previous_close = close def get_value(self) -> float: return self.atr_value ``` ### Usage Examples #### Memory-Efficient ATR ```python # Create memory-efficient ATR simple_atr = SimpleATRState(period=14) # Process large amounts of data with constant memory for i, (high, low, close) in enumerate(large_ohlc_dataset): simple_atr.update_ohlc(high, low, close) if i % 1000 == 0: # Print every 1000 updates print(f"ATR after {i} updates: {simple_atr.get_value():.4f}") ``` #### Volatility Breakout Strategy ```python class VolatilityBreakout: def __init__(self, atr_period: int = 14, breakout_multiplier: float = 1.5): self.atr = SimpleATRState(atr_period) self.breakout_multiplier = breakout_multiplier self.previous_close = None def update(self, high: float, low: float, close: float): self.atr.update_ohlc(high, low, close) self.previous_close = close def get_breakout_levels(self, current_close: float) -> tuple: """Get upper and lower breakout levels.""" if not self.atr.is_ready() or self.previous_close is None: return current_close * 1.01, current_close * 0.99 atr_value = self.atr.get_value() breakout_distance = atr_value * self.breakout_multiplier upper_breakout = self.previous_close + breakout_distance lower_breakout = self.previous_close - breakout_distance return upper_breakout, lower_breakout def check_breakout(self, current_high: float, current_low: float, current_close: float) -> str: """Check if current price breaks out of volatility range.""" upper_level, lower_level = self.get_breakout_levels(current_close) if current_high > upper_level: return "BULLISH_BREAKOUT" elif current_low < lower_level: return "BEARISH_BREAKOUT" return "NO_BREAKOUT" # Usage breakout_detector = VolatilityBreakout(atr_period=14, breakout_multiplier=1.5) for high, low, close in ohlc_data: breakout_detector.update(high, low, close) breakout_signal = breakout_detector.check_breakout(high, low, close) if breakout_signal != "NO_BREAKOUT": print(f"Breakout detected: {breakout_signal} at {close:.2f}") ``` ### Performance Characteristics - **Time Complexity**: O(1) per update - **Space Complexity**: O(1) - **Memory Usage**: ~32 bytes (constant) ## Comparison: ATRState vs SimpleATRState | Aspect | ATRState | SimpleATRState | |--------|----------|----------------| | **Memory Usage** | O(period) | O(1) | | **Calculation Method** | Simple Moving Average | Exponential Smoothing | | **Accuracy** | Higher (true SMA) | Good (Wilder's method) | | **Responsiveness** | Moderate | Slightly more responsive | | **Historical Compatibility** | Modern | Traditional (Wilder's) | ### When to Use ATRState - **Precise Calculations**: When you need exact simple moving average of True Range - **Backtesting**: For historical analysis where memory isn't constrained - **Research**: When studying exact ATR behavior - **Small Periods**: When period is small (< 20) and memory isn't an issue ### When to Use SimpleATRState - **Memory Efficiency**: When processing large amounts of data - **Real-time Systems**: For high-frequency trading applications - **Traditional Analysis**: When following Wilder's original methodology - **Large Periods**: When using large ATR periods (> 50) ## Advanced Usage Patterns ### Multi-Timeframe ATR Analysis ```python class MultiTimeframeATR: def __init__(self): self.atr_short = SimpleATRState(period=7) # Short-term volatility self.atr_medium = SimpleATRState(period=14) # Medium-term volatility self.atr_long = SimpleATRState(period=28) # Long-term volatility def update(self, high: float, low: float, close: float): self.atr_short.update_ohlc(high, low, close) self.atr_medium.update_ohlc(high, low, close) self.atr_long.update_ohlc(high, low, close) def get_volatility_regime(self) -> str: """Determine current volatility regime.""" if not all([self.atr_short.is_ready(), self.atr_medium.is_ready(), self.atr_long.is_ready()]): return "UNKNOWN" short_atr = self.atr_short.get_value() medium_atr = self.atr_medium.get_value() long_atr = self.atr_long.get_value() # Compare short-term to long-term volatility volatility_ratio = short_atr / long_atr if long_atr > 0 else 1.0 if volatility_ratio > 1.5: return "HIGH_VOLATILITY" elif volatility_ratio < 0.7: return "LOW_VOLATILITY" else: return "NORMAL_VOLATILITY" def get_adaptive_stop_multiplier(self) -> float: """Get adaptive stop-loss multiplier based on volatility regime.""" regime = self.get_volatility_regime() if regime == "HIGH_VOLATILITY": return 2.5 # Wider stops in high volatility elif regime == "LOW_VOLATILITY": return 1.5 # Tighter stops in low volatility else: return 2.0 # Standard stops in normal volatility # Usage multi_atr = MultiTimeframeATR() for high, low, close in ohlc_data: multi_atr.update(high, low, close) regime = multi_atr.get_volatility_regime() stop_multiplier = multi_atr.get_adaptive_stop_multiplier() print(f"Volatility Regime: {regime}, Stop Multiplier: {stop_multiplier:.1f}") ``` ### ATR-Based Position Sizing ```python class ATRPositionSizer: def __init__(self, atr_period: int = 14): self.atr = SimpleATRState(atr_period) self.price_history = [] def update(self, high: float, low: float, close: float): self.atr.update_ohlc(high, low, close) self.price_history.append(close) # Keep only recent price history if len(self.price_history) > 100: self.price_history.pop(0) def calculate_position_size(self, account_balance: float, risk_percent: float, entry_price: float, stop_loss_atr_multiplier: float = 2.0) -> dict: """Calculate position size based on ATR risk management.""" if not self.atr.is_ready(): return {"position_size": 0, "risk_amount": 0, "stop_loss": entry_price * 0.95} atr_value = self.atr.get_value() risk_amount = account_balance * (risk_percent / 100) # Calculate stop loss based on ATR stop_loss = entry_price - (atr_value * stop_loss_atr_multiplier) risk_per_share = entry_price - stop_loss # Calculate position size if risk_per_share > 0: position_size = risk_amount / risk_per_share else: position_size = 0 return { "position_size": position_size, "risk_amount": risk_amount, "stop_loss": stop_loss, "atr_value": atr_value, "risk_per_share": risk_per_share } def get_volatility_percentile(self) -> float: """Get current ATR percentile compared to recent history.""" if not self.atr.is_ready() or len(self.price_history) < 20: return 50.0 # Default to median current_atr = self.atr.get_value() # Calculate ATR for recent periods recent_atrs = [] for i in range(len(self.price_history) - 14): if i + 14 < len(self.price_history): # Simplified ATR calculation for comparison price_range = max(self.price_history[i:i+14]) - min(self.price_history[i:i+14]) recent_atrs.append(price_range) if not recent_atrs: return 50.0 # Calculate percentile sorted_atrs = sorted(recent_atrs) position = sum(1 for atr in sorted_atrs if atr <= current_atr) percentile = (position / len(sorted_atrs)) * 100 return percentile # Usage position_sizer = ATRPositionSizer(atr_period=14) for high, low, close in ohlc_data: position_sizer.update(high, low, close) # Calculate position for a potential trade trade_info = position_sizer.calculate_position_size( account_balance=10000, risk_percent=2.0, entry_price=close, stop_loss_atr_multiplier=2.0 ) volatility_percentile = position_sizer.get_volatility_percentile() print(f"Price: {close:.2f}, Position Size: {trade_info['position_size']:.0f}, " f"ATR Percentile: {volatility_percentile:.1f}%") ``` ## Integration with Strategies ### ATR-Enhanced Strategy Example ```python class ATRTrendStrategy(IncStrategyBase): def __init__(self, name: str, params: dict = None): super().__init__(name, params) # Initialize indicators self.atr = SimpleATRState(self.params.get('atr_period', 14)) self.sma = MovingAverageState(self.params.get('sma_period', 20)) # ATR parameters self.atr_stop_multiplier = self.params.get('atr_stop_multiplier', 2.0) self.atr_entry_multiplier = self.params.get('atr_entry_multiplier', 0.5) def _process_aggregated_data(self, timestamp: int, ohlcv: tuple) -> IncStrategySignal: open_price, high, low, close, volume = ohlcv # Update indicators self.atr.update_ohlc(high, low, close) self.sma.update(close) # Wait for indicators to be ready if not all([self.atr.is_ready(), self.sma.is_ready()]): return IncStrategySignal.HOLD() atr_value = self.atr.get_value() sma_value = self.sma.get_value() # Calculate dynamic entry threshold based on ATR entry_threshold = atr_value * self.atr_entry_multiplier # Generate signals based on trend and volatility if close > sma_value + entry_threshold: # Strong uptrend with sufficient volatility confidence = min(0.9, (close - sma_value) / atr_value * 0.1) # Calculate stop loss stop_loss = close - (atr_value * self.atr_stop_multiplier) return IncStrategySignal.BUY( confidence=confidence, metadata={ 'atr_value': atr_value, 'sma_value': sma_value, 'stop_loss': stop_loss, 'entry_threshold': entry_threshold } ) elif close < sma_value - entry_threshold: # Strong downtrend with sufficient volatility confidence = min(0.9, (sma_value - close) / atr_value * 0.1) # Calculate stop loss stop_loss = close + (atr_value * self.atr_stop_multiplier) return IncStrategySignal.SELL( confidence=confidence, metadata={ 'atr_value': atr_value, 'sma_value': sma_value, 'stop_loss': stop_loss, 'entry_threshold': entry_threshold } ) return IncStrategySignal.HOLD() ``` ## Performance Optimization Tips ### 1. Choose the Right ATR Implementation ```python # For memory-constrained environments atr = SimpleATRState(period=14) # O(1) memory # For precise calculations atr = ATRState(period=14) # O(period) memory ``` ### 2. Batch Processing for Multiple ATRs ```python def update_multiple_atrs(atrs: list, high: float, low: float, close: float): """Efficiently update multiple ATR indicators.""" for atr in atrs: atr.update_ohlc(high, low, close) return [atr.get_value() for atr in atrs if atr.is_ready()] ``` ### 3. Cache ATR Values for Complex Calculations ```python class CachedATR: def __init__(self, period: int): self.atr = SimpleATRState(period) self._cached_value = 0.0 self._cache_valid = False def update_ohlc(self, high: float, low: float, close: float): self.atr.update_ohlc(high, low, close) self._cache_valid = False def get_value(self) -> float: if not self._cache_valid: self._cached_value = self.atr.get_value() self._cache_valid = True return self._cached_value ``` --- *ATR indicators are essential for risk management and volatility analysis. Use ATRState for precise calculations or SimpleATRState for memory efficiency in high-frequency applications.*