""" Unit tests for the OHLCVData module. """ import pytest from datetime import datetime, timezone from decimal import Decimal from data.common.ohlcv_data import OHLCVData, DataValidationError, validate_ohlcv_data class TestOHLCVData: """Test cases for OHLCVData validation.""" def test_valid_ohlcv_data(self): """Test creating valid OHLCV data.""" ohlcv = OHLCVData( symbol="BTC-USDT", timeframe="1m", timestamp=datetime.now(timezone.utc), open=Decimal("50000"), high=Decimal("50100"), low=Decimal("49900"), close=Decimal("50050"), volume=Decimal("1.5"), trades_count=100 ) assert ohlcv.symbol == "BTC-USDT" assert ohlcv.timeframe == "1m" assert isinstance(ohlcv.open, Decimal) assert ohlcv.trades_count == 100 def test_invalid_ohlcv_relationships(self): """Test OHLCV validation for invalid price relationships.""" with pytest.raises(DataValidationError): OHLCVData( symbol="BTC-USDT", timeframe="1m", timestamp=datetime.now(timezone.utc), open=Decimal("50000"), high=Decimal("49000"), # High is less than open low=Decimal("49900"), close=Decimal("50050"), volume=Decimal("1.5") ) def test_ohlcv_decimal_conversion(self): """Test automatic conversion to Decimal.""" ohlcv = OHLCVData( symbol="BTC-USDT", timeframe="1m", timestamp=datetime.now(timezone.utc), open=50000.0, # float high=50100, # int low=49900, # int close=50050.0, # float volume=1.5 # float ) assert isinstance(ohlcv.open, Decimal) assert isinstance(ohlcv.high, Decimal) assert isinstance(ohlcv.low, Decimal) assert isinstance(ohlcv.close, Decimal) assert isinstance(ohlcv.volume, Decimal) def test_timezone_handling(self): """Test that naive datetimes get UTC timezone.""" naive_timestamp = datetime(2023, 1, 1, 12, 0, 0) ohlcv = OHLCVData( symbol="BTC-USDT", timeframe="1m", timestamp=naive_timestamp, open=50000, high=50100, low=49900, close=50050, volume=1.5 ) assert ohlcv.timestamp.tzinfo == timezone.utc def test_invalid_price_types(self): """Test validation fails for invalid price types.""" with pytest.raises(DataValidationError, match="All OHLCV prices must be numeric"): OHLCVData( symbol="BTC-USDT", timeframe="1m", timestamp=datetime.now(timezone.utc), open="invalid", # Invalid type high=50100, low=49900, close=50050, volume=1.5 ) def test_invalid_volume_type(self): """Test validation fails for invalid volume type.""" with pytest.raises(DataValidationError, match="Volume must be numeric"): OHLCVData( symbol="BTC-USDT", timeframe="1m", timestamp=datetime.now(timezone.utc), open=50000, high=50100, low=49900, close=50050, volume="invalid" # Invalid type ) class TestValidateOhlcvData: """Test cases for validate_ohlcv_data function.""" def test_validate_success(self): """Test successful OHLCV data validation.""" raw_data = { "timestamp": 1609459200000, # Unix timestamp in ms "open": "50000", "high": "50100", "low": "49900", "close": "50050", "volume": "1.5", "trades_count": 100 } ohlcv = validate_ohlcv_data(raw_data, "BTC-USDT", "1m") assert ohlcv.symbol == "BTC-USDT" assert ohlcv.timeframe == "1m" assert ohlcv.trades_count == 100 assert isinstance(ohlcv.open, Decimal) assert ohlcv.open == Decimal("50000") def test_validate_missing_field(self): """Test validation with missing required field.""" raw_data = { "timestamp": 1609459200000, "open": "50000", "high": "50100", # Missing 'low' field "close": "50050", "volume": "1.5" } with pytest.raises(DataValidationError, match="Missing required field: low"): validate_ohlcv_data(raw_data, "BTC-USDT", "1m") def test_validate_invalid_timestamp_string(self): """Test validation with invalid timestamp string.""" raw_data = { "timestamp": "invalid_timestamp", "open": "50000", "high": "50100", "low": "49900", "close": "50050", "volume": "1.5" } with pytest.raises(DataValidationError): validate_ohlcv_data(raw_data, "BTC-USDT", "1m") def test_validate_timestamp_formats(self): """Test validation with different timestamp formats.""" base_data = { "open": "50000", "high": "50100", "low": "49900", "close": "50050", "volume": "1.5" } # Unix timestamp in milliseconds data1 = {**base_data, "timestamp": 1609459200000} ohlcv1 = validate_ohlcv_data(data1, "BTC-USDT", "1m") assert isinstance(ohlcv1.timestamp, datetime) # Unix timestamp in seconds (float) data2 = {**base_data, "timestamp": 1609459200.5} ohlcv2 = validate_ohlcv_data(data2, "BTC-USDT", "1m") assert isinstance(ohlcv2.timestamp, datetime) # ISO format string data3 = {**base_data, "timestamp": "2021-01-01T00:00:00Z"} ohlcv3 = validate_ohlcv_data(data3, "BTC-USDT", "1m") assert isinstance(ohlcv3.timestamp, datetime) # Already a datetime object data4 = {**base_data, "timestamp": datetime.now(timezone.utc)} ohlcv4 = validate_ohlcv_data(data4, "BTC-USDT", "1m") assert isinstance(ohlcv4.timestamp, datetime) def test_validate_invalid_numeric_data(self): """Test validation with invalid numeric price data.""" raw_data = { "timestamp": 1609459200000, "open": "invalid_number", "high": "50100", "low": "49900", "close": "50050", "volume": "1.5" } with pytest.raises(DataValidationError, match="Invalid OHLCV data for BTC-USDT"): validate_ohlcv_data(raw_data, "BTC-USDT", "1m") def test_validate_with_optional_fields(self): """Test validation works correctly with optional fields.""" raw_data = { "timestamp": 1609459200000, "open": "50000", "high": "50100", "low": "49900", "close": "50050", "volume": "1.5" # No trades_count } ohlcv = validate_ohlcv_data(raw_data, "BTC-USDT", "1m") assert ohlcv.trades_count is None # With trades_count raw_data["trades_count"] = 250 ohlcv = validate_ohlcv_data(raw_data, "BTC-USDT", "1m") assert ohlcv.trades_count == 250 if __name__ == "__main__": pytest.main([__file__, "-v"])