One-Factor Model Calibration

This example demonstrates techniques to calibrate a one-factor model for estimating portfolio credit losses using the creditDefaultCopula or creditMigrationCopula classes.

This example uses equity return data as a proxy for credit fluctuations. With equity data, sensitivity to a single factor is estimated as a correlation between a stock and an index. The data set contains daily return data for a series of equities, but the one-factor model requires calibration on a year-over-year basis. Assuming that there is no autocorrelation, then the daily cross-correlation between a stock and the market index is equal to the annual cross-correlation. For stocks exhibiting autocorrelation, this example shows how to compute implied annual correlations incorporating the effect of autocorrelation.

Fitting a One-Factor Model

Since corporate defaults are rare, it is common to use a proxy for creditworthiness when calibrating default models. The one-factor copula models the credit worthiness of a company using a latent variable, A:

A=wX+1-w2ϵ

where X is the systemic credit factor, w is the weight that defines the sensitivity of a company to the one factor, and ϵ is the idiosyncratic factor. w and ϵ have mean of 0 and variance of 1 and typically are assumed to be either Gaussian or else t distributions.

Compute the correlation between X and A:

Corr(A,X)=Cov(A,X)σAσX

Since X and A have a variance of 1 by construction and ϵ is uncorrelated with X, then:

Corr(A,X)=Cov(A,X)=Cov(wX+1-w2ϵ,X)=wCov(X,X)+1-w2Cov(X,ϵ)=w

If you use stock returns as a proxy for A and the market index returns are a proxy for X, then the weight parameter, w, is the correlation between the stock and the index.

Prepare the Data

Use the returns of the Dow Jones Industrial Average (DJIA) as a signal for the overall credit movement of the market. The returns for the 30 component companies are used to calibrate the sensitivity of each company to the systemic credit movement. Weights for other companies in the stock market are estimated in the same way.

% Read one year of DJIA price data
t = readtable('dowPortfolio.xlsx');

% The table contains dates and the prices for each company at market close
% as well as the DJIA.
disp(head(t(:,1:7)))
      Dates       DJI      AA       AIG      AXP      BA        C  
    _________    _____    _____    _____    _____    _____    _____

     1/3/2006    10847    28.72    68.41    51.53    68.63    45.26
     1/4/2006    10880    28.89    68.51    51.03    69.34    44.42
     1/5/2006    10882    29.12     68.6    51.57    68.53    44.65
     1/6/2006    10959    29.02    68.89    51.75    67.57    44.65
     1/9/2006    11012    29.37    68.57    53.04    67.01    44.43
    1/10/2006    11012    28.44    69.18    52.88    67.33    44.57
    1/11/2006    11043    28.05     69.6    52.59     68.3    44.98
    1/12/2006    10962    27.68    69.04     52.6     67.9    45.02
% We separate the dates and the index from the table and compute daily returns using
% tick2ret.
dates = t{2:end,1};
index_adj_close = t{:,2};
stocks_adj_close = t{:,3:end};

index_returns = tick2ret(index_adj_close);
stocks_returns = tick2ret(stocks_adj_close);

Compute Single Factor Weights

Compute the single-factor weights from the correlation coefficients between the index returns and the stock returns for each company.

[C,daily_pval] = corr([index_returns stocks_returns]);
w_daily = C(2:end,1);

These values can be used directly when using a one-factor creditDefaultCopula or creditMigrationCopula.

Linear regression is often used in the context of factor models. For a one-factor model, a linear regression of the stock returns on the market returns is used by exploiting the fact that the correlation coefficient matches the square root of the coefficient of determination (R-squared) of a linear regression.

w_daily_regress = zeros(30,1);
for i = 1:30
    lm = fitlm(index_returns,stocks_returns(:,i));
    w_daily_regress(i) = sqrt(lm.Rsquared.Ordinary);
end

% The regressed R values are equal to the index cross correlations
fprintf('Max Abs Diff : %e\n',max(abs(w_daily_regress(:) - w_daily(:))))
Max Abs Diff : 7.771561e-16

This linear regression fits a model of the form A=α+βX+ϵ, which in general does not match the one-factor model specifications. For example, A and X do not have a zero mean and a standard deviation of 1. In general, there is no relationship between the coefficient β and the standard deviation of the error term ϵ. Linear regression is used above only as a tool to get the correlation coefficient between the variables given by the square root of the R-squared value.

For one-factor model calibration, a useful alternative is to fit a linear regression using the standardized stock and market return data A and X. "Standardize" here means to subtract the mean and divide by the standard deviation. The model is A=α+βX+ϵ. However, because both A and X have a zero mean, the intercept α is always zero, and because both A and X have standard deviation of 1, the standard deviation of the error term satisfies std(ϵ)=1-β2. This exactly matches the specifications of the coefficients of a one-factor model. The one-factor parameter w is set to the coefficient β, and is the same as the value found directly through correlation earlier.

w_regress_std = zeros(30,1);
index_returns_std = zscore(index_returns);
stocks_returns_std = zscore(stocks_returns);
for i = 1:30
    lm = fitlm(index_returns_std,stocks_returns_std(:,i));
    w_regress_std(i) = lm.Coefficients{'x1','Estimate'};
end

% The regressed R values are equal to the index cross correlations
fprintf('Max Abs Diff : %e\n',max(abs(w_regress_std(:) - w_daily(:))))
Max Abs Diff : 5.551115e-16

This approach makes it natural to explore the distributional assumptions of the variables. The creditDefaultCopula and creditMigrationCopula objects support either normal distributions, or t distributions for the underlying variables. For example, when using normplot the market returns have heavy tails, therefore a t-copula is more consistent with the data.

normplot(index_returns_std)

Estimating Correlations for Longer Periods

The weights are computed based on the daily correlation between the stocks and the index. However, the usual goal is to estimate potential losses from credit defaults at some time further in the future, often one year out.

To that end, it is necessary to calibrate the weights such that they correspond to the one-year correlations. It is not practical to calibrate directly against historical annual return data since any reasonable data set does not have enough data to be statistically significant due to the sparsity of the data points.

You then face the problem of computing annual return correlation from a more frequently sampled data set, for example, daily returns. One approach to solving this problem is to use an overlapping window. This way you can consider the set of all overlapping periods of a given length.

% As an example, consider an overlapping 1-week window.
index_overlapping_returns = index_adj_close(6:end) ./ index_adj_close(1:end-5) - 1;
stocks_overlapping_returns = stocks_adj_close(6:end,:) ./ stocks_adj_close(1:end-5,:) - 1;

C = corr([index_overlapping_returns stocks_overlapping_returns]);
w_weekly_overlapping = C(2:end,1);

% Compare the correlation with the daily correlation.
% Show the daily vs. the overlapping weekly correlations
barh([w_daily w_weekly_overlapping])
yticks(1:30)
yticklabels(t.Properties.VariableNames(3:end))
title('Correlation with the Index');
legend('daily','overlapping weekly');

The maximum cross-correlation p-value for daily returns show a strong statistical significance.

maxdailypvalue = max(daily_pval(2:end,1));
disp(table(maxdailypvalue,...
    'VariableNames',{'Daily'},...
    'rownames',{'Maximum p-value'}))
                         Daily   
                       __________

    Maximum p-value    1.5383e-08

Moving to an overlapping rolling-window-style weekly correlation gives slightly different correlations. This is a convenient way to estimate longer period correlations from daily data. However, the returns of adjacent overlapping windows are correlated so the corresponding p-values for the overlapping weekly returns are not valid since the p-value calculation in the corr function does not account for overlapping window data sets. For example, adjacent overlapping window returns are composed of many of the same datapoints. This tradeoff is necessary since moving to nonoverlapping windows could result is an unacceptably sparse sample.

% Compare to non-overlapping weekly returns
fridays = weekday(dates) == 6;
index_weekly_close = index_adj_close(fridays);
stocks_weekly_close = stocks_adj_close(fridays,:);

index_weekly_returns = tick2ret(index_weekly_close);
stocks_weekly_returns = tick2ret(stocks_weekly_close);

[C,weekly_pval] = corr([index_weekly_returns stocks_weekly_returns]);
w_weekly_nonoverlapping = C(2:end,1);
maxweeklypvalue = max(weekly_pval(2:end,1));

% Compare the correlation with the daily and overlapping.
barh([w_daily w_weekly_overlapping w_weekly_nonoverlapping])
yticks(1:30)
yticklabels(t.Properties.VariableNames(3:end))
title('Correlation with the Index');
legend('daily','overlapping weekly','non-overlapping weekly');

The p-values for the nonoverlapping weekly correlations are much higher, indicating a loss of statistical significance.

% Compute the number of samples in each series
numDaily = numel(index_returns);
numOverlapping = numel(index_overlapping_returns);
numWeekly = numel(index_weekly_returns);

disp(table([maxdailypvalue;numDaily],[NaN;numOverlapping],[maxweeklypvalue;numWeekly],...
    'VariableNames',{'Daily','Overlapping','Non_Overlapping'},...
    'rownames',{'Maximum p-value','Sample Size'}))
                         Daily       Overlapping    Non_Overlapping
                       __________    ___________    _______________

    Maximum p-value    1.5383e-08        NaN            0.66625    
    Sample Size               250        246                 50    

Extrapolating Annual Correlation

A common assumption with financial data is that asset returns are temporally uncorrelated. That is, the asset return at time T is uncorrelated to the previous return at time T-1. Under this assumption, the annual cross-correlation is exactly equal to the daily cross-correlation.

Let Xt be the daily log return of the market index on day t and At be the daily return of a correlated asset. Using CAPM, the relation is modeled as:

At=α+βXt+ϵt

The one-factor model is a special case of this relationship.

Under the assumption that asset and index returns are each uncorrelated with their respective past, then:

y, st:

cov(Xs,Xt)=0

cov(ϵs,ϵt)=0

cov(As,At)=0

Let the aggregate annual (log) return for each series be

X=t=1TXt

A=t=1TAt

where T could be 252 depending on the underlying daily data.

Let σX2=var(Xt) and σA2=var(At) be the daily variances, which are estimated from the daily return data.

The daily covariance between Xt and At is:

cov(Xt,At)=cov(Xt,α+βXt+ϵt)=βσX2

The daily correlation between Xt and At is:

corr(Xt,At)=cov(Xt,At)σX2σA2=βσXσA

Consider the variances and covariances for the aggregate year of returns. Under the assumption of no autocorrelation:

var(X)=var(t=1TXt)=TσX2

var(A)=var(t=1TAt)=TσA2

cov(X,A)=cov[t=1TXt,t=1T(α+βXt+ϵt)]=βcov(X,X)=βvar(X)=βTσx2

The annual correlation between the asset and the index is:

corr(X,A)=cov(X,A)var(X)var(A)=βTσX2TσX2TσA2=βσXσA=w

Under the assumption of no autocorrelation, notice that the daily cross-correlation is in fact equal to the annual cross-correlation. You can use this assumption directly in the one-factor model by setting the one-factor weights to the daily cross-correlation.

Handling Autocorrelation

If the assumption that assets have no autocorrelation is loosened, then the transformation from daily to annual cross-correlation between assets is not as straightforward. The var(X) now has additional terms.

First consider the simplest case of computing the variance of X when T is equal to 2.

var(X)=[σ1σ2][1ρ12ρ121][σ1σ2]=σ12+σ22+2ρ12σ1σ2

Since σ1=σ2=σX, then:

var(X)=σX2(2+2ρ12)

Consider T = 3. Indicate the correlation between daily returns that are k days apart as ρΔk.

var(X)=[σ1σ2σ3][1ρΔ1ρΔ2ρΔ11ρΔ1ρΔ2ρΔ11][σ1σ2σ3]=σ12+σ22+σ32+2ρΔ1σ1σ2+2ρΔ1σ2σ3+2ρΔ2σ1σ3=σX2(3+4ρΔ1+2ρΔ2)

In the general case, for the variance of an aggregate T-day return with autocorrelation from trailing k days, there is:

var(X)=2σX2(T/2+(T-1)ρΔ1X+(T-2)ρΔ2X+...+(T-k)ρΔkX)

This is also the same formula for the asset variance:

var(A)=2σA2(T/2+(T-1)ρΔ1A+(T-2)ρΔ2A+...+(T-k)ρΔkA)

The covariance between X and A as shown earlier is equal to βvar(X).

Therefore, the cross-correlation between the index and the asset with autocorrelation from a trailing 1 through k days is:

corr(X,A)=cov(X,A)var(X)var(A)=βvar(X)var(X)var(A)=βvar(X)var(A)=...

corr(X,A)=β2σX2(T/2+(T-1)ρΔ1X+(T-2)ρΔ2X+...+(T-k)ρΔkX)2σA2(T/2+(T-1)ρΔ1A+(T-2)ρΔ2A+...+(T-k)ρΔkA)

corr(X,A)=βσXσAT/2+(T-1)ρΔ1X+(T-2)ρΔ2X+...+(T-k)ρΔkXT/2+(T-1)ρΔ1A+(T-2)ρΔ2A+...+(T-k)ρΔkA

Note that βσXσA is the weight under the assumption of no autocorrelation. The square root term provides the adjustment to account for autocorrelation in the series. The adjustment depends more on the difference between the index autocorrelation and the stock autocorrelation, rather than the magnitudes of these autocorrelations. So the annual one-factor weight adjusted for autocorrelation is:

wadjusted=wT/2+(T-1)ρΔ1X+(T-2)ρΔ2X+...+(T-k)ρΔkXT/2+(T-1)ρΔ1A+(T-2)ρΔ2A+...+(T-k)ρΔkA

Compute Weights with Autocorrelation

Look for autocorrelation in each of the stocks with the previous day's return, and adjust the weights to incorporate the effect of a one-day autocorrelation.

corr1 = zeros(30,1);
pv1 = zeros(30,1);
for stockidx = 1:30
    [corr1(stockidx),pv1(stockidx)] = corr(stocks_returns(2:end,stockidx),stocks_returns(1:end-1,stockidx));
end
autocorrIdx = find(pv1 < 0.05)
autocorrIdx = 4×1

    10
    18
    26
    27

There are four stocks with low p-values that may indicate the presence of autocorrelation. Estimate the annual cross-correlation with the index under this model, considering the one-day autocorrelation.

% The weights based off of yearly cross correlation are equal to the daily cross
% correlation multiplied by an additional factor.
T = 252;

w_yearly = w_daily;
[rho_index, pval_index] = corr(index_returns(1:end-1),index_returns(2:end));

% Check to see if our index has any significant autocorrelation
fprintf('One day autocorrelation in the index p-value: %f\n',pval_index);
One day autocorrelation in the index p-value: 0.670196
if pval_index < 0.05
    % If the p-value indicates there is no significant autocorrelation in the index,
    % set its rho to 0.
    rho_index = 0;
end

w_yearly(autocorrIdx) = w_yearly(autocorrIdx) .*...
    sqrt((T/2 + (T-1) .* rho_index) ./ (T/2 + (T-1) .* corr1(autocorrIdx)));

% Compare the adjusted annual cross correlation values to the daily values
barh([w_daily(autocorrIdx) w_yearly(autocorrIdx)])
yticks(1:4);
allNames = t.Properties.VariableNames(3:end);
yticklabels(allNames(autocorrIdx))
title('Annual One Factor Weights');
legend('No autocorrelation','With autocorrelation','location','southeast');