Initial commit — Trading AI Secure project complet

Architecture Docker (8 services), FastAPI, TimescaleDB, Redis, Streamlit.
Stratégies : scalping, intraday, swing. MLEngine + RegimeDetector (HMM).
BacktestEngine + WalkForwardAnalyzer + Optuna optimizer.
Routes API complètes dont /optimize async.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Tika
2026-03-08 17:38:09 +00:00
commit da30ef19ed
111 changed files with 31723 additions and 0 deletions

View File

@@ -0,0 +1,318 @@
"""
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