Vasily.onl c9ae507bb7 Implement Incremental Trading Framework
- Introduced a comprehensive framework for incremental trading strategies, including modules for strategy execution, backtesting, and data processing.
- Added key components such as `IncTrader`, `IncBacktester`, and various trading strategies (e.g., `MetaTrendStrategy`, `BBRSStrategy`, `RandomStrategy`) to facilitate real-time trading and backtesting.
- Implemented a robust backtesting framework with configuration management, parallel execution, and result analysis capabilities.
- Developed an incremental indicators framework to support real-time data processing with constant memory usage.
- Enhanced documentation to provide clear usage examples and architecture overview, ensuring maintainability and ease of understanding for future development.
- Ensured compatibility with existing strategies and maintained a focus on performance and scalability throughout the implementation.
2025-05-28 16:29:48 +08:00

316 lines
11 KiB
Python

"""
Supertrend Indicator State
This module implements incremental Supertrend calculation that maintains constant memory usage
and provides identical results to traditional batch calculations. Supertrend is used by
the DefaultStrategy for trend detection.
"""
from typing import Dict, Union, Optional
from .base import OHLCIndicatorState
from .atr import ATRState
class SupertrendState(OHLCIndicatorState):
"""
Incremental Supertrend calculation state.
Supertrend is a trend-following indicator that uses Average True Range (ATR)
to calculate dynamic support and resistance levels. It provides clear trend
direction signals: +1 for uptrend, -1 for downtrend.
The calculation involves:
1. Calculate ATR for the given period
2. Calculate basic upper and lower bands using ATR and multiplier
3. Calculate final upper and lower bands with trend logic
4. Determine trend direction based on price vs bands
Attributes:
period (int): ATR period for Supertrend calculation
multiplier (float): Multiplier for ATR in band calculation
atr_state (ATRState): ATR calculation state
previous_close (float): Previous period's close price
previous_trend (int): Previous trend direction (+1 or -1)
final_upper_band (float): Current final upper band
final_lower_band (float): Current final lower band
Example:
supertrend = SupertrendState(period=10, multiplier=3.0)
# Add OHLC data incrementally
ohlc = {'open': 100, 'high': 105, 'low': 98, 'close': 103}
result = supertrend.update(ohlc)
trend = result['trend'] # +1 or -1
supertrend_value = result['supertrend'] # Supertrend line value
"""
def __init__(self, period: int = 10, multiplier: float = 3.0):
"""
Initialize Supertrend state.
Args:
period: ATR period for Supertrend calculation (default: 10)
multiplier: Multiplier for ATR in band calculation (default: 3.0)
Raises:
ValueError: If period is not positive or multiplier is not positive
"""
super().__init__(period)
if multiplier <= 0:
raise ValueError(f"Multiplier must be positive, got {multiplier}")
self.multiplier = multiplier
self.atr_state = ATRState(period)
# State variables
self.previous_close = None
self.previous_trend = None # Don't assume initial trend, let first calculation determine it
self.final_upper_band = None
self.final_lower_band = None
# Current values
self.current_trend = None
self.current_supertrend = None
self.is_initialized = True
def update(self, ohlc_data: Dict[str, float]) -> Dict[str, float]:
"""
Update Supertrend with new OHLC data.
Args:
ohlc_data: Dictionary with 'open', 'high', 'low', 'close' keys
Returns:
Dictionary with 'trend', 'supertrend', 'upper_band', 'lower_band' keys
Raises:
ValueError: If OHLC data is invalid
TypeError: If ohlc_data is not a dictionary
"""
# Validate input
if not isinstance(ohlc_data, dict):
raise TypeError(f"ohlc_data must be a dictionary, got {type(ohlc_data)}")
self.validate_input(ohlc_data)
high = float(ohlc_data['high'])
low = float(ohlc_data['low'])
close = float(ohlc_data['close'])
# Update ATR
atr_value = self.atr_state.update(ohlc_data)
# Calculate HL2 (typical price)
hl2 = (high + low) / 2.0
# Calculate basic upper and lower bands
basic_upper_band = hl2 + (self.multiplier * atr_value)
basic_lower_band = hl2 - (self.multiplier * atr_value)
# Calculate final upper band
if self.final_upper_band is None or basic_upper_band < self.final_upper_band or self.previous_close > self.final_upper_band:
final_upper_band = basic_upper_band
else:
final_upper_band = self.final_upper_band
# Calculate final lower band
if self.final_lower_band is None or basic_lower_band > self.final_lower_band or self.previous_close < self.final_lower_band:
final_lower_band = basic_lower_band
else:
final_lower_band = self.final_lower_band
# Determine trend
if self.previous_close is None:
# First calculation - match original logic
# If close <= upper_band, trend is -1 (downtrend), else trend is 1 (uptrend)
trend = -1 if close <= basic_upper_band else 1
else:
# Trend logic for subsequent calculations
if self.previous_trend == 1 and close <= final_lower_band:
trend = -1
elif self.previous_trend == -1 and close >= final_upper_band:
trend = 1
else:
trend = self.previous_trend
# Calculate Supertrend value
if trend == 1:
supertrend_value = final_lower_band
else:
supertrend_value = final_upper_band
# Store current state
self.previous_close = close
self.previous_trend = trend
self.final_upper_band = final_upper_band
self.final_lower_band = final_lower_band
self.current_trend = trend
self.current_supertrend = supertrend_value
self.values_received += 1
# Prepare result
result = {
'trend': trend,
'supertrend': supertrend_value,
'upper_band': final_upper_band,
'lower_band': final_lower_band,
'atr': atr_value
}
self._current_values = result
return result
def is_warmed_up(self) -> bool:
"""
Check if Supertrend has enough data for reliable values.
Returns:
True if ATR state is warmed up
"""
return self.atr_state.is_warmed_up()
def reset(self) -> None:
"""Reset Supertrend state to initial conditions."""
self.atr_state.reset()
self.previous_close = None
self.previous_trend = None
self.final_upper_band = None
self.final_lower_band = None
self.current_trend = None
self.current_supertrend = None
self.values_received = 0
self._current_values = {}
def get_current_value(self) -> Optional[Dict[str, float]]:
"""
Get current Supertrend values without updating.
Returns:
Dictionary with current Supertrend values, or None if not warmed up
"""
if not self.is_warmed_up():
return None
return self._current_values.copy() if self._current_values else None
def get_current_trend(self) -> int:
"""
Get current trend direction.
Returns:
Current trend (+1 for uptrend, -1 for downtrend, 0 if not warmed up)
"""
return self.current_trend if self.current_trend is not None else 0
def get_current_supertrend_value(self) -> Optional[float]:
"""
Get current Supertrend line value.
Returns:
Current Supertrend value, or None if not warmed up
"""
return self.current_supertrend
def get_state_summary(self) -> dict:
"""Get detailed state summary for debugging."""
base_summary = super().get_state_summary()
base_summary.update({
'multiplier': self.multiplier,
'previous_close': self.previous_close,
'previous_trend': self.previous_trend,
'current_trend': self.current_trend,
'current_supertrend': self.current_supertrend,
'final_upper_band': self.final_upper_band,
'final_lower_band': self.final_lower_band,
'atr_state': self.atr_state.get_state_summary()
})
return base_summary
class SupertrendCollection:
"""
Collection of multiple Supertrend indicators for meta-trend calculation.
This class manages multiple Supertrend indicators with different parameters
and provides meta-trend calculation based on their agreement.
"""
def __init__(self, supertrend_configs: list):
"""
Initialize collection of Supertrend indicators.
Args:
supertrend_configs: List of (period, multiplier) tuples
"""
self.supertrends = []
self.configs = supertrend_configs
for period, multiplier in supertrend_configs:
supertrend = SupertrendState(period=period, multiplier=multiplier)
self.supertrends.append(supertrend)
def update(self, ohlc_data: Dict[str, float]) -> Dict[str, Union[int, list]]:
"""
Update all Supertrend indicators and calculate meta-trend.
Args:
ohlc_data: OHLC data dictionary
Returns:
Dictionary with 'meta_trend' and 'trends' keys
"""
trends = []
# Update each Supertrend and collect trends
for supertrend in self.supertrends:
result = supertrend.update(ohlc_data)
trends.append(result['trend'])
# Calculate meta-trend
meta_trend = self.get_current_meta_trend()
return {
'meta_trend': meta_trend,
'trends': trends
}
def is_warmed_up(self) -> bool:
"""Check if all Supertrend indicators are warmed up."""
return all(st.is_warmed_up() for st in self.supertrends)
def reset(self) -> None:
"""Reset all Supertrend indicators."""
for supertrend in self.supertrends:
supertrend.reset()
def get_current_meta_trend(self) -> int:
"""
Calculate current meta-trend from all Supertrend indicators.
Meta-trend logic:
- If all trends agree, return that trend
- If trends disagree, return 0 (neutral)
Returns:
Meta-trend value (1, -1, or 0)
"""
if not self.is_warmed_up():
return 0
trends = [st.get_current_trend() for st in self.supertrends]
# Check if all trends agree
if all(trend == trends[0] for trend in trends):
return trends[0] # All agree: return the common trend
else:
return 0 # Neutral when trends disagree
def get_state_summary(self) -> dict:
"""Get detailed state summary for all Supertrend indicators."""
return {
'configs': self.configs,
'meta_trend': self.get_current_meta_trend(),
'is_warmed_up': self.is_warmed_up(),
'supertrends': [st.get_state_summary() for st in self.supertrends]
}