ttzzs's picture
Deploy Chronos2 Forecasting API v3.0.0 with new SOLID architecture
c40c447 verified
raw
history blame
5.38 kB
"""
Caso de uso para Backtesting.
Implementa la l贸gica de aplicaci贸n para evaluar la precisi贸n
de pron贸sticos usando datos hist贸ricos.
"""
import math
from typing import List
from app.domain.services.forecast_service import ForecastService
from app.domain.models.time_series import TimeSeries
from app.domain.models.forecast_config import ForecastConfig
from app.application.dtos.backtest_dtos import (
BacktestInputDTO,
BacktestOutputDTO,
BacktestMetricsDTO
)
from app.utils.logger import setup_logger
logger = setup_logger(__name__)
class BacktestUseCase:
"""
Caso de uso: Backtesting.
Responsabilidad: Evaluar precisi贸n del modelo usando datos hist贸ricos.
Divide los datos en train/test y calcula m茅tricas de error.
"""
def __init__(self, forecast_service: ForecastService):
"""
Inicializa el caso de uso.
Args:
forecast_service: Servicio de dominio para forecasting
"""
self.forecast_service = forecast_service
logger.info("BacktestUseCase initialized")
def execute(self, input_dto: BacktestInputDTO) -> BacktestOutputDTO:
"""
Ejecuta el caso de uso de backtesting.
Args:
input_dto: Datos de entrada con serie completa y tama帽o de test
Returns:
BacktestOutputDTO: Resultados del backtest con m茅tricas
Raises:
ValueError: Si los datos son inv谩lidos
RuntimeError: Si falla el backtest
"""
logger.info(
f"Executing backtest: {len(input_dto.values)} total points, "
f"{input_dto.test_size} test points"
)
# Validar entrada
input_dto.validate()
# Dividir en train/test
train_values = input_dto.values[:-input_dto.test_size]
test_values = input_dto.values[-input_dto.test_size:]
train_timestamps = None
test_timestamps = None
if input_dto.timestamps:
train_timestamps = input_dto.timestamps[:-input_dto.test_size]
test_timestamps = input_dto.timestamps[-input_dto.test_size:]
logger.info(f"Train size: {len(train_values)}, Test size: {len(test_values)}")
# Crear modelos de dominio para train
train_series = TimeSeries(
values=train_values,
timestamps=train_timestamps,
freq=input_dto.freq
)
config = ForecastConfig(
prediction_length=input_dto.test_size,
quantile_levels=input_dto.quantile_levels,
freq=input_dto.freq
)
# Ejecutar pron贸stico sobre datos de train
try:
result = self.forecast_service.forecast_univariate(train_series, config)
logger.info(f"Forecast completed: {len(result.median)} predictions")
except Exception as e:
logger.error(f"Backtest forecast failed: {e}", exc_info=True)
raise RuntimeError(f"Backtest forecast failed: {str(e)}") from e
# Comparar con valores reales
forecast_values = result.median
actual_values = test_values
# Calcular errores
errors = [
actual - forecast
for actual, forecast in zip(actual_values, forecast_values)
]
# Calcular m茅tricas
metrics = self._calculate_metrics(actual_values, forecast_values)
logger.info(
f"Backtest metrics - MAE: {metrics.mae:.2f}, "
f"MAPE: {metrics.mape:.2f}%, RMSE: {metrics.rmse:.2f}"
)
# Preparar timestamps de salida
if test_timestamps:
output_timestamps = test_timestamps
else:
output_timestamps = result.timestamps
# Crear DTO de salida
output_dto = BacktestOutputDTO(
forecast_values=forecast_values,
actual_values=actual_values,
errors=errors,
metrics=metrics,
timestamps=output_timestamps,
quantiles=result.quantiles if result.quantiles else None
)
return output_dto
def _calculate_metrics(
self,
actual: List[float],
forecast: List[float]
) -> BacktestMetricsDTO:
"""
Calcula m茅tricas de error para el backtest.
Args:
actual: Valores reales
forecast: Valores pronosticados
Returns:
BacktestMetricsDTO: M茅tricas calculadas
"""
n = len(actual)
# Mean Absolute Error
mae = sum(abs(a - f) for a, f in zip(actual, forecast)) / n
# Mean Absolute Percentage Error
mape_values = []
for a, f in zip(actual, forecast):
if a != 0:
mape_values.append(abs((a - f) / a))
mape = (sum(mape_values) / len(mape_values) * 100) if mape_values else 0.0
# Mean Squared Error
mse = sum((a - f) ** 2 for a, f in zip(actual, forecast)) / n
# Root Mean Squared Error
rmse = math.sqrt(mse)
return BacktestMetricsDTO(
mae=mae,
mape=mape,
rmse=rmse,
mse=mse
)