African Economic Stress Monitor avatar

African Economic Stress Monitor

Pricing

from $1.50 / crisis scan

Go to Apify Store
African Economic Stress Monitor

African Economic Stress Monitor

Rules-based economic stress monitor for 11 African economies. FX momentum, inflation, and commodity shock signals produce a transparent verdict: stable, watch, elevated, or stress. Covers Nigeria, Ghana, Kenya, South Africa, Tanzania, Uganda, Zambia, Morocco, Côte d'Ivoire, Ethiopia, Rwanda.

Pricing

from $1.50 / crisis scan

Rating

0.0

(0)

Developer

Simon M

Simon M

Maintained by Community

Actor stats

0

Bookmarked

2

Total users

1

Monthly active users

13 hours ago

Last modified

Share

A cross-signal economic stress monitor for 11 African economies. It combines FX, inflation, export-weighted commodity, real-interest-rate, and official-reserve data into a transparent, rules-based stress verdict plus a continuous 0–100 stress score — and a second gauge for slow-moving structural vulnerability (sovereign debt, fiscal balance, banking-sector health, external-buffer adequacy, currency valuation, and political/institutional risk). Never a black box: every verdict shows exactly which signals fired and why, and each run reports what changed since the last one.

Delayed data. Verdicts are computed from delayed official sources (central-bank FX, national CPI, World Bank commodity prices, central-bank policy rates, IMF reserves, World Bank debt & external-sector statistics), never real-time feeds.


Modes

ModeInputOutputPrice
crisis-scan (default)noneall 11 countries, ranked by stress$1.50 flat / run
country-snapshotcountry (ISO alpha-2)full profile + verdict for one country$0.50 / country

The verdict

Gauge 1 — acute stress. Five fast-moving signals, each fired / not-fired / unavailable:

SignalFires when
FX momentumcurrency depreciates >5% over 30 days, or >15% over 90 days
Inflation levelYoY CPI >20%, or >10% and accelerating month-on-month
Commodity shockthe country's primary-export commodity move, weighted by how much the country depends on it, implies a terms-of-trade hit of ≥5 percentage points of export value (price YoY% × commodity's share of the country's exports). A global price fall is no longer the same signal for every producer — cocoa −50% is a −14pp hit for Côte d'Ivoire (cocoa ≈28% of exports) but a fraction of that where the commodity is marginal. (Where no export-share figure exists, falls back to a volatility-tiered price threshold: 10% gold/platinum/phosphate · 20% copper/cocoa/coffee · 25% crude/gas/wheat.)
Real interest ratepolicy rate − inflation YoY is deeply negative (< −3pp) — savings erosion / capital-flight / financial-repression risk. Available where both a central-bank policy rate and CPI are published (currently 8 of 11 — see coverage table).
Reserve drawdownofficial FX reserves (IMF) fall >12.5% over 6 months, or >20% YoY — the classic FX-crisis predictor (a central bank burning reserves to defend the currency / cover external gaps). Rising reserves never fire.

Verdict from the count of fired signals: 0 → stable · 1 → watch · 2 → elevated · 3+ → stress.

Stress score (0–100). Alongside the categorical verdict, each record carries a continuous, z-aware stressScore — a weighted blend of how far each signal is past its threshold and how unusual the move is versus the country's own history. It differentiates countries the coarse verdict lumps together, and it is the ranking key for crisis-scan. Weights: FX 0.28 · reserves 0.20 · inflation 0.22 · real rate 0.16 · commodity 0.14 (lowest — it's an exogenous global price; the two external-account signals FX + reserves carry the most). stressScoreBasis shows each signal's contribution.

Gauge 2 — structural vulnerability. Alongside the acute read, each record carries a separate vulnerabilityScore (0–100) and vulnerabilityLevel (low/moderate/high) — how fragile the country is underneath, distinct from how much stress it's under right now. Six slow-moving structural signals feed it:

Structural signalFires when
Debt distressexternal debt >60% of GNI, or debt service >25% of exports, or short-term debt >100% of reserves (Guidotti-Greenspan)
External adequacyreserve buffer <3 months of imports, or a current-account deficit wider than 8% of GDP
Fiscal solvencyfiscal deficit wider than 6% of GDP, or government debt >70% of GDP — the flow that builds the debt overhang
REER misalignmentthe real effective exchange rate sits >15% above the country's own multi-year average — an overvalued currency, a correction/devaluation risk
Financial stressbank nonperforming loans >10% of gross loans (banking asset quality), or a credit-to-GDP boom — private credit >9 percentage points of GDP above its own recent trend (the BIS / Schularick-Taylor early-warning indicator)
Political stabilityWorld Bank WGI governance — Political Stability & Absence of Violence below −1.0, or Rule of Law below −1.0 (estimate −2.5…+2.5; a low reading = elevated political / event risk, the trigger that turns latent vulnerability into crisis)

These never touch the acute verdict or stressScore — a country can be calm today yet structurally fragile (or the reverse). The headline alarm is when both gauges are hot: the cross-gauge acute_stress_x_high_vulnerability compound flag fires when an elevated/stress verdict meets high vulnerability — the setup Zambia, Ghana and Ethiopia were all in before they defaulted. (World Bank data is annual and CC-BY licensed; these are slow-moving ratios — external-debt/GNI moves only ~3–5pp a year — so a ~2-year-old vintage is representative. Each value carries its dataYear; anything older than ~3 years is dropped per metric (these series publish with a ~2-year lag). Broad-money growth was evaluated and excluded — it swings too fast to read from annual data. The banking signal currently fires on asset quality — Ghana and Kenya carry double-digit NPLs; the credit-boom axis is deliberately measured in points of GDP, not relative % (a few-point uptick in a shallow-finance economy is not a dangerous boom), so it stays quiet across this sample — no country is in a credit boom — while future-proofing the gauge for when one is.)

z-score. Each signal reports a zScore — how unusual the current move is against that country's own history (a 4% move means more for a managed peg than a free float). It feeds the stress score and the reason text but, by design, never flips the categorical fire/not-fire (which stays on the same documented absolute rule everywhere).

Trend & compound flags. Each signal reports a trend (deteriorating/improving/stable); and compoundFlags surfaces reinforcing combinations — twin_external_pressure (FX + commodity), fx_inflation_spiral, stagflation_risk (inflation + negative real rate), reserve_defence (FX depreciating while reserves are drawn down — defending the currency by burning reserves), and the cross-gauge acute_stress_x_high_vulnerability (acute stress meeting high structural fragility). These are context; they don't change the fired count.

What changed (delta). Each record diffs against the previous run: verdictChange, stressScoreDelta, firedCountDelta, and per-signal status changes. null on the first ever run. The diff is only as meaningful as the interval between runs — schedule the actor (daily or weekly) so the comparison spans a real period; two runs minutes apart will show sinceRunAgeDays: 0 and little movement. sinceRunAgeDays is always reported so you can see the window the delta covers, and the underlying official data refreshes on its own cadence (FX daily → CPI/commodity monthly), so a weekly schedule is a sensible default.

Staleness: each signal has a freshness cutoff (FX 7 days, inflation 90 days, commodity 45 days, reserves 180 days — IMF data lags more). Beyond it the signal is unavailable — stale data never drives a verdict. Reserves are an end-of-period stock, so the latest figure is aged from the last day of its month (a "2025-12" level is the Dec-31 balance, ~158 days old here — not 188), the correct convention for stock data.

Honesty rules:

  • If fewer than 2 signals are computable for a country, the verdict is insufficient-data (and stressScore is null) — never a falsely reassuring "stable". "Stable" always means we checked and nothing fired, not we couldn't look.
  • Every record carries coverageLevel (full/partial/minimal/none), a dataCoherence flag (fresh = computable signals within 45 days of each other · mixed = 45–90 days apart · stale = >90 days apart — a mixed/stale flag adds a warnings[] entry naming the oldest signal and its age), and a per-signal breakdown with the numbers, the threshold, the trend, and a plain-language reason.

Coverage (verified 2026-06-07)

Where a central-bank feed is frozen or blocked, we use the official revenue-authority reference rate instead (Kenya, Uganda) — labelled as such, never presented as a central-bank interbank rate. The commodity signal is a weighted export basket (each country's main Pink-Sheet-priced exports × their share of total exports), so a global price move becomes a country-specific terms-of-trade impact.

CountryFXInflationCommodity basketReal rateReservesSignals wired*
Nigeria (NG)✅ CBN✅ CBN/NBS✅ crude oil✅ CBN MPR✅ IMF5
Ghana (GH)✅ BoG (via proxy)✅ GSS → IMF fallback✅ gold · oil · cocoa✅ BoG MPR✅ IMF5
Kenya (KE)✅ KRA (weekly customs rate)✅ CBK✅ tea · coffee✅ CBK CBR✅ IMF5
South Africa (ZA)✅ SARB✅ SARB✅ gold · platinum · coal · iron ore✅ SARB repo✅ IMF5
Zambia (ZM)✅ BoZ✅ BoZ✅ copper✅ BoZ MPR✅ IMF5
Morocco (MA)✅ BAM✅ HCP✅ phosphates & fertilizer✅ BAM✅ IMF5
Ethiopia (ET)✅ NBE✅ IMF✅ coffee · gold✅ NBE NBR✅ IMF5
Côte d'Ivoire (CI)✅ BCEAO/XOF✅ IMF✅ cocoa · gold✅ BCEAO— (WAEMU collective)4
Tanzania (TZ)✅ BoT (via proxy)✅ NBS✅ gold✅ IMF4
Rwanda (RW)✅ BNR✅ IMF✅ coffee · tea✅ IMF4
Uganda (UG)⚠️ URA (datacenter-blocked)✅ UBOS✅ coffee✅ IMF3–4

* "Signals wired" is the potential — sources with a feed connected. The live computableCount per run is the truth and is often lower, especially for reserves (IMF update lag; see below). Treat this column as the ceiling, not a promise.

Notes. Commodity is a weighted basket of each country's Pink-Sheet-priced exports — so diversified exporters like South Africa (gold/platinum/coal/iron-ore) get a signal, and a country's shares can offset (e.g. a cocoa crash partly cushioned by a gold rally). Inflation is from national statistics offices for 8 countries; Côte d'Ivoire, Ethiopia and Rwanda are filled from the IMF CPI dataflow (attributed; derived YoY/MoM). Ghana adds the IMF dataflow as a freshness fallback (E13): GSS's machine-readable StatsBank database trails its own bulletins by months, so when the national series lags past the staleness guard the IMF tail carries inflation instead — national when fresh, IMF when stale. Real rate (policy − inflation) covers 8 of 11 — policy rates from CBN (NG), BoG (GH), CBK (KE), SARB (ZA), BoZ (ZM), Bank Al-Maghrib (MA), BCEAO (CI, WAEMU-wide) and NBE (ET); Tanzania, Rwanda and Uganda publish their rate only via PDF/JS apps and are added as feeds become reliably machine-readable. The real rate is only computed when the inflation print itself is fresh — a real rate resting on stale inflation would be a stale read masquerading as live, so it goes unavailable with inflation (this is what the Ghana fallback restores). Reserves (IMF International Liquidity) are attempted for 10 of 11 (Côte d'Ivoire is absent — WAEMU members report reserves collectively), but the drawdown signal is only usable where the central bank reports to the IMF promptly enough to clear the 180-day cutoff — currently ~5 (Nigeria, South Africa, Morocco, Rwanda, Kenya). Many reporters lag far longer (Ghana ~7mo, Zambia ~14mo, Ethiopia ~18mo, Tanzania's IMF figure is stuck in 2019), so reserves shows unavailable for them rather than a stale number. This is an IMF-update-cadence limitation, not a bug — see Known limitations. Uganda FX (URA) is currently blocked from datacenter IPs. IMF-sourced CPI and reserves carry the IMF attribution and are derived figures, not bulk redistribution. Gaps are flagged honestly via coverageLevel.

Not yet covered: Tunisia was dropped in v0.2 (no daily FX feed + export-diversified → it was almost always insufficient-data). Egypt and Angola are deferred: every Egyptian official source (CBE, CAPMAS, the national data portal) is WAF/JS/Cloudflare-blocked to automated access, and Angola's central bank publishes rates only through a JavaScript app with no HTTP-reachable endpoint.


Output (one record per country)

{
"schemaVersion": 8,
"country": "GH",
"countryName": "Ghana",
// ── Gauge 1 — acute stress (fast signals). Ghana's national CPI database (GSS StatsBank) lags ~5
// months, so inflation — and the real rate, which needs it — are served from the IMF CPI FALLBACK
// (E13): national when it's fresh, the IMF tail when the national DB lags. Only IMF reserves stay
// `unavailable` here (Ghana reports to the IMF ~7mo late). With 4 of 5 signals computable the lone
// FX fire is contextualised by the calm rest, so the blended stressScore is modest. ──
"verdict": "watch",
"stressScore": 23.1,
"stressScoreBasis": [ { "signal": "fxMomentum", "weight": 0.28, "subScore": 0.644 }, { "signal": "inflationLevel", "weight": 0.22, "subScore": 0.0 }, { "signal": "commodityShock", "weight": 0.14, "subScore": 0.03 }, { "signal": "realRate", "weight": 0.16, "subScore": 0.0 } ],
"coverageLevel": "full",
"dataCoherence": "fresh",
"firedCount": 1,
"computableCount": 4,
"asOf": "2026-06-05",
"signals": {
"fxMomentum": { "status": "fired", "reason": "USD/GHS 30d +6.2% ...; +2.1σ vs own history", "value": 6.2, "threshold": ">5% over 30d, or >15% over 90d", "trend": "deteriorating", "zScore": 2.1 },
"inflationLevel": { "status": "not-fired", "reason": "YoY 3.24%, MoM steady/easing (2026-03)", "value": 3.24, "threshold": "YoY >20%, or >10% and MoM accelerating", "trend": "stable", "zScore": -1.81 }, // ← IMF fallback (national GSS stale at 2026-01)
"commodityShock": { "status": "not-fired", "reason": "Export basket +1.4pp terms-of-trade impact [Gold +4.0%×45%, oil ..., cocoa ...]", "value": 1.4, "threshold": "basket terms-of-trade impact ≤ −5pp", "trend": "improving", "zScore": -0.3 },
"realRate": { "status": "not-fired", "reason": "Real rate +24.76pp = policy 28% − inflation 3.24% (note: unusually high positive real rate — tight stance)", "value": 24.76, "threshold": "real rate < −3pp (policy rate − inflation YoY)", "trend": "insufficient-history", "zScore": null },
"reserveDrawdown": { "status": "unavailable", "reason": "reserves stale (latest 2025-10, ~249d old)", "value": null, "threshold": "reserves down >12.5% over 6 months, or >20% YoY", "trend": "insufficient-history", "zScore": null }
},
// ── Gauge 2 — structural vulnerability (slow annual World Bank data; computed independently of the
// acute read above, so it still works when CPI/reserves are stale — as here). ──
"vulnerabilityScore": 58.1,
"vulnerabilityLevel": "moderate",
"vulnerabilitySignals": {
"debtDistress": { "status": "not-fired", "reason": "Debt within thresholds [debt/GNI 47%, service/exports 9%, ST-debt/reserves 59%] (2024, annual)", "value": 47, "threshold": "ext debt >60% of GNI, or service >25% of exports, or ST-debt >100% of reserves", "trend": "insufficient-history", "zScore": null },
"externalAdequacy": { "status": "fired", "reason": "External buffer thin: 1.6mo import cover (2024, annual)", "value": 1.6, "threshold": "import cover <3 months, or CA deficit >8% of GDP", "trend": "insufficient-history", "zScore": null },
"fiscalSolvency": { "status": "not-fired", "reason": "Fiscal position within thresholds [fiscal bal -1.7%] (2023, annual)", "value": -1.7, "threshold": "deficit >6% of GDP, or govt debt >70% of GDP", "trend": "insufficient-history", "zScore": null },
"reerMisalignment": { "status": "not-fired", "reason": "REER -8.4% vs its 9y avg (2024; undervalued, fires >+15%)", "value": -8.4, "threshold": ">15% above the country's own multi-year average", "trend": "insufficient-history", "zScore": null },
"financialStress": { "status": "fired", "reason": "Banking stress: NPLs 20.6% of loans [NPLs 20.6%, credit/GDP 9% (-3.2pp vs 4y avg)] (2024, annual)", "value": 20.6, "threshold": "NPLs >10% of gross loans, or credit/GDP >9pp above its 4y average", "trend": "insufficient-history", "zScore": null },
"politicalStability": { "status": "not-fired", "reason": "Governance within thresholds [stability 0.01, rule of law 0.05] (2024, WGI estimate -2.5..+2.5, lower = weaker)", "value": 0.01, "threshold": "WGI political stability < -1.0 or rule of law < -1.0", "trend": "insufficient-history", "zScore": null }
},
"compoundFlags": [],
// ── Underlying delayed values (acute), attributed; `null` where the signal is unavailable ──
"fx": { "pair": "USD/GHS", "latest": 12.4, "asOf": "2026-06-05", "change30dPct": 6.2, "change90dPct": 11.0, "source": { /* BoG — attributed */ } },
"inflation": { "period": "2026-03", "yoyPct": 3.24, "momPct": 0.14, "accelerating": false, "source": { /* IMF CPI fallback — attributed; YoY/MoM derived */ } },
"commodity": { "commodityCode": "GOLD", "commodityName": "Gold", "period": "2026-05", "yoyPct": 4.0, "dependencePct": 72, "exportImpactPct": 1.4, "components": [ /* gold, oil, cocoa */ ], "source": { /* World Bank Pink Sheet — CC BY 4.0 */ } },
"realRate": { "policyRatePct": 28, "inflationYoyPct": 3.24, "realRatePct": 24.76, "asOf": "2026-05", "source": { /* Bank of Ghana MPR — attributed */ } },
"reserves": null,
// ── Structural detail (World Bank WDI / IDS / WGI — CC BY 4.0; each carries its annual `dataYear`) ──
"debtDistress": { "extDebtGniPct": 47, "debtServiceExportsPct": 9, "shortTermDebtReservesPct": 59, "dataYear": 2024, "source": { /* World Bank — CC BY 4.0 */ } },
"externalAdequacy": { "importCoverMonths": 1.6, "currentAccountGdpPct": -2.1, "dataYear": 2024, "source": { /* ... */ } },
"fiscalSolvency": { "fiscalBalanceGdpPct": -1.7, "govtDebtGdpPct": null, "dataYear": 2023, "source": { /* ... */ } },
"reerMisalignment": { "reerIndex": 92.0, "trailingAvg": 100.4, "overvaluationPct": -8.4, "latestYear": 2024, "source": { /* ... */ } },
"financialStress": { "nplPct": 20.6, "creditGdpPct": 9.0, "creditGdpGapPp": -3.2, "dataYear": 2024, "source": { /* ... */ } },
"politicalStability": { "politicalStabilityEst": 0.01, "ruleOfLawEst": 0.05, "dataYear": 2024, "source": { /* World Bank WGI — CC BY 4.0 */ } },
"delta": { "sinceISO": "2026-05-30T...", "sinceRunAgeDays": 7, "verdictChange": null, "stressScoreDelta": 1.1, "firedCountDelta": 0, "signalChanges": [] },
"warnings": ["inflation_unavailable", "real_rate_unavailable", "reserves_unavailable"]
}

crisis-scan returns the 11 records sorted by stressScore, most-stressed first (insufficient-data last).


Known limitations / structural ceilings

  • Commodity dependence is a structural weight, not a live figure. The export-share percentages (exportDependence.json, sourced from OEC, ~annual) are slow-moving structural ratios surfaced with their dataYear, not a real-time datum. The commodity price is live; the weight is structural.
  • One computable signal is never a verdict. A country with only one usable signal is insufficient-data (and stressScore: null) regardless of whether that signal fired — a single axis is not enough to call macro stress.
  • The commodity signal is exogenous by nature. It's a global price; the dependence weight makes it country-specific in impact, but it's still weighted lowest (0.14) in the stress score precisely because the price itself isn't an idiosyncratic country event.
  • Real rate covers 8 of 11 (NG, GH, KE, ZA, plus ZM, CI, MA, ET). The remaining three (TZ, RW, UG) publish the policy rate only via PDF/JS apps; the signal is unavailable there until a reliable machine-readable feed exists — never guessed.
  • Policy rates are cached against central-bank outages. A policy rate is valid until the next MPC decision (banks meet only ~6–8×/year), so the last-known rate is kept in a named key-value store and reused when a central-bank site is unreachable for a run — instead of dropping the real rate entirely (Ghana's BoG site, behind the residential proxy, goes down intermittently). A fresh fetch always wins; a cached fallback is served only within a 180-day guard and discloses itself in the rate's licenseNote so the staleness is never hidden, and the run is still flagged degraded for monitoring. The cache warms from the first run where the source is reachable.
  • Reserves coverage is sparse — this is the signal's main limitation. The reserve-drawdown signal is the most predictive when present, but it depends entirely on the IMF International Liquidity update cadence, which varies enormously by country. Prompt reporters (Nigeria, South Africa, Morocco, Rwanda, Kenya) clear the 180-day cutoff; many others lag far longer (Ghana ~7 months, Zambia ~14, Ethiopia ~18, Tanzania's figure is stuck in 2019) and Côte d'Ivoire reports only collectively (WAEMU). So reserves is usable for only ~5 of 11 at any time and shows unavailable elsewhere — we deliberately do not serve a year-old reserves number as a live signal. National monthly feeds were investigated as a fix for the laggards and found impractical: the central banks publish reserves only behind PDFs (Kenya's weekly bulletin), login walls (Tanzania), JS apps (Uganda), or in tables staler than the IMF's own (Ghana's online series stops in 2023) — none yields a clean fresher series, so the IMF lag is structural. Data is IMF (🟡 commercial reuse): a small, attributed, derived figure, reuse permission requested in parallel — not bulk redistribution.
  • dataCoherence and compoundFlags are context, not overrides. They never change the categorical fired count or verdict.
  • CI/ET/RW have no national CPI source — inflation is gap-filled from the IMF CPI dataflow (attributed, derived YoY/MoM), so they reach at least FX + commodity + inflation. Per-country coverage is always reported via coverageLevel.
  • Ghana's CPI is national-with-IMF-fallback (E13). GSS's machine-readable StatsBank database trails its own published bulletins by months (recon: latest 2026-01 while IMF carried 2026-03), so Ghana's national series alone would sit unavailable most of the time. The IMF CPI dataflow is wired as a fallback behind it: the national figure is preferred whenever it's within the staleness guard, and the IMF tail takes over when it lags. This restores Ghana's inflation and real-rate signals (the real rate needs a fresh inflation print). It is a fallback, not a replacement — if GSS catches up, its authoritative figure wins automatically.

Sources & licensing

This actor aggregates the same official sources as our single-domain actors, each attributed on every record:

  • FX — national central banks (CBN, BoG, BoT, SARB, BoZ) and, where the central-bank feed is unavailable, the national revenue authorities (KRA, URA — official tax/customs reference rates).
  • Inflation — national statistics offices / central banks (GSS, HCP, SARB, BoZ, CBK, UBOS, CBN/NBS, NBS Tanzania).
  • Commodity — World Bank Commodity Price Data (Pink Sheet), CC BY 4.0. Export-dependence weights from the Observatory of Economic Complexity (OEC), ~annual structural figures.
  • Policy rates — central banks (CBN MPR, BoG MPR, CBK CBR, SARB repo), for the real-rate signal.
  • Official reserves — IMF International Financial Statistics / International Liquidity (total reserves, gold at market value, USD), attributed on every record. Used as a small, derived figure (latest value + recent change); IMF reuse permission requested in parallel.

Data is delayed and presented with attribution. It is analysis built on public official data, not a redistribution product.


Notes

  • Not investment advice. A transparent screening tool; the signals and thresholds are documented judgements, not a forecast.
  • FX needs a residential proxy for Ghana (BoG blocks data-centre IPs). If unavailable at runtime, Ghana's FX signal degrades to unavailable rather than failing the run.
  • Schedule monthly–weekly; the underlying data refreshes daily (FX) to monthly (CPI/commodity).
  • Shared scraping/parsing logic is imported from the internal fx-core / inflation-core / commodity-core libraries (no actor-to-actor calls).