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 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 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
| Service | Image / Port | Role |
|---|---|---|
api | custom · :8000 | FastAPI — 11 endpoint groups, JWT auth, SQLAlchemy ORM |
dash | custom · :8050 | Plotly Dash — 8 interactive panels, callback-driven |
db | postgres:16 · :5432 | Time-series indicator storage, Alembic migrations |
cache | redis:7 · :6379 | ARIMA/Prophet result cache (TTL 24h — fits slow runs) |
worker | custom · (internal) | Celery — async forecast jobs, auto-order ARIMA queued here |
nginx | nginx:alpine · :80/:443 | Reverse proxy, rate limiting, Let's Encrypt TLS |
grafana | grafana/grafana · :3000 | Prometheus 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.
Data ingestion pipeline — IMF + World Bank APIs
44 years, 12 indicator series, validation and gap-filling logic for missing annual observations.
Time-series analysis — ARIMA/SARIMA auto-order
AIC grid search over 768 candidate models, KPSS stationarity tests, ACF/PACF diagnostic plots.
Prophet + Monte Carlo simulation
Changepoint detection at GFC and COVID, correlated Cholesky shocks, 10,000-path fan chart rendering.
FastAPI + Plotly Dash architecture
11 endpoint groups, Docker Compose 7 services, JWT RBAC 3-tier access model, Redis + Celery async jobs.
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.