Edwin Salguero
Initial commit: Enhanced Algorithmic Trading System with Synthetic Data Generation, Comprehensive Logging, and Extensive Testing
859af74
import pytest | |
import pandas as pd | |
import numpy as np | |
from datetime import datetime, timedelta | |
from agentic_ai_system.strategy_agent import StrategyAgent | |
class TestStrategyAgent: | |
"""Test cases for StrategyAgent""" | |
def config(self): | |
"""Sample configuration for testing""" | |
return { | |
'trading': { | |
'symbol': 'AAPL', | |
'timeframe': '1min', | |
'capital': 100000 | |
}, | |
'risk': { | |
'max_position': 100, | |
'max_drawdown': 0.05 | |
}, | |
'execution': { | |
'broker_api': 'paper', | |
'order_size': 10 | |
} | |
} | |
def strategy_agent(self, config): | |
"""Create a StrategyAgent instance""" | |
return StrategyAgent(config) | |
def sample_data(self): | |
"""Create sample market data for testing""" | |
dates = pd.date_range(start='2024-01-01', periods=100, freq='1min') | |
# Generate realistic price data | |
base_price = 150.0 | |
prices = [] | |
for i in range(100): | |
# Add some trend and noise | |
price = base_price + (i * 0.1) + np.random.normal(0, 2) | |
prices.append(max(price, 1)) # Ensure positive prices | |
data = [] | |
for i, (date, close_price) in enumerate(zip(dates, prices)): | |
# Generate OHLC from close price | |
noise = np.random.normal(0, 1) | |
open_price = close_price + noise | |
high_price = max(open_price, close_price) + abs(np.random.normal(0, 2)) | |
low_price = min(open_price, close_price) - abs(np.random.normal(0, 2)) | |
volume = np.random.randint(1000, 100000) | |
data.append({ | |
'timestamp': date, | |
'open': round(open_price, 2), | |
'high': round(high_price, 2), | |
'low': round(low_price, 2), | |
'close': round(close_price, 2), | |
'volume': volume | |
}) | |
return pd.DataFrame(data) | |
def test_initialization(self, strategy_agent, config): | |
"""Test agent initialization""" | |
assert strategy_agent.symbol == config['trading']['symbol'] | |
assert strategy_agent.capital == config['trading']['capital'] | |
assert strategy_agent.max_position == config['risk']['max_position'] | |
assert strategy_agent.max_drawdown == config['risk']['max_drawdown'] | |
def test_act_with_valid_data(self, strategy_agent, sample_data): | |
"""Test signal generation with valid data""" | |
signal = strategy_agent.act(sample_data) | |
# Check signal structure | |
assert isinstance(signal, dict) | |
assert 'action' in signal | |
assert 'symbol' in signal | |
assert 'quantity' in signal | |
assert 'price' in signal | |
assert 'confidence' in signal | |
# Check action values | |
assert signal['action'] in ['buy', 'sell', 'hold'] | |
assert signal['symbol'] == strategy_agent.symbol | |
assert signal['quantity'] >= 0 | |
assert signal['price'] > 0 | |
assert 0 <= signal['confidence'] <= 1 | |
def test_act_with_empty_data(self, strategy_agent): | |
"""Test signal generation with empty data""" | |
empty_data = pd.DataFrame() | |
signal = strategy_agent.act(empty_data) | |
assert signal['action'] == 'hold' | |
assert signal['quantity'] == 0 | |
assert signal['confidence'] == 0.0 | |
def test_calculate_indicators(self, strategy_agent, sample_data): | |
"""Test technical indicator calculations""" | |
indicators = strategy_agent._calculate_indicators(sample_data) | |
# Check that indicators are calculated | |
expected_indicators = ['sma_20', 'sma_50', 'rsi', 'bb_upper', 'bb_lower', 'macd', 'macd_signal'] | |
for indicator in expected_indicators: | |
assert indicator in indicators | |
# Check that indicators have reasonable values | |
if len(indicators['sma_20']) > 0: | |
assert indicators['sma_20'][-1] > 0 | |
if len(indicators['rsi']) > 0: | |
rsi_value = indicators['rsi'][-1] | |
assert 0 <= rsi_value <= 100 | |
def test_calculate_sma(self, strategy_agent): | |
"""Test Simple Moving Average calculation""" | |
prices = np.array([100, 101, 102, 103, 104, 105, 106, 107, 108, 109]) | |
# Test SMA with window 3 | |
sma = strategy_agent._calculate_sma(prices, 3) | |
expected_sma = np.array([101, 102, 103, 104, 105, 106, 107, 108]) | |
np.testing.assert_array_almost_equal(sma, expected_sma, decimal=2) | |
# Test with insufficient data | |
short_prices = np.array([100, 101]) | |
sma_short = strategy_agent._calculate_sma(short_prices, 3) | |
assert len(sma_short) == 0 | |
def test_calculate_rsi(self, strategy_agent): | |
"""Test RSI calculation""" | |
# Create price data with known pattern | |
prices = np.array([100, 101, 102, 101, 100, 99, 98, 99, 100, 101]) | |
rsi = strategy_agent._calculate_rsi(prices, window=3) | |
# RSI should be between 0 and 100 | |
if len(rsi) > 0: | |
assert 0 <= rsi[-1] <= 100 | |
def test_calculate_bollinger_bands(self, strategy_agent): | |
"""Test Bollinger Bands calculation""" | |
prices = np.array([100, 101, 102, 103, 104, 105, 106, 107, 108, 109]) | |
bb_upper, bb_lower = strategy_agent._calculate_bollinger_bands(prices, window=5) | |
if len(bb_upper) > 0 and len(bb_lower) > 0: | |
# Upper band should be above lower band | |
assert bb_upper[-1] > bb_lower[-1] | |
def test_calculate_position_size(self, strategy_agent): | |
"""Test position size calculation""" | |
price = 150.0 | |
# Test normal case | |
quantity = strategy_agent._calculate_position_size(price) | |
expected_quantity = int((strategy_agent.capital * 0.1) / price) | |
expected_quantity = min(expected_quantity, strategy_agent.max_position) | |
assert quantity == expected_quantity | |
assert quantity >= 1 | |
# Test with very high price | |
high_price = 10000.0 | |
quantity_high = strategy_agent._calculate_position_size(high_price) | |
assert quantity_high == 1 # Minimum quantity | |
def test_generate_no_action_signal(self, strategy_agent): | |
"""Test no-action signal generation""" | |
signal = strategy_agent._generate_no_action_signal() | |
assert signal['action'] == 'hold' | |
assert signal['quantity'] == 0 | |
assert signal['price'] == 0 | |
assert signal['confidence'] == 0.0 | |
assert signal['symbol'] == strategy_agent.symbol | |
def test_signal_generation_logic(self, strategy_agent, sample_data): | |
"""Test signal generation logic with different market conditions""" | |
# Test with upward trending data (should generate buy signal) | |
upward_data = sample_data.copy() | |
upward_data['close'] = upward_data['close'] * 1.1 # 10% increase | |
signal_up = strategy_agent.act(upward_data) | |
# Test with downward trending data (should generate sell signal) | |
downward_data = sample_data.copy() | |
downward_data['close'] = downward_data['close'] * 0.9 # 10% decrease | |
signal_down = strategy_agent.act(downward_data) | |
# Both should be valid signals | |
assert signal_up['action'] in ['buy', 'sell', 'hold'] | |
assert signal_down['action'] in ['buy', 'sell', 'hold'] | |
def test_error_handling(self, strategy_agent): | |
"""Test error handling in signal generation""" | |
# Test with invalid data | |
invalid_data = pd.DataFrame({'invalid_column': [1, 2, 3]}) | |
# Should not raise exception, should return hold signal | |
signal = strategy_agent.act(invalid_data) | |
assert signal['action'] == 'hold' | |
def test_technical_indicators_edge_cases(self, strategy_agent): | |
"""Test technical indicators with edge cases""" | |
# Test with constant prices | |
constant_prices = np.ones(50) * 100 | |
rsi_constant = strategy_agent._calculate_rsi(constant_prices) | |
# Test with all increasing prices | |
increasing_prices = np.arange(100, 150) | |
rsi_increasing = strategy_agent._calculate_rsi(increasing_prices) | |
# Test with all decreasing prices | |
decreasing_prices = np.arange(150, 100, -1) | |
rsi_decreasing = strategy_agent._calculate_rsi(decreasing_prices) | |
# All should return valid arrays (possibly empty) | |
assert isinstance(rsi_constant, np.ndarray) | |
assert isinstance(rsi_increasing, np.ndarray) | |
assert isinstance(rsi_decreasing, np.ndarray) | |
def test_macd_calculation(self, strategy_agent): | |
"""Test MACD calculation""" | |
prices = np.array([100 + i * 0.1 + np.random.normal(0, 1) for i in range(50)]) | |
macd, signal = strategy_agent._calculate_macd(prices) | |
# Both should be numpy arrays | |
assert isinstance(macd, np.ndarray) | |
assert isinstance(signal, np.ndarray) | |
# If we have enough data, both should have values | |
if len(prices) >= 26: | |
assert len(macd) > 0 | |
if len(macd) >= 9: # Need enough data for signal line | |
assert len(signal) > 0 | |
def test_ema_calculation(self, strategy_agent): | |
"""Test Exponential Moving Average calculation""" | |
prices = np.array([100, 101, 102, 103, 104, 105, 106, 107, 108, 109]) | |
ema = strategy_agent._calculate_ema(prices, window=5) | |
assert isinstance(ema, np.ndarray) | |
if len(ema) > 0: | |
assert ema[-1] > 0 # EMA should be positive |