Development

Time Series Forecasting App - Amazon Chronos-2

Building a production forecasting application without the complexity of traditional ML model training and feature engineering.

Alexandre Agius

Alexandre Agius

AWS Solutions Architect

4 min read
Share:

The Problem

Time series forecasting traditionally requires:

  • Extensive feature engineering
  • Model training on historical data
  • Hyperparameter tuning
  • Ongoing model retraining as patterns change

For many use cases, this complexity isn’t justified. You just need reasonable forecasts without becoming a machine learning team.

The Solution

Built a forecasting application using Amazon’s Chronos-2 foundation model β€” a 120M parameter model that provides zero-shot probabilistic forecasting. It works on any time series without task-specific training.

The stack:

  • Frontend: React 19 + Cloudscape Design System
  • Backend: AWS Amplify Gen 2 (Cognito, AppSync, DynamoDB)
  • ML: SageMaker endpoint running Chronos-2

How It Works

Architecture

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                              FRONTEND                                        β”‚
β”‚                     React 19 + Cloudscape Design System                      β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                                    β”‚
                                    β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                          AWS AMPLIFY GEN 2                                   β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚
β”‚  β”‚   Amplify   β”‚    β”‚   Cognito    β”‚    β”‚   AppSync   β”‚    β”‚  DynamoDB   β”‚ β”‚
β”‚  β”‚   Hosting   β”‚    β”‚    Auth      β”‚    β”‚   GraphQL   β”‚    β”‚   Tables    β”‚ β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                                    β”‚
                                    β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                              AWS LAMBDA                                      β”‚
β”‚                     chronos-forecast-handler (Node.js)                       β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                                    β”‚
                                    β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                           AMAZON SAGEMAKER                                   β”‚
β”‚                    Chronos-2 Real-time Inference Endpoint                    β”‚
β”‚                      (ml.g5.xlarge GPU instance)                             β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Lambda β†’ SageMaker Invocation

The Lambda function handles the AppSync GraphQL request and invokes SageMaker:

import { SageMakerRuntimeClient, InvokeEndpointCommand } from "@aws-sdk/client-sagemaker-runtime";

const sagemakerRuntime = new SageMakerRuntimeClient({ region: "eu-west-1" });

export async function generateForecast(routeId: string, historicalPrices: number[]) {
  const payload = {
    inputs: [{
      item_id: routeId,
      target: historicalPrices,
    }],
    parameters: {
      prediction_length: 4,
      output_type: "quantiles",
      quantiles: ["0.1", "0.5", "0.9"]  // Confidence intervals
    }
  };

  const command = new InvokeEndpointCommand({
    EndpointName: process.env.SAGEMAKER_ENDPOINT,
    ContentType: "application/json",
    Body: JSON.stringify(payload),
  });

  const response = await sagemakerRuntime.send(command);
  return JSON.parse(new TextDecoder().decode(response.Body));
}

Amplify Gen 2 Schema

Define the GraphQL schema with custom queries:

// amplify/data/resource.ts
export const data = defineData({
  schema: `
    type Route @model @auth(rules: [{ allow: private }]) {
      routeId: ID! @primaryKey
      name: String!
      historicalPrices: [Float!]!
    }

    type ForecastResult {
      routeId: ID!
      predictions: [PredictionPoint!]!
    }

    type PredictionPoint {
      period: String!
      p10: Float!
      p50: Float!
      p90: Float!
    }

    type Query {
      generateForecast(routeId: ID!): ForecastResult
        @function(name: "chronos-forecast")
        @auth(rules: [{ allow: private }])
    }
  `,
});

Visualizing Probabilistic Forecasts

Display P10/P50/P90 confidence intervals with Recharts:

import { LineChart, Line, Area, XAxis, YAxis, Tooltip } from "recharts";

function ForecastChart({ data }) {
  return (
    <LineChart data={data}>
      <XAxis dataKey="period" />
      <YAxis tickFormatter={(v) => `$${v}`} />

      {/* Confidence interval band */}
      <Area dataKey="p90" stroke="none" fill="#e3f2fd" fillOpacity={0.5} />
      <Area dataKey="p10" stroke="none" fill="#ffffff" />

      {/* Median forecast line */}
      <Line dataKey="p50" stroke="#1976d2" strokeWidth={2} dot={{ r: 4 }} />

      <Tooltip
        formatter={(value, name) => [
          `$${value.toFixed(2)}`,
          name === "p50" ? "Forecast" : name === "p10" ? "Lower bound" : "Upper bound"
        ]}
      />
    </LineChart>
  );
}

Accuracy Metrics

Track forecast quality:

// MAPE - Mean Absolute Percentage Error
function calculateMAPE(actual: number[], predicted: number[]): number {
  const sumAbsPercentError = actual.reduce((sum, val, i) => {
    if (val === 0) return sum;
    return sum + Math.abs((val - predicted[i]) / val);
  }, 0);
  return (sumAbsPercentError / actual.length) * 100;
}

// Interval Coverage - % of actuals within P10-P90
function calculateCoverage(actual: number[], p10: number[], p90: number[]): number {
  const withinInterval = actual.filter(
    (val, i) => val >= p10[i] && val <= p90[i]
  ).length;
  return (withinInterval / actual.length) * 100;
}

What I Learned

  • Chronos-2 simplifies forecasting β€” No feature engineering or model training required
  • Amplify Gen 2 accelerates full-stack development β€” Schema-driven backend with TypeScript
  • Probabilistic forecasts are more useful β€” P10/P50/P90 enables risk-aware decisions instead of false precision
  • Proper backtesting is essential β€” Walk-forward validation with actual model predictions, not just historical fit

What’s Next

  • Implement SageMaker Model Monitor for data drift detection
  • Add comparison view against naive forecasts (last value, moving average)
  • Build MLOps pipeline for model updates

Alexandre Agius

Alexandre Agius

AWS Solutions Architect

Passionate about AI & Security. Building scalable cloud solutions and helping organizations leverage AWS services to innovate faster. Specialized in Generative AI, serverless architectures, and security best practices.

Related Posts

Back to Blog