ML Engine Updates: - Updated BTCUSD with Polygon API data (2024-2025): 215,699 new records - Re-trained all ML models: Attention (R²: 0.223), Base, Metamodel (87.3% confidence) - Backtest results: +176.71R profit with aggressive_filter strategy Documentation Consolidation: - Created docs/99-analisis/_MAP.md index with 13 new analysis documents - Consolidated inventories: removed duplicates from orchestration/inventarios/ - Updated ML_INVENTORY.yml with BTCUSD metrics and training results - Added execution reports: FASE11-BTCUSD, correction issues, alignment validation Architecture & Integration: - Updated all module documentation with NEXUS v3.4 frontmatter - Fixed _MAP.md indexes across all folders - Updated orchestration plans and traces Files: 229 changed, 5064 insertions(+), 1872 deletions(-) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
16 KiB
16 KiB
| id | title | type | status | priority | epic | project | version | created_date | updated_date |
|---|---|---|---|---|---|---|---|---|---|
| ET-ML-004 | FastAPI Endpoints | Technical Specification | Done | Alta | OQI-006 | trading-platform | 1.0.0 | 2025-12-05 | 2026-01-04 |
ET-ML-004: FastAPI Endpoints
Metadata
| Campo | Valor |
|---|---|
| ID | ET-ML-004 |
| É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
Especificar los endpoints de la API REST del ML Engine, incluyendo schemas de request/response, validación, autenticación y documentación OpenAPI.
Base URL
Production: https://ml.trading.com/api/v1
Development: http://localhost:8000/api/v1
Autenticación
Todos los endpoints requieren API Key en el header:
X-API-Key: your-api-key-here
Endpoints
1. Predictions
POST /predictions
Genera predicción de rango de precio.
Request:
{
symbol: string; // "BTCUSDT" | "ETHUSDT"
horizon: number; // 6 | 18 | 36 | 72 (candles)
}
Response:
{
success: boolean;
data: {
symbol: string;
horizon: number;
horizon_label: string; // "scalping" | "intraday" | "swing" | "position"
timestamp: string; // ISO 8601
current_price: number;
predicted_high: number;
predicted_low: number;
delta_high_percent: number;
delta_low_percent: number;
range_percent: number;
confidence: {
mae: number; // Historical MAE for this horizon
model_version: string;
};
expires_at: string; // ISO 8601 - When this prediction expires
};
metadata: {
request_id: string;
latency_ms: number;
cached: boolean;
};
}
Example:
curl -X POST https://ml.trading.com/api/v1/predictions \
-H "X-API-Key: your-key" \
-H "Content-Type: application/json" \
-d '{"symbol": "BTCUSDT", "horizon": 18}'
Response Example:
{
"success": true,
"data": {
"symbol": "BTCUSDT",
"horizon": 18,
"horizon_label": "intraday",
"timestamp": "2025-12-05T10:30:00Z",
"current_price": 43250.50,
"predicted_high": 43520.75,
"predicted_low": 43012.30,
"delta_high_percent": 0.625,
"delta_low_percent": -0.551,
"range_percent": 1.176,
"confidence": {
"mae": 0.32,
"model_version": "v1.2.0"
},
"expires_at": "2025-12-05T12:00:00Z"
},
"metadata": {
"request_id": "req_abc123",
"latency_ms": 45,
"cached": false
}
}
2. Signals
POST /signals
Genera señal de trading.
Request:
{
symbol: string;
horizon: number;
include_range?: boolean; // Include price range prediction
include_tpsl?: boolean; // Include TP/SL prediction
}
Response:
{
success: boolean;
data: {
symbol: string;
horizon: number;
timestamp: string;
signal: {
type: "buy" | "sell" | "hold";
confidence: number; // 0.0 - 1.0
strength: "weak" | "moderate" | "strong";
probabilities: {
hold: number;
buy: number;
sell: number;
};
};
price_range?: { // If include_range = true
current: number;
predicted_high: number;
predicted_low: number;
};
tpsl?: { // If include_tpsl = true
prediction: "take_profit" | "stop_loss";
probability_tp: number;
probability_sl: number;
suggested_tp_percent: number;
suggested_sl_percent: number;
};
recommendation: {
action: "BUY" | "SELL" | "HOLD";
entry_zone?: {
min: number;
max: number;
};
take_profit?: number;
stop_loss?: number;
risk_reward?: string; // "1:2.5"
quality: "low" | "medium" | "high";
reason: string;
};
};
}
Example:
curl -X POST https://ml.trading.com/api/v1/signals \
-H "X-API-Key: your-key" \
-H "Content-Type: application/json" \
-d '{"symbol": "BTCUSDT", "horizon": 18, "include_range": true, "include_tpsl": true}'
GET /signals/history
Obtiene historial de señales.
Query Parameters:
symbol: string (required)
horizon: number (optional)
type: string (optional) - "buy" | "sell" | "hold"
from: string (optional) - ISO 8601 date
to: string (optional) - ISO 8601 date
limit: number (optional) - default 50, max 100
offset: number (optional) - default 0
Response:
{
success: boolean;
data: {
signals: Array<{
id: string;
symbol: string;
horizon: number;
type: string;
confidence: number;
current_price: number;
created_at: string;
outcome?: {
result: "profit" | "loss" | "pending";
pnl_percent?: number;
closed_at?: string;
};
}>;
pagination: {
total: number;
limit: number;
offset: number;
has_more: boolean;
};
};
}
3. Indicators
GET /indicators
Obtiene indicadores técnicos actuales.
Query Parameters:
symbol: string (required)
indicators: string (optional) - comma-separated list
Response:
{
success: boolean;
data: {
symbol: string;
timestamp: string;
price: number;
indicators: {
rsi_14: number;
macd: {
line: number;
signal: number;
histogram: number;
};
bollinger: {
upper: number;
middle: number;
lower: number;
position: number; // 0-100
};
moving_averages: {
sma_20: number;
sma_50: number;
ema_12: number;
ema_26: number;
};
momentum: {
roc_10: number;
stochastic_k: number;
stochastic_d: number;
williams_r: number;
};
volume: {
current: number;
avg_20: number;
ratio: number;
mfi_14: number;
};
volatility: {
atr_14: number;
atr_percent: number;
std_20: number;
};
};
summary: {
trend: "bullish" | "bearish" | "neutral";
momentum: "overbought" | "oversold" | "neutral";
volatility: "high" | "normal" | "low";
};
};
}
4. Models
GET /models/status
Estado de los modelos cargados.
Response:
{
success: boolean;
data: {
models: Array<{
name: string;
type: "regressor" | "classifier";
version: string;
loaded: boolean;
last_trained: string;
metrics: {
accuracy?: number;
mae?: number;
f1_score?: number;
};
symbols: string[];
horizons: number[];
}>;
system: {
total_models: number;
loaded_models: number;
memory_usage_mb: number;
};
};
}
GET /models/{model_name}/metrics
Métricas detalladas de un modelo.
Response:
{
success: boolean;
data: {
model_name: string;
version: string;
training: {
samples: number;
features: number;
trained_at: string;
training_time_seconds: number;
};
performance: {
// For regressors
mae?: number;
mse?: number;
rmse?: number;
mape?: number;
// For classifiers
accuracy?: number;
precision?: number;
recall?: number;
f1_score?: number;
auc?: number;
};
feature_importance: Array<{
feature: string;
importance: number;
}>;
recent_predictions: {
total: number;
correct: number;
accuracy_24h: number;
};
};
}
5. Health
GET /health
Health check básico.
Response:
{
status: "healthy" | "degraded" | "unhealthy";
timestamp: string;
}
GET /health/detailed
Health check detallado.
Response:
{
status: "healthy" | "degraded" | "unhealthy";
timestamp: string;
components: {
api: {
status: "up" | "down";
response_time_ms: number;
};
models: {
status: "up" | "down";
loaded_count: number;
total_count: number;
};
redis: {
status: "up" | "down";
latency_ms: number;
};
binance: {
status: "up" | "down";
last_price_update: string;
};
database: {
status: "up" | "down";
connection_pool: {
active: number;
idle: number;
max: number;
};
};
};
metrics: {
uptime_seconds: number;
requests_per_minute: number;
avg_latency_ms: number;
error_rate_percent: number;
};
}
Schemas (Pydantic)
# app/schemas/prediction.py
from pydantic import BaseModel, Field, validator
from typing import Optional, Literal
from datetime import datetime
class PredictionRequest(BaseModel):
symbol: str = Field(..., description="Trading pair symbol")
horizon: int = Field(..., description="Prediction horizon in candles")
@validator('symbol')
def validate_symbol(cls, v):
valid_symbols = ['BTCUSDT', 'ETHUSDT']
if v not in valid_symbols:
raise ValueError(f'Symbol must be one of: {valid_symbols}')
return v
@validator('horizon')
def validate_horizon(cls, v):
valid_horizons = [6, 18, 36, 72]
if v not in valid_horizons:
raise ValueError(f'Horizon must be one of: {valid_horizons}')
return v
class Config:
schema_extra = {
"example": {
"symbol": "BTCUSDT",
"horizon": 18
}
}
class PredictionConfidence(BaseModel):
mae: float
model_version: str
class PredictionData(BaseModel):
symbol: str
horizon: int
horizon_label: str
timestamp: datetime
current_price: float
predicted_high: float
predicted_low: float
delta_high_percent: float
delta_low_percent: float
range_percent: float
confidence: PredictionConfidence
expires_at: datetime
class ResponseMetadata(BaseModel):
request_id: str
latency_ms: int
cached: bool
class PredictionResponse(BaseModel):
success: bool
data: PredictionData
metadata: ResponseMetadata
# app/schemas/signal.py
from pydantic import BaseModel, Field
from typing import Optional, Literal
from datetime import datetime
class SignalRequest(BaseModel):
symbol: str
horizon: int
include_range: bool = False
include_tpsl: bool = False
class SignalProbabilities(BaseModel):
hold: float
buy: float
sell: float
class Signal(BaseModel):
type: Literal["buy", "sell", "hold"]
confidence: float = Field(..., ge=0, le=1)
strength: Literal["weak", "moderate", "strong"]
probabilities: SignalProbabilities
class PriceRange(BaseModel):
current: float
predicted_high: float
predicted_low: float
class TPSL(BaseModel):
prediction: Literal["take_profit", "stop_loss"]
probability_tp: float
probability_sl: float
suggested_tp_percent: float
suggested_sl_percent: float
class EntryZone(BaseModel):
min: float
max: float
class Recommendation(BaseModel):
action: Literal["BUY", "SELL", "HOLD"]
entry_zone: Optional[EntryZone]
take_profit: Optional[float]
stop_loss: Optional[float]
risk_reward: Optional[str]
quality: Literal["low", "medium", "high"]
reason: str
class SignalData(BaseModel):
symbol: str
horizon: int
timestamp: datetime
signal: Signal
price_range: Optional[PriceRange]
tpsl: Optional[TPSL]
recommendation: Recommendation
class SignalResponse(BaseModel):
success: bool
data: SignalData
Router Implementation
# app/api/routers/predictions.py
from fastapi import APIRouter, Depends, HTTPException
from app.schemas.prediction import PredictionRequest, PredictionResponse
from app.services.predictor import PredictorService
from app.core.security import validate_api_key
from app.core.rate_limit import limiter
import uuid
import time
router = APIRouter()
@router.post("", response_model=PredictionResponse)
@limiter.limit("100/minute")
async def create_prediction(
request: PredictionRequest,
api_key: str = Depends(validate_api_key),
predictor: PredictorService = Depends()
):
"""
Generate price range prediction for a trading pair.
- **symbol**: Trading pair (BTCUSDT, ETHUSDT)
- **horizon**: Prediction horizon in 5-minute candles (6, 18, 36, 72)
"""
start_time = time.time()
request_id = str(uuid.uuid4())[:8]
try:
prediction, cached = await predictor.predict(
symbol=request.symbol,
horizon=request.horizon
)
latency_ms = int((time.time() - start_time) * 1000)
return PredictionResponse(
success=True,
data=prediction,
metadata={
"request_id": f"req_{request_id}",
"latency_ms": latency_ms,
"cached": cached
}
)
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
# app/api/routers/signals.py
from fastapi import APIRouter, Depends, Query
from typing import Optional, List
from app.schemas.signal import SignalRequest, SignalResponse, SignalHistoryResponse
from app.services.signal_generator import SignalGeneratorService
from app.core.security import validate_api_key
router = APIRouter()
@router.post("", response_model=SignalResponse)
async def generate_signal(
request: SignalRequest,
api_key: str = Depends(validate_api_key),
signal_gen: SignalGeneratorService = Depends()
):
"""Generate trading signal with optional range and TP/SL predictions."""
return await signal_gen.generate(
symbol=request.symbol,
horizon=request.horizon,
include_range=request.include_range,
include_tpsl=request.include_tpsl
)
@router.get("/history")
async def get_signal_history(
symbol: str,
horizon: Optional[int] = None,
type: Optional[str] = Query(None, regex="^(buy|sell|hold)$"),
limit: int = Query(50, le=100),
offset: int = 0,
api_key: str = Depends(validate_api_key),
signal_gen: SignalGeneratorService = Depends()
):
"""Get historical signals with optional filters."""
return await signal_gen.get_history(
symbol=symbol,
horizon=horizon,
signal_type=type,
limit=limit,
offset=offset
)
Error Responses
# app/core/exceptions.py
from fastapi import HTTPException
class APIError(HTTPException):
"""Base API error"""
pass
class ValidationError(APIError):
def __init__(self, detail: str):
super().__init__(status_code=400, detail=detail)
class AuthenticationError(APIError):
def __init__(self):
super().__init__(status_code=401, detail="Invalid API key")
class RateLimitError(APIError):
def __init__(self):
super().__init__(status_code=429, detail="Rate limit exceeded")
class ModelError(APIError):
def __init__(self, detail: str):
super().__init__(status_code=500, detail=f"Model error: {detail}")
Error Response Format:
{
"success": false,
"error": {
"code": "VALIDATION_ERROR",
"message": "Symbol XYZUSDT is not supported",
"details": {
"field": "symbol",
"valid_values": ["BTCUSDT", "ETHUSDT"]
}
},
"metadata": {
"request_id": "req_abc123",
"timestamp": "2025-12-05T10:30:00Z"
}
}
Rate Limits
| Endpoint | Limit | Window |
|---|---|---|
| POST /predictions | 100 | 1 minute |
| POST /signals | 100 | 1 minute |
| GET /signals/history | 60 | 1 minute |
| GET /indicators | 120 | 1 minute |
| GET /models/* | 30 | 1 minute |
| GET /health | Unlimited | - |
Referencias
Autor: Requirements-Analyst Fecha: 2025-12-05