Economic Research · UNESWA Dissertation · 2026

Eswatini Macroeconomic
Intelligence Dashboard

UNESWA final-year dissertation project. National economic indicator forecasting system using 44 years of IMF and World Bank data. Auto-order ARIMA/SARIMA, Facebook Prophet with changepoint detection, and Monte Carlo fiscal sustainability simulation with 10,000 paths and 95% confidence bounds.

Python FastAPI Plotly Dash statsmodels Prophet pandas SQLAlchemy PostgreSQL Docker Compose pytest JWT
44 Years of data (1980–2024)
10,000 Monte Carlo paths
11 API endpoint groups
54 pytest tests
100% Pass rate
7 Docker services

Scope & Data Sources

Eswatini is a small open economy in SADC with significant fiscal constraints — remittances from South Africa, sugar export dependence, and SACU revenue make macroeconomic forecasting non-trivial. The Central Bank of Eswatini (CBoE) and Ministry of Finance manage forecasting manually with spreadsheets.

This system automates the full analytical pipeline: data ingestion from IMF API and World Bank Open Data → time-series decomposition → forecasting → fiscal scenario simulation → interactive visualisation for policymakers.

Data Sources

IMF WEO API, World Bank Open Data, CBoE Statistical Bulletin. 1980–2024 annual series. GDP, inflation (CPI), fiscal balance, SACU revenue, remittances, FDI, external debt, foreign reserves.

Time-Series Analysis

STL decomposition (seasonal-trend-residual). Auto-order ARIMA/SARIMA selection via AIC minimisation over p=[0..3], d=[0..2], q=[0..3], P/D/Q=[0..1] seasonal grid. ACF/PACF plots surfaced in dashboard.

Prophet Forecasting

Facebook Prophet for each indicator. Changepoint detection at structural breaks (2008 GFC, 2020 COVID). Custom seasonality added for SACU revenue (fiscal year patterns). Growth modes: linear and logistic cap.

Monte Carlo Fiscal Simulation

10,000 paths for fiscal balance projection. Correlated shocks drawn from historical covariance matrix. GDP growth, SACU revenue, and expenditure modelled jointly. 95% CI bounds plotted as fan chart.

ARIMA/SARIMA Auto-Order Selection

Manual ARIMA order selection is slow and expert-dependent. The system automates AIC grid search over all valid ARIMA(p,d,q)×(P,D,Q)[s] combinations. d is determined by KPSS stationarity test before the search begins, reducing the search space and avoiding over-differencing.

The grid evaluates up to 4×3×4×2×2×2 = 768 candidate models per indicator series. Failed fits (non-invertible, convergence failures) are silently skipped. The lowest-AIC model is returned as the fitted SARIMAX object ready for out-of-sample forecasting.

core/forecasting/arima_selector.py Python
# core/forecasting/arima_selector.py
from itertools import product
import statsmodels.api as sm

def auto_arima(series: pd.Series, seasonal_periods: int = 12) -> sm.tsa.SARIMAX:
    best_aic, best_order, best_seasonal = float('inf'), None, None
    for p, d, q in product(range(4), range(3), range(4)):
        for P, D, Q in product(range(2), range(2), range(2)):
            try:
                model = sm.tsa.SARIMAX(
                    series, order=(p, d, q),
                    seasonal_order=(P, D, Q, seasonal_periods),
                    enforce_stationarity=False,
                    enforce_invertibility=False,
                )
                result = model.fit(disp=False)
                if result.aic < best_aic:
                    best_aic = result.aic
                    best_order = (p, d, q)
                    best_seasonal = (P, D, Q, seasonal_periods)
            except Exception:
                continue
    return sm.tsa.SARIMAX(series, order=best_order, seasonal_order=best_seasonal).fit(disp=False)

Monte Carlo Fiscal Sustainability Simulation

A single-point forecast of fiscal balance is misleading for policy — the distribution of outcomes matters. The simulation draws 10,000 correlated scenarios from historical volatility, producing a fan chart that shows the range of plausible fiscal paths under uncertainty.

Key assumption: GDP growth, SACU revenue, and primary expenditure are correlated (historically ρ ≈ 0.6 between GDP and SACU). Cholesky decomposition is used to produce correlated normal draws that respect this joint structure, rather than treating each variable as independent.

core/simulation/monte_carlo.py Python
# core/simulation/monte_carlo.py
def fiscal_monte_carlo(
    gdp_mean: float, gdp_std: float,
    sacu_mean: float, sacu_std: float,
    corr_matrix: np.ndarray,
    n_paths: int = 10_000, n_years: int = 5
) -> np.ndarray:
    """Returns shape (n_paths, n_years) fiscal balance as % of GDP."""
    L = np.linalg.cholesky(corr_matrix)   # Cholesky for correlated shocks
    paths = np.zeros((n_paths, n_years))
    for t in range(n_years):
        z = np.random.standard_normal((2, n_paths))
        correlated = (L @ z)               # shape (2, n_paths)
        gdp_shock = gdp_mean + gdp_std * correlated[0]
        sacu_shock = sacu_mean + sacu_std * correlated[1]
        paths[:, t] = (sacu_shock - EXPENDITURE_MEAN) / (1 + gdp_shock)
    return paths  # percentile bands extracted for fan chart

Impulse response analysis

Beyond unconditional forecasts, the dashboard supports impulse response analysis for named shock scenarios. A canonical example: a 20% SACU revenue shock — modelling what happens to the fiscal balance, reserves adequacy, and GDP trajectory if SACU transfers drop by a fifth (historically possible during South African recessions). The shock is applied as a one-period deviation in the Monte Carlo initial conditions; the resulting fan of recovery paths shows both the median recovery timeline and the tail risk of fiscal unsustainability. This analysis is surfaced in the "SACU sensitivity" panel in Plotly Dash.

Architecture: FastAPI + Plotly Dash + Docker Compose

Seven Docker services orchestrated via docker-compose.yml, with Redis caching for expensive ARIMA runs (TTL 24h) and Celery workers handling async forecast jobs queued from the API layer.

Docker Services

api — FastAPI

11 endpoint groups: indicators, forecasts, simulations, scenarios, auth, admin, reports, metadata, health, audit, docs. JWT RBAC with 3 tiers.

dash — Plotly Dash

8 interactive panels: GDP trend, CPI decomposition, fiscal fan chart, SACU sensitivity, FDI flow, debt sustainability, reserves adequacy, impulse response.

db — PostgreSQL 16

Time-series indicator storage. SQLAlchemy ORM, Alembic migrations. Separate schemas per indicator domain.

cache — Redis

Forecast result caching. ARIMA grid search runs are CPU-expensive; results cached at 24h TTL keyed by series hash + order parameters.

worker — Celery

Async forecast jobs. Long-running SARIMA fits offloaded from the request cycle; clients poll a job status endpoint.

nginx — Reverse Proxy

Rate limiting, Let's Encrypt TLS termination, upstream routing to FastAPI and Dash services.

grafana — Observability

Prometheus metrics scraping FastAPI. Alert rules for API latency p99 and Celery queue depth.

Docker Compose — 7 services

ServiceImage / PortRole
apicustom · :8000FastAPI — 11 endpoint groups, JWT auth, SQLAlchemy ORM
dashcustom · :8050Plotly Dash — 8 interactive panels, callback-driven
dbpostgres:16 · :5432Time-series indicator storage, Alembic migrations
cacheredis:7 · :6379ARIMA/Prophet result cache (TTL 24h — fits slow runs)
workercustom · (internal)Celery — async forecast jobs, auto-order ARIMA queued here
nginxnginx:alpine · :80/:443Reverse proxy, rate limiting, Let's Encrypt TLS
grafanagrafana/grafana · :3000Prometheus metrics, API latency dashboards, alert rules

JWT RBAC — 3-Tier Access Model

Viewer: read-only dashboard access, no forecast triggers. Analyst: trigger new forecasts, upload data corrections, export reports. Admin: manage users, run Monte Carlo simulations, configure alert thresholds.

Testing: 54 pytest Tests, 100% Pass Rate

Full test suite covering unit, integration, schema validation, and statistical correctness. Tests are categorised and run in CI on every push. 54 tests across 6 categories: 12 unit (ARIMA selector), 8 unit (Monte Carlo statistics), 11 API contract (schema + auth), 8 ORM (CRUD + migrations), 7 Prophet output shape, 8 integration (full pipeline).

12 — ARIMA Unit Tests

Auto-order selection logic: AIC minimisation correctness, grid search coverage, degenerate series handling (constant, all-NaN).

8 — Monte Carlo Statistics

Output shape (n_paths, n_years), mean convergence, standard deviation bounds, 95% CI width under known covariance inputs.

11 — FastAPI Endpoint Contracts

Schema validation against Pydantic models, auth enforcement (401 on missing JWT, 403 on wrong tier), error response structure.

8 — SQLAlchemy ORM

CRUD operations, Alembic migration rollback, constraint enforcement, cascade delete on indicator series removal.

7 — Prophet Output Shape

Forecast horizon, changepoint detection (≥1 changepoint on COVID period), uncertainty interval width monotonicity.

8 — Integration Tests

Full pipeline: ingest → decompose → forecast → API response. End-to-end latency budget, Redis cache hit on second call.

1

Data ingestion pipeline — IMF + World Bank APIs

44 years, 12 indicator series, validation and gap-filling logic for missing annual observations.

2

Time-series analysis — ARIMA/SARIMA auto-order

AIC grid search over 768 candidate models, KPSS stationarity tests, ACF/PACF diagnostic plots.

3

Prophet + Monte Carlo simulation

Changepoint detection at GFC and COVID, correlated Cholesky shocks, 10,000-path fan chart rendering.

4

FastAPI + Plotly Dash architecture

11 endpoint groups, Docker Compose 7 services, JWT RBAC 3-tier access model, Redis + Celery async jobs.

5

54 pytest tests — 100% pass rate

Unit, integration, schema validation, and statistical correctness across all pipeline stages.

Need economic or financial modelling?

I build quantitative systems for policy analysis, forecasting, and decision support. Available for government, academic, and financial sector clients across SADC.