poniedziałek, 9 czerwca 2025

Krótka analiza i prognoza premii za ryzyko S&P 500 (język R)

Powiedzmy, że chcemy oszacować premię za ryzyko amerykańskiej giełdy w roku 2025 i 2026. Określenie "premia za ryzyko" używam jako skrót na oznaczenie nadwyżki stopy zwrotu z indeksu (tu S&P 500) nad stopę zwrotu z 10-letnich obligacji skarbowych. Precyzyjnie przecież mówiąc o premii mamy na myśli oczekiwane lub wymagane wynagrodzenie za przyszłe ryzyko, czyli takie, którego okres jeszcze nie zaczął się. I może się zrealizować (wtedy albo stracimy, albo mniej zarobimy), albo nie zrealizować (zarobimy co najmniej tyle, ile wymagamy). Dodatnia premia za ryzyko wynika z tego, że inwestorzy są niechętni wobec ryzyka. To znaczy, że wyceniają niżej aktywa o wyższym ryzyku. Mechanizm ten pozwala łatwo zrozumieć, dlaczego oczekiwana (a więc i średnia) stopa zwrotu jest dodatnia, a nie zerowa, jak w hazardzie. Otóż cała tajemnica leży w tym, że to ludzie wyceniają aktywa, a nie natura. Jeżeli wielu inwestorów będzie wyceniać niżej akcje z powodu ich większego ryzyka, to przecież spółka nie przestaje zarabiać tyle co zarabiała, jej wartość księgowa się nie zmienia. Spadek ceny oznacza więc, że inwestorzy mogą więcej zarobić, sprzedając w przyszłości akcje. Ryzyko jest mimo wszystko miarą psychologiczną, a więc dość subiektywną - i to właśnie sprawia, że inwestowanie daje dużo większe możliwości niż hazard.

Historyczne roczne premie za ryzyko S&P 500 dla lat 1945-2024 biorę ze strony Damodarana. Średnia wyniosła 7,7%. To coś dużo biorąc pod uwagę, że średnia stopa wolna od ryzyka to ok. 5%. Porównałem więc te dane z S&P U.S. Equity Risk Premium Index. Obejmuje on zajęcie długiej pozycji w indeksie S&P 500 Futures Excess Return Index i krótkiej pozycji w indeksie S&P U.S. Treasury Bond Futures Excess Return Index [zob. S&P Factor Indices Methodology]. Od 2015 roku dostałem taki wykres:

A więc dane są poprawne, bo idealnie korelują. Oczywiście w tym okresie średnia była jeszcze wyższa i dla S&P U.S. Equity Risk Premium Index wyniosła 13,2%, a Damodarana 13,5%. 

Stabilność średniej

Pytanie brzmi czy średnia się zmieniała w ciągu tych 80 lat. Przez pierwsze 40 lat wyniosła ok. 8,6%, a kolejne niecałe 6,8%. Sprawdźmy czy możemy uznać to za coś więcej niż przypadek. 

Jeżeli dzielimy okres na dwie części, to najlepiej użyć testu Chowa. W tym artykule pokazałem, jak go wykonać w R.

if (require("strucchange")==FALSE) {

  install.packages("strucchange")

  library("strucchange")

}

sctest(premia ~ 1, type="Chow", point=40)

	Chow test

data:  premia ~ 1
F = 0.18, p-value = 0.7

Jak widać nie ma podstaw do odrzucenia hipotezy, że średnia jest stała. Wykonałem dodatkowo najróżniejsze testy (ADF, KPSS, Bai-Perrona, CUSUM, MOSUM) i nie wykryłem niestacjonarności czy niestabilności. 

Modelowanie

Jeżeli średnia się nie zmienia, to trzeba sprawdzić autokorelację. Najpierw ACF:

acf(premia, 15)

Do dziś nie mogę z tego, że R pokazuje bez sensu ten zerowy rząd. Głupota. W cząstkowej jest już normalnie od pierwszego rzędu: 

pacf(premia, 15)

W Dodatku do tego artykułu zrobiłem analogiczną analizę dla nadwyżki nad bonami. Tam PACF 4-tego rzędu jest na granicy istotności. Tutaj natomiast nie można mówić o jakiejkolwiek istotności. Z tego z kolei wynika, że obligacje skarbowe mają swój udział w cyklu 4-letnim (bo je odjęliśmy). 

Mimo wszystko sprawdzę model AR(4) i jego stabilność. W R kod będzie następujący:

  # Pierwszy model - arima

  premia_model_arma1 <- arima(

    premia,

    order           = c(4, 0, 0),          # ARMA(4, 0, 0)

    include.mean    = TRUE,                # estymuj μ

    fixed           = c(0, 0, 0, NA, NA),  # φ1=0, φ2=0, φ3=0, φ4=estymuj, μ=estymuj

    transform.pars  = FALSE                # aby fixed=0 było literalne

  )

  summary(premia_model_arma1)

Coefficients:
      ar1  ar2  ar3    ar4  intercept
        0    0    0  0.205      0.079
s.e.    0    0    0  0.111      0.026

sigma^2 estimated as 0.0344:  log likelihood = 21.16,  aic = -36.33

Gdyby zastosować taki model, dostalibyśmy rzeczywiście średnią 7,9% (+/- 2,6%), ale dodatkowo współczynnik 4-tego rzędu 0,205 (+/- 0,11).

Aby sprawdzić stabilność tego modelu, trzeba niestety zamienić na formę typu:

premia4 <- tail(zmienna, -4)

premia_4 <- head(zmienna, -4)

premia.model <- premia4 ~ premia_4

i przykładowo, żeby użyć testu CUSUM-Score, wpisać:

par(mfrow=c(3,1), mar=c(2.5,5,1.5,3))

cus = efp(premia.model, type="Score-CUSUM")

plot(cus, functional=NULL)

Za jednym zamachem mamy tu sprawdzenie stabilności wszystkich współczynników, w tym wariancji. Brak jakichkolwiek wychyleń (byłyby zaznaczenia) świadczy o stabilności. 

To jednak tylko punkt wyjścia, bo znacznie lepszym sposobem jest użycie autoarfima() w rugarch lub auto.arima() w forecast. Wybrałem ten pierwszy, bo można w nim ustawić metodę "full". Ten argument  omówiłem w tym artykule. Sprawdziłem więc w autoarfima max ARMA(7, 7) w metodzie full. Wybrałem rozkład "std", bo dawał najlepsze rezultaty, jeśli chodzi o statystyki dopasowania. Wadą rugarch jest czasochłonność obliczeń, dlatego koniecznie trzeba używać argumentu cluster tam gdzie się da. Ponadto wybrałem solver "nlminb", ponieważ wydaje się dość efektywny. Użyłem funkcji dwukrotnie - wg kryterium BIC i AIC (HQIC nie używam, bo danych jest za mało, zob. tu). Okazało się, że oba dały ten sam model. Zwracam też uwagę, że nie stosuję tutaj modelu GARCH ze względu na małą liczbę danych. Efekt:

# Drugi model - autoarfima (rugarch)

    if (require("rugarch")==FALSE) {

      install.packages("rugarch")

    }

    library("rugarch")  

# Kryterium BIC

  klaster <- makeCluster(detectCores() - 1)

  premia_model_arma_bic <- autoarfima(data = premia, ar.max = 7, ma.max = 7, criterion = "BIC",

             method = "full", arfima = FALSE, include.mean = NULL,

             distribution.model = "std", cluster = klaster, external.regressors = NULL,

             solver = "nlminb")

  stopCluster(klaster)

  premia_model_arma_bic

  # Kryterium AIC

  klaster <- makeCluster(detectCores() - 1)

  premia_model_arma_aic <- autoarfima(data = premia, ar.max = 7, ma.max = 7, criterion = "AIC",

                                      method = "full", arfima = FALSE, include.mean = NULL,

                                      distribution.model = "std", cluster = klaster, external.regressors = NULL,

                                      solver = "nlminb")

  stopCluster(klaster)

  premia_model_arma_aic

  premia_model_arma2 <- premia_model_arma_aic


*----------------------------------*

*          ARFIMA Model Fit        *
*----------------------------------*
Mean Model	: ARFIMA(7,0,4)
Distribution	: std 

Optimal Parameters
------------------------------------
       Estimate  Std. Error    t value Pr(>|t|)
ar1     0.44901    0.000926    484.974        0
ar2     0.00000          NA         NA       NA
ar3     0.00000          NA         NA       NA
ar4     0.00000          NA         NA       NA
ar5    -0.22529    0.000465   -484.974        0
ar6     0.00000          NA         NA       NA
ar7     0.34287    0.000027  12785.413        0
ma1    -1.28980    0.000061 -21191.451        0
ma2     0.00000          NA         NA       NA
ma3     0.61606    0.000029  21191.451        0
ma4     0.44741    0.000021  21191.451        0
sigma   0.13607    0.005403     25.184        0
shape 100.00000    0.004585  21809.383        0

Robust Standard Errors:
       Estimate  Std. Error    t value Pr(>|t|)
ar1     0.44901    2.324983    0.19312  0.84686
ar2     0.00000          NA         NA       NA
ar3     0.00000          NA         NA       NA
ar4     0.00000          NA         NA       NA
ar5    -0.22529    1.166585   -0.19312  0.84686
ar6     0.00000          NA         NA       NA
ar7     0.34287    0.000385  891.10681  0.00000
ma1    -1.28980    0.005203 -247.88844  0.00000
ma2     0.00000          NA         NA       NA
ma3     0.61606    0.000766  804.54128  0.00000
ma4     0.44741    0.001619  276.38488  0.00000
sigma   0.13607    0.619235    0.21974  0.82608
shape 100.00000    1.325372   75.45050  0.00000

LogLikelihood : 46.12 

Information Criteria
------------------------------------
                     
Akaike       -0.95299
Bayes        -0.71479
Shibata      -0.97067
Hannan-Quinn -0.85749

Weighted Ljung-Box Test on Standardized Residuals
------------------------------------
                         statistic p-value
Lag[1]                    0.000611  0.9803
Lag[2*(p+q)+(p+q)-1][32] 11.645908  1.0000
Lag[4*(p+q)+(p+q)-1][54] 17.816677  0.9976

H0 : No serial correlation

Weighted Ljung-Box Test on Standardized Squared Residuals
------------------------------------
                        statistic p-value
Lag[1]                     0.4785  0.4891
Lag[2*(p+q)+(p+q)-1][2]    0.7811  0.5750
Lag[4*(p+q)+(p+q)-1][5]    2.3214  0.5447


ARCH LM Tests
------------------------------------
             Statistic DoF P-Value
ARCH Lag[2]      1.519   2  0.4678
ARCH Lag[5]      2.647   5  0.7542
ARCH Lag[10]     9.429  10  0.4920

Nyblom stability test
------------------------------------
Joint Statistic:  231429
Individual Statistics:             
ar1   0.09969
ar5   0.05997
ar7   0.01355
ma1   0.03352
ma3   0.11951
ma4   0.09983
sigma 0.13154
shape 0.42456

Asymptotic Critical Values (10% 5% 1%)
Joint Statistic:     	 1.89 2.11 2.59
Individual Statistic:	 0.35 0.47 0.75

BIC i AIC wskazały ten sam model: AR o rzędach 1, 5 i 7; MA o rzędach 1, 3 i 4. W nieodpornych statystykach (tj. przy założeniu danego rozkładu, tu t-Studenta) wszystkie są istotne. Jednak statystyki odporne (na odchylenia od założonego rozkładu reszt) wskazują jedynie istotność rzędu 7 dla AR, a dla MA wszystkie są istotne. Pozostałe statystyki są okej, wszystkie współczynniki są stabilne, chociaż znowu cały model mocno niestabilny (pewnie niezbyt idealny rozkład).

Test MCS

Aby się upewnić, że na pewno drugi model jest lepszy od pierwszego, użyjemy testu MCS, który omówiłem w poprzednim artykule. Tak jak wtedy za stratę uznam sytuację, gdy znak prognozy in-sample nie zgadza się ze znakiem rzeczywistej wartości.

  #MCS test

  Funkcja_strat <- function(rzeczywiste, prognoza) {

    roznica <- rzeczywiste - prognoza

    strata <- ifelse(sign(rzeczywiste) != sign(prognoza), abs(roznica), 0)

    return(strata)

  }

  # Obliczenie funkcji strat dla każdego modelu (in-sample)

  straty1 <- Funkcja_strat(premia, as.numeric(fitted(premia_model_arma1)))

  straty2 <- Funkcja_strat(premia, premia_model_arma2$fit@fit$fitted.values) 

  straty <- cbind(straty1, straty2)

  mcs_stationary <- mcsTest(straty, alpha = 0.05, nboot = 500, nblock = 10, boot = "stationary")

  print(mcs_stationary)

$includedR
[1] 1 2

$pvalsR
     [,1]
[1,] 0.23
[2,] 1.00

$excludedR
NULL

$includedSQ
[1] 1 2

$pvalsSQ
     [,1]
[1,] 0.23
[2,] 1.00

$excludedSQ
NULL

Test MCS nie pozwolił wykluczyć żadnego z dwóch modeli, ale wskazuje na zachowanie drugiego, tj. z autoarfima, więc go wybierzemy.

Wykres modelu

Wobec tego sprawdźmy jak te dopasowania dla najlepszego modelu wyglądają:

  # Wartości dopasowane

  dopas <- premia_model_arma2$fit@fit$fitted.values

  okres <- c(1945:2024)

  # Wykres najlepszego modelu z prawdziwymi obserwacjami

  plot(x=okres, y=premia, type="l", ylab = "")

  lines(x=okres, y=dopas, col="red")

  abline(h=0, col="grey")

  abline(h=mean(premia), col="blue", lwd=2)

  legend("topright", legend=c("premia za ryzyko S&P 500", "prognoza in-sample", "średnia premia za ryzyko"), 

         col=c("black","red", "blue"), lwd=2, cex=0.6)



Prognoza na 2025-2026

Niestety nie mam wiedzy, czy można od razu w rugarch robić prognozy mając już najlepszy model z autoarfima. jestem niemal pewny, że jest sposób, ale szybciej jest mi tak zrobić. Dlatego robię to "na piechotę". Obliczam arfimaspec(), z tego arfimafit i dopiero z tego arfimaforecast(). W arfimaspec() wpisujemy normalnie maksymalne rzędy ARMA, ale za to dodajemy argument fixed.pars = list(ar2=0, ar3=0), który właśnie kontroluje ustawienie konkretnych współczynników. 

  rzadAR <- premia_model_arma2$fit@model$modelinc[2]

  rzadMA <- premia_model_arma2$fit@model$modelinc[3]

  premia_spec <- arfimaspec(mean.model = list(armaOrder = c(rzadAR, rzadMA), include.mean = TRUE,

                                              arfima = FALSE, external.regressors = NULL), distribution.model = "std",

                            start.pars = list(), fixed.pars = list(ar2=0,                                                                ar3=0)) 

  premia_fit <- arfimafit(spec = premia_spec, data = premia, solver = "nlminb")

  prognoza <- arfimaforecast(premia_fit, n.ahead = 2)

*----------------------------------*
*        ARFIMA Model Forecast     *
*----------------------------------*

Horizon: 2
Roll Steps: 0
Out of Sample: 0

0-roll forecast: 
    T+1     T+2 
0.14876 0.02238


Na rok 2025 dostajemy premię niecałe 15% i na 2026 2,2%. Dodajemy nowe prognozy do wykresu:

  nowa_prognoza <- as.numeric(prognoza@forecast$seriesFor)
  
  # Wykres najlepszego modelu z prawdziwymi obserwacjami
  plot(x=c(okres, 2025, 2026), y=c(premia, NA, NA), type="l", ylab = "")
  lines(x=c(okres, 2025, 2026), y=c(dopas, NA, NA), col="red")
  lines(x=c(okres, 2025, 2026), y=c(rep(NA, length(premia)-1), tail(dopas, 1), nowa_prognoza), col="green", lwd=2, type="o", cex=0.4)
  abline(h=0, col="grey")
  abline(h=mean(premia), col="blue", lwd=2)
  legend("topright", legend=c("premia za ryzyko S&P 500", "prognoza in-sample", "prognoza poza próbą", "średnia premia za ryzyko"), 
         col=c("black","red", "green", "blue"), lwd=2, cex = 0.6)


Prognoza krocząca

Wygląda to całkiem ciekawie, ale mamy tu prognozy in-sample, a nie out-of-sample. Aby uzyskać bardziej realny obraz dobroci modelu, wykonam prognozy kroczące za pomocą arfimaroll(). Jest to odpowiednik ugarchroll bez procesu GARCH (zob. ten art.). Zobaczmy 10 ostatnich kroczących-nawijanych prognoz, tj. od roku 2015:

  # Prognoza krocząca-nawijana
  klaster <- makeCluster(detectCores() - 1)
  premia_prognoza_roll <- arfimaroll(spec = premia_spec, data = premia, forecast.length = 10,
                                     refit.every = 1, refit.window = "recursive",
                                     window.size = NULL, solver = "nlminb", fit.control = list(ar2=0, ar3=0),
                                     cluster=klaster)
  premia_prognoza_roll <- resume(premia_prognoza_roll)
  stopCluster(klaster)
  prognoza_roll <- premia_prognoza_roll@forecast$density$Mu
  okres_prognozy_roll <- c(2015:2024)
  names(prognoza_roll) <- okres_prognozy_roll
  prognoza_roll 

  
  # Wykres z prognozą kroczącą
  plot(x=c(okres, 2025, 2026), y=c(premia, NA, NA), type="l", ylab = "", xlim=c(2015, 2026))
  lines(x=c(okres, 2025, 2026), y=c(dopas, NA, NA), col="red")
  lines(x=c(okres, 2025, 2026), y=c(rep(NA, length(premia)-1), tail(prognoza_roll, 1), nowa_prognoza), col="green", lwd=2, type="o", cex=0.4)
  lines(x=c(NA, okres_prognozy_roll), y=c(tail(dopas, 1), prognoza_roll), col="green", lwd=2, type="o", cex=0.4)
  abline(h=0, col="grey")
  abline(h=mean(premia), col="blue", lwd=2)
  legend("topright", legend=c("premia za ryzyko S&P 500", "prognoza in-sample", "prognoza poza próbą", "średnia premia za ryzyko"), 
         col=c("black","red", "green", "blue"), lwd=2, cex = 0.6)
  



Prognoza sugeruje spadek premii w 2025 i może wzrost 2026. Zauważmy tylko, że spadek nie poniżej zera, tylko poniżej średniej. No i oczywiście nie mówi to nic na temat zmian w obligacjach. 

Rozkład częstości i gęstości

Skupiając się na prognozowaniu łatwo jednak zgubić szeroki obraz, tj. ekonomiczne znaczenie premii za ryzyko. Dlatego musimy mieć w pamięci, że prawdopodobnie tak będzie, ale faktycznie może się zdarzyć niespodzianka. Ostatnie lata przyzwyczajają nie tylko zwykłych ludzi, ale i ekspertów do tego, że co roku z indeksów USA jest dodatnia stopa zwrotu. Od 2009 r do 2024, czyli przez 16 lat zdarzyły się zaledwie 2 ujemne roczne (całkowite) stopy zwrotu! Tworzy to złudzenie, że ryzyka nie ma, bo wystarczy przeczekać parę lat. Jednak ekonomia robi swoje i musi to jakoś wyrównać. Dlatego muszą nie tylko przydarzać się szokowe, bardzo silne i szybkie spadki (prędzej czy później). Muszą występować dłuższe okresy, w których obligacje skarbowe przyniosą większe zwroty od akcji. Gdyby nie ich nie było, to rzeczywiście nie byłoby ryzyka.

Nawet historyczna ujemna skośność (średnia < mediana) i kurtoza w rozkładzie premii o tym świadczy, zobaczmy:

  
  # Zapisujemy stare ustawienia graficzne
  old_par <- par(no.readonly = TRUE)
  
  # Dajemy więcej miejsca po prawej (dół, lewo, góra, prawo)
  par(mar = c(3.5, 5, 4, 5) - 0.1, mgp = c(2, 1, 0))
  
  # 1. Obliczamy histogram, ale nie rysujemy
  h <- hist(premia,
            breaks = 10,
            plot   = FALSE)
  
  # 2. Estymujemy KDE (Kernel Density Estimation)
  d <- density(premia,
               kernel = "gaussian",
               bw     = "nrd0")
  
  # 3. Rysujemy histogram na lewą oś Y (counts)
  plot(h,
       freq   = TRUE,
       col    = "lightgray",
       border = "white",
       xlab   = "Premia",
       ylab   = "Częstość",
       xlim  = range(h$breaks, d$x),
       ylim   = c(0, max(h$counts) * 1.1),
       main   = "Histogram i gęstość rozkładu – lewa oś: częstość, prawa oś: gęstość")
  
  # 4. Nałóż KDE na ten sam wykres
  par(new = TRUE)
  plot(d$x, d$y,
       type  = "l",
       lwd   = 2,
       axes  = FALSE,
       xlab  = "",
       ylab  = "",
       ylim  = c(0, max(d$y) * 1.1))
  
  # 5. Dodajemy prawą oś Y (density)
  axis(side = 4,
       at   = pretty(c(0, max(d$y) * 1.1)))
  mtext("Gęstość (KDE)", side = 4, line = 3)
  
  # 7. Przywracamy poprzednie ustawienia
  par(old_par)
  



Jeżeli rynek jest efektywny w długim okresie, to powinniśmy się spodziewać, że podobny rozkład wystąpi dla okresów wieloletnich. Zobaczmy dla 4-letnich. Zagregujmy stopy zwrotu:

  grupy <- ceiling(1:length(premia)/4)
  premia_4lata <- exp(tapply(X = log(1+premia), INDEX = grupy, FUN = sum)) - 1


I powtarzamy kod dla wykresu rozkładu częstości i gęstości, zastępując zmienną premia przez premia_4lata. Dostajemy:


Rozkład gęstości 4-letnich premii zawiera niewiele danych, ale też występuje na nim skośność. 


Dodatek dla premii za ryzyko nad bonami skarbowymi 3-miesięcznymi

Powtórzmy całą procedurę dla nadwyżki ponad bony skarbowe. Będzie to tylko premia bez ryzyka rynkowego, albowiem zawiera ryzyko długoterminowych obligacji skarbowych w postaci zmienności stopy procentowej.
 
Stabilność średniej

Podobny rezultat jak w przypadku premii nad obligacjami uzyskałem dla premii nad bonami:

	Chow test

data:  premia ~ 1
F = 0.22, p-value = 0.6

ACF i PACF

acf(premia, 15)


pacf(premia, 15)

Okazuje się, że jedynie ta 4-tego rzędu jest na granicy istotności (przekracza 0,2). To potwierdzałoby istnienie 4-letnich cykli, chociaż wykorzystanie tego fenomenu na pewno nie jest proste. 

Modelowanie
Sprawdzamy zatem AR(4). Efekt:


Gdyby zastosować taki model, dostalibyśmy średnią 9% (+/- 2,4%) -  faktycznie, średnia arytmetyczna tej premii wynosi ok. 9%. Dodatkowo współczynnik 4-tego rzędu 0,24 (+/- 0,11).

*----------------------------------*
*          ARFIMA Model Fit        *
*----------------------------------*
Mean Model	: ARFIMA(4,0,5)
Distribution	: std 

Optimal Parameters
------------------------------------
        Estimate  Std. Error    t value Pr(>|t|)
mu      0.161833    0.000000 2959700.74        0
ar1     0.151799    0.000009   17708.57        0
ar2     0.000000          NA         NA       NA
ar3     0.000000          NA         NA       NA
ar4     0.848201    0.000052   16219.51        0
ma1    -0.241419    0.000017  -13997.21        0
ma2    -0.672269    0.000053  -12747.99        0
ma3     0.327000    0.000022   14596.10        0
ma4    -1.166367    0.000080  -14596.10        0
ma5     0.063512    0.000001   68899.16        0
sigma   0.119640    0.000749     159.68        0
shape 100.000000    0.776030     128.86        0

Robust Standard Errors:
        Estimate  Std. Error    t value Pr(>|t|)
mu      0.161833    0.000002 66502.9198 0.000000
ar1     0.151799    0.000199   764.2664 0.000000
ar2     0.000000          NA         NA       NA
ar3     0.000000          NA         NA       NA
ar4     0.848201    0.002818   301.0231 0.000000
ma1    -0.241419    0.001761  -137.1293 0.000000
ma2    -0.672269    0.002135  -314.8811 0.000000
ma3     0.327000    0.001856   176.1863 0.000000
ma4    -1.166367    0.007626  -152.9473 0.000000
ma5     0.063512    0.000124   512.7090 0.000000
sigma   0.119640    0.037396     3.1992 0.001378
shape 100.000000   41.141275     2.4306 0.015072

LogLikelihood : 56.39 

Information Criteria
------------------------------------
                     
Akaike       -1.15972
Bayes        -0.86197
Shibata      -1.18658
Hannan-Quinn -1.04034

Weighted Ljung-Box Test on Standardized Residuals
------------------------------------
                         statistic p-value
Lag[1]                      0.3203  0.5714
Lag[2*(p+q)+(p+q)-1][26]   11.3668  0.9999
Lag[4*(p+q)+(p+q)-1][44]   21.5256  0.5907

H0 : No serial correlation

Weighted Ljung-Box Test on Standardized Squared Residuals
------------------------------------
                        statistic p-value
Lag[1]                      1.925  0.1653
Lag[2*(p+q)+(p+q)-1][2]     2.722  0.1669
Lag[4*(p+q)+(p+q)-1][5]     3.404  0.3381


ARCH LM Tests
------------------------------------
             Statistic DoF P-Value
ARCH Lag[2]      3.306   2  0.1915
ARCH Lag[5]      4.479   5  0.4827
ARCH Lag[10]     7.391  10  0.6881

Nyblom stability test
------------------------------------
Joint Statistic:  669.4
Individual Statistics:              
mu    0.029974
ar1   0.017018
ar4   0.027913
ma1   0.009996
ma2   0.036438
ma3   0.056745
ma4   0.087832
ma5   0.021669
sigma 0.093384
shape 0.629655

Asymptotic Critical Values (10% 5% 1%)
Joint Statistic:     	 2.29 2.54 3.05
Individual Statistic:	 0.35 0.47 0.75

Okazało się, że najlepszym modelem nie jest wcale AR(4), ale ARMA(4, 5), przy usuniętych ar2 i ar3.

Z poszczególnych współczynników jedynie shape, czyli kształt rozkładu reszt, jest trochę niestabilny. Wynika to po prostu z tego, że rozkład t-Studenta nie jest idealnym rozkładem tutaj, ale nie jest to wcale tak istotne. Ważne, że inne współczynniki są stabilne. Jednak całkowity model okazuje się niestabilny, bo Joint Statistic = 669. To może być jednak efekt tego kształtu rozkładu.

Test MCS

$includedR
[1] 2

$pvalsR
     [,1]
[1,]    0
[2,]    1

$excludedR
[1] 1

$includedSQ
[1] 2

$pvalsSQ
     [,1]
[1,]    0
[2,]    1

$excludedSQ
[1] 1

Test MCS potwierdza jednoznacznie, że drugi model (uzyskany z autoarfima) jest lepszy, albowiem każe odrzucić pierwszy model.

Prognozy kroczące i dwie nowe




Prognozę na 2025 czytałbym tak jak na 2020 - może będzie poniżej średniej, ale dodatnia. 

Rozkład częstości i gęstości

Roczne premie:


4-letnie premie:


Porównując ze sobą obydwie premie, to co się rzuca w oczy, to wpływ obligacji skarbowych na 4-letnią cykliczność stopy zwrotu. Ponadto odwrotne prognozy dla nich pokazują, że obligacje dywersyfikują ryzyko akcji.

sobota, 3 maja 2025

Modele w zbiorze ufności w rugarch (język R)

Jeśli powiem, że każda prognoza gospodarcza czy giełdowa jest okraszona niepewnością, to będzie to banał. Ale jeśli powiem, że celem prognozy powinno być nie tyle odgadnięcie przyszłej wartości, co minimalizacja jej niepewności, to już banałem nie jest. Dla uproszczenia założymy, że niepewność to przedział ufności dla prognozy naszego modelu. Im więcej informacji zawiera model, tym przedział ufności staje się mniejszy, zbliżając się tym samym do prognozy punktowej. Widzimy więc, że idea nie jest wcale banalna: należy zebrać jak najwięcej istotnych informacji, aby zawężać przedział prognozy, zmniejszając tym samym ryzyko.

Analogicznie do przedziału ufności, Hansen et al.* zaproponowali zbiór ufności dla wielu modeli: z całej puli testowanych modeli, poszukiwane są te najlepsze, które wpadają do zbioru ufności. Im więcej posiadamy informacji, tym mniejszy będzie ten zbiór, zbliżając się tym samym do jednego najlepszego modelu.

Standardową metodą wyboru najlepszego modelu jest użycie kryterium informacyjnego, np. AIC, BIC czy HQIC. Niestety nie uwzględniają one niepewności czy przypadkowości parametrów. Z tym się ciągle zmagamy. Przykładowo, mam zawsze wątpliwości co do używania modeli parametrycznych, ponieważ wydają się one sztuczne, realizując uśrednianie na siłę. Dlatego tak ważne są testy stabilności parametrów. Ale stabilność to dopiero początek, bo: 
(1) jeśli spośród wielu modeli różnica w kryterium informacyjnym jest niewielka, to może ona wynikać po prostu z przypadku,
(2) dodanie tylko jednej obserwacji bardzo często zmienia wybór modelu.

W rugarch jest funkcja mcsTest (Model Confidence Set - MCS), która testuje, czy dany model należy zachować w zbiorze ufności czy go odrzucić. Ma ona następujące argumenty:

- losses: macierz lub ramka danych z wartościami strat dla każdego modelu (każda kolumna odpowiada jednemu modelowi). To w jaki sposób definiujemy straty, zależy od nas. W przypadku rozkładów symetrycznych, najczęściej stosuje się kwadraty odchyleń od prognozy lub wartości absolutne tych odchyleń. Dla rozkładów niesymetrycznych stosować można semi-odchylenia albo miary oparte na wartości zagrożonej.  

- alpha: poziom istotności (domyślnie 0.05).

- nboot: liczba replikacji bootstrapowych (domyślnie 100). Bootstrap to procedura wielokrotnego losowania próbki ze zwracaniem. nboot to właśnie liczba tych losowań.

- nblock: długość bloku dla bootstrappu blokowego (domyślnie 1). Jeżeli obserwacje mogą być zależne od siebie, to bootstrap musi losować blok występujących po sobie x obserwacji. Jeżeli uważamy, że każda obserwacja jest niezależna od innej, to x = 1. Im rząd autokorelacji większy, tym blok powinien być naturalnie też większy.

- boot: rodzaj bootstrapu ("stationary" lub "block"). Chociaż w obydwu przypadkach mamy do czynienia z blokami, to metoda blokowa, "block", oznacza, że całą próbę dzielimy na stałą liczbę przedziałów, tak że każdy przedział składa się bloku obserwacji o długości wyznaczonej dokładnie przez nblock. Potem dopiero losujemy blok, powtarzamy to losowanie dokładnie nboot razy. Natomiast  metoda stacjonarna, "stationary", tworzy bloki o losowej długości ze średnią równą nblock. Jeśli występuje autokorelacja w szeregu czasowym, to blok nie może być za krótki. Najczęściej "stationary" jest lepszy od "block".


Co właściwie robi ten test? W skrócie, kiedy mamy macierz strat dla każdego modelu, liczymy różnice między każdym modelem (prognozą każdego modelu) i są one odpowiednio uśredniane. Tworzymy bloki bootstrapowe i dla każdego bloku obliczamy średnie różnice między średnią stratą w bloku, średnią stratą dla całego modelu i średnią stratą ze wszystkich modeli. Dla każdego modelu będą różne wartości tworzące rozkład różnic. Wartości zbyt odchylone na plus (tzn. prawostronne ogony rozkładu) zostają odrzucone i tak uzyskujemy zbiór ufności. 

Oczywiście najpierw sami budujemy funkcję strat. Ja użyłem definicji najbardziej dla mnie intuicyjnej: jeżeli znak prognozy różni się od znaku wartości rzeczywistej, to bierzemy moduł z różnicy między nimi. Jeżeli znak jest ten sam, to strata wynosi zero. 

Przetestujmy tę funkcję. Będziemy używać mcsTest na zbiorze uczącym. Reguły używania poniższych funkcji w rugarch omówiłem szczegółowo tutaj
Stwierdziłem wcześniej, że próba między 300 a 400 tygodniowych danych giełdowych dla GARCH wydaje się już w miarę odpowiednia. Zrobię symulację EGARCH o próbie równej 330. Powiedzmy, że prawdziwy proces to EGARCH(1, 1)-ARMA(3, 1). Obok niego mamy też fałszywe - wszystkie EGARCH: ARMA(0, 0), ARMA(1, 0), ARMA(2, 0), ARMA(3, 0). Zobaczmy które z nich zostaną wykluczone przez mcsTest, a które przyjęte do zbioru ufności. Zrobimy test w dwóch wariantach: blokowy i stacjonarny. Ustawienia: nblock = 20 oraz nboot = 1000. 

library("rugarch")
library("xts")

# 1. Definicje stałych parametrów
stale_3_1 <- list(
  omega  = 1,
  alpha1 = 0.3,
  beta1  = 0.2,
  mu     = 0.0001,
  ar1    = -1,
  ar2    = 0.5,
  ar3    = 0.7,
  ma1    = 0.4,
  gamma1 = 0.05
)

stale_0_0 <- list(
  omega  = 1,
  alpha1 = 0.3,
  beta1  = 0.2,
  mu     = 0.0001,
  gamma1 = 0.05
)

stale_1_0 <- list(
  omega  = 1,
  alpha1 = 0.3,
  beta1  = 0.2,
  mu     = 0.0001,
  ar1    = -1,
  gamma1 = 0.05
)

stale_2_0 <- list(
  omega  = 1,
  alpha1 = 0.3,
  beta1  = 0.2,
  mu     = 0.0001,
  ar1    = -1,
  ar2    = 0.5,
  gamma1 = 0.05
)

stale_3_0 <- list(
  omega  = 1,
  alpha1 = 0.3,
  beta1  = 0.2,
  mu     = 0.0001,
  ar1    = -1,
  ar2    = 0.5,
  ar3    = 0.7,
  gamma1 = 0.05
)

stale_0_1 <- list(
  omega  = 1,
  alpha1 = 0.3,
  beta1  = 0.2,
  mu     = 0.0001,
  ma1    = 0.4,
  gamma1 = 0.05
)

# 2. Specyfikacje modeli EGARCH(1,1) z różnymi specyfikacjami średniej:
spec_3_1 <- ugarchspec(
  variance.model = list(model = "eGARCH", garchOrder = c(1,1)),
  mean.model     = list(armaOrder = c(3,1), include.mean = TRUE),
  distribution.model = "norm",
  fixed.pars = stale_3_1
)

spec_0_0 <- ugarchspec(
  variance.model = list(model = "eGARCH", garchOrder = c(1,1)),
  mean.model     = list(armaOrder = c(0,0), include.mean = TRUE),
  distribution.model = "norm",
  fixed.pars = stale_0_0
)

spec_1_0 <- ugarchspec(
  variance.model = list(model = "eGARCH", garchOrder = c(1,1)),
  mean.model     = list(armaOrder = c(1,0), include.mean = TRUE),
  distribution.model = "norm",
  fixed.pars = stale_1_0
)

spec_2_0 <- ugarchspec(
  variance.model = list(model = "eGARCH", garchOrder = c(1,1)),
  mean.model     = list(armaOrder = c(2,0), include.mean = TRUE),
  distribution.model = "norm",
  fixed.pars = stale_2_0
)

spec_3_0 <- ugarchspec(
  variance.model = list(model = "eGARCH", garchOrder = c(1,1)),
  mean.model     = list(armaOrder = c(3,0), include.mean = TRUE),
  distribution.model = "norm",
  fixed.pars = stale_3_0
)

spec_0_1 <- ugarchspec(
  variance.model = list(model = "eGARCH", garchOrder = c(1,1)),
  mean.model     = list(armaOrder = c(0,1), include.mean = TRUE),
  distribution.model = "norm",
  fixed.pars = stale_0_1
)

# 3. Symulacja 330 obserwacji według poprawnego modelu (ARMA(3,1))
set.seed(765)
symulacja <- ugarchpath(spec_3_1, n.sim = 330)
dane_symulowane <- as.numeric(fitted(symulacja))

# 4. Dopasowanie modeli do całego szeregu (in-sample)
dopasowanie_3_1 <- ugarchfit(spec = spec_3_1, data = dane_symulowane)
dopasowanie_0_0 <- ugarchfit(spec = spec_0_0, data = dane_symulowane)
dopasowanie_1_0 <- ugarchfit(spec = spec_1_0, data = dane_symulowane)
dopasowanie_2_0 <- ugarchfit(spec = spec_2_0, data = dane_symulowane)
dopasowanie_3_0 <- ugarchfit(spec = spec_3_0, data = dane_symulowane)
dopasowanie_0_1 <- ugarchfit(spec = spec_0_1, data = dane_symulowane)

# 5. Zapisanie wyników dopasowania w liście (dla przejrzystości)
dopasowania <- list(
  model_3_1 = dopasowanie_3_1,
  model_0_0 = dopasowanie_0_0,
  model_1_0 = dopasowanie_1_0,
  model_2_0 = dopasowanie_2_0,
  model_3_0 = dopasowanie_3_0,
  model_3_0 = dopasowanie_0_1
)
print("Dopasowane modele:")
print(names(dopasowania))

# 6. Definicja funkcji strat:
Funkcja_strat <- function(rzeczywiste, prognoza) {
  roznica <- rzeczywiste - prognoza
  strata <- ifelse(sign(rzeczywiste) != sign(prognoza), abs(roznica), 0)
  return(strata)
}

# 7. Obliczenie funkcji strat dla każdego modelu (in-sample)
strata_3_1 <- Funkcja_strat(dane_symulowane, fitted(dopasowanie_3_1))
strata_0_0 <- Funkcja_strat(dane_symulowane, fitted(dopasowanie_0_0))
strata_1_0 <- Funkcja_strat(dane_symulowane, fitted(dopasowanie_1_0))
strata_2_0 <- Funkcja_strat(dane_symulowane, fitted(dopasowanie_2_0))
strata_3_0 <- Funkcja_strat(dane_symulowane, fitted(dopasowanie_3_0))
strata_0_1 <- Funkcja_strat(dane_symulowane, fitted(dopasowanie_0_1))

# Łączenie strat w macierz – wiersze odpowiadają obserwacjom, kolumny modelom
straty <- cbind(
  model_3_1 = strata_3_1,
  model_0_0 = strata_0_0,
  model_1_0 = strata_1_0,
  model_2_0 = strata_2_0,
  model_3_0 = strata_3_0,
  model_3_0 = strata_0_1
)

# 8. Usunięcie wierszy składających się tylko z zer
straty <- straty[rowSums(straty != 0) > 0, ]
straty <- coredata(straty)

# 9. Test MCS
# 9.1. Wariant 1: stacjonarny
mcs_stationary <- mcsTest(straty, alpha = 0.05, nboot = 1000, nblock = 20, boot = "stationary")

# 9.2. Wariant 1: blokowy
mcs_block <- mcsTest(straty, alpha = 0.05, nboot = 1000, nblock = 20, boot = "block")

# 10. Wyświetlenie wyników:
cat("\nWyniki mcsTest (ustawienie nboot = 1000, nblock = 20, boot = stationary):\n")
print(mcs_stationary)

Wyniki mcsTest (ustawienie nboot = 1000, nblock = 20, boot = stationary):

$includedR
[1] 1 5

$pvalsR
      [,1]
[1,] 0.000
[2,] 0.000
[3,] 0.000
[4,] 0.004
[5,] 0.697
[6,] 1.000

$excludedR
[1] 6 2 4 3

$includedSQ
[1] 1 5

$pvalsSQ
      [,1]
[1,] 0.000
[2,] 0.000
[3,] 0.000
[4,] 0.003
[5,] 0.697
[6,] 1.000

$excludedSQ
[1] 6 2 4 3


Należy zauważyć, że mamy tu 2 typy testów: statystyka R (Range statistic) oraz SQ (Semi-Quadratic statistic). Pierwszy z nich skupia się na różnicach w maksymalnych stratach między modelami, a drugi na różnicach w kwadratach strat. Najlepiej, jeśli obydwa wskazują ten sam zbiór, bo wtedy dostajemy potwierdzenie, że zbiór ufności zawiera odpowiednie modele.

Jak widać obydwa testy rzeczywiście wskazują to samo: included 1 oraz 5, czyli zbiór ufności zawiera tylko dwa modele - pierwszy oraz piąty. Pozostałe modele zostają odrzucone (excluded), zarówno przez R jak SQ. Możemy powiedzieć, że test zadziałał poprawnie, mimo przyjęcia błędnego modelu ARMA(3, 0). Model ten jest podobny do prawidłowego ARMA(3, 1), a co więcej poprawny model ma największe p-value = 1 (piąty 0.697). O tym może jeszcze powiem. Otóż wydrukowana lista może być trochę myląca, bo kolejność nie pokazuje p-value w kolejności od modelu 1 do 6, tylko w kolejności od najbardziej istotnego do najmniej istotnego wyniku dotyczącego odrzucenia modelu ze zbioru ufności. Im mniejsze p-value, tym większe prawdopodobieństwo, że dany model należy wyrzucić ze zbioru. 

Żeby zawęzić zbiór ufności, należy zwiększyć próbę. Po zwiększeniu jej do 1600, dostałem:

Wyniki mcsTest (ustawienie nboot = 1000, nblock = 20, boot = stationary):

$includedR
[1] 1

$pvalsR
      [,1]
[1,] 0.000
[2,] 0.000
[3,] 0.000
[4,] 0.000
[5,] 0.047
[6,] 1.000

$excludedR
[1] 6 2 4 3 5

$includedSQ
[1] 1

$pvalsSQ
      [,1]
[1,] 0.000
[2,] 0.000
[3,] 0.000
[4,] 0.000
[5,] 0.047
[6,] 1.000

$excludedSQ
[1] 6 2 4 3 5


Przy tak dużej próbie test nie ma już wątpliwości, że jedyny prawidłowy model to ten pierwszy.

Bardzo podobne wyniki dostałem dla boot = "block" i nie ma sensu pokazywać ich. Podobnie jak dla "stationary" musiałem ustawić próbę  liczną 1600, aby uzyskać wykluczenie wszystkich fałszywych modeli. Co jednak zauważyłem, to to, że w przypadku "block" wykluczenie modelu 5 było ledwo, ledwo, na styk, bo p-value wyniosło 0.049 (dla stationary też, ale niższe, bo 0.047).  Stacjonarny test generalnei jest lepszą alternatywą.

Powyższe przykłady zakładały rozkład normalny reszt modelu. Dla rynków może być lepszy inny, np. skośny rozkład t-Studenta. W takiej sytuacji potrzebne są dwa dodatkowe parametry do stałych: skew oraz shape. Zamiast "norm" będzie "sstd". Przykładowo kod będzie taki:

# 1. Definicje stałych parametrów
stale_3_1 <- list(
  omega  = 1,
  alpha1 = 0.3,
  beta1  = 0.2,
  mu     = 0.0001,
  ar1    = -1,
  ar2    = 0.5,
  ar3    = 0.7,
  ma1    = 0.4,
  gamma1 = 0.05,
  skew = 1.5,
  shape = 5
)

stale_0_0 <- list(
  omega  = 1,
  alpha1 = 0.3,
  beta1  = 0.2,
  mu     = 0.0001,
  gamma1 = 0.05,
  skew = 1.5,
  shape = 5
)

stale_1_0 <- list(
  omega  = 1,
  alpha1 = 0.3,
  beta1  = 0.2,
  mu     = 0.0001,
  ar1    = -1,
  gamma1 = 0.05,
  skew = 1.5,
  shape = 5
)

stale_2_0 <- list(
  omega  = 1,
  alpha1 = 0.3,
  beta1  = 0.2,
  mu     = 0.0001,
  ar1    = -1,
  ar2    = 0.5,
  gamma1 = 0.05,
  skew = 1.5,
  shape = 5
)

stale_3_0 <- list(
  omega  = 1,
  alpha1 = 0.3,
  beta1  = 0.2,
  mu     = 0.0001,
  ar1    = -1,
  ar2    = 0.5,
  ar3    = 0.7,
  gamma1 = 0.05,
  skew = 1.5,
  shape = 5
)

stale_0_1 <- list(
  omega  = 1,
  alpha1 = 0.3,
  beta1  = 0.2,
  mu     = 0.0001,
  ma1    = 0.4,
  gamma1 = 0.05,
  skew = 1.5,
  shape = 5
)

# 2. Specyfikacje modeli EGARCH(1,1) z różnymi specyfikacjami średniej:
spec_3_1 <- ugarchspec(
  variance.model = list(model = "eGARCH", garchOrder = c(1,1)),
  mean.model     = list(armaOrder = c(3,1), include.mean = TRUE),
  distribution.model = "sstd",
  fixed.pars = stale_3_1
)

spec_0_0 <- ugarchspec(
  variance.model = list(model = "eGARCH", garchOrder = c(1,1)),
  mean.model     = list(armaOrder = c(0,0), include.mean = TRUE),
  distribution.model = "sstd",
  fixed.pars = stale_0_0
)

spec_1_0 <- ugarchspec(
  variance.model = list(model = "eGARCH", garchOrder = c(1,1)),
  mean.model     = list(armaOrder = c(1,0), include.mean = TRUE),
  distribution.model = "sstd",
  fixed.pars = stale_1_0
)

spec_2_0 <- ugarchspec(
  variance.model = list(model = "eGARCH", garchOrder = c(1,1)),
  mean.model     = list(armaOrder = c(2,0), include.mean = TRUE),
  distribution.model = "sstd",
  fixed.pars = stale_2_0
)

spec_3_0 <- ugarchspec(
  variance.model = list(model = "eGARCH", garchOrder = c(1,1)),
  mean.model     = list(armaOrder = c(3,0), include.mean = TRUE),
  distribution.model = "sstd",
  fixed.pars = stale_3_0
)

spec_0_1 <- ugarchspec(
  variance.model = list(model = "eGARCH", garchOrder = c(1,1)),
  mean.model     = list(armaOrder = c(0,1), include.mean = TRUE),
  distribution.model = "sstd",
  fixed.pars = stale_0_1
)

# 3. Symulacja 330 obserwacji według poprawnego modelu (ARMA(3,1))
set.seed(55)
symulacja <- ugarchpath(spec_3_1, n.sim = 330)
dane_symulowane <- as.numeric(fitted(symulacja))

# 4. Dopasowanie modeli do całego szeregu (in-sample)
dopasowanie_3_1 <- ugarchfit(spec = spec_3_1, data = dane_symulowane)
dopasowanie_0_0 <- ugarchfit(spec = spec_0_0, data = dane_symulowane)
dopasowanie_1_0 <- ugarchfit(spec = spec_1_0, data = dane_symulowane)
dopasowanie_2_0 <- ugarchfit(spec = spec_2_0, data = dane_symulowane)
dopasowanie_3_0 <- ugarchfit(spec = spec_3_0, data = dane_symulowane)
dopasowanie_0_1 <- ugarchfit(spec = spec_0_1, data = dane_symulowane)

# 5. Zapisanie wyników dopasowania w liście (dla przejrzystości)
dopasowania <- list(
  model_3_1 = dopasowanie_3_1,
  model_0_0 = dopasowanie_0_0,
  model_1_0 = dopasowanie_1_0,
  model_2_0 = dopasowanie_2_0,
  model_3_0 = dopasowanie_3_0,
  model_3_0 = dopasowanie_0_1
)
print("Dopasowane modele:")
print(names(dopasowania))

# 6. Definicja funkcji strat:
Funkcja_strat <- function(rzeczywiste, prognoza) {
  roznica <- rzeczywiste - prognoza
  strata <- ifelse(sign(rzeczywiste) != sign(prognoza), abs(roznica), 0)
  return(strata)
}

# 7. Obliczenie funkcji strat dla każdego modelu (in-sample)
strata_3_1 <- Funkcja_strat(dane_symulowane, fitted(dopasowanie_3_1))
strata_0_0 <- Funkcja_strat(dane_symulowane, fitted(dopasowanie_0_0))
strata_1_0 <- Funkcja_strat(dane_symulowane, fitted(dopasowanie_1_0))
strata_2_0 <- Funkcja_strat(dane_symulowane, fitted(dopasowanie_2_0))
strata_3_0 <- Funkcja_strat(dane_symulowane, fitted(dopasowanie_3_0))
strata_0_1 <- Funkcja_strat(dane_symulowane, fitted(dopasowanie_0_1))

# Łączenie strat w macierz – wiersze odpowiadają obserwacjom, kolumny modelom
straty <- cbind(
  model_3_1 = strata_3_1,
  model_0_0 = strata_0_0,
  model_1_0 = strata_1_0,
  model_2_0 = strata_2_0,
  model_3_0 = strata_3_0,
  model_3_0 = strata_0_1
)

# 8. Usunięcie wierszy składających się tylko z zer
straty <- straty[rowSums(straty != 0) > 0, ]
straty <- coredata(straty)

# 9.1. Wariant stacjonarny
mcs_stationary <- mcsTest(straty, alpha = 0.05, nboot = 1000, nblock = 20, boot = "stationary")

# 10. Wyświetlenie wyników:
cat("\nWyniki mcsTest (ustawienie nboot = 1000, nblock = 20, boot = stationary):\n")
print(mcs_stationary)

Wyniki mcsTest (ustawienie nboot = 1000, nblock = 20, boot = stationary):

$includedR
[1] 5 1

$pvalsR
      [,1]
[1,] 0.000
[2,] 0.000
[3,] 0.000
[4,] 0.000
[5,] 0.071
[6,] 1.000

$excludedR
[1] 6 2 4 3

$includedSQ
[1] 5 1

$pvalsSQ
      [,1]
[1,] 0.000
[2,] 0.000
[3,] 0.000
[4,] 0.000
[5,] 0.071
[6,] 1.000

$excludedSQ
[1] 6 2 4 3


Jak widać, ponownie można by podwyższyć p-value do 10%, żeby dostać tylko model 1. Tak czy inaczej test działa bardzo dobrze także dla innych rozkładów.

Oczywiście to bardzo wzorcowe przykłady i w praktyce będzie to trudniejsze, ale to jest dobry punkt wyjścia.

* Hansen, P., A. Lunde, and J. Nason (2011). The model confidence set. Econometrica 79, 453–497.