217 lines
5.2 KiB
Python
217 lines
5.2 KiB
Python
"""
|
|
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 |