Estimation bayésienne par MCMC d’un modèle à volatilité stochastique avec prior spatial en MATLAB

Description mathématique du modèle SV-SPM

Le modèle de volatilité stochastique avec prior spatial (SV-SPM) intègre une structure spatiale dans la modélisation de la volatilité des séries financières. Il est particulièrement utile pour capturer les dépendances temporellles et spatiales dans les rendements d'actifs.

Équation d’observation

Fichier principle sv_spm_bayes_main.m

% Simulation et estimation MCMC d'un modèle SV-SPM
clear; clc; close all;

%% 1. Génération des données simulées
fprintf('Génération des données...\n');
[N, returns, params_true] = simulate_sv_spatial_data(800);

fprintf('Taille de l’échantillon : %d\n', N);
disp('Valeurs réelles des paramètres :');
fprintf('  α = %.4f\n', params_true.alpha);
fprintf('  ν = %.4f\n', params_true.nu);
fprintf('  ρ = %.4f\n', params_true.rho);
fprintf('  τ = %.4f\n', sqrt(params_true.tau_sq));
fprintf('  σ_g = %.4f\n', sqrt(params_true.sigma_g_sq));
fprintf('  ℓ = %.4f\n', params_true.length_scale);

%% 2. Configuration de la chaîne MCMC
config.iter_max = 6000;
config.burn_in = 1500;
config.thin_by = 5;
config.samples_saved = floor((config.iter_max - config.burn_in)/config.thin_by);

% Priors informatifs
priors.alpha_mean = 0;     priors.alpha_var = 100;
priors.nu_mean   = 0;      priors.nu_var   = 100;
priors.rho_a     = 15;     priors.rho_b    = 1.2;
priors.tau_shape = 2.0;    priors.tau_rate = 0.02;
priors.gp_shape  = 2.0;    priors.gp_rate  = 0.02;
priors.len_shape = 1.8;    priors.len_rate = 0.08;

% Variances des propositions (marche aléatoire)
props.alpha_var = 0.015;
props.nu_var    = 0.015;
props.rho_var   = 0.012;
props.tau_var   = 0.018;
props.gp_var    = 0.020;
props.len_var   = 0.010;

fprintf('Configuration MCMC :\n');
fprintf('  Itérations totales : %d\n', config.iter_max);
fprintf('  Phase de burn-in : %d\n', config.burn_in);
fprintf('  Échantillons conservés : %d\n', config.samples_saved);

%% 3. Initialisation des paramètres
psi.alpha     = median(returns);
psi.nu        = log(var(returns));
psi.rho       = 0.92;
psi.tau_sq    = 0.12;
psi.sigma_g_sq = 0.8;
psi.length_scale = 1.1;

s_state = psi.nu * ones(N,1);  % État initial de la volatilité

% Préallocation des chaînes
chains = struct();
chains.alpha     = zeros(config.samples_saved, 1);
chains.nu        = zeros(config.samples_saved, 1);
chains.rho       = zeros(config.samples_saved, 1);
chains.tau_sq    = zeros(config.samples_saved, 1);
chains.sigma_g_sq = zeros(config.samples_saved, 1);
chains.length_scale = zeros(config.samples_saved, 1);
chains.posterior = zeros(config.samples_saved, 1);

accept_rates = zeros(6,1);

%% 4. Exécution de l'échantillonnage MCMC
fprintf('Démarrage de l’échantillonnage...\n');
current_psi = psi;
latent_vol = s_state;
idx_save = 0;

for iter = 1:config.iter_max
    if mod(iter, 500) == 0
        fprintf('  Itération %d/%d, taux d’acceptation moyen : %.1f%%\n', ...
                iter, config.iter_max, mean(accept_rates)*100);
    end

    % Mise à jour de chaque composante
    [current_psi.alpha, acc] = update_alpha(returns, latent_vol, current_psi, priors, props);
    accept_rates(1) = accept_rates(1) + acc;

    [current_psi.nu, latent_vol, acc] = update_nu_with_gp(returns, latent_vol, current_psi, priors, props, N);
    accept_rates(2) = accept_rates(2) + acc;

    [current_psi.rho, acc] = update_rho(latent_vol, current_psi, priors, props);
    accept_rates(3) = accept_rates(3) + acc;

    [current_psi.tau_sq, acc] = update_tau_sq(latent_vol, current_psi, priors, props);
    accept_rates(4) = accept_rates(4) + acc;

    [current_psi.sigma_g_sq, acc] = update_gp_variance(current_psi.nu, priors, props);
    accept_rates(5) = accept_rates(5) + acc;

    [current_psi.length_scale, acc] = update_length_scale(current_psi.nu, priors, props);
    accept_rates(6) = accept_rates(6) + acc;

    % Rééchantillonnage de l'état caché via FFBS
    latent_vol = sample_latent_volatility(returns, current_psi, latent_vol, N);

    % Stockage post-burn-in
    if iter > config.burn_in && mod(iter, config.thin_by) == 0
        idx_save = idx_save + 1;
        chains.alpha(idx_save)        = current_psi.alpha;
        chains.nu(idx_save)           = current_psi.nu;
        chains.rho(idx_save)          = current_psi.rho;
        chains.tau_sq(idx_save)       = current_psi.tau_sq;
        chains.sigma_g_sq(idx_save)   = current_psi.sigma_g_sq;
        chains.length_scale(idx_save) = current_psi.length_scale;
        chains.posterior(idx_save)    = compute_log_posterior(returns, latent_vol, current_psi, priors, N);
    end
end

fprintf('Estimation terminée.\n');

%% 5. Analyse des résultats
analyze_mcmc_output(chains, params_true, config, returns, latent_vol);

%% 6. Sauvegarde
save('sv_spm_estimation_results.mat', 'chains', 'params_true', 'config', 'returns', 'latent_vol');
fprintf('Résultats sauvegardés.\n');

Générateur de données simulate_sv_spatial_data.m

function [T, r, theta] = simulate_sv_spatial_data(T)
theta.alpha = 0.03;
theta.nu = -0.6;
theta.rho = 0.97;
theta.tau_sq = 0.06;
theta.sigma_g_sq = 0.6;
theta.length_scale = 0.9;

positions = (1:T)';
D = squareform(pdist(positions));
Sigma_nu = theta.sigma_g_sq * exp(-D.^2 ./ (2*theta.length_scale^2)) + 1e-6*eye(T);
nu_vec = chol(Sigma_nu,'lower') * randn(T,1);

s = zeros(T,1);
s(1) = nu_vec(1);
for t = 2:T
    s(t) = nu_vec(t) + theta.rho*(s(t-1)-nu_vec(t-1)) + sqrt(theta.tau_sq)*randn();
end

r = theta.alpha + exp(s/2).*randn(T,1);
fprintf('Données générées : T=%d\n', T);
end

Fonctions d’échantillonnage Metropolis-Hastings

% Mise à jour de alpha
function [a_new, accepted] = update_alpha(y, s, psi, pr, prop)
a_curr = psi.alpha;
a_prop = a_curr + sqrt(prop.alpha_var)*randn();
log_ratio = log_likelihood_alpha(y, s, a_prop) - log_likelihood_alpha(y, s, a_curr) + ...
            log_prior_alpha(a_prop, pr) - log_prior_alpha(a_curr, pr);
accepted = log(rand()) < log_ratio;
a_new = accepted ? a_prop : a_curr;
end

% Mise à jour de nu avec GP
function [nu_new, s_new, accepted] = update_nu_with_gp(y, s, psi, pr, prop, T)
nu_curr = psi.nu;
nu_prop = nu_curr + sqrt(prop.nu_var)*randn();

psi_temp = psi; psi_temp.nu = nu_prop;
log_ratio = log_likelihood_nu_gp(y, s, psi_temp, T) - log_likelihood_nu_gp(y, s, psi, T) + ...
            log_prior_nu(nu_prop, pr) - log_prior_nu(nu_curr, pr);
accepted = log(rand()) < log_ratio;

if accepted
    nu_new = nu_prop;
    s_new = sample_latent_volatility(y, psi_temp, s, T);
else
    nu_new = nu_curr;
    s_new = s;
end
end

% Mise à jour de rho
function [r_new, accepted] = update_rho(s, psi, pr, prop)
r_curr = psi.rho;
r_prop = max(min(r_curr + sqrt(prop.rho_var)*randn(), 0.995), -0.995);
log_ratio = log_likelihood_rho(s, psi, r_prop) - log_likelihood_rho(s, psi, r_curr) + ...
            log_prior_rho(r_prop, pr) - log_prior_rho(r_curr, pr);
accepted = log(rand()) < log_ratio;
r_new = accepted ? r_prop : r_curr;
end

% Mise à jour de tau_sq
function [t_new, accepted] = update_tau_sq(s, psi, pr, prop)
t_curr = psi.tau_sq;
log_t_prop = log(t_curr) + sqrt(prop.tau_var)*randn();
t_prop = exp(log_t_prop);
log_ratio = log_likelihood_tau(s, psi, t_prop) - log_likelihood_tau(s, psi, t_curr) + ...
            log_prior_tau(t_prop, pr) - log_prior_tau(t_curr, pr);
accepted = log(rand()) < log_ratio;
t_new = accepted ? t_prop : t_curr;
end

Rééchantillonnage de la volatilité latente (FFBS)

function s_out = sample_latent_volatility(y, psi, s_in, T)
s_out = zeros(T,1);
mu_filt = zeros(T,1); var_filt = zeros(T,1);

% Filtrage avant
mu_filt(1) = psi.nu;
var_filt(1) = psi.tau_sq;

for t = 2:T
    mu_pred = psi.nu + psi.rho*(s_in(t-1) - psi.nu);
    var_pred = psi.rho^2 * var_filt(t-1) + psi.tau_sq;
    
    innov = y(t) - psi.alpha;
    F = exp(s_in(t));
    gain = var_pred / (var_pred + F);
    mu_filt(t) = mu_pred + gain*(innov - mu_pred);
    var_filt(t) = (1 - gain)*var_pred;
end

% Échantillonnage arrière
s_out(T) = mu_filt(T) + sqrt(var_filt(T))*randn();
for t = T-1:-1:1
    J = psi.rho * var_filt(t) / (psi.rho^2 * var_filt(t) + psi.tau_sq);
    s_out(t) = mu_filt(t) + J*(s_out(t+1) - psi.nu - psi.rho*(s_out(t+1) - psi.nu));
end
end

Fonctions de vraisemblance et de prior

function ll = log_likelihood_alpha(y, s, a)
ll = -0.5 * sum((y - a).^2 ./ exp(s));
end

function lp = log_prior_alpha(a, pr)
lp = -0.5*(a - pr.alpha_mean)^2 / pr.alpha_var;
end

function lp = log_prior_rho(r, pr)
lp = (pr.rho_a - 1)*log(r + 1) + (pr.rho_b - 1)*log(1 - r);
end

function lp = log_prior_tau(t, pr)
lp = (pr.tau_shape - 1)*log(t) - pr.tau_rate / t;
end

Analyse des sorties MCMC

function analyze_mcmc_output(chains, truth, cfg, data, vol)
figure('Color','w','Position',[100 100 1300 900]);

% Tracés des chaînes
subplot(3,3,1); plot(chains.alpha, 'k'); hold on; yline(truth.alpha, 'r--');
title('Chaîne pour α'); xlabel('Itération'); ylabel('Valeur'); grid on;

subplot(3,3,2); plot(chains.rho, 'k'); hold on; yline(truth.rho, 'r--');
title('Chaîne pour ρ'); xlabel('Itération'); ylabel('Valeur'); grid on;

subplot(3,3,4); histogram(chains.alpha, 40, 'Normalization','pdf'); hold on;
xline(truth.alpha, 'r--'); title('Distribution a posteriori de α');

subplot(3,3,5); histogram(chains.rho, 40, 'Normalization','pdf'); hold on;
xline(truth.rho, 'r--'); title('Distribution a posteriori de ρ');

subplot(3,3,7); plot(exp(vol/2), 'b'); title('Volatilité estimée');
xlabel('Temps'); ylabel('Volatilité'); grid on;

subplot(3,3,8); autocorr(chains.alpha, 40); title('ACF de α');

sgtitle('Analyse bayésienne du modèle SV-SPM', 'FontSize',14);

% Résumé statistique
fprintf('\n=== Résultats de l’estimation ===\n');
fields = {'alpha','nu','rho','tau_sq','sigma_g_sq','length_scale'};
names = {'α','ν','ρ','τ²','σ_g²','ℓ'};

for k = 1:length(fields)
    chain_k = chains.(fields{k});
    m = mean(chain_k); sd = std(chain_k);
    ci = prctile(chain_k, [2.5 97.5]);
    true_val = truth.(fields{k});
    fprintf('%s : %.4f ± %.4f [%.4f, %.4f], vrai=%.4f\n', ...
            names{k}, m, sd, ci(1), ci(2), true_val);
end
end

Étiquettes: MCMC Bayesian inference Stochastic volatility Spatial prior MATLAB

Publié le 29 septembre à 18h45