20 KiB
20 KiB
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.