Enhance Bollinger Bands validation and add DatetimeIndex handling in strategies
- Added validation to ensure the specified price column exists in the DataFrame for Bollinger Bands calculations. - Introduced a new method to ensure the DataFrame has a proper DatetimeIndex, improving time-series operations in strategy processing. - Updated strategy run method to call the new DatetimeIndex validation method before processing data. - Improved logging for better traceability of data transformations and potential issues.
This commit is contained in:
parent
e5c2988d71
commit
7af8cdcb32
@ -37,12 +37,13 @@ class BollingerBands:
|
|||||||
'UpperBand',
|
'UpperBand',
|
||||||
'LowerBand'.
|
'LowerBand'.
|
||||||
"""
|
"""
|
||||||
if price_column not in data_df.columns:
|
|
||||||
raise ValueError(f"Price column '{price_column}' not found in DataFrame.")
|
|
||||||
|
|
||||||
# Work on a copy to avoid modifying the original DataFrame passed to the function
|
# Work on a copy to avoid modifying the original DataFrame passed to the function
|
||||||
data_df = data_df.copy()
|
data_df = data_df.copy()
|
||||||
|
|
||||||
|
if price_column not in data_df.columns:
|
||||||
|
raise ValueError(f"Price column '{price_column}' not found in DataFrame.")
|
||||||
|
|
||||||
if not squeeze:
|
if not squeeze:
|
||||||
period = self.config['bb_period']
|
period = self.config['bb_period']
|
||||||
bb_width_threshold = self.config['bb_width']
|
bb_width_threshold = self.config['bb_width']
|
||||||
|
|||||||
@ -14,7 +14,56 @@ class Strategy:
|
|||||||
self.config = config
|
self.config = config
|
||||||
self.logging = logging
|
self.logging = logging
|
||||||
|
|
||||||
|
def _ensure_datetime_index(self, data):
|
||||||
|
"""
|
||||||
|
Ensure the DataFrame has a DatetimeIndex for proper time-series operations.
|
||||||
|
If the DataFrame has a 'timestamp' column but not a DatetimeIndex, convert it.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
data (DataFrame): Input DataFrame
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
DataFrame: DataFrame with proper DatetimeIndex
|
||||||
|
"""
|
||||||
|
if data.empty:
|
||||||
|
return data
|
||||||
|
|
||||||
|
# Check if we have a DatetimeIndex already
|
||||||
|
if isinstance(data.index, pd.DatetimeIndex):
|
||||||
|
return data
|
||||||
|
|
||||||
|
# Check if we have a 'timestamp' column that we can use as index
|
||||||
|
if 'timestamp' in data.columns:
|
||||||
|
data_copy = data.copy()
|
||||||
|
# Convert timestamp column to datetime if it's not already
|
||||||
|
if not pd.api.types.is_datetime64_any_dtype(data_copy['timestamp']):
|
||||||
|
data_copy['timestamp'] = pd.to_datetime(data_copy['timestamp'])
|
||||||
|
# Set timestamp as index and drop the column
|
||||||
|
data_copy = data_copy.set_index('timestamp')
|
||||||
|
if self.logging:
|
||||||
|
self.logging.info("Converted 'timestamp' column to DatetimeIndex for strategy processing.")
|
||||||
|
return data_copy
|
||||||
|
|
||||||
|
# If we have a regular index but it might be datetime strings, try to convert
|
||||||
|
try:
|
||||||
|
if data.index.dtype == 'object':
|
||||||
|
data_copy = data.copy()
|
||||||
|
data_copy.index = pd.to_datetime(data_copy.index)
|
||||||
|
if self.logging:
|
||||||
|
self.logging.info("Converted index to DatetimeIndex for strategy processing.")
|
||||||
|
return data_copy
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# If we can't create a proper DatetimeIndex, warn and return as-is
|
||||||
|
if self.logging:
|
||||||
|
self.logging.warning("Could not create DatetimeIndex for strategy processing. Time-based operations may fail.")
|
||||||
|
return data
|
||||||
|
|
||||||
def run(self, data, strategy_name):
|
def run(self, data, strategy_name):
|
||||||
|
# Ensure proper DatetimeIndex before processing
|
||||||
|
data = self._ensure_datetime_index(data)
|
||||||
|
|
||||||
if strategy_name == "MarketRegimeStrategy":
|
if strategy_name == "MarketRegimeStrategy":
|
||||||
result = self.MarketRegimeStrategy(data)
|
result = self.MarketRegimeStrategy(data)
|
||||||
return self.standardize_output(result, strategy_name)
|
return self.standardize_output(result, strategy_name)
|
||||||
@ -126,8 +175,8 @@ class Strategy:
|
|||||||
DataFrame: A unified DataFrame containing original data, BB, RSI, and signals.
|
DataFrame: A unified DataFrame containing original data, BB, RSI, and signals.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# data = aggregate_to_hourly(data, 4)
|
data = aggregate_to_hourly(data, 1)
|
||||||
data = aggregate_to_daily(data)
|
# data = aggregate_to_daily(data)
|
||||||
|
|
||||||
# Calculate Bollinger Bands
|
# Calculate Bollinger Bands
|
||||||
bb_calculator = BollingerBands(config=self.config)
|
bb_calculator = BollingerBands(config=self.config)
|
||||||
@ -263,6 +312,8 @@ class Strategy:
|
|||||||
if self.logging:
|
if self.logging:
|
||||||
self.logging.warning("CryptoTradingStrategy: Input data is empty or missing 'close'/'volume' columns.")
|
self.logging.warning("CryptoTradingStrategy: Input data is empty or missing 'close'/'volume' columns.")
|
||||||
return pd.DataFrame() # Return empty DataFrame if essential data is missing
|
return pd.DataFrame() # Return empty DataFrame if essential data is missing
|
||||||
|
|
||||||
|
print(f"data: {data.head()}")
|
||||||
|
|
||||||
# Aggregate data
|
# Aggregate data
|
||||||
data_15m = aggregate_to_minutes(data.copy(), 15)
|
data_15m = aggregate_to_minutes(data.copy(), 15)
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user