""" Data validation utilities for the COBY system. """ import re from typing import List, Optional from decimal import Decimal, InvalidOperation def validate_symbol(symbol: str) -> bool: """ Validate trading symbol format. Args: symbol: Trading symbol to validate Returns: bool: True if valid, False otherwise """ if not symbol or not isinstance(symbol, str): return False # Basic symbol format validation (e.g., BTCUSDT, ETH-USD) pattern = r'^[A-Z0-9]{2,10}[-/]?[A-Z0-9]{2,10}$' return bool(re.match(pattern, symbol.upper())) def validate_price(price: float) -> bool: """ Validate price value. Args: price: Price to validate Returns: bool: True if valid, False otherwise """ if not isinstance(price, (int, float, Decimal)): return False try: price_decimal = Decimal(str(price)) return price_decimal > 0 and price_decimal < Decimal('1e10') # Reasonable upper bound except (InvalidOperation, ValueError): return False def validate_volume(volume: float) -> bool: """ Validate volume value. Args: volume: Volume to validate Returns: bool: True if valid, False otherwise """ if not isinstance(volume, (int, float, Decimal)): return False try: volume_decimal = Decimal(str(volume)) return volume_decimal >= 0 and volume_decimal < Decimal('1e15') # Reasonable upper bound except (InvalidOperation, ValueError): return False def validate_exchange_name(exchange: str) -> bool: """ Validate exchange name. Args: exchange: Exchange name to validate Returns: bool: True if valid, False otherwise """ if not exchange or not isinstance(exchange, str): return False # Exchange name should be alphanumeric with possible underscores/hyphens pattern = r'^[a-zA-Z0-9_-]{2,20}$' return bool(re.match(pattern, exchange)) def validate_timestamp_range(start_time, end_time) -> List[str]: """ Validate timestamp range. Args: start_time: Start timestamp end_time: End timestamp Returns: List[str]: List of validation errors (empty if valid) """ errors = [] if start_time is None: errors.append("Start time cannot be None") if end_time is None: errors.append("End time cannot be None") if start_time and end_time and start_time >= end_time: errors.append("Start time must be before end time") return errors def validate_bucket_size(bucket_size: float) -> bool: """ Validate price bucket size. Args: bucket_size: Bucket size to validate Returns: bool: True if valid, False otherwise """ if not isinstance(bucket_size, (int, float, Decimal)): return False try: size_decimal = Decimal(str(bucket_size)) return size_decimal > 0 and size_decimal <= Decimal('1000') # Reasonable upper bound except (InvalidOperation, ValueError): return False def validate_speed_multiplier(speed: float) -> bool: """ Validate replay speed multiplier. Args: speed: Speed multiplier to validate Returns: bool: True if valid, False otherwise """ if not isinstance(speed, (int, float)): return False return 0.01 <= speed <= 100.0 # 1% to 100x speed def sanitize_symbol(symbol: str) -> str: """ Sanitize and normalize symbol format. Args: symbol: Symbol to sanitize Returns: str: Sanitized symbol """ if not symbol: return "" # Remove whitespace and convert to uppercase sanitized = symbol.strip().upper() # Remove invalid characters sanitized = re.sub(r'[^A-Z0-9/-]', '', sanitized) return sanitized def validate_percentage(value: float, min_val: float = 0.0, max_val: float = 100.0) -> bool: """ Validate percentage value. Args: value: Percentage value to validate min_val: Minimum allowed value max_val: Maximum allowed value Returns: bool: True if valid, False otherwise """ if not isinstance(value, (int, float)): return False return min_val <= value <= max_val def validate_connection_config(config: dict) -> List[str]: """ Validate connection configuration. Args: config: Configuration dictionary Returns: List[str]: List of validation errors (empty if valid) """ errors = [] # Required fields required_fields = ['host', 'port'] for field in required_fields: if field not in config: errors.append(f"Missing required field: {field}") # Validate host if 'host' in config: host = config['host'] if not isinstance(host, str) or not host.strip(): errors.append("Host must be a non-empty string") # Validate port if 'port' in config: port = config['port'] if not isinstance(port, int) or not (1 <= port <= 65535): errors.append("Port must be an integer between 1 and 65535") return errors