532 lines
20 KiB
Python
532 lines
20 KiB
Python
|
|
"""
|
|||
|
|
Incremental BBRS Strategy
|
|||
|
|
|
|||
|
|
This module implements an incremental version of the Bollinger Bands + RSI Strategy (BBRS)
|
|||
|
|
for real-time data processing. It maintains constant memory usage and provides
|
|||
|
|
identical results to the batch implementation after the warm-up period.
|
|||
|
|
|
|||
|
|
Key Features:
|
|||
|
|
- Accepts minute-level data input for real-time compatibility
|
|||
|
|
- Internal timeframe aggregation (1min, 5min, 15min, 1h, etc.)
|
|||
|
|
- Incremental Bollinger Bands calculation
|
|||
|
|
- Incremental RSI calculation with Wilder's smoothing
|
|||
|
|
- Market regime detection (trending vs sideways)
|
|||
|
|
- Real-time signal generation
|
|||
|
|
- Constant memory usage
|
|||
|
|
"""
|
|||
|
|
|
|||
|
|
from typing import Dict, Optional, Union, Tuple
|
|||
|
|
import numpy as np
|
|||
|
|
import pandas as pd
|
|||
|
|
from datetime import datetime, timedelta
|
|||
|
|
from .indicators.bollinger_bands import BollingerBandsState
|
|||
|
|
from .indicators.rsi import RSIState
|
|||
|
|
|
|||
|
|
|
|||
|
|
class TimeframeAggregator:
|
|||
|
|
"""
|
|||
|
|
Handles real-time aggregation of minute data to higher timeframes.
|
|||
|
|
|
|||
|
|
This class accumulates minute-level OHLCV data and produces complete
|
|||
|
|
bars when a timeframe period is completed.
|
|||
|
|
"""
|
|||
|
|
|
|||
|
|
def __init__(self, timeframe_minutes: int = 15):
|
|||
|
|
"""
|
|||
|
|
Initialize timeframe aggregator.
|
|||
|
|
|
|||
|
|
Args:
|
|||
|
|
timeframe_minutes: Target timeframe in minutes (e.g., 60 for 1h, 15 for 15min)
|
|||
|
|
"""
|
|||
|
|
self.timeframe_minutes = timeframe_minutes
|
|||
|
|
self.current_bar = None
|
|||
|
|
self.current_bar_start = None
|
|||
|
|
self.last_completed_bar = None
|
|||
|
|
|
|||
|
|
def update(self, timestamp: pd.Timestamp, ohlcv_data: Dict[str, float]) -> Optional[Dict[str, float]]:
|
|||
|
|
"""
|
|||
|
|
Update with new minute data and return completed bar if timeframe is complete.
|
|||
|
|
|
|||
|
|
Args:
|
|||
|
|
timestamp: Timestamp of the data
|
|||
|
|
ohlcv_data: OHLCV data dictionary
|
|||
|
|
|
|||
|
|
Returns:
|
|||
|
|
Completed OHLCV bar if timeframe period ended, None otherwise
|
|||
|
|
"""
|
|||
|
|
# Calculate which timeframe bar this timestamp belongs to
|
|||
|
|
bar_start = self._get_bar_start_time(timestamp)
|
|||
|
|
|
|||
|
|
# Check if we're starting a new bar
|
|||
|
|
if self.current_bar_start != bar_start:
|
|||
|
|
# Save the completed bar (if any)
|
|||
|
|
completed_bar = self.current_bar.copy() if self.current_bar is not None else None
|
|||
|
|
|
|||
|
|
# Start new bar
|
|||
|
|
self.current_bar_start = bar_start
|
|||
|
|
self.current_bar = {
|
|||
|
|
'timestamp': bar_start,
|
|||
|
|
'open': ohlcv_data['close'], # Use current close as open for new bar
|
|||
|
|
'high': ohlcv_data['close'],
|
|||
|
|
'low': ohlcv_data['close'],
|
|||
|
|
'close': ohlcv_data['close'],
|
|||
|
|
'volume': ohlcv_data['volume']
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
# Return the completed bar (if any)
|
|||
|
|
if completed_bar is not None:
|
|||
|
|
self.last_completed_bar = completed_bar
|
|||
|
|
return completed_bar
|
|||
|
|
else:
|
|||
|
|
# Update current bar with new data
|
|||
|
|
if self.current_bar is not None:
|
|||
|
|
self.current_bar['high'] = max(self.current_bar['high'], ohlcv_data['high'])
|
|||
|
|
self.current_bar['low'] = min(self.current_bar['low'], ohlcv_data['low'])
|
|||
|
|
self.current_bar['close'] = ohlcv_data['close']
|
|||
|
|
self.current_bar['volume'] += ohlcv_data['volume']
|
|||
|
|
|
|||
|
|
return None # No completed bar yet
|
|||
|
|
|
|||
|
|
def _get_bar_start_time(self, timestamp: pd.Timestamp) -> pd.Timestamp:
|
|||
|
|
"""Calculate the start time of the timeframe bar for given timestamp."""
|
|||
|
|
# Round down to the nearest timeframe boundary
|
|||
|
|
minutes_since_midnight = timestamp.hour * 60 + timestamp.minute
|
|||
|
|
bar_minutes = (minutes_since_midnight // self.timeframe_minutes) * self.timeframe_minutes
|
|||
|
|
|
|||
|
|
return timestamp.replace(
|
|||
|
|
hour=bar_minutes // 60,
|
|||
|
|
minute=bar_minutes % 60,
|
|||
|
|
second=0,
|
|||
|
|
microsecond=0
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
def get_current_bar(self) -> Optional[Dict[str, float]]:
|
|||
|
|
"""Get the current incomplete bar (for debugging)."""
|
|||
|
|
return self.current_bar.copy() if self.current_bar is not None else None
|
|||
|
|
|
|||
|
|
def reset(self):
|
|||
|
|
"""Reset aggregator state."""
|
|||
|
|
self.current_bar = None
|
|||
|
|
self.current_bar_start = None
|
|||
|
|
self.last_completed_bar = None
|
|||
|
|
|
|||
|
|
|
|||
|
|
class BBRSIncrementalState:
|
|||
|
|
"""
|
|||
|
|
Incremental BBRS strategy state for real-time processing.
|
|||
|
|
|
|||
|
|
This class maintains all the state needed for the BBRS strategy and can
|
|||
|
|
process new minute-level price data incrementally, internally aggregating
|
|||
|
|
to the configured timeframe before running indicators.
|
|||
|
|
|
|||
|
|
Attributes:
|
|||
|
|
timeframe_minutes (int): Strategy timeframe in minutes (default: 60 for 1h)
|
|||
|
|
bb_period (int): Bollinger Bands period
|
|||
|
|
rsi_period (int): RSI period
|
|||
|
|
bb_width_threshold (float): BB width threshold for market regime detection
|
|||
|
|
trending_bb_multiplier (float): BB multiplier for trending markets
|
|||
|
|
sideways_bb_multiplier (float): BB multiplier for sideways markets
|
|||
|
|
trending_rsi_thresholds (tuple): RSI thresholds for trending markets (low, high)
|
|||
|
|
sideways_rsi_thresholds (tuple): RSI thresholds for sideways markets (low, high)
|
|||
|
|
squeeze_strategy (bool): Enable squeeze strategy
|
|||
|
|
|
|||
|
|
Example:
|
|||
|
|
# Initialize strategy for 1-hour timeframe
|
|||
|
|
config = {
|
|||
|
|
"timeframe_minutes": 60, # 1 hour bars
|
|||
|
|
"bb_period": 20,
|
|||
|
|
"rsi_period": 14,
|
|||
|
|
"bb_width": 0.05,
|
|||
|
|
"trending": {
|
|||
|
|
"bb_std_dev_multiplier": 2.5,
|
|||
|
|
"rsi_threshold": [30, 70]
|
|||
|
|
},
|
|||
|
|
"sideways": {
|
|||
|
|
"bb_std_dev_multiplier": 1.8,
|
|||
|
|
"rsi_threshold": [40, 60]
|
|||
|
|
},
|
|||
|
|
"SqueezeStrategy": True
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
strategy = BBRSIncrementalState(config)
|
|||
|
|
|
|||
|
|
# Process minute-level data in real-time
|
|||
|
|
for minute_data in live_data_stream:
|
|||
|
|
result = strategy.update_minute_data(minute_data['timestamp'], minute_data)
|
|||
|
|
if result is not None: # New timeframe bar completed
|
|||
|
|
if result['buy_signal']:
|
|||
|
|
print("Buy signal generated!")
|
|||
|
|
"""
|
|||
|
|
|
|||
|
|
def __init__(self, config: Dict):
|
|||
|
|
"""
|
|||
|
|
Initialize incremental BBRS strategy.
|
|||
|
|
|
|||
|
|
Args:
|
|||
|
|
config: Strategy configuration dictionary
|
|||
|
|
"""
|
|||
|
|
# Store configuration
|
|||
|
|
self.timeframe_minutes = config.get("timeframe_minutes", 60) # Default to 1 hour
|
|||
|
|
self.bb_period = config.get("bb_period", 20)
|
|||
|
|
self.rsi_period = config.get("rsi_period", 14)
|
|||
|
|
self.bb_width_threshold = config.get("bb_width", 0.05)
|
|||
|
|
|
|||
|
|
# Market regime specific parameters
|
|||
|
|
trending_config = config.get("trending", {})
|
|||
|
|
sideways_config = config.get("sideways", {})
|
|||
|
|
|
|||
|
|
self.trending_bb_multiplier = trending_config.get("bb_std_dev_multiplier", 2.5)
|
|||
|
|
self.sideways_bb_multiplier = sideways_config.get("bb_std_dev_multiplier", 1.8)
|
|||
|
|
self.trending_rsi_thresholds = tuple(trending_config.get("rsi_threshold", [30, 70]))
|
|||
|
|
self.sideways_rsi_thresholds = tuple(sideways_config.get("rsi_threshold", [40, 60]))
|
|||
|
|
|
|||
|
|
self.squeeze_strategy = config.get("SqueezeStrategy", True)
|
|||
|
|
|
|||
|
|
# Initialize timeframe aggregator
|
|||
|
|
self.aggregator = TimeframeAggregator(self.timeframe_minutes)
|
|||
|
|
|
|||
|
|
# Initialize indicators with different multipliers for regime detection
|
|||
|
|
self.bb_trending = BollingerBandsState(self.bb_period, self.trending_bb_multiplier)
|
|||
|
|
self.bb_sideways = BollingerBandsState(self.bb_period, self.sideways_bb_multiplier)
|
|||
|
|
self.bb_reference = BollingerBandsState(self.bb_period, 2.0) # For regime detection
|
|||
|
|
self.rsi = RSIState(self.rsi_period)
|
|||
|
|
|
|||
|
|
# State tracking
|
|||
|
|
self.bars_processed = 0
|
|||
|
|
self.current_price = None
|
|||
|
|
self.current_volume = None
|
|||
|
|
self.volume_ma = None
|
|||
|
|
self.volume_sum = 0.0
|
|||
|
|
self.volume_history = [] # For volume MA calculation
|
|||
|
|
|
|||
|
|
# Signal state
|
|||
|
|
self.last_buy_signal = False
|
|||
|
|
self.last_sell_signal = False
|
|||
|
|
self.last_result = None
|
|||
|
|
|
|||
|
|
def update_minute_data(self, timestamp: pd.Timestamp, ohlcv_data: Dict[str, float]) -> Optional[Dict[str, Union[float, bool]]]:
|
|||
|
|
"""
|
|||
|
|
Update strategy with new minute-level OHLCV data.
|
|||
|
|
|
|||
|
|
This method accepts minute-level data and internally aggregates to the
|
|||
|
|
configured timeframe. It only processes indicators and generates signals
|
|||
|
|
when a complete timeframe bar is formed.
|
|||
|
|
|
|||
|
|
Args:
|
|||
|
|
timestamp: Timestamp of the minute data
|
|||
|
|
ohlcv_data: Dictionary with 'open', 'high', 'low', 'close', 'volume'
|
|||
|
|
|
|||
|
|
Returns:
|
|||
|
|
Strategy result dictionary if a timeframe bar completed, None otherwise
|
|||
|
|
"""
|
|||
|
|
# Validate input
|
|||
|
|
required_keys = ['open', 'high', 'low', 'close', 'volume']
|
|||
|
|
for key in required_keys:
|
|||
|
|
if key not in ohlcv_data:
|
|||
|
|
raise ValueError(f"Missing required key: {key}")
|
|||
|
|
|
|||
|
|
# Update timeframe aggregator
|
|||
|
|
completed_bar = self.aggregator.update(timestamp, ohlcv_data)
|
|||
|
|
|
|||
|
|
if completed_bar is not None:
|
|||
|
|
# Process the completed timeframe bar
|
|||
|
|
return self._process_timeframe_bar(completed_bar)
|
|||
|
|
|
|||
|
|
return None # No completed bar yet
|
|||
|
|
|
|||
|
|
def update(self, ohlcv_data: Dict[str, float]) -> Dict[str, Union[float, bool]]:
|
|||
|
|
"""
|
|||
|
|
Update strategy with pre-aggregated timeframe data (for testing/compatibility).
|
|||
|
|
|
|||
|
|
This method is for backward compatibility and testing with pre-aggregated data.
|
|||
|
|
For real-time use, prefer update_minute_data().
|
|||
|
|
|
|||
|
|
Args:
|
|||
|
|
ohlcv_data: Dictionary with 'open', 'high', 'low', 'close', 'volume'
|
|||
|
|
|
|||
|
|
Returns:
|
|||
|
|
Strategy result dictionary
|
|||
|
|
"""
|
|||
|
|
# Create a fake timestamp for compatibility
|
|||
|
|
fake_timestamp = pd.Timestamp.now()
|
|||
|
|
|
|||
|
|
# Process directly as a completed bar
|
|||
|
|
completed_bar = {
|
|||
|
|
'timestamp': fake_timestamp,
|
|||
|
|
'open': ohlcv_data['open'],
|
|||
|
|
'high': ohlcv_data['high'],
|
|||
|
|
'low': ohlcv_data['low'],
|
|||
|
|
'close': ohlcv_data['close'],
|
|||
|
|
'volume': ohlcv_data['volume']
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return self._process_timeframe_bar(completed_bar)
|
|||
|
|
|
|||
|
|
def _process_timeframe_bar(self, bar_data: Dict[str, float]) -> Dict[str, Union[float, bool]]:
|
|||
|
|
"""
|
|||
|
|
Process a completed timeframe bar and generate signals.
|
|||
|
|
|
|||
|
|
Args:
|
|||
|
|
bar_data: Completed timeframe bar data
|
|||
|
|
|
|||
|
|
Returns:
|
|||
|
|
Strategy result dictionary
|
|||
|
|
"""
|
|||
|
|
close_price = float(bar_data['close'])
|
|||
|
|
volume = float(bar_data['volume'])
|
|||
|
|
|
|||
|
|
# Update indicators
|
|||
|
|
bb_trending_result = self.bb_trending.update(close_price)
|
|||
|
|
bb_sideways_result = self.bb_sideways.update(close_price)
|
|||
|
|
bb_reference_result = self.bb_reference.update(close_price)
|
|||
|
|
rsi_value = self.rsi.update(close_price)
|
|||
|
|
|
|||
|
|
# Update volume tracking
|
|||
|
|
self._update_volume_tracking(volume)
|
|||
|
|
|
|||
|
|
# Determine market regime
|
|||
|
|
market_regime = self._determine_market_regime(bb_reference_result)
|
|||
|
|
|
|||
|
|
# Select appropriate BB values based on regime
|
|||
|
|
if market_regime == "sideways":
|
|||
|
|
bb_result = bb_sideways_result
|
|||
|
|
rsi_thresholds = self.sideways_rsi_thresholds
|
|||
|
|
else: # trending
|
|||
|
|
bb_result = bb_trending_result
|
|||
|
|
rsi_thresholds = self.trending_rsi_thresholds
|
|||
|
|
|
|||
|
|
# Generate signals
|
|||
|
|
buy_signal, sell_signal = self._generate_signals(
|
|||
|
|
close_price, volume, bb_result, rsi_value,
|
|||
|
|
market_regime, rsi_thresholds
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
# Update state
|
|||
|
|
self.current_price = close_price
|
|||
|
|
self.current_volume = volume
|
|||
|
|
self.bars_processed += 1
|
|||
|
|
self.last_buy_signal = buy_signal
|
|||
|
|
self.last_sell_signal = sell_signal
|
|||
|
|
|
|||
|
|
# Create comprehensive result
|
|||
|
|
result = {
|
|||
|
|
# Timeframe info
|
|||
|
|
'timestamp': bar_data['timestamp'],
|
|||
|
|
'timeframe_minutes': self.timeframe_minutes,
|
|||
|
|
|
|||
|
|
# Price data
|
|||
|
|
'open': bar_data['open'],
|
|||
|
|
'high': bar_data['high'],
|
|||
|
|
'low': bar_data['low'],
|
|||
|
|
'close': close_price,
|
|||
|
|
'volume': volume,
|
|||
|
|
|
|||
|
|
# Bollinger Bands (regime-specific)
|
|||
|
|
'upper_band': bb_result['upper_band'],
|
|||
|
|
'middle_band': bb_result['middle_band'],
|
|||
|
|
'lower_band': bb_result['lower_band'],
|
|||
|
|
'bb_width': bb_result['bandwidth'],
|
|||
|
|
|
|||
|
|
# RSI
|
|||
|
|
'rsi': rsi_value,
|
|||
|
|
|
|||
|
|
# Market regime
|
|||
|
|
'market_regime': market_regime,
|
|||
|
|
'bb_width_reference': bb_reference_result['bandwidth'],
|
|||
|
|
|
|||
|
|
# Volume analysis
|
|||
|
|
'volume_ma': self.volume_ma,
|
|||
|
|
'volume_spike': self._check_volume_spike(volume),
|
|||
|
|
|
|||
|
|
# Signals
|
|||
|
|
'buy_signal': buy_signal,
|
|||
|
|
'sell_signal': sell_signal,
|
|||
|
|
|
|||
|
|
# Strategy metadata
|
|||
|
|
'is_warmed_up': self.is_warmed_up(),
|
|||
|
|
'bars_processed': self.bars_processed,
|
|||
|
|
'rsi_thresholds': rsi_thresholds,
|
|||
|
|
'bb_multiplier': bb_result.get('std_dev', self.trending_bb_multiplier)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
self.last_result = result
|
|||
|
|
return result
|
|||
|
|
|
|||
|
|
def _update_volume_tracking(self, volume: float) -> None:
|
|||
|
|
"""Update volume moving average tracking."""
|
|||
|
|
# Simple moving average for volume (20 periods)
|
|||
|
|
volume_period = 20
|
|||
|
|
|
|||
|
|
if len(self.volume_history) >= volume_period:
|
|||
|
|
# Remove oldest volume
|
|||
|
|
self.volume_sum -= self.volume_history[0]
|
|||
|
|
self.volume_history.pop(0)
|
|||
|
|
|
|||
|
|
# Add new volume
|
|||
|
|
self.volume_history.append(volume)
|
|||
|
|
self.volume_sum += volume
|
|||
|
|
|
|||
|
|
# Calculate moving average
|
|||
|
|
if len(self.volume_history) > 0:
|
|||
|
|
self.volume_ma = self.volume_sum / len(self.volume_history)
|
|||
|
|
else:
|
|||
|
|
self.volume_ma = volume
|
|||
|
|
|
|||
|
|
def _determine_market_regime(self, bb_reference: Dict[str, float]) -> str:
|
|||
|
|
"""
|
|||
|
|
Determine market regime based on Bollinger Band width.
|
|||
|
|
|
|||
|
|
Args:
|
|||
|
|
bb_reference: Reference BB result for regime detection
|
|||
|
|
|
|||
|
|
Returns:
|
|||
|
|
"sideways" or "trending"
|
|||
|
|
"""
|
|||
|
|
if not self.bb_reference.is_warmed_up():
|
|||
|
|
return "trending" # Default to trending during warm-up
|
|||
|
|
|
|||
|
|
bb_width = bb_reference['bandwidth']
|
|||
|
|
|
|||
|
|
if bb_width < self.bb_width_threshold:
|
|||
|
|
return "sideways"
|
|||
|
|
else:
|
|||
|
|
return "trending"
|
|||
|
|
|
|||
|
|
def _check_volume_spike(self, current_volume: float) -> bool:
|
|||
|
|
"""Check if current volume represents a spike (≥1.5× average)."""
|
|||
|
|
if self.volume_ma is None or self.volume_ma == 0:
|
|||
|
|
return False
|
|||
|
|
|
|||
|
|
return current_volume >= 1.5 * self.volume_ma
|
|||
|
|
|
|||
|
|
def _generate_signals(self, price: float, volume: float, bb_result: Dict[str, float],
|
|||
|
|
rsi_value: float, market_regime: str,
|
|||
|
|
rsi_thresholds: Tuple[float, float]) -> Tuple[bool, bool]:
|
|||
|
|
"""
|
|||
|
|
Generate buy/sell signals based on strategy logic.
|
|||
|
|
|
|||
|
|
Args:
|
|||
|
|
price: Current close price
|
|||
|
|
volume: Current volume
|
|||
|
|
bb_result: Bollinger Bands result
|
|||
|
|
rsi_value: Current RSI value
|
|||
|
|
market_regime: "sideways" or "trending"
|
|||
|
|
rsi_thresholds: (low_threshold, high_threshold)
|
|||
|
|
|
|||
|
|
Returns:
|
|||
|
|
(buy_signal, sell_signal)
|
|||
|
|
"""
|
|||
|
|
# Don't generate signals during warm-up
|
|||
|
|
if not self.is_warmed_up():
|
|||
|
|
return False, False
|
|||
|
|
|
|||
|
|
# Don't generate signals if RSI is NaN
|
|||
|
|
if np.isnan(rsi_value):
|
|||
|
|
return False, False
|
|||
|
|
|
|||
|
|
upper_band = bb_result['upper_band']
|
|||
|
|
lower_band = bb_result['lower_band']
|
|||
|
|
rsi_low, rsi_high = rsi_thresholds
|
|||
|
|
|
|||
|
|
volume_spike = self._check_volume_spike(volume)
|
|||
|
|
|
|||
|
|
buy_signal = False
|
|||
|
|
sell_signal = False
|
|||
|
|
|
|||
|
|
if market_regime == "sideways":
|
|||
|
|
# Sideways market (Mean Reversion)
|
|||
|
|
buy_condition = (price <= lower_band) and (rsi_value <= rsi_low)
|
|||
|
|
sell_condition = (price >= upper_band) and (rsi_value >= rsi_high)
|
|||
|
|
|
|||
|
|
if self.squeeze_strategy:
|
|||
|
|
# Add volume contraction filter for sideways markets
|
|||
|
|
volume_contraction = volume < 0.7 * (self.volume_ma or volume)
|
|||
|
|
buy_condition = buy_condition and volume_contraction
|
|||
|
|
sell_condition = sell_condition and volume_contraction
|
|||
|
|
|
|||
|
|
buy_signal = buy_condition
|
|||
|
|
sell_signal = sell_condition
|
|||
|
|
|
|||
|
|
else: # trending
|
|||
|
|
# Trending market (Breakout Mode)
|
|||
|
|
buy_condition = (price < lower_band) and (rsi_value < 50) and volume_spike
|
|||
|
|
sell_condition = (price > upper_band) and (rsi_value > 50) and volume_spike
|
|||
|
|
|
|||
|
|
buy_signal = buy_condition
|
|||
|
|
sell_signal = sell_condition
|
|||
|
|
|
|||
|
|
return buy_signal, sell_signal
|
|||
|
|
|
|||
|
|
def is_warmed_up(self) -> bool:
|
|||
|
|
"""
|
|||
|
|
Check if strategy is warmed up and ready for reliable signals.
|
|||
|
|
|
|||
|
|
Returns:
|
|||
|
|
True if all indicators are warmed up
|
|||
|
|
"""
|
|||
|
|
return (self.bb_trending.is_warmed_up() and
|
|||
|
|
self.bb_sideways.is_warmed_up() and
|
|||
|
|
self.bb_reference.is_warmed_up() and
|
|||
|
|
self.rsi.is_warmed_up() and
|
|||
|
|
len(self.volume_history) >= 20)
|
|||
|
|
|
|||
|
|
def get_current_incomplete_bar(self) -> Optional[Dict[str, float]]:
|
|||
|
|
"""
|
|||
|
|
Get the current incomplete timeframe bar (for monitoring).
|
|||
|
|
|
|||
|
|
Returns:
|
|||
|
|
Current incomplete bar data or None
|
|||
|
|
"""
|
|||
|
|
return self.aggregator.get_current_bar()
|
|||
|
|
|
|||
|
|
def reset(self) -> None:
|
|||
|
|
"""Reset strategy state to initial conditions."""
|
|||
|
|
self.aggregator.reset()
|
|||
|
|
self.bb_trending.reset()
|
|||
|
|
self.bb_sideways.reset()
|
|||
|
|
self.bb_reference.reset()
|
|||
|
|
self.rsi.reset()
|
|||
|
|
|
|||
|
|
self.bars_processed = 0
|
|||
|
|
self.current_price = None
|
|||
|
|
self.current_volume = None
|
|||
|
|
self.volume_ma = None
|
|||
|
|
self.volume_sum = 0.0
|
|||
|
|
self.volume_history.clear()
|
|||
|
|
|
|||
|
|
self.last_buy_signal = False
|
|||
|
|
self.last_sell_signal = False
|
|||
|
|
self.last_result = None
|
|||
|
|
|
|||
|
|
def get_state_summary(self) -> Dict:
|
|||
|
|
"""Get comprehensive state summary for debugging."""
|
|||
|
|
return {
|
|||
|
|
'strategy_type': 'BBRS_Incremental',
|
|||
|
|
'timeframe_minutes': self.timeframe_minutes,
|
|||
|
|
'bars_processed': self.bars_processed,
|
|||
|
|
'is_warmed_up': self.is_warmed_up(),
|
|||
|
|
'current_price': self.current_price,
|
|||
|
|
'current_volume': self.current_volume,
|
|||
|
|
'volume_ma': self.volume_ma,
|
|||
|
|
'current_incomplete_bar': self.get_current_incomplete_bar(),
|
|||
|
|
'last_signals': {
|
|||
|
|
'buy': self.last_buy_signal,
|
|||
|
|
'sell': self.last_sell_signal
|
|||
|
|
},
|
|||
|
|
'indicators': {
|
|||
|
|
'bb_trending': self.bb_trending.get_state_summary(),
|
|||
|
|
'bb_sideways': self.bb_sideways.get_state_summary(),
|
|||
|
|
'bb_reference': self.bb_reference.get_state_summary(),
|
|||
|
|
'rsi': self.rsi.get_state_summary()
|
|||
|
|
},
|
|||
|
|
'config': {
|
|||
|
|
'bb_period': self.bb_period,
|
|||
|
|
'rsi_period': self.rsi_period,
|
|||
|
|
'bb_width_threshold': self.bb_width_threshold,
|
|||
|
|
'trending_bb_multiplier': self.trending_bb_multiplier,
|
|||
|
|
'sideways_bb_multiplier': self.sideways_bb_multiplier,
|
|||
|
|
'trending_rsi_thresholds': self.trending_rsi_thresholds,
|
|||
|
|
'sideways_rsi_thresholds': self.sideways_rsi_thresholds,
|
|||
|
|
'squeeze_strategy': self.squeeze_strategy
|
|||
|
|
}
|
|||
|
|
}
|