--- id: "ET-ML-001" title: "Arquitectura ML Engine" type: "Technical Specification" status: "Done" priority: "Alta" epic: "OQI-006" project: "trading-platform" version: "1.0.0" created_date: "2025-12-05" updated_date: "2026-01-04" --- # ET-ML-001: Arquitectura ML Engine ## Metadata | Campo | Valor | |-------|-------| | **ID** | ET-ML-001 | | **Épica** | OQI-006 - Señales ML | | **Tipo** | Especificación Técnica | | **Versión** | 1.0.0 | | **Estado** | Aprobado | | **Última actualización** | 2025-12-05 | --- ## Propósito Definir la arquitectura completa del ML Engine, incluyendo la estructura de servicios, comunicación entre componentes, y flujos de datos para predicciones y señales de trading. --- ## Arquitectura General ### Diagrama de Alto Nivel ``` ┌─────────────────────────────────────────────────────────────────────────────┐ │ ML ENGINE │ │ ┌─────────────────────────────────────────────────────────────────────┐ │ │ │ FastAPI Application │ │ │ │ ┌──────────────┐ ┌──────────────┐ ┌──────────────────────────┐ │ │ │ │ │ Routers │ │ Services │ │ Background Tasks │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ /predictions │ │ Predictor │ │ ┌─────────────────────┐ │ │ │ │ │ │ /signals │ │ SignalGen │ │ │ Market Data Fetcher │ │ │ │ │ │ │ /indicators │ │ Indicators │ │ └─────────────────────┘ │ │ │ │ │ │ /models │ │ ModelManager │ │ ┌─────────────────────┐ │ │ │ │ │ │ /health │ │ │ │ │ Training Pipeline │ │ │ │ │ │ └──────────────┘ └──────────────┘ │ └─────────────────────┘ │ │ │ │ │ │ ┌─────────────────────┐ │ │ │ │ │ ┌─────────────────────────────────┐ │ │ Signal Publisher │ │ │ │ │ │ │ Models Layer │ │ └─────────────────────┘ │ │ │ │ │ │ ┌─────────────┐ ┌───────────┐ │ └──────────────────────────┘ │ │ │ │ │ │ XGBoost │ │ Ensemble │ │ │ │ │ │ │ │ Models │ │ Manager │ │ │ │ │ │ │ └─────────────┘ └───────────┘ │ │ │ │ │ └─────────────────────────────────┘ │ │ │ └─────────────────────────────────────────────────────────────────────┘ │ │ │ │ ┌─────────────────────────────────────────────────────────────────────┐ │ │ │ Data Layer │ │ │ │ ┌──────────────┐ ┌──────────────┐ ┌──────────────────────────┐ │ │ │ │ │ Redis │ │ PostgreSQL │ │ Model Storage │ │ │ │ │ │ Cache │ │ (Metrics) │ │ (File System) │ │ │ │ │ └──────────────┘ └──────────────┘ └──────────────────────────┘ │ │ │ └─────────────────────────────────────────────────────────────────────┘ │ └─────────────────────────────────────────────────────────────────────────────┘ │ │ │ ▼ ▼ ▼ ┌───────────┐ ┌───────────┐ ┌───────────┐ │ Binance │ │ Backend │ │ Frontend │ │ API │ │ Express │ │ React │ └───────────┘ └───────────┘ └───────────┘ ``` --- ## Estructura de Directorios ``` apps/ml-engine/ ├── app/ │ ├── __init__.py │ ├── main.py # FastAPI entry point │ ├── config/ │ │ ├── __init__.py │ │ ├── settings.py # Pydantic settings │ │ └── logging.py # Logging configuration │ ├── api/ │ │ ├── __init__.py │ │ ├── dependencies.py # Dependency injection │ │ └── routers/ │ │ ├── __init__.py │ │ ├── predictions.py # /predictions endpoints │ │ ├── signals.py # /signals endpoints │ │ ├── indicators.py # /indicators endpoints │ │ ├── models.py # /models endpoints │ │ └── health.py # /health endpoints │ ├── core/ │ │ ├── __init__.py │ │ ├── exceptions.py # Custom exceptions │ │ ├── security.py # API key validation │ │ └── middleware.py # Custom middleware │ ├── services/ │ │ ├── __init__.py │ │ ├── predictor.py # Prediction service │ │ ├── signal_generator.py # Signal generation │ │ ├── indicator_calculator.py # Technical indicators │ │ ├── model_manager.py # Model lifecycle │ │ └── market_data.py # Data fetching │ ├── models/ │ │ ├── __init__.py │ │ ├── base.py # Base model class │ │ ├── range_predictor.py # Price range predictor │ │ ├── tpsl_classifier.py # TP/SL classifier │ │ └── signal_classifier.py # Signal classifier │ ├── features/ │ │ ├── __init__.py │ │ ├── builder.py # Feature builder │ │ ├── volatility.py # Volatility features │ │ ├── momentum.py # Momentum features │ │ ├── trend.py # Trend features │ │ └── volume.py # Volume features │ ├── schemas/ │ │ ├── __init__.py │ │ ├── prediction.py # Prediction DTOs │ │ ├── signal.py # Signal DTOs │ │ └── indicator.py # Indicator DTOs │ └── tasks/ │ ├── __init__.py │ ├── market_data_fetcher.py # Background data fetch │ ├── training_pipeline.py # Model training │ └── signal_publisher.py # Signal broadcast ├── data/ │ ├── models/ # Trained model files │ │ ├── range_predictor/ │ │ ├── tpsl_classifier/ │ │ └── signal_classifier/ │ └── cache/ # Cached market data ├── tests/ │ ├── __init__.py │ ├── conftest.py │ ├── unit/ │ └── integration/ ├── scripts/ │ ├── train_models.py │ └── evaluate_models.py ├── requirements.txt ├── Dockerfile └── docker-compose.yml ``` --- ## Componentes Principales ### 1. FastAPI Application ```python # app/main.py from fastapi import FastAPI from fastapi.middleware.cors import CORSMiddleware from contextlib import asynccontextmanager from app.config.settings import settings from app.api.routers import predictions, signals, indicators, models, health from app.tasks.market_data_fetcher import MarketDataFetcher from app.services.model_manager import ModelManager @asynccontextmanager async def lifespan(app: FastAPI): # Startup model_manager = ModelManager() await model_manager.load_all_models() market_fetcher = MarketDataFetcher() await market_fetcher.start() yield # Shutdown await market_fetcher.stop() app = FastAPI( title="Trading Platform ML Engine", version="1.0.0", lifespan=lifespan ) app.add_middleware( CORSMiddleware, allow_origins=settings.CORS_ORIGINS, allow_methods=["*"], allow_headers=["*"], ) app.include_router(health.router, tags=["Health"]) app.include_router(predictions.router, prefix="/predictions", tags=["Predictions"]) app.include_router(signals.router, prefix="/signals", tags=["Signals"]) app.include_router(indicators.router, prefix="/indicators", tags=["Indicators"]) app.include_router(models.router, prefix="/models", tags=["Models"]) ``` ### 2. Configuration ```python # app/config/settings.py from pydantic_settings import BaseSettings from typing import List class Settings(BaseSettings): # API API_HOST: str = "0.0.0.0" API_PORT: int = 8000 API_KEY: str # Database DATABASE_URL: str REDIS_URL: str # Binance BINANCE_API_KEY: str BINANCE_API_SECRET: str # Models MODEL_PATH: str = "./data/models" SUPPORTED_SYMBOLS: List[str] = ["BTCUSDT", "ETHUSDT"] # Features DEFAULT_HORIZONS: List[int] = [6, 18, 36, 72] # candles # CORS CORS_ORIGINS: List[str] = ["http://localhost:3000"] class Config: env_file = ".env" settings = Settings() ``` --- ## Flujos de Datos ### 1. Flujo de Predicción ``` ┌──────────┐ ┌──────────┐ ┌────────────┐ ┌──────────┐ ┌──────────┐ │ Client │───▶│ Router │───▶│ Service │───▶│ Features │───▶│ Model │ │ │ │ │ │ │ │ Builder │ │ │ └──────────┘ └──────────┘ └────────────┘ └──────────┘ └──────────┘ │ │ ▼ │ ┌────────────┐ │ │ Cache │◀─────────────────────────┘ │ (Redis) │ └────────────┘ ``` **Secuencia:** 1. Cliente solicita predicción vía API 2. Router valida request y extrae parámetros 3. Service verifica cache (Redis) 4. Si no hay cache: Feature Builder genera features 5. Model realiza predicción 6. Resultado se cachea y retorna ### 2. Flujo de Señales ``` ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ Market Data │───▶│ Feature │───▶│ Signal │───▶│ Publisher │ │ Fetcher │ │ Builder │ │ Generator │ │ (Redis) │ └──────────────┘ └──────────────┘ └──────────────┘ └──────────────┘ │ │ │ ▼ ▼ ▼ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ Binance │ │ PostgreSQL │ │ Backend │ │ API │ │ (Logs) │ │ Express │ └──────────────┘ └──────────────┘ └──────────────┘ ``` --- ## Comunicación Entre Servicios ### Backend ↔ ML Engine ```yaml # Comunicación HTTP Protocol: REST over HTTPS Authentication: API Key (X-API-Key header) Format: JSON Timeout: 30 seconds # Endpoints principales POST /predictions # Solicitar predicción POST /signals # Generar señal GET /indicators # Obtener indicadores GET /models/status # Estado de modelos ``` ### ML Engine ↔ Redis ```python # Cache de predicciones Key pattern: "prediction:{symbol}:{horizon}:{timestamp}" TTL: 60 seconds (1 candle de 5min) # Publicación de señales Channel: "signals:{symbol}" Message format: JSON serialized Signal object ``` ### ML Engine ↔ PostgreSQL ```sql -- Solo para métricas y logs CREATE TABLE ml.prediction_logs ( id UUID PRIMARY KEY, symbol VARCHAR(20), horizon INTEGER, predicted_high DECIMAL(20, 8), predicted_low DECIMAL(20, 8), actual_high DECIMAL(20, 8), actual_low DECIMAL(20, 8), mae DECIMAL(10, 6), created_at TIMESTAMPTZ DEFAULT NOW() ); CREATE TABLE ml.signal_logs ( id UUID PRIMARY KEY, symbol VARCHAR(20), signal_type VARCHAR(10), confidence DECIMAL(5, 4), entry_price DECIMAL(20, 8), outcome VARCHAR(10), pnl_percent DECIMAL(10, 4), created_at TIMESTAMPTZ DEFAULT NOW() ); ``` --- ## Concurrencia y Escalabilidad ### Workers Configuration ```python # uvicorn config workers = 4 # CPU cores worker_class = "uvicorn.workers.UvicornWorker" timeout = 60 keepalive = 5 # Thread pool for CPU-bound tasks from concurrent.futures import ThreadPoolExecutor executor = ThreadPoolExecutor(max_workers=8) ``` ### Rate Limiting ```python from slowapi import Limiter from slowapi.util import get_remote_address limiter = Limiter(key_func=get_remote_address) # Limits @router.get("/predictions") @limiter.limit("100/minute") async def get_prediction(): ... ``` ### Caching Strategy ```python # Redis caching decorator from functools import wraps def cache_prediction(ttl: int = 60): def decorator(func): @wraps(func) async def wrapper(symbol: str, horizon: int, *args, **kwargs): cache_key = f"pred:{symbol}:{horizon}:{current_candle_time()}" cached = await redis.get(cache_key) if cached: return json.loads(cached) result = await func(symbol, horizon, *args, **kwargs) await redis.setex(cache_key, ttl, json.dumps(result)) return result return wrapper return decorator ``` --- ## Manejo de Errores ### Exception Hierarchy ```python # app/core/exceptions.py class MLEngineError(Exception): """Base exception for ML Engine""" pass class ModelNotFoundError(MLEngineError): """Model file not found""" pass class ModelLoadError(MLEngineError): """Failed to load model""" pass class PredictionError(MLEngineError): """Error during prediction""" pass class MarketDataError(MLEngineError): """Error fetching market data""" pass class FeatureError(MLEngineError): """Error calculating features""" pass ``` ### Error Handlers ```python # app/main.py from fastapi import Request from fastapi.responses import JSONResponse @app.exception_handler(MLEngineError) async def ml_exception_handler(request: Request, exc: MLEngineError): return JSONResponse( status_code=500, content={ "error": exc.__class__.__name__, "message": str(exc), "path": str(request.url) } ) ``` --- ## Monitoreo y Observabilidad ### Health Checks ```python # app/api/routers/health.py from fastapi import APIRouter, Depends from app.services.model_manager import ModelManager router = APIRouter() @router.get("/health") async def health_check(): return {"status": "healthy"} @router.get("/health/detailed") async def detailed_health(model_manager: ModelManager = Depends()): return { "status": "healthy", "models": model_manager.get_status(), "cache": await check_redis(), "database": await check_postgres(), "binance": await check_binance_connection() } ``` ### Metrics (Prometheus) ```python from prometheus_client import Counter, Histogram, Gauge # Counters predictions_total = Counter( 'ml_predictions_total', 'Total predictions made', ['symbol', 'horizon'] ) # Histograms prediction_latency = Histogram( 'ml_prediction_latency_seconds', 'Prediction latency', buckets=[.01, .025, .05, .1, .25, .5, 1.0] ) # Gauges model_accuracy = Gauge( 'ml_model_accuracy', 'Current model accuracy', ['model_name'] ) ``` --- ## Seguridad ### API Key Authentication ```python # app/core/security.py from fastapi import Security, HTTPException from fastapi.security import APIKeyHeader api_key_header = APIKeyHeader(name="X-API-Key") async def validate_api_key(api_key: str = Security(api_key_header)): if api_key != settings.API_KEY: raise HTTPException(status_code=401, detail="Invalid API Key") return api_key ``` ### Input Validation ```python # app/schemas/prediction.py from pydantic import BaseModel, validator from typing import Literal class PredictionRequest(BaseModel): symbol: str horizon: int @validator('symbol') def validate_symbol(cls, v): if v not in settings.SUPPORTED_SYMBOLS: raise ValueError(f'Symbol {v} not supported') return v @validator('horizon') def validate_horizon(cls, v): if v not in settings.DEFAULT_HORIZONS: raise ValueError(f'Horizon {v} not supported') return v ``` --- ## Deployment ### Docker Configuration ```dockerfile # Dockerfile FROM python:3.11-slim WORKDIR /app RUN pip install --no-cache-dir poetry COPY pyproject.toml poetry.lock ./ RUN poetry install --no-dev COPY app/ ./app/ COPY data/models/ ./data/models/ EXPOSE 8000 CMD ["poetry", "run", "uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"] ``` ### Docker Compose ```yaml # docker-compose.yml version: '3.8' services: ml-engine: build: . ports: - "8000:8000" environment: - DATABASE_URL=${DATABASE_URL} - REDIS_URL=${REDIS_URL} - API_KEY=${ML_API_KEY} - BINANCE_API_KEY=${BINANCE_API_KEY} - BINANCE_API_SECRET=${BINANCE_API_SECRET} volumes: - ./data/models:/app/data/models depends_on: - redis restart: unless-stopped redis: image: redis:7-alpine ports: - "6379:6379" volumes: - redis_data:/data volumes: redis_data: ``` --- ## Referencias - [RF-ML-001: Predicción de Precios](../requerimientos/RF-ML-001-predicciones.md) - [ET-ML-002: Modelos XGBoost](./ET-ML-002-modelos.md) - [ET-ML-003: Feature Engineering](./ET-ML-003-features.md) - [FastAPI Documentation](https://fastapi.tiangolo.com/) --- **Autor:** Requirements-Analyst **Fecha:** 2025-12-05