Main Content

Measure Transition Risk for Loan Portfolios Based on Climate Scenarios with Paths

This example shows the effect of transition risk on portfolios of loans from two banks under two different climate scenarios. In this example, each climate scenario uses 400 simulations and the visualizations reflect a distribution of portfolio outcomes.

Download and Import Climate Data

This example uses climate scenario data from the MIT Joint Program on the Science and Policy of Global Change. For details about this freely available data, see the MIT data repository.

Download the raw data from the MathWorks support files area.

filename = matlab.internal.examples.downloadSupportFile("risk","MITUncertaintyData.zip");

Make a folder in your current directory for the climate data.

if not(isfolder("MITUncertaintyData"))
    mkdir("MITUncertaintyData")
end

Unzip the downloaded files into the created folder.

MITDataFolder = fullfile(pwd,"MITUncertaintyData");
unzip(filename,MITDataFolder)

The data set has three climate scenarios. The Scenario_Descriptions.docx file provides a description of the scenarios.

scenarios = ["1.5C","2C","Ref"];

Read the climate data into MATLAB® by using the helper function importClimateData in a for–loop.

energies = ["Coal" "Gas" "Oil" "Nuclear" "Hydro" "Biomass" "Renewables"];
regions = ["GLB" "USA" "CAN" "MEX" "JPN" "ANZ" "EUR" "ROE" "RUS" "ASI" "CHN" "IND" "BRA" "AFR" "MES" "LAM" "REA" "KOR" "IDZ"];

climateDataDictionary = dictionary;

for scenario = scenarios
    for energy = energies
        for region = regions
            key = strcat(scenario, energy, region);
            climateDataDictionary{key} = importClimateData(scenario, energy, region);
        end
    end
end

Stack the scenarios into a tall data set.

vals = values(climateDataDictionary);
allClimateData = vertcat(vals{:});
head(allClimateData)
    Simulation    Year    Energy Use    Scenario    Region    Energy
    __________    ____    __________    ________    ______    ______

        1         2020      151.31        1.5C       GLB       Coal 
        1         2025      137.82        1.5C       GLB       Coal 
        1         2030      135.75        1.5C       GLB       Coal 
        1         2035      14.962        1.5C       GLB       Coal 
        1         2040      18.553        1.5C       GLB       Coal 
        1         2045      33.185        1.5C       GLB       Coal 
        1         2050      42.375        1.5C       GLB       Coal 
        1         2055      40.205        1.5C       GLB       Coal 

Process Climate Data

The tall data set, allClimateData, has one column for energy use. Unstack the data so that the energy use for each climate scenario has a separate column.

regionEnergyClimateData = unstack(allClimateData, "Energy Use", "Scenario", "GroupingVariables", ["Simulation", "Year", "Region", "Energy"], "NewDataVariableNames", ["EnergyUse1_5C", "EnergyUse2C", "EnergyUseRef"]);
regionEnergyClimateData = regionEnergyClimateData(:, ["Region", "Energy", "Year", "Simulation", "EnergyUseRef", "EnergyUse1_5C", "EnergyUse2C"]);
head(regionEnergyClimateData)
    Region    Energy    Year    Simulation    EnergyUseRef    EnergyUse1_5C    EnergyUse2C
    ______    ______    ____    __________    ____________    _____________    ___________

     GLB       Coal     2020        1             156.3          151.31          151.31   
     GLB       Coal     2025        1            158.09          137.82          137.82   
     GLB       Coal     2030        1            159.73          135.75          135.75   
     GLB       Coal     2035        1            167.23          14.962          97.463   
     GLB       Coal     2040        1            158.86          18.553          70.469   
     GLB       Coal     2045        1             159.5          33.185          45.762   
     GLB       Coal     2050        1            161.88          42.375          56.019   
     GLB       Coal     2055        1             162.6          40.205          70.153   

Create rows for the total by simulation, year, and region for each scenario. You need the totals to compute market share and shocks.

totalData = groupsummary(regionEnergyClimateData, ...
    ["Simulation", "Year", "Region"], ...
    "sum",["EnergyUse1_5C", "EnergyUse2C", "EnergyUseRef"]);
totalData = removevars(totalData,"GroupCount");
totalData = renamevars(totalData, ...
    ["sum_EnergyUse1_5C", "sum_EnergyUse2C", "sum_EnergyUseRef"], ...
    ["EnergyUse1_5C", "EnergyUse2C", "EnergyUseRef"]);
totalData.Energy = repmat("Total",[height(totalData) 1]);
totalData.Energy = categorical(totalData.Energy);
regionEnergyClimateData = [regionEnergyClimateData; totalData];
regionEnergyClimateData = movevars(regionEnergyClimateData, ...
    ["Region", "Energy"],"Before","Simulation");
regionEnergyClimateData = sortrows(regionEnergyClimateData, ...
    ["Region","Energy","Simulation","Year"]);

Join the total data as a column.

totalDataJoin = removevars(totalData,"Energy");
totalDataJoin = renamevars(totalDataJoin,["EnergyUse1_5C" "EnergyUse2C" "EnergyUseRef"], ...
    ["TotalEnergyUse1_5C" "TotalEnergyUse2C" "TotalEnergyUseRef"]);
regionEnergyClimateData = join(regionEnergyClimateData,totalDataJoin);

Define the market share shock. For more details on this definition, see Profitability and Shocks on Market Share.

Let mS,R(P,t*) denote the market share at time t* of sector S in the region R in scenario P. Define mS,R(B,t*) similarly. Then, the market share shock uS,R(P,B,t*) is defined as

uS,R(P,B,t*)=mS,R(P,t*)-mS,R(B,t*)mS,R(B,t*).

Compute the market share and shocks.

% Compute in chunks
c = cell(1,400);

for i = 1:400
    dataSlice = regionEnergyClimateData(regionEnergyClimateData.Simulation == i,:);

    % Market share is forced to be nonzero.
    dataSlice.MarketShareRef = max(dataSlice.EnergyUseRef ./ dataSlice.TotalEnergyUseRef, 1e-6);
    dataSlice.MarketShare1_5C = max(dataSlice.EnergyUse1_5C ./ dataSlice.TotalEnergyUse1_5C, 1e-6);
    dataSlice.MarketShare2C = max(dataSlice.EnergyUse2C ./ dataSlice.TotalEnergyUse2C, 1e-6);

    % Compute the scenario shocks.
    dataSlice.Shock1_5C = (dataSlice.MarketShare1_5C - dataSlice.MarketShareRef) ./ dataSlice.MarketShareRef;
    dataSlice.Shock2C = (dataSlice.MarketShare2C - dataSlice.MarketShareRef) ./ dataSlice.MarketShareRef;

    % Cap the shock so the maximum positive shock has the same magnitude as
    % the maximum negative shock
    dataSlice.Shock1_5CCap = min(dataSlice.Shock1_5C, 1);
    dataSlice.Shock2CCap = min(dataSlice.Shock2C, 1);

    c{1,i} = dataSlice;
end
regionEnergyClimateData = vertcat(c{:});
head(regionEnergyClimateData)
    Year    Region    Energy    Simulation    EnergyUseRef    EnergyUse1_5C    EnergyUse2C    TotalEnergyUse1_5C    TotalEnergyUse2C    TotalEnergyUseRef    MarketShareRef    MarketShare1_5C    MarketShare2C    Shock1_5C      Shock2C      Shock1_5CCap    Shock2CCap
    ____    ______    ______    __________    ____________    _____________    ___________    __________________    ________________    _________________    ______________    _______________    _____________    __________    __________    ____________    __________

    2020     GLB       Coal         1             156.3          151.31          151.31             562.85               562.85              577.26             0.27077            0.26882           0.26882       -0.0071761    -0.0071761     -0.0071761     -0.0071761
    2025     GLB       Coal         1            158.09          137.82          137.82             571.77               571.77              613.93             0.25751            0.24104           0.24104        -0.063951     -0.063951      -0.063951      -0.063951
    2030     GLB       Coal         1            159.73          135.75          135.75             584.18               584.18              636.23             0.25106            0.23237           0.23237        -0.074431     -0.074431      -0.074431      -0.074431
    2035     GLB       Coal         1            167.23          14.962          97.463             292.45               546.08              690.27             0.24227            0.05116           0.17848         -0.78883       -0.2633       -0.78883        -0.2633
    2040     GLB       Coal         1            158.86          18.553          70.469             285.89               521.59              719.77             0.22071           0.064898            0.1351         -0.70596      -0.38786       -0.70596       -0.38786
    2045     GLB       Coal         1             159.5          33.185          45.762             287.79               500.71              753.71             0.21162            0.11531          0.091395         -0.45511      -0.56811       -0.45511       -0.56811
    2050     GLB       Coal         1            161.88          42.375          56.019             297.72               455.21              788.06             0.20542            0.14233           0.12306         -0.30711      -0.40091       -0.30711       -0.40091
    2055     GLB       Coal         1             162.6          40.205          70.153             298.28               423.58              813.78             0.19981            0.13479           0.16562         -0.32543      -0.17114       -0.32543       -0.17114

Process Portfolio Data

To process the portfolio data, you use a simulation that is similar to the one in Measure Transition Risk for Loan Portfolios with Respect to Climate Scenarios, but this simulation has more granular regions and additional simulated loan-level data.

First, load the data.

load("ClimateLoanPortfolioDataGranular.mat")
ClimateLoanPortfolioDataGranular.BorrowerSector = renamecats(ClimateLoanPortfolioDataGranular.BorrowerSector,["Bioenergy", "Renewables (wind&solar)"],["Biomass","Renewables"]);

Generate histograms to show the distribution of loans by region and sector for the two simulated banks.

LoanPortfolioDataBank1 = ClimateLoanPortfolioDataGranular(ClimateLoanPortfolioDataGranular.Bank=="Bank1",:);
LoanPortfolioDataBank2 = ClimateLoanPortfolioDataGranular(ClimateLoanPortfolioDataGranular.Bank=="Bank2",:);

[CountsRegion1, BinsRegion1] = histcounts(LoanPortfolioDataBank1.GranularBorrowerRegion);
[CountsSector1, BinsSector1] = histcounts(LoanPortfolioDataBank1.BorrowerSector);
[CountsRegion2, BinsRegion2] = histcounts(LoanPortfolioDataBank2.GranularBorrowerRegion);
[CountsSector2, BinsSector2] = histcounts(LoanPortfolioDataBank2.BorrowerSector);

figure
tiledlayout(2,1)
nexttile
b1 = barh(categorical(BinsRegion1), [CountsRegion1; CountsRegion2], "FaceColor", "flat");
xlabel("Loans")
ylabel("Regions")
title("Distribution of Regions")
legend(["Bank 1", "Bank 2"],"Location","best")
b1(1).CData = [0.633, 0.078, 0.243];
b1(2).CData = [0.467, 0.675, 0.188];
fig = gcf;
currentPosition = get(fig, 'Position');
newHeight = currentPosition(4) * 1.5;
set(fig, 'Position', [currentPosition(1), currentPosition(2), currentPosition(3), newHeight]);

nexttile
b2 = barh(categorical(BinsSector1), [CountsSector1; CountsSector2], "FaceColor", "flat");
xlabel("Loans")
ylabel("Sectors")
title("Distribution of Sectors")
legend(["Bank 1", "Bank 2"],"Location","best")
b2(1).CData = [0.633, 0.078, 0.243];
b2(2).CData = [0.467, 0.675, 0.188];

Figure contains 2 axes objects. Axes object 1 with title Distribution of Regions, xlabel Loans, ylabel Regions contains 2 objects of type bar. These objects represent Bank 1, Bank 2. Axes object 2 with title Distribution of Sectors, xlabel Loans, ylabel Sectors contains 2 objects of type bar. These objects represent Bank 1, Bank 2.

Bank 1 is heavily invested in oil fossil fuel projects, whereas Bank 2 is heavily invested in hydro green energy products. Regarding the geographical distribution of projects, both banks are similar, with a larger number of projects in developing regions like China, India, and Africa. Because Bank 2 requires a faster transition away from fossil fuels, it is likely to outperform Bank 1 more quickly in the 1.5C scenario than in the 2C scenario.

The portfolio represents a snapshot taken on December 31, 2020. This implies a static balance sheet during the time horizon, which might not be realistic. You can use a dynamic balance sheet in this step to analyze different portfolio evolution scenarios. Create a data set that represents the same portfolio with snapshots at five-year intervals from 2020 to 2095.

portfolioSnapshots = dictionary;
for year = 2020:5:2095
    value = ClimateLoanPortfolioDataGranular;
    value.Snapshot = categorical(repmat(year,height(ClimateLoanPortfolioDataGranular),1));
    portfolioSnapshots{year} = value;
end

portfolioSnapshots = vertcat(portfolioSnapshots.values{:});

Compute Change in Valuation

Create a large data set that contains each loan and snapshot with the 400 shocks by performing an outer join between the portfolio snapshots and climate data.

First, prepare the climate data for joining, then join the data sets.

regionEnergyClimateDataJoin = table(regionEnergyClimateData.Energy, ...
    regionEnergyClimateData.Region, ...
    regionEnergyClimateData.Year, ...
    regionEnergyClimateData.Simulation, ...
    regionEnergyClimateData.Shock1_5CCap, ...
    regionEnergyClimateData.Shock2CCap, ...
    VariableNames = ...
    ["BorrowerSector", ...
    "GranularBorrowerRegion", ...
    "Snapshot", ...
    "Simulation", ...
    "Shock1_5CCap", ...
    "Shock2CCap"]);
portfolioSnapshotsClimateData = outerjoin(portfolioSnapshots,regionEnergyClimateDataJoin,"MergeKeys",true,"Type","left");

Calculate the loan level changes in the probability of default (PD) and valuation by using the following equations derived in Borrower's Default Conditions and Climate Policy Shocks

Δpj(P,B)=-χuS,R(P,B,t*)Ej(t0)δj

ΔVj(t0,P,B)=-Fj(1-rj)Δpj(P,B).

Assume that χ=1, rj=0, and that δj controls the size of the exogenous shock ηj. Because the exogenous shock is independent of climate shock, you can chose it independently of the simulation. It suffices to let δj be the maximum over each loan ID and snapshot pair of the quantity 2Ej(t0)(1+abs(uS,R(P,B,t*))) derived in Profitability and Shocks on Market Share.

Next, compute the observation level δj.

portfolioSnapshotsClimateData.deltajTemp1_5C = 2 .* portfolioSnapshotsClimateData.BookValue .* (1 + abs(portfolioSnapshotsClimateData.Shock1_5CCap));
portfolioSnapshotsClimateData.deltajTemp2C = 2 .* portfolioSnapshotsClimateData.BookValue .* (1 + abs(portfolioSnapshotsClimateData.Shock2CCap));

Find the maximum over each loan ID and snapshot pair, which is independent of the scenario and simulation.

deltaTableTemp = grpstats(portfolioSnapshotsClimateData, ...
    ["LoanID", "Snapshot"], "max", ...
    "DataVars", ["deltajTemp1_5C", "deltajTemp2C"]);
deltaTableJoin = table;
deltaTableJoin.LoanID = deltaTableTemp.LoanID;
deltaTableJoin.Snapshot = deltaTableTemp.Snapshot;
deltaTableJoin.deltaj1_5C = deltaTableTemp.max_deltajTemp1_5C;
deltaTableJoin.deltaj2C = deltaTableTemp.max_deltajTemp2C;

Join the δj values back to the main data set.

portfolioSnapshotsClimateData = outerjoin(portfolioSnapshotsClimateData, ...
    deltaTableJoin, "MergeKeys", true, "Type", "left");
portfolioSnapshotsClimateData.pdChange1_5C = -portfolioSnapshotsClimateData.Shock1_5CCap .* portfolioSnapshotsClimateData.BookValue ./ portfolioSnapshotsClimateData.deltajTemp1_5C;
portfolioSnapshotsClimateData.pdChange2C = -portfolioSnapshotsClimateData.Shock2CCap .* portfolioSnapshotsClimateData.BookValue ./ portfolioSnapshotsClimateData.deltajTemp2C;
portfolioSnapshotsClimateData.valChange1_5C = -portfolioSnapshotsClimateData.FaceValue .* portfolioSnapshotsClimateData.pdChange1_5C;
portfolioSnapshotsClimateData.valChange2C = -portfolioSnapshotsClimateData.FaceValue .* portfolioSnapshotsClimateData.pdChange2C;

Aggregate the valuation changes by bank, snapshot, and simulation.

valChangeSummary = groupsummary(portfolioSnapshotsClimateData, ...
    ["Bank","Snapshot","Simulation"], "sum", ...
    ["FaceValue","valChange1_5C","valChange2C"]);
valChangeSummary.percentChangeVal1_5C = 100 * valChangeSummary.sum_valChange1_5C ./ valChangeSummary.sum_FaceValue;
valChangeSummary.percentChangeVal2C = 100 * valChangeSummary.sum_valChange2C ./ valChangeSummary.sum_FaceValue;

valChangeSummary = removevars(valChangeSummary, ...
    ["GroupCount","sum_FaceValue","sum_valChange1_5C","sum_valChange2C"]);

Calculate percentiles for the valuation changes.

valChangePrctiles = groupsummary(valChangeSummary, ...
    ["Bank","Snapshot"], ...
    {@(y) prctile(y,5), @(y) prctile(y,50), @(y) prctile(y,95)});

% Rename the percentile variables
valChangePrctiles = renamevars(valChangePrctiles, ...
    ["fun1_percentChangeVal1_5C", ...
    "fun2_percentChangeVal1_5C", ...
    "fun3_percentChangeVal1_5C", ...
    "fun1_percentChangeVal2C", ...
    "fun2_percentChangeVal2C", ...
    "fun3_percentChangeVal2C"], ...
    ["percentChangeVal1_5C_5", ...
    "percentChangeVal1_5C_50", ...
    "percentChangeVal1_5C_95", ...
    "percentChangeVal2C_5", ...
    "percentChangeVal2C_50", ...
    "percentChangeVal2C_95"]);
valChangePrctiles = removevars(valChangePrctiles, ["fun1_Simulation", "fun2_Simulation", "fun3_Simulation"]);

Visualize Results

Create a histogram of the distribution of changes in valuation by year. In the 1.5C scenario, the greener Bank 2 distribution separates from Bank 1 much faster than in the 2C scenario.

scenario = "1.5C";
scenarioDataName = matlab.lang.makeValidName("percentChangeVal" + scenario);

figure;
t = tiledlayout(5,3);
for year = 2025:5:2095

    bank1Data = valChangeSummary(valChangeSummary.Snapshot == string(year) & valChangeSummary.Bank == "Bank1",:);
    bank2Data = valChangeSummary(valChangeSummary.Snapshot == string(year) & valChangeSummary.Bank == "Bank2",:);

    ax = nexttile;
    h1 = histogram(ax, bank1Data.(scenarioDataName));
    hold(ax, "on");
    h2 = histogram(ax, bank2Data.(scenarioDataName));
    h1.BinWidth = .5;
    h2.BinWidth = .5;
    titleString = string(year);
    title(ax, titleString);
    xlim(ax, [-20,20]);

    % Define colors for Bank 1 and Bank 2
    colorBank1 = [0.633, 0.078, 0.243];
    colorBank2 = [0.467, 0.675, 0.188];

    h1.FaceColor = colorBank1;
    h2.FaceColor = colorBank2;
end
leg = legend(["Bank 1", "Bank 2"]);
leg.Layout.Tile = "south";
title(t,"Change in Portfolio Value " + scenario +  " (%)");

Figure contains 15 axes objects. Axes object 1 with title 2025 contains 2 objects of type histogram. Axes object 2 with title 2030 contains 2 objects of type histogram. Axes object 3 with title 2035 contains 2 objects of type histogram. Axes object 4 with title 2040 contains 2 objects of type histogram. Axes object 5 with title 2045 contains 2 objects of type histogram. Axes object 6 with title 2050 contains 2 objects of type histogram. Axes object 7 with title 2055 contains 2 objects of type histogram. Axes object 8 with title 2060 contains 2 objects of type histogram. Axes object 9 with title 2065 contains 2 objects of type histogram. Axes object 10 with title 2070 contains 2 objects of type histogram. Axes object 11 with title 2075 contains 2 objects of type histogram. Axes object 12 with title 2080 contains 2 objects of type histogram. Axes object 13 with title 2085 contains 2 objects of type histogram. Axes object 14 with title 2090 contains 2 objects of type histogram. Axes object 15 with title 2095 contains 2 objects of type histogram. These objects represent Bank 1, Bank 2.

The following plots show the change in portfolio value by percentile in each snapshot. The losses for the Bank 1 are generally highest near the end of the horizon. The gains for Bank 2 are closer to the middle of the horizon. You can interpret the percentile values as a type of value at risk (VaR).

valChangePrctilesUnstack = unstack(valChangePrctiles, ...
    ["percentChangeVal1_5C_5","percentChangeVal1_5C_50","percentChangeVal1_5C_95","percentChangeVal2C_5","percentChangeVal2C_50","percentChangeVal2C_95"],"Bank");

x = valChangePrctilesUnstack.Snapshot;
scenario = "1.5C";
y = table2array(valChangePrctilesUnstack(:,contains(valChangePrctilesUnstack.Properties.VariableNames, matlab.lang.makeValidName("percentChangeVal" + scenario))));

figure;

% Plot Bank 1 lines
hold on;
plot(x, y(:,1), "-", 'LineWidth', 1.5, "Color", colorBank1, "DisplayName", "Bank 1 - 5th percentile");
plot(x, y(:,3), "--", 'LineWidth', 1.5, "Color", colorBank1, "DisplayName", "Bank 1 - 50th percentile");
plot(x, y(:,5), ":", 'LineWidth', 1.5, "Color", colorBank1, "DisplayName", "Bank 1 - 95th percentile");

% Plot Bank 2 lines
plot(x, y(:,2), "-", 'LineWidth', 1.5, "Color", colorBank2, "DisplayName", "Bank 2 - 5th percentile");
plot(x, y(:,4), "--", 'LineWidth', 1.5, "Color", colorBank2, "DisplayName", "Bank 2 - 50th percentile");
plot(x, y(:,6), ":", 'LineWidth', 1.5, "Color", colorBank2, "DisplayName", "Bank 2 - 95th percentile");

legend("Location","bestoutside");
xlabel("Snapshot Year");
title("Change in Portfolio Value " + scenario + " (%)", "FontWeight", "normal");
hold off;

Figure contains an axes object. The axes object with title Change in Portfolio Value 1.5C (%), xlabel Snapshot Year contains 6 objects of type line. These objects represent Bank 1 - 5th percentile, Bank 1 - 50th percentile, Bank 1 - 95th percentile, Bank 2 - 5th percentile, Bank 2 - 50th percentile, Bank 2 - 95th percentile.

The box and whisker plots offer an alternative visualization. The Bank 1 pathways outperform Bank 2 in the short term, especially in the 2C scenario. The separation of distributions becomes clearer further into the future, and the separation occurs more quickly in the 1.5C scenario.

scenario = "1.5C";
scenarioDataName = matlab.lang.makeValidName("percentChangeVal" + scenario);

figure;
boxchart(valChangeSummary.Snapshot, valChangeSummary.(scenarioDataName), "GroupByColor", valChangeSummary.Bank);
colororder([colorBank1; colorBank2]);
xlabel("Snapshot Year");
title("Change in Portfolio Value " + scenario + " (%)", "FontWeight", "normal");
legend(["Bank 1", "Bank 2"]);

Figure contains an axes object. The axes object with title Change in Portfolio Value 1.5C (%), xlabel Snapshot Year contains 2 objects of type boxchart. These objects represent Bank 1, Bank 2.

Conclusion

The methodology used in this example provides a way to calculate the impact of climate scenarios on an obligor's PD. This example uses climate scenarios that have 400 simulations, which allows you to visualize a distribution of portfolio outcomes. The portfolio that is invested in green technologies performs better in the scenarios requiring a faster transition away from fossil fuels.

Methodology

Background

Monasterolo et al provide a methodology for performing a portfolio valuation that accounts for climate scenarios [1]. The methodology first computes a market share shock for different energy sectors based on the scenarios, and then converts this into a shock for the PD of each investment. By aggregating the shocks to the PD, you can calculate the change in value of the portfolio. For more information about this methodology, see [1].

Consider a financial actor endowed with a portfolio of investments through loan contracts. Each investment is represented as a distinct borrower, j. Consider three time variables:

  • Let t0 denote the time at which the portfolio valuation is taken.

  • Let t* denote the time at which a climate policy shock potentially occurs.

  • Let tj denote the maturity of borrower j's obligation.

In particular, for each borrower j,

t0<t*<tj.

Let Vj(t0) denote the valuation of obligation j at time t0. Then, the valuation of the portfolio at time t0, V(t0), is given by

V(t0)=jVj(t0).

Assume that the valuation of obligation j is based on the expected value of the loan. For each obligor j, consider the following:

  • Let Fj denote the face value of the loan, already including the time-discounting factor.

  • Let pj(t0)=pj(t0,tj) be the probability that the borrower defaults at time tj given the information available at time t0.

  • Let rj denote the recovery rate of the loan contract.

Then, assume

Vj(t0)=pj(t0)rjFj+(1-pj(t0))Fj=Fj(1-(1-rj)pj(t0)).

Climate Policy Shocks

At time t*, the occurrence of a climate policy shock implies that the economy switches from a business-as-usual scenario characterized by no climate policy, B, to a scenario, P, where the market shares of some economic sectors are affected. Assume that this transition modifies the PD of the borrower j through changes in the market share of the economic sector of j. Let Δpj(P,B) denote the change in default probability for obligor j when going from scenario B to scenario P. The change in default probability implies a proportional change in the expected value of the loan:

ΔVj(t0,P,B)=-Fj(1-rj)Δpj(P,B).

Next, assume rj=0 below.

Borrower's Default Conditions

Let Aj(t,P,B) denote the total assets of borrower j at time t in scenario P. Let ξj(t*,P,B) be the shock occurring at time t* associated with the shift from scenario B to scenario P. Let ηj=ηj(tj) denote an exogenous stochastic shock hitting the asset side of the borrower at the maturity date tj. Then,

Aj(tj,P,B)=Aj(t0)+ξj(t*,P,B)+ηj.

Assume that the borrower defaults at time tj if its net worth, defined as assets minus liabilities, at maturity becomes negative as a result of the two shocks. Let Ej(t,P,B) denote the net worth at time t in scenario P. Note that Ej(t0,P,B)=Ej(t0) is independent of the climate scenarios. Let Lj denote the value of obligor j's liabilities, which is assumed to be independent of time t and the climate scenarios. Then, the default condition becomes

Ej(tj,P,B)=Aj(tj,P,B)-Lj=Aj(t0)+ξj(t*,P,B)+ηj-Lj=Ej(t0)+ξj(t*,P,B)+ηj<0.

You can rewrite the default condition as

ηj<θj(P,B)=-(Ej(t0)+ξj(t*,P,B)).

When P=B, there is no policy shock:

ηj<θj(B,B)=-Ej(t0).

Let pj be the probability density function of the idiosyncratic shock ηj. Then,

P{ηj<θj(P,B)}=-θJ(P,B)pj(x)dx,

which allows you to express the difference in probability as

Δpj(P,B)=θj(B,B)θj(P,B)pj(x)dx.

Profitability and Shocks on Market Share

Assume that the policy shock impacts the obligor's balance sheet, and thus the expected value of the loan, via a change in the market share of the obligor's economic sector. Let mS,R(P,t*) denote the market share at time t* of sector S in the region R in scenario P. Define mS,R(B,t*) similarly. Then the market share shock uS,R(P,B,t*) is defined as

uS,R(P,B,t*)=mS,R(P,t*)-mS,R(B,t*)mS,R(B,t*).

Let χ denote the elasticity of profitability with respect to market share. A single value is chosen for all obligors. Assume that the relative change in net worth due to the change in scenarios is proportional to the relative market share shock:

Ej(tj,P,B)-Ej(tj,B,B)Ej(t0)=χuS,R(P,B,t*).

Assume that ηj is uniformly distributed with support width δj and mean μj. For δj sufficiently large, you can express the difference in probability as

Δpj(P,B)=θj(B,B)θj(P,B)pj(x)=θj(P,B)-θj(B,B)δj.

Then,

Δpj(P,B)=θj(P,B)-θj(B,B)δj=-ξj(t*,P,B)δj=Ej(tj,B,B)-Ej(tj,P,B)δj=-χuS,R(P,B,t*)Ej(t0)δj.

The change in PD has been linked to the climate scenarios. However, you need to choose δj and χ appropriately. Assume that χ=1 which you should view as a limitation because χ could be chosen at the obligor level to reflect empirical data.

Before addressing δj in more detail, consider the previous equation:

Δpj(P,B)=-χuS,R(P,B,t*)Ej(t0)δj.

The left hand side must be in the range [-1,1]. However, the range in practice is actually much smaller. The climate scenarios in this example express a wide range of outcomes, which leads to very large shocks on the right side of the equation. As such, choose a sufficiently large δj to move those shocks back into the expected range. You cap the shocks to keep them in a more reasonable range. In practice, an institution implementing this methodology decides what shock levels are consistent with their expectations and data.

Next, identify bounds for δj. To compute Δpj(P,B) as above, you need the interval of integration [θj(B,B),θj(P,B)] to be contained in the support of the function pj(x), which has been assumed to be [μj-δj/2,μj+δj/2]. Since you have no view on the direction of the exogenous shock ηj, assume that μj=0. It suffices for δj/2 to be at least as large as Ej(t0)+abs(ξj(t*,P,B)). The latter term is the absolute value of the shock uS,R(P,B,t*) times the starting book value Ej(t0). It suffices to choose δj such that

δj2Ej(t0)(1+abs(uS,R(P,B,t*))).

In practice, choose δj to be equal to the right hand side.

Note that δj implicitly depends on P and B and you can choose δj uniformly across climate scenarios. As δj reflects the size of the exogenous shocks, ensure that δj does not depend on the choice of a particular climate scenario.

Finally, note that the quantity Ej(t0)/δj appearing in the expression for Δpj(P,B) does not depend on Ej(t0) when you choose δj as above.

Local Functions

importClimateData

The following helper function reads in the downloaded Excel® climate data into a table based on the scenario, energy type, and region.

function climateData = importClimateData(scenario, energy, region)
    % Construct the path to the scenario folder
    scenarioFolder = fullfile(pwd, "MITUncertaintyData", scenario);

    % Find the file with the energy use data
    filePattern = fullfile(scenarioFolder, "*energy_use_" + energy + "*");
    fileStruct = dir(filePattern);
    file = fileStruct.name;
    filePath = fullfile(scenarioFolder, file);

    % Get the sheet names and find the index of the sheet that contains the region
    sheets = sheetnames(filePath);
    sheetIdx = contains(sheets, region);
    sheet = sheets(sheetIdx);

    % Set up the Import Options and import the data
    opts = spreadsheetImportOptions("NumVariables", 21);

    % Specify sheet and range
    opts.Sheet = sheet;
    opts.DataRange = "A2:U401";

    % Specify column names and types
    opts.VariableNames = ["Var1", "Var2", "Var3", "VarName4", "VarName5", "VarName6", "VarName7", "VarName8", "VarName9", "VarName10", "VarName11", "VarName12", "VarName13", "VarName14", "VarName15", "VarName16", "VarName17", "VarName18", "VarName19", "VarName20", "VarName21"];
    opts.SelectedVariableNames = ["VarName4", "VarName5", "VarName6", "VarName7", "VarName8", "VarName9", "VarName10", "VarName11", "VarName12", "VarName13", "VarName14", "VarName15", "VarName16", "VarName17", "VarName18", "VarName19", "VarName20", "VarName21"];
    opts.VariableTypes = ["char", "char", "char", "double", "double", "double", "double", "double", "double", "double", "double", "double", "double", "double", "double", "double", "double", "double", "double", "double", "double"];

    % Specify variable properties
    opts = setvaropts(opts, ["Var1", "Var2", "Var3"], "WhitespaceRule", "preserve");
    opts = setvaropts(opts, ["Var1", "Var2", "Var3"], "EmptyFieldRule", "auto");
    opts = setvaropts(opts, ["VarName4", "VarName5", "VarName6", "VarName7", "VarName8", "VarName9", "VarName10", "VarName11", "VarName12", "VarName13", "VarName14", "VarName15", "VarName16", "VarName17", "VarName18", "VarName19", "VarName20", "VarName21"], "FillValue", 0);

    % Import the data
    climateData = readtable(filePath, opts, "UseExcel", false);

    clear opts

    % Reshape and format the data
    climateData = stack(climateData,2:18,"NewDataVariableName","Energy Use","IndexVariableName","Year");
    climateData.Year = renamecats(climateData.Year,["VarName5","VarName6","VarName7","VarName8","VarName9","VarName10","VarName11","VarName12","VarName13","VarName14","VarName15","VarName16","VarName17","VarName18","VarName19","VarName20","VarName21"], ...
        ["2020", "2025", "2030", "2035", "2040", "2045", "2050", "2055", "2060", "2065", "2070", "2075", "2080", "2085", "2090", "2095", "2100"]);

    dataHeight = height(climateData);
    climateData = renamevars(climateData,"VarName4","Simulation");
    climateData.Scenario = categorical(repmat(scenario,[dataHeight 1]));
    climateData.Region = categorical(repmat(region,[dataHeight 1]));
    climateData.Energy = categorical(repmat(energy,[dataHeight 1]));
end

References

[1] Monasterolo, I., Zheng, Jiani I., and S. Battiston. "Climate Transition Risk and Development Finance: A Carbon Risk Assessment of China's Overseas Energy Portfolios." China and World Economy. 26, 6(116–142), 2018. Available at: https://onlinelibrary.wiley.com/doi/epdf/10.1111/cwe.12264.

Related Topics