36 lines
1.5 KiB
Python
36 lines
1.5 KiB
Python
import pandas as pd
|
|
from strategies.base_strategy import BaseStrategy
|
|
import logging
|
|
|
|
class MaCrossStrategy(BaseStrategy):
|
|
"""
|
|
A strategy based on a fast Simple Moving Average (SMA) crossing
|
|
a slow SMA.
|
|
"""
|
|
def calculate_signals(self, df: pd.DataFrame) -> pd.DataFrame:
|
|
# Support multiple naming conventions: some configs use 'fast'/'slow'
|
|
# while others use 'short_ma'/'long_ma'. Normalize here so both work.
|
|
fast_ma_period = self.params.get('short_ma') or self.params.get('fast') or 0
|
|
slow_ma_period = self.params.get('long_ma') or self.params.get('slow') or 0
|
|
|
|
# If parameters are missing, return a neutral signal frame.
|
|
if not fast_ma_period or not slow_ma_period:
|
|
logging.warning(f"Missing MA period parameters (fast={fast_ma_period}, slow={slow_ma_period}).")
|
|
df['signal'] = 0
|
|
return df
|
|
|
|
if len(df) < slow_ma_period:
|
|
logging.warning(f"Not enough data for MA periods {fast_ma_period}/{slow_ma_period}. Need {slow_ma_period}, have {len(df)}.")
|
|
df['signal'] = 0
|
|
return df
|
|
|
|
df['fast_sma'] = df['close'].rolling(window=fast_ma_period).mean()
|
|
df['slow_sma'] = df['close'].rolling(window=slow_ma_period).mean()
|
|
|
|
# Signal is 1 for Golden Cross (fast > slow), -1 for Death Cross
|
|
df['signal'] = 0
|
|
df.loc[df['fast_sma'] > df['slow_sma'], 'signal'] = 1
|
|
df.loc[df['fast_sma'] < df['slow_sma'], 'signal'] = -1
|
|
|
|
return df
|