""" Tests Unitaires - Strategies. Tests des stratégies de trading: - BaseStrategy - ScalpingStrategy - IntradayStrategy - SwingStrategy """ import pytest import pandas as pd import numpy as np from src.strategies.base_strategy import BaseStrategy, Signal from src.strategies.scalping.scalping_strategy import ScalpingStrategy from src.strategies.intraday.intraday_strategy import IntradayStrategy from src.strategies.swing.swing_strategy import SwingStrategy class TestBaseStrategy: """Tests de la classe BaseStrategy.""" def test_cannot_instantiate_abstract_class(self): """Vérifie qu'on ne peut pas instancier BaseStrategy directement.""" with pytest.raises(TypeError): BaseStrategy({}) def test_position_sizing_kelly(self, sample_config): """Test calcul position sizing avec Kelly Criterion.""" strategy = ScalpingStrategy(sample_config['strategy_params']['scalping_strategy']) # Simuler historique strategy.win_rate = 0.6 strategy.avg_win = 100 strategy.avg_loss = -50 signal = Signal( symbol='EURUSD', direction='LONG', entry_price=1.1000, stop_loss=1.0950, take_profit=1.1100, confidence=0.8, timestamp=pd.Timestamp.now(), strategy='scalping', metadata={} ) position_size = strategy.calculate_position_size( signal=signal, portfolio_value=10000, current_volatility=0.02 ) assert position_size > 0 assert position_size < 10000 # Pas plus que le portfolio class TestScalpingStrategy: """Tests de la stratégie Scalping.""" def test_initialization(self, sample_config): """Test initialisation de la stratégie.""" strategy = ScalpingStrategy(sample_config['strategy_params']['scalping_strategy']) assert strategy.name == 'scalping' assert 'bb_period' in strategy.parameters assert 'rsi_period' in strategy.parameters def test_calculate_indicators(self, sample_config, sample_ohlcv_data): """Test calcul des indicateurs.""" strategy = ScalpingStrategy(sample_config['strategy_params']['scalping_strategy']) df = strategy.calculate_indicators(sample_ohlcv_data) # Vérifier que tous les indicateurs sont présents assert 'bb_upper' in df.columns assert 'bb_lower' in df.columns assert 'bb_position' in df.columns assert 'rsi' in df.columns assert 'macd' in df.columns assert 'macd_hist' in df.columns assert 'atr' in df.columns def test_analyze_generates_signal(self, sample_config, sample_ohlcv_data): """Test génération de signal.""" strategy = ScalpingStrategy(sample_config['strategy_params']['scalping_strategy']) # Créer données oversold df = sample_ohlcv_data.copy() df = strategy.calculate_indicators(df) signal = strategy.analyze(df) # Signal peut être None ou Signal valide if signal is not None: assert isinstance(signal, Signal) assert signal.direction in ['LONG', 'SHORT'] assert signal.stop_loss is not None assert signal.take_profit is not None assert 0 <= signal.confidence <= 1 def test_oversold_conditions(self, sample_config): """Test détection conditions oversold.""" strategy = ScalpingStrategy(sample_config['strategy_params']['scalping_strategy']) # Créer données oversold artificielles dates = pd.date_range(start='2024-01-01', periods=100, freq='5min') df = pd.DataFrame(index=dates) # Prix descendant puis rebond prices = np.linspace(1.1000, 1.0900, 100) df['close'] = prices df['open'] = df['close'].shift(1).fillna(df['close'].iloc[0]) df['high'] = df[['open', 'close']].max(axis=1) * 1.001 df['low'] = df[['open', 'close']].min(axis=1) * 0.999 df['volume'] = 5000 df = strategy.calculate_indicators(df) # Vérifier RSI oversold assert df['rsi'].iloc[-1] < 50 # Devrait être bas class TestIntradayStrategy: """Tests de la stratégie Intraday.""" def test_initialization(self, sample_config): """Test initialisation.""" config = { 'name': 'intraday', 'timeframe': '1h', 'risk_per_trade': 0.02, 'max_holding_time': 28800, 'max_trades_per_day': 20, 'adaptive_params': { 'ema_fast': 9, 'ema_slow': 21, 'ema_trend': 50, 'adx_threshold': 25, } } strategy = IntradayStrategy(config) assert strategy.name == 'intraday' assert strategy.parameters['ema_fast'] == 9 assert strategy.parameters['ema_slow'] == 21 def test_calculate_adx(self, sample_config, sample_ohlcv_data): """Test calcul ADX.""" config = { 'name': 'intraday', 'timeframe': '1h', 'adaptive_params': { 'ema_fast': 9, 'ema_slow': 21, 'ema_trend': 50, } } strategy = IntradayStrategy(config) df = strategy.calculate_indicators(sample_ohlcv_data) # Vérifier ADX calculé assert 'adx' in df.columns assert 'pos_di' in df.columns assert 'neg_di' in df.columns # ADX devrait être entre 0 et 100 adx_values = df['adx'].dropna() assert (adx_values >= 0).all() assert (adx_values <= 100).all() def test_ema_crossover_detection(self, sample_config): """Test détection croisement EMA.""" config = { 'name': 'intraday', 'timeframe': '1h', 'adaptive_params': { 'ema_fast': 9, 'ema_slow': 21, 'ema_trend': 50, 'adx_threshold': 25, } } strategy = IntradayStrategy(config) # Créer données avec croisement dates = pd.date_range(start='2024-01-01', periods=100, freq='1H') df = pd.DataFrame(index=dates) # Tendance haussière df['close'] = np.linspace(1.0900, 1.1100, 100) df['open'] = df['close'].shift(1).fillna(df['close'].iloc[0]) df['high'] = df[['open', 'close']].max(axis=1) * 1.001 df['low'] = df[['open', 'close']].min(axis=1) * 0.999 df['volume'] = 5000 df = strategy.calculate_indicators(df) # Vérifier que EMA fast > EMA slow en fin de tendance assert df['ema_fast'].iloc[-1] > df['ema_slow'].iloc[-1] class TestSwingStrategy: """Tests de la stratégie Swing.""" def test_initialization(self): """Test initialisation.""" config = { 'name': 'swing', 'timeframe': '4h', 'adaptive_params': { 'sma_short': 20, 'sma_long': 50, 'fibonacci_lookback': 50, } } strategy = SwingStrategy(config) assert strategy.name == 'swing' assert strategy.parameters['sma_short'] == 20 assert strategy.parameters['sma_long'] == 50 def test_fibonacci_levels(self, sample_ohlcv_data): """Test calcul niveaux Fibonacci.""" config = { 'name': 'swing', 'timeframe': '4h', 'adaptive_params': { 'sma_short': 20, 'sma_long': 50, 'fibonacci_lookback': 50, } } strategy = SwingStrategy(config) df = strategy.calculate_indicators(sample_ohlcv_data) # Vérifier niveaux Fibonacci assert 'fib_236' in df.columns assert 'fib_382' in df.columns assert 'fib_500' in df.columns assert 'fib_618' in df.columns assert 'fib_786' in df.columns # Vérifier ordre des niveaux last_row = df.iloc[-1] assert last_row['fib_high'] >= last_row['fib_236'] assert last_row['fib_236'] >= last_row['fib_382'] assert last_row['fib_382'] >= last_row['fib_500'] assert last_row['fib_500'] >= last_row['fib_618'] assert last_row['fib_618'] >= last_row['fib_786'] assert last_row['fib_786'] >= last_row['fib_low'] def test_get_strategy_info(self): """Test récupération infos stratégie.""" config = { 'name': 'swing', 'timeframe': '4h', 'adaptive_params': {} } strategy = SwingStrategy(config) info = strategy.get_strategy_info() assert 'name' in info assert 'type' in info assert 'timeframe' in info assert 'indicators' in info assert info['type'] == 'swing' class TestSignal: """Tests de la classe Signal.""" def test_signal_creation(self): """Test création d'un signal.""" signal = Signal( symbol='EURUSD', direction='LONG', entry_price=1.1000, stop_loss=1.0950, take_profit=1.1100, confidence=0.75, timestamp=pd.Timestamp.now(), strategy='scalping', metadata={'rsi': 25, 'bb_position': 0.1} ) assert signal.symbol == 'EURUSD' assert signal.direction == 'LONG' assert signal.confidence == 0.75 assert 'rsi' in signal.metadata def test_signal_risk_reward(self): """Test calcul risk/reward d'un signal.""" signal = Signal( symbol='EURUSD', direction='LONG', entry_price=1.1000, stop_loss=1.0950, # 50 pips risk take_profit=1.1100, # 100 pips reward confidence=0.75, timestamp=pd.Timestamp.now(), strategy='scalping', metadata={} ) risk = abs(signal.entry_price - signal.stop_loss) reward = abs(signal.take_profit - signal.entry_price) rr_ratio = reward / risk assert rr_ratio == 2.0 # R:R de 2:1