merton.scenarios.climate

Climate-transition scenarios.

This module provides the building blocks for climate stress testing in the TCFD / NGFS tradition. A climate scenario specifies:

  1. A carbon-price path c(t) (USD per tonne of CO₂-equivalent).

  2. Sectoral asset writedowns as a function of the carbon price and sector-specific emission intensities.

  3. Sectoral PD multipliers that capture residual default-risk premia not absorbed into the asset value (e.g. demand destruction, regulatory change, technology obsolescence).

Stress-testing a firm under scenario s then becomes:

stressed_firm = s.apply(firm, sector=Sector.ENERGY).firm
result = MertonModel().fit(stressed_firm)

Sector emission intensities default to the (rounded) IEA 2024 NetZero pathway figures for the listed sectors, expressed as tonnes CO₂-equivalent per million USD of enterprise value (tCO₂e/M$). Users can override these with their own bottom-up estimates per portfolio.

References

NGFS (2024). NGFS Phase V Climate Scenarios for Central Banks and Supervisors. https://www.ngfs.net/ngfs-scenarios-portal/ IEA (2024). World Energy Outlook 2024. International Energy Agency. TCFD (2017). Recommendations of the Task Force on Climate-Related Financial Disclosures. Financial Stability Board.

Classes

Sector

GICS-aligned sector enumeration used throughout the climate module.

ClimateScenario

A climate-transition stress scenario.

Functions

sectoral_carbon_intensity(→ float)

Return the default emission intensity (tCO₂e per $M EV) for a sector.

carbon_price_to_writedown(→ float)

Asset writedown factor implied by a carbon price.

carbon_price_curve(→ collections.abc.Callable[[float], ...)

Build a piecewise-linear carbon-price curve from (t, price) knots.

Module Contents

class merton.scenarios.climate.Sector[source]

Bases: str, enum.Enum

GICS-aligned sector enumeration used throughout the climate module.

ENERGY = 'energy'[source]
UTILITIES = 'utilities'[source]
MATERIALS = 'materials'[source]
INDUSTRIALS = 'industrials'[source]
TRANSPORT = 'transport'[source]
REAL_ESTATE = 'real_estate'[source]
CONSUMER = 'consumer'[source]
FINANCIALS = 'financials'[source]
HEALTHCARE = 'healthcare'[source]
TECH = 'tech'[source]
merton.scenarios.climate.sectoral_carbon_intensity(sector: Sector | str) float[source]

Return the default emission intensity (tCO₂e per $M EV) for a sector.

Practitioners typically replace these with bottom-up Scope-1+2 figures from their internal data; the defaults are reasonable order-of-magnitude placeholders for top-down portfolio stress.

merton.scenarios.climate.carbon_price_to_writedown(carbon_price: float, sector: Sector | str, *, pass_through: float = 0.5, intensity: float | None = None) float[source]

Asset writedown factor implied by a carbon price.

The fraction of enterprise value destroyed by a carbon price c is

\[\text{writedown}(c, \text{sector}) = \min\!\Bigl(1,\ (1 - \text{pass\_through}) \cdot \frac{c \cdot I_{\text{sector}}}{10^6}\Bigr),\]

where I_sector is the emission intensity (tCO₂e per $M EV) and pass_through is the share of the cost the firm passes through to customers (0 → fully absorbed by equity; 1 → fully passed through). The factor returned is the fractional writedown (e.g., 0.12 = 12 % asset value destroyed); apply it as A_stressed = A · (1 writedown).

Parameters:
  • carbon_price – Carbon price c in USD per tonne of CO₂-equivalent.

  • sector – Sector tag (Sector enum or a string the enum can parse).

  • pass_through – Fraction of the carbon cost passed through to customers, in [0, 1]. Higher pass-through → smaller writedown.

  • intensity – Override the default sector intensity (tCO₂e per $M EV).

class merton.scenarios.climate.ClimateScenario[source]

Bases: merton.scenarios.base.Scenario

A climate-transition stress scenario.

Parameters:
  • name – Short identifier (e.g. "NGFS Net Zero 2050").

  • carbon_price_path – Callable t c(t) returning carbon price (USD/tCO₂e) at horizon t (years). Use carbon_price_curve() for a piecewise-linear helper.

  • pd_multipliers{sector: multiplier} mapping. multiplier > 1 increases PD relative to the no-stress baseline; < 1 decreases it. Sectors absent from the mapping default to 1.0.

  • pass_through – Fraction of the carbon cost passed through to customers. Default 0.5 (half passed through, half borne by equity holders).

  • physical_writedown – Additional asset writedown to capture chronic physical risk (stranded assets, flooding, etc.) as a fraction of EV per year of horizon. Default 0.

  • description – Human-readable summary used in audit trails / reports.

name: str[source]
carbon_price_path: collections.abc.Callable[[float], float][source]
pd_multipliers: dict[Sector, float][source]
pass_through: float = 0.5[source]
physical_writedown: float = 0.0[source]
description: str = ''[source]
carbon_price(horizon: float) float[source]

Return the carbon price at horizon (years from now).

asset_writedown(horizon: float, sector: Sector | str, *, intensity: float | None = None) float[source]

Total fractional asset writedown (transition + physical).

pd_multiplier(sector: Sector | str) float[source]

Return the PD multiplier for sector (1.0 if unset).

apply(firm: merton.core.firm.Firm, *, sector: Sector | str | None = None, intensity: float | None = None, **kwargs: Any) merton.scenarios.base.ScenarioResult[source]

Apply the climate stress to firm and return a stressed copy.

The transformation only writes down equity (the observable input to Merton). ClimateOverlay is the recommended wrapper if you also want PD-multiplier scaling applied to the structural output.

merton.scenarios.climate.carbon_price_curve(knots: list[tuple[float, float]]) collections.abc.Callable[[float], float][source]

Build a piecewise-linear carbon-price curve from (t, price) knots.

Knots are sorted by time; beyond the last knot the function holds the final value constant (the NGFS convention).

Examples

>>> from merton.scenarios.climate import carbon_price_curve
>>> path = carbon_price_curve([(0.0, 50.0), (10.0, 300.0)])
>>> path(5.0)
175.0