Vasily.onl fd5a59fc39 4.0 - 1.0 Implement strategy engine foundation with modular components
- Introduced a new `strategies` package containing the core structure for trading strategies, including `BaseStrategy`, `StrategyFactory`, and various strategy implementations (EMA, RSI, MACD).
- Added utility functions for signal detection and validation in `strategies/utils.py`, enhancing modularity and maintainability.
- Updated `pyproject.toml` to include the new `strategies` package in the build configuration.
- Implemented comprehensive unit tests for the strategy foundation components, ensuring reliability and adherence to project standards.

These changes establish a solid foundation for the strategy engine, aligning with project goals for modularity, performance, and maintainability.
2025-06-12 14:41:16 +08:00

150 lines
4.9 KiB
Python

"""
Base classes and interfaces for trading strategies.
This module provides the foundation for all trading strategies
with common functionality and type definitions.
"""
from abc import ABC, abstractmethod
from typing import List, Dict, Any, Optional
import pandas as pd
from utils.logger import get_logger
from .data_types import StrategyResult
from data.common.data_types import OHLCVCandle
class BaseStrategy(ABC):
"""
Abstract base class for all trading strategies.
Provides common functionality and enforces consistent interface
across all strategy implementations.
"""
def __init__(self, logger=None):
"""
Initialize base strategy.
Args:
logger: Optional logger instance
"""
if logger is None:
self.logger = get_logger(__name__)
self.logger = logger
def prepare_dataframe(self, candles: List[OHLCVCandle]) -> pd.DataFrame:
"""
Convert OHLCV candles to pandas DataFrame for calculations.
Args:
candles: List of OHLCV candles (can be sparse)
Returns:
DataFrame with OHLCV data, sorted by timestamp
"""
if not candles:
return pd.DataFrame()
# Convert to DataFrame
data = []
for candle in candles:
data.append({
'timestamp': candle.end_time, # Right-aligned timestamp
'symbol': candle.symbol,
'timeframe': candle.timeframe,
'open': float(candle.open),
'high': float(candle.high),
'low': float(candle.low),
'close': float(candle.close),
'volume': float(candle.volume),
'trade_count': candle.trade_count
})
df = pd.DataFrame(data)
# Sort by timestamp to ensure proper order
df = df.sort_values('timestamp').reset_index(drop=True)
# Set timestamp as index for time-series operations
df['timestamp'] = pd.to_datetime(df['timestamp'])
# Set as index, but keep as column
df.set_index('timestamp', inplace=True)
# Ensure it's datetime
df['timestamp'] = df.index
return df
@abstractmethod
def calculate(self, df: pd.DataFrame, indicators_data: Dict[str, pd.DataFrame], **kwargs) -> List[StrategyResult]:
"""
Calculate the strategy signals.
Args:
df: DataFrame with OHLCV data
indicators_data: Dictionary of pre-calculated indicator DataFrames
**kwargs: Additional parameters specific to each strategy
Returns:
List of strategy results with signals
"""
pass
@abstractmethod
def get_required_indicators(self) -> List[Dict[str, Any]]:
"""
Get list of indicators required by this strategy.
Returns:
List of indicator configurations needed for strategy calculation
Format: [{'type': 'sma', 'period': 20}, {'type': 'ema', 'period': 12}]
"""
pass
def validate_dataframe(self, df: pd.DataFrame, min_periods: int) -> bool:
"""
Validate that DataFrame has sufficient data for calculation.
Args:
df: DataFrame to validate
min_periods: Minimum number of periods required
Returns:
True if DataFrame is valid, False otherwise
"""
if df.empty or len(df) < min_periods:
if self.logger:
self.logger.warning(
f"Insufficient data: got {len(df)} periods, need {min_periods}"
)
return False
return True
def validate_indicators_data(self, indicators_data: Dict[str, pd.DataFrame],
required_indicators: List[Dict[str, Any]]) -> bool:
"""
Validate that all required indicators are present and have sufficient data.
Args:
indicators_data: Dictionary of indicator DataFrames
required_indicators: List of required indicator configurations
Returns:
True if all required indicators are available, False otherwise
"""
for indicator_config in required_indicators:
indicator_key = f"{indicator_config['type']}_{indicator_config.get('period', 'default')}"
if indicator_key not in indicators_data:
if self.logger:
self.logger.warning(f"Missing required indicator: {indicator_key}")
return False
if indicators_data[indicator_key].empty:
if self.logger:
self.logger.warning(f"Empty data for indicator: {indicator_key}")
return False
return True