18: tests, fixes

This commit is contained in:
Dobromir Popov
2025-08-05 14:11:49 +03:00
parent 71442f766c
commit 622d059aae
24 changed files with 1959 additions and 1638 deletions

View File

@ -1,57 +1,23 @@
"""
Custom exceptions for the COBY system.
Custom exceptions for COBY system.
"""
class COBYException(Exception):
"""Base exception for COBY system"""
def __init__(self, message: str, error_code: str = None, details: dict = None):
super().__init__(message)
self.message = message
self.error_code = error_code
self.details = details or {}
def to_dict(self) -> dict:
"""Convert exception to dictionary"""
return {
'error': self.__class__.__name__,
'message': self.message,
'error_code': self.error_code,
'details': self.details
}
pass
class ConnectionError(COBYException):
"""Exception raised for connection-related errors"""
"""Connection related errors"""
pass
class ValidationError(COBYException):
"""Exception raised for data validation errors"""
"""Data validation errors"""
pass
class ProcessingError(COBYException):
"""Exception raised for data processing errors"""
pass
class StorageError(COBYException):
"""Exception raised for storage-related errors"""
pass
class ConfigurationError(COBYException):
"""Exception raised for configuration errors"""
pass
class ReplayError(COBYException):
"""Exception raised for replay-related errors"""
pass
class AggregationError(COBYException):
"""Exception raised for aggregation errors"""
"""Data processing errors"""
pass

View File

@ -1,206 +1,15 @@
"""
Timing utilities for the COBY system.
Timing utilities for COBY system.
"""
import time
from datetime import datetime, timezone
from typing import Optional
def get_current_timestamp() -> datetime:
"""
Get current UTC timestamp.
Returns:
datetime: Current UTC timestamp
"""
"""Get current UTC timestamp"""
return datetime.now(timezone.utc)
def format_timestamp(timestamp: datetime, format_str: str = "%Y-%m-%d %H:%M:%S.%f") -> str:
"""
Format timestamp to string.
Args:
timestamp: Timestamp to format
format_str: Format string
Returns:
str: Formatted timestamp string
"""
return timestamp.strftime(format_str)
def parse_timestamp(timestamp_str: str, format_str: str = "%Y-%m-%d %H:%M:%S.%f") -> datetime:
"""
Parse timestamp string to datetime.
Args:
timestamp_str: Timestamp string to parse
format_str: Format string
Returns:
datetime: Parsed timestamp
"""
dt = datetime.strptime(timestamp_str, format_str)
# Ensure timezone awareness
if dt.tzinfo is None:
dt = dt.replace(tzinfo=timezone.utc)
return dt
def timestamp_to_unix(timestamp: datetime) -> float:
"""
Convert datetime to Unix timestamp.
Args:
timestamp: Datetime to convert
Returns:
float: Unix timestamp
"""
return timestamp.timestamp()
def unix_to_timestamp(unix_time: float) -> datetime:
"""
Convert Unix timestamp to datetime.
Args:
unix_time: Unix timestamp
Returns:
datetime: Converted datetime (UTC)
"""
return datetime.fromtimestamp(unix_time, tz=timezone.utc)
def calculate_time_diff(start: datetime, end: datetime) -> float:
"""
Calculate time difference in seconds.
Args:
start: Start timestamp
end: End timestamp
Returns:
float: Time difference in seconds
"""
return (end - start).total_seconds()
def is_timestamp_recent(timestamp: datetime, max_age_seconds: int = 60) -> bool:
"""
Check if timestamp is recent (within max_age_seconds).
Args:
timestamp: Timestamp to check
max_age_seconds: Maximum age in seconds
Returns:
bool: True if recent, False otherwise
"""
now = get_current_timestamp()
age = calculate_time_diff(timestamp, now)
return age <= max_age_seconds
def sleep_until(target_time: datetime) -> None:
"""
Sleep until target time.
Args:
target_time: Target timestamp to sleep until
"""
now = get_current_timestamp()
sleep_seconds = calculate_time_diff(now, target_time)
if sleep_seconds > 0:
time.sleep(sleep_seconds)
def get_milliseconds() -> int:
"""
Get current timestamp in milliseconds.
Returns:
int: Current timestamp in milliseconds
"""
return int(time.time() * 1000)
def milliseconds_to_timestamp(ms: int) -> datetime:
"""
Convert milliseconds to datetime.
Args:
ms: Milliseconds timestamp
Returns:
datetime: Converted datetime (UTC)
"""
return datetime.fromtimestamp(ms / 1000.0, tz=timezone.utc)
def round_timestamp(timestamp: datetime, seconds: int) -> datetime:
"""
Round timestamp to nearest interval.
Args:
timestamp: Timestamp to round
seconds: Interval in seconds
Returns:
datetime: Rounded timestamp
"""
unix_time = timestamp_to_unix(timestamp)
rounded_unix = round(unix_time / seconds) * seconds
return unix_to_timestamp(rounded_unix)
class Timer:
"""Simple timer for measuring execution time"""
def __init__(self):
self.start_time: Optional[float] = None
self.end_time: Optional[float] = None
def start(self) -> None:
"""Start the timer"""
self.start_time = time.perf_counter()
self.end_time = None
def stop(self) -> float:
"""
Stop the timer and return elapsed time.
Returns:
float: Elapsed time in seconds
"""
if self.start_time is None:
raise ValueError("Timer not started")
self.end_time = time.perf_counter()
return self.elapsed()
def elapsed(self) -> float:
"""
Get elapsed time.
Returns:
float: Elapsed time in seconds
"""
if self.start_time is None:
return 0.0
end = self.end_time or time.perf_counter()
return end - self.start_time
def __enter__(self):
"""Context manager entry"""
self.start()
return self
def __exit__(self, exc_type, exc_val, exc_tb):
"""Context manager exit"""
self.stop()
def format_timestamp(timestamp: datetime) -> str:
"""Format timestamp as ISO string"""
return timestamp.isoformat()

View File

@ -1,217 +1,31 @@
"""
Data validation utilities for the COBY system.
Validation utilities for COBY system.
"""
import re
from typing import List, Optional
from decimal import Decimal, InvalidOperation
from typing import Any
def validate_symbol(symbol: str) -> bool:
"""
Validate trading symbol format.
Args:
symbol: Trading symbol to validate
Returns:
bool: True if valid, False otherwise
"""
"""Validate trading symbol format"""
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}$'
# Basic symbol validation (letters and numbers, 3-12 chars)
pattern = r'^[A-Z0-9]{3,12}$'
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
"""Validate price value"""
return isinstance(price, (int, float)) and price > 0
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
"""Validate volume value"""
return isinstance(volume, (int, float)) and volume >= 0
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
def validate_timestamp(timestamp: Any) -> bool:
"""Validate timestamp"""
return timestamp is not None