577 lines
20 KiB
Markdown
577 lines
20 KiB
Markdown
# 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
|
||
|
||
```python
|
||
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
|
||
```python
|
||
# 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
|
||
```python
|
||
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
|
||
|
||
```python
|
||
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
|
||
```python
|
||
# 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
|
||
```python
|
||
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
|
||
```python
|
||
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
|
||
```python
|
||
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
|
||
```python
|
||
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
|
||
```python
|
||
# 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
|
||
```python
|
||
# 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
|
||
```python
|
||
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.* |