2025-05-28 22:37:53 +08:00

20 KiB
Raw Blame History

Trend Indicators

Overview

Trend indicators help identify the direction and strength of market trends. IncrementalTrader provides Supertrend implementations that combine price action with volatility to generate clear trend signals.

SupertrendState

Individual Supertrend indicator that tracks trend direction and provides support/resistance levels.

Features

  • Trend Direction: Clear bullish/bearish trend identification
  • Dynamic Support/Resistance: Adaptive levels based on volatility
  • ATR-Based: Uses Average True Range for volatility adjustment
  • Real-time Updates: Incremental calculation for live trading

Mathematical Formula

Basic Upper Band = (High + Low) / 2 + (Multiplier × ATR)
Basic Lower Band = (High + Low) / 2 - (Multiplier × ATR)

Final Upper Band = Basic Upper Band < Previous Final Upper Band OR Previous Close > Previous Final Upper Band 
                   ? Basic Upper Band : Previous Final Upper Band

Final Lower Band = Basic Lower Band > Previous Final Lower Band OR Previous Close < Previous Final Lower Band 
                   ? Basic Lower Band : Previous Final Lower Band

Supertrend = Close <= Final Lower Band ? Final Lower Band : Final Upper Band
Trend = Close <= Final Lower Band ? DOWN : UP

Class Definition

from IncrementalTrader.strategies.indicators import SupertrendState

class SupertrendState(OHLCIndicatorState):
    def __init__(self, period: int, multiplier: float):
        super().__init__(period)
        self.multiplier = multiplier
        self.atr = SimpleATRState(period)
        
        # Supertrend state
        self.supertrend_value = 0.0
        self.trend = 1  # 1 for up, -1 for down
        self.final_upper_band = 0.0
        self.final_lower_band = 0.0
        self.previous_close = 0.0
    
    def _process_ohlc_data(self, high: float, low: float, close: float):
        # Update ATR
        self.atr.update_ohlc(high, low, close)
        
        if not self.atr.is_ready():
            return
        
        # Calculate basic bands
        hl2 = (high + low) / 2.0
        atr_value = self.atr.get_value()
        
        basic_upper_band = hl2 + (self.multiplier * atr_value)
        basic_lower_band = hl2 - (self.multiplier * atr_value)
        
        # Calculate final bands
        if self.data_count == 1:
            self.final_upper_band = basic_upper_band
            self.final_lower_band = basic_lower_band
        else:
            # Final upper band logic
            if basic_upper_band < self.final_upper_band or self.previous_close > self.final_upper_band:
                self.final_upper_band = basic_upper_band
            
            # Final lower band logic
            if basic_lower_band > self.final_lower_band or self.previous_close < self.final_lower_band:
                self.final_lower_band = basic_lower_band
        
        # Determine trend and supertrend value
        if close <= self.final_lower_band:
            self.trend = -1  # Downtrend
            self.supertrend_value = self.final_lower_band
        else:
            self.trend = 1   # Uptrend
            self.supertrend_value = self.final_upper_band
        
        self.previous_close = close
    
    def get_value(self) -> float:
        return self.supertrend_value
    
    def get_trend(self) -> int:
        """Get current trend direction: 1 for up, -1 for down."""
        return self.trend
    
    def is_bullish(self) -> bool:
        """Check if current trend is bullish."""
        return self.trend == 1
    
    def is_bearish(self) -> bool:
        """Check if current trend is bearish."""
        return self.trend == -1

Usage Examples

Basic Supertrend Usage

# Create Supertrend with 10-period ATR and 3.0 multiplier
supertrend = SupertrendState(period=10, multiplier=3.0)

# 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:
    supertrend.update_ohlc(high, low, close)
    if supertrend.is_ready():
        trend_direction = "BULLISH" if supertrend.is_bullish() else "BEARISH"
        print(f"Supertrend: {supertrend.get_value():.2f}, Trend: {trend_direction}")

Trend Change Detection

class SupertrendSignals:
    def __init__(self, period: int = 10, multiplier: float = 3.0):
        self.supertrend = SupertrendState(period, multiplier)
        self.previous_trend = None
    
    def update(self, high: float, low: float, close: float):
        self.supertrend.update_ohlc(high, low, close)
    
    def get_signal(self) -> str:
        if not self.supertrend.is_ready():
            return "HOLD"
        
        current_trend = self.supertrend.get_trend()
        
        # Check for trend change
        if self.previous_trend is not None and self.previous_trend != current_trend:
            if current_trend == 1:
                signal = "BUY"  # Trend changed to bullish
            else:
                signal = "SELL"  # Trend changed to bearish
        else:
            signal = "HOLD"
        
        self.previous_trend = current_trend
        return signal
    
    def get_support_resistance(self) -> float:
        """Get current support/resistance level."""
        return self.supertrend.get_value()

# Usage
signals = SupertrendSignals(period=10, multiplier=3.0)

for high, low, close in ohlc_data:
    signals.update(high, low, close)
    signal = signals.get_signal()
    support_resistance = signals.get_support_resistance()
    
    if signal != "HOLD":
        print(f"Signal: {signal} at {close:.2f}, S/R: {support_resistance:.2f}")

Performance Characteristics

  • Time Complexity: O(1) per update
  • Space Complexity: O(ATR_period)
  • Memory Usage: ~8 bytes per ATR period + constant overhead

SupertrendCollection

Collection of multiple Supertrend indicators for meta-trend analysis.

Features

  • Multiple Timeframes: Combines different Supertrend configurations
  • Consensus Signals: Requires agreement among multiple indicators
  • Trend Strength: Measures trend strength through consensus
  • Flexible Configuration: Customizable periods and multipliers

Class Definition

class SupertrendCollection:
    def __init__(self, configs: list):
        """
        Initialize with list of (period, multiplier) tuples.
        Example: [(10, 3.0), (14, 2.0), (21, 1.5)]
        """
        self.supertrendss = []
        for period, multiplier in configs:
            self.supertrendss.append(SupertrendState(period, multiplier))
        
        self.configs = configs
    
    def update_ohlc(self, high: float, low: float, close: float):
        """Update all Supertrend indicators."""
        for st in self.supertrendss:
            st.update_ohlc(high, low, close)
    
    def is_ready(self) -> bool:
        """Check if all indicators are ready."""
        return all(st.is_ready() for st in self.supertrendss)
    
    def get_consensus_trend(self) -> int:
        """Get consensus trend: 1 for bullish, -1 for bearish, 0 for mixed."""
        if not self.is_ready():
            return 0
        
        trends = [st.get_trend() for st in self.supertrendss]
        bullish_count = sum(1 for trend in trends if trend == 1)
        bearish_count = sum(1 for trend in trends if trend == -1)
        
        if bullish_count > bearish_count:
            return 1
        elif bearish_count > bullish_count:
            return -1
        else:
            return 0
    
    def get_trend_strength(self) -> float:
        """Get trend strength as percentage of indicators agreeing."""
        if not self.is_ready():
            return 0.0
        
        consensus_trend = self.get_consensus_trend()
        if consensus_trend == 0:
            return 0.0
        
        trends = [st.get_trend() for st in self.supertrendss]
        agreeing_count = sum(1 for trend in trends if trend == consensus_trend)
        
        return agreeing_count / len(trends)
    
    def get_supertrend_values(self) -> list:
        """Get all Supertrend values."""
        return [st.get_value() for st in self.supertrendss if st.is_ready()]
    
    def get_average_supertrend(self) -> float:
        """Get average Supertrend value."""
        values = self.get_supertrend_values()
        return sum(values) / len(values) if values else 0.0

Usage Examples

Multi-Timeframe Trend Analysis

# Create collection with different configurations
configs = [
    (10, 3.0),  # Fast Supertrend
    (14, 2.5),  # Medium Supertrend
    (21, 2.0)   # Slow Supertrend
]

supertrend_collection = SupertrendCollection(configs)

for high, low, close in ohlc_data:
    supertrend_collection.update_ohlc(high, low, close)
    
    if supertrend_collection.is_ready():
        consensus = supertrend_collection.get_consensus_trend()
        strength = supertrend_collection.get_trend_strength()
        avg_supertrend = supertrend_collection.get_average_supertrend()
        
        trend_name = {1: "BULLISH", -1: "BEARISH", 0: "MIXED"}[consensus]
        print(f"Consensus: {trend_name}, Strength: {strength:.1%}, Avg S/R: {avg_supertrend:.2f}")

Meta-Trend Strategy

class MetaTrendStrategy:
    def __init__(self):
        # Multiple Supertrend configurations
        self.supertrend_collection = SupertrendCollection([
            (10, 3.0),  # Fast
            (14, 2.5),  # Medium
            (21, 2.0),  # Slow
            (28, 1.5)   # Very slow
        ])
        
        self.previous_consensus = None
    
    def update(self, high: float, low: float, close: float):
        self.supertrend_collection.update_ohlc(high, low, close)
    
    def get_meta_signal(self) -> dict:
        if not self.supertrend_collection.is_ready():
            return {"signal": "HOLD", "confidence": 0.0, "strength": 0.0}
        
        current_consensus = self.supertrend_collection.get_consensus_trend()
        strength = self.supertrend_collection.get_trend_strength()
        
        # Check for consensus change
        signal = "HOLD"
        if self.previous_consensus is not None and self.previous_consensus != current_consensus:
            if current_consensus == 1:
                signal = "BUY"
            elif current_consensus == -1:
                signal = "SELL"
        
        # Calculate confidence based on strength and consensus
        confidence = strength if current_consensus != 0 else 0.0
        
        self.previous_consensus = current_consensus
        
        return {
            "signal": signal,
            "confidence": confidence,
            "strength": strength,
            "consensus": current_consensus,
            "avg_supertrend": self.supertrend_collection.get_average_supertrend()
        }

# Usage
meta_strategy = MetaTrendStrategy()

for high, low, close in ohlc_data:
    meta_strategy.update(high, low, close)
    result = meta_strategy.get_meta_signal()
    
    if result["signal"] != "HOLD":
        print(f"Meta Signal: {result['signal']}, Confidence: {result['confidence']:.1%}")

Performance Characteristics

  • Time Complexity: O(n) per update (where n is number of Supertrends)
  • Space Complexity: O(sum of all ATR periods)
  • Memory Usage: Scales with number of indicators

Advanced Usage Patterns

Adaptive Supertrend

class AdaptiveSupertrend:
    def __init__(self, base_period: int = 14, base_multiplier: float = 2.0):
        self.base_period = base_period
        self.base_multiplier = base_multiplier
        
        # Volatility measurement for adaptation
        self.atr_short = SimpleATRState(period=5)
        self.atr_long = SimpleATRState(period=20)
        
        # Current adaptive Supertrend
        self.current_supertrend = SupertrendState(base_period, base_multiplier)
        
        # Adaptation parameters
        self.min_multiplier = 1.0
        self.max_multiplier = 4.0
    
    def update_ohlc(self, high: float, low: float, close: float):
        # Update volatility measurements
        self.atr_short.update_ohlc(high, low, close)
        self.atr_long.update_ohlc(high, low, close)
        
        # Calculate adaptive multiplier
        if self.atr_long.is_ready() and self.atr_short.is_ready():
            volatility_ratio = self.atr_short.get_value() / self.atr_long.get_value()
            
            # Adjust multiplier based on volatility
            adaptive_multiplier = self.base_multiplier * volatility_ratio
            adaptive_multiplier = max(self.min_multiplier, min(self.max_multiplier, adaptive_multiplier))
            
            # Update Supertrend if multiplier changed significantly
            if abs(adaptive_multiplier - self.current_supertrend.multiplier) > 0.1:
                self.current_supertrend = SupertrendState(self.base_period, adaptive_multiplier)
        
        # Update current Supertrend
        self.current_supertrend.update_ohlc(high, low, close)
    
    def get_value(self) -> float:
        return self.current_supertrend.get_value()
    
    def get_trend(self) -> int:
        return self.current_supertrend.get_trend()
    
    def is_ready(self) -> bool:
        return self.current_supertrend.is_ready()
    
    def get_current_multiplier(self) -> float:
        return self.current_supertrend.multiplier

# Usage
adaptive_st = AdaptiveSupertrend(base_period=14, base_multiplier=2.0)

for high, low, close in ohlc_data:
    adaptive_st.update_ohlc(high, low, close)
    
    if adaptive_st.is_ready():
        trend = "BULLISH" if adaptive_st.get_trend() == 1 else "BEARISH"
        multiplier = adaptive_st.get_current_multiplier()
        print(f"Adaptive Supertrend: {adaptive_st.get_value():.2f}, "
              f"Trend: {trend}, Multiplier: {multiplier:.2f}")

Supertrend with Stop Loss Management

class SupertrendStopLoss:
    def __init__(self, period: int = 14, multiplier: float = 2.0, buffer_percent: float = 0.5):
        self.supertrend = SupertrendState(period, multiplier)
        self.buffer_percent = buffer_percent / 100.0
        
        self.current_position = None  # "LONG", "SHORT", or None
        self.entry_price = 0.0
        self.stop_loss = 0.0
    
    def update(self, high: float, low: float, close: float):
        previous_trend = self.supertrend.get_trend() if self.supertrend.is_ready() else None
        self.supertrend.update_ohlc(high, low, close)
        
        if not self.supertrend.is_ready():
            return
        
        current_trend = self.supertrend.get_trend()
        supertrend_value = self.supertrend.get_value()
        
        # Check for trend change (entry signal)
        if previous_trend is not None and previous_trend != current_trend:
            if current_trend == 1:  # Bullish trend
                self.enter_long(close, supertrend_value)
            else:  # Bearish trend
                self.enter_short(close, supertrend_value)
        
        # Update stop loss for existing position
        if self.current_position:
            self.update_stop_loss(supertrend_value)
    
    def enter_long(self, price: float, supertrend_value: float):
        self.current_position = "LONG"
        self.entry_price = price
        self.stop_loss = supertrend_value * (1 - self.buffer_percent)
        print(f"LONG entry at {price:.2f}, Stop: {self.stop_loss:.2f}")
    
    def enter_short(self, price: float, supertrend_value: float):
        self.current_position = "SHORT"
        self.entry_price = price
        self.stop_loss = supertrend_value * (1 + self.buffer_percent)
        print(f"SHORT entry at {price:.2f}, Stop: {self.stop_loss:.2f}")
    
    def update_stop_loss(self, supertrend_value: float):
        if self.current_position == "LONG":
            new_stop = supertrend_value * (1 - self.buffer_percent)
            if new_stop > self.stop_loss:  # Only move stop up
                self.stop_loss = new_stop
        elif self.current_position == "SHORT":
            new_stop = supertrend_value * (1 + self.buffer_percent)
            if new_stop < self.stop_loss:  # Only move stop down
                self.stop_loss = new_stop
    
    def check_stop_loss(self, current_price: float) -> bool:
        """Check if stop loss is hit."""
        if not self.current_position:
            return False
        
        if self.current_position == "LONG" and current_price <= self.stop_loss:
            print(f"LONG stop loss hit at {current_price:.2f}")
            self.current_position = None
            return True
        elif self.current_position == "SHORT" and current_price >= self.stop_loss:
            print(f"SHORT stop loss hit at {current_price:.2f}")
            self.current_position = None
            return True
        
        return False

# Usage
st_stop_loss = SupertrendStopLoss(period=14, multiplier=2.0, buffer_percent=0.5)

for high, low, close in ohlc_data:
    st_stop_loss.update(high, low, close)
    
    # Check stop loss on each update
    if st_stop_loss.check_stop_loss(close):
        print("Position closed due to stop loss")

Integration with Strategies

Supertrend Strategy Example

class SupertrendStrategy(IncStrategyBase):
    def __init__(self, name: str, params: dict = None):
        super().__init__(name, params)
        
        # Initialize Supertrend collection
        configs = self.params.get('supertrend_configs', [(10, 3.0), (14, 2.5), (21, 2.0)])
        self.supertrend_collection = SupertrendCollection(configs)
        
        # Strategy parameters
        self.min_strength = self.params.get('min_strength', 0.75)
        self.previous_consensus = None
    
    def _process_aggregated_data(self, timestamp: int, ohlcv: tuple) -> IncStrategySignal:
        open_price, high, low, close, volume = ohlcv
        
        # Update Supertrend collection
        self.supertrend_collection.update_ohlc(high, low, close)
        
        # Wait for indicators to be ready
        if not self.supertrend_collection.is_ready():
            return IncStrategySignal.HOLD()
        
        # Get consensus and strength
        current_consensus = self.supertrend_collection.get_consensus_trend()
        strength = self.supertrend_collection.get_trend_strength()
        
        # Check for strong consensus change
        if (self.previous_consensus is not None and 
            self.previous_consensus != current_consensus and 
            strength >= self.min_strength):
            
            if current_consensus == 1:
                # Strong bullish consensus
                return IncStrategySignal.BUY(
                    confidence=strength,
                    metadata={
                        'consensus': current_consensus,
                        'strength': strength,
                        'avg_supertrend': self.supertrend_collection.get_average_supertrend()
                    }
                )
            elif current_consensus == -1:
                # Strong bearish consensus
                return IncStrategySignal.SELL(
                    confidence=strength,
                    metadata={
                        'consensus': current_consensus,
                        'strength': strength,
                        'avg_supertrend': self.supertrend_collection.get_average_supertrend()
                    }
                )
        
        self.previous_consensus = current_consensus
        return IncStrategySignal.HOLD()

Performance Optimization Tips

1. Choose Appropriate Configurations

# For fast signals (more noise)
fast_configs = [(7, 3.0), (10, 2.5)]

# For balanced signals
balanced_configs = [(10, 3.0), (14, 2.5), (21, 2.0)]

# For slow, reliable signals
slow_configs = [(14, 2.0), (21, 1.5), (28, 1.0)]

2. Optimize Memory Usage

# Use SimpleATRState for memory efficiency
class MemoryEfficientSupertrend(SupertrendState):
    def __init__(self, period: int, multiplier: float):
        super().__init__(period, multiplier)
        # Replace ATRState with SimpleATRState
        self.atr = SimpleATRState(period)

3. Batch Processing

def update_multiple_supertrends(supertrends: list, high: float, low: float, close: float):
    """Efficiently update multiple Supertrend indicators."""
    for st in supertrends:
        st.update_ohlc(high, low, close)
    
    return [(st.get_value(), st.get_trend()) for st in supertrends if st.is_ready()]

Supertrend indicators provide clear trend direction and dynamic support/resistance levels. Use single Supertrend for simple trend following or SupertrendCollection for robust meta-trend analysis.