Jest oczywiste, że nawet jeśli dałoby się dokładnie przewidzieć księgowe zyski czy rentowności spółek, to nie oznacza jeszcze, że możemy przewidzieć choćby kierunek ich ceny. Jak się zastanowić, to paradoksalnie właśnie ich losowość pomaga w prognozie. Bo to jak usuwanie sezonowości z danych - jeśli wiadomo, że w danym sezonie zawsze są lepsze wyniki, to lepszy lub gorszy sezon nie będzie miał wpływu na ceny, bo rynek uwzględnia w cenie takie zależności. Warunkiem jest pewność tej sezonowości, bo jeżeli sezonowość zaczyna się wahać, okazuje się nieregularnym cyklem, to nie usuwamy jej i wchodzi w grę możliwość wykorzystania jej do przewidywania kierunku. Powiedzmy, że mamy model wskazujący, że ROE wzrośnie w następnym roku. W teorii oznacza to, że rynek powinien to uwzględnić poprzez wyższą wycenę akcji, ale w praktyce występują różne korekty, w tym przypadku spadkowe, które można wykorzystać do kupienia, aby sprzedać później po tej wyższej cenie.
Warto na początku pokazać relację między rocznymi stopami zwrotu a zmianami ROE S&P 500 od 1981 r. do 2024:
Dane ROE od 1980 r. wyłuskałem z dwóch źródeł:
1) raport "Economic and Capital Markets Analysis: 2023 Year in Review U.S. Chartbook" opracowany przez batesgroup.com, s. 19;
2) https://www.multpl.com/sitemap
Ten pierwszy pokazuje na wykresie historyczne P/BV indeksu od 1980. Aby uzyskać ROE, podzieliłem P/BV przez P/E, które z kolei dostałem z tego drugiego źródła. Z niego także dostaniemy P/BV, ale od 1999 r. Dlatego do 1998 mam szacunkowe ROE, a od 1999 już dokładne. To opracowanie historyczne ROE wraz z analizą udostępniam tutaj. Poniżej końcowy wykres:
To co się rzuca w oczy, to niezwykle niskie ROE w latach 80-tych (5-6%). Jednak jak sprawdzimy, P/E wynosił wtedy ok. 10-11%, czyli 3 razy mniej niż obecnie. Ale ROE obecne jest też ponad 3x większe. Czyli wszystko się zgadza.
Tak jak pisałem poprzednio, ROE bywa rożnie definiowane. Z punktu widzenia wyceny interesuje nas postać EPS / BV * (1+w), gdzie w to tempo wzrostu EPS w następnym roku (lub oczekiwane w danym roku). Mógłbym oczywiście rozbić te wszystkie czynniki i sprawdzać oddzielnie, może nawet do jakichś interesujących wniosków bym doszedł, ale nie chcę komplikować analizy, dlatego będę prognozować w całości taką zmienną i będę ją nazywał po prostu ROE. Oprócz wspomnianej poprawności teoretycznej zaletą tego podejścia jest uwzględnienie jednocześnie rentowności i wzrostu zysku.
Chociaż rok 2025 jeszcze nie minął, to są prognozy EPS S&P 500 na ten rok. Np. na tej stronie podane są kwartalne prognozy i sumarycznie przewiduje się 239 dol. Byłby to wzrost o 14,5%. Zakładając obecne wskaźniki P/E i P/BV, w = 14,5%, ROE(2025) = 20,3%. W sumie dostalibyśmy następujący wykres:
Wszystkie poniższe kody są zapisane w języku R. Ładowane są pakiety: forecast, tseries, lmtest oraz strucchange.
Luka PKB jako zmienna niezależna
ROE można próbować prognozować za pomocą zmiennych różnych ekonomicznych, jak wzrost PKB, inflacja lub stopa procentowa. Wszystkie te zmienne są ze sobą powiązane, zatem nie możemy ich wrzucać po prostu do modelu, jakby były niezależne. Zamiast tworzyć skomplikowane modele, możemy zauważyć, że pewnym pośrednikiem tych zmiennych jest luka PKB. Ostatnio pisałem o tym, że FED powinien podnosić stopę procentową, aby zmniejszyć lukę, która jest wyraźnie dodatnia. Wiemy, że FED zachował się politycznie pod dyktando Trumpa - w związku z tym inflacja wzrośnie, choćby z powodu nakładanych ceł, które nie zostaną skompensowane niższą stopą proc.
Nakładając wykres luki PKB na ROE, dostajemy zaskakującą korelację:
Granger causality test
Model 1: zmianyRoe ~ Lags(zmianyRoe, 1:1) + Lags(zmianyLuka, 1:1)
Model 2: zmianyRoe ~ Lags(zmianyRoe, 1:1)
Res.Df Df F Pr(>F)
1 40
2 41 -1 4.439 0.0415 *
---
Signif. codes: 0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1
, który potwierdza, że to nie przypadkowa korelacja, ale przyczynowość (luka jest wcześniej). Następnie zbudujemy liniowy model z opóźnieniem zmian ROE i zbadamy i jego stabilność
> zmianyRoe_ts <- ts(zmianyRoe, start = 1982)
> zmianyLuka_ts <- ts(zmianyLuka, start = 1982)> zmianyRoe_ts1 <- window(zmianyRoe_ts, start = 1983)
> zmianyLuka_ts1 <- window(zmianyLuka_ts, start = 1982, end = 2024)
> modelRoe <- zmianyRoe_ts1 ~ zmianyLuka_ts1
> bai = breakpoints(modelRoe)
> bai
Optimal 2-segment partition:
Call:
breakpoints.formula(formula = modelRoe)
Breakpoints at observation number:
24
Corresponding to breakdates:
2006
Test Bai-Perrona wskazuje na zmianę struktury w 2006 r. Sprawdźmy jak zmieniają się współczynniki:
> coef(bai)
(Intercept) zmianyLuka_ts1
1983 - 2006 0.562118 0.0796585
2007 - 2025 0.241999 -2.1666214
Wg testu do 2006 r. ujemna zależność w ogóle nie istniała. Żeby się upewnić czy przyczyną nie jest jakiś inny efekt jak autokorelacja reszt, kwadratów reszt czy heteroskedastyczność wykonujemy kilka testów:
> reszty <- resid(lm(modelRoe))
>
> # autokorelacje reszt:
> bgtest(modelRoe, order = 2)
Breusch-Godfrey test for serial correlation of order up to 2
data: modelRoe
LM test = 3.643, df = 2, p-value = 0.162
> Box.test(reszty, type = "Ljung-Box")
Box-Ljung test
data: reszty
X-squared = 0.1114, df = 1, p-value = 0.739
>
> # autokorelacje kwadratów reszt:
> Box.test(reszty^2, type = "Ljung-Box")
Box-Ljung test
data: reszty^2
X-squared = 0.4259, df = 1, p-value = 0.514
>
> # heteroskedastyczność
> bptest(modelRoe)
studentized Breusch-Pagan test
data: modelRoe
BP = 2.145, df = 1, p-value = 0.143
> gqtest(modelRoe)
Goldfeld-Quandt test
data: modelRoe
GQ = 0.9562, df1 = 20, df2 = 19, p-value = 0.54
alternative hypothesis: variance increases from segment 1 to 2
> hmctest(modelRoe)
Harrison-McCabe test
data: modelRoe
HMC = 0.4871, p-value = 0.504
Żaden z tych testów nie wykrył problemów z resztami, a zatem zmiana struktury wydaje się faktem. Ostatni test, jaki warto przeprowadzić, aby się upewnić, że zmiana struktury dotyczy współczynnika kierunkowego, a nie wyrazu wolnego, to Score-CUSUM, oddzielający graficznie możliwe załamanie w każdym współczynniku:
Wynik pokrywa się z testem Bai-Perrona, który wskazał rok 2006 jako rok zmiany struktury. Teraz dodatkowo mamy dowód, że chodzi wyłącznie o zmiany luki PKB, a nie wyrazu wolnego czy wariancji.
Nadal nie odpowiedziałem skąd może wynikać ta zmiana. Biorąc pod uwagę, że rok 2007 był przełomowy w historii finansów, możliwe, że tu coś "pękło". Danych jest trochę mało, ale taki model nadal jest istotny:
> zmianyRoe_ts2 <- window(zmianyRoe_ts, start = 2007)
> zmianyLuka_ts2 <- window(zmianyLuka_ts, start = 2006, end = 2024)
> modelRoe <- zmianyRoe_ts2 ~ zmianyLuka_ts2
> summary(lm(modelRoe))
Call:
lm(formula = modelRoe)
Residuals:
Min 1Q Median 3Q Max
-11.044 -0.403 0.184 1.762 4.152
Coefficients:
Estimate Std. Error t value Pr(>|t|)
(Intercept) 0.242 0.758 0.32 0.75338
zmianyLuka_ts2 -2.167 0.463 -4.68 0.00022 ***
---
Signif. codes: 0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1
Residual standard error: 3.3 on 17 degrees of freedom
Multiple R-squared: 0.563, Adjusted R-squared: 0.537
F-statistic: 21.9 on 1 and 17 DF, p-value: 0.000215
Rysunek dobrze naświetla co tu się dzieje:
Rok 2007-2008 był wstrząsem który mógł zmienić strukturę zachowania samego PKB (być może jest to związane z działaniami państwa w celu zminimalizowania kryzysu). Rzeczywiście, od 2007-2008 r. pojawiła się ujemna autokorelacja 1 rzędu dla zmian wzrostu PKB (do 2005 była ujemna, ale nieistotna stat., po tym roku wynosi ponad 0,6). Logika podpowiada, że to właśnie ta autokorelacja jest przyczyną ujemnej opóźnionej korelacji z ROE. Wyżej widzieliśmy, że ROE koreluje dodatnio ze zmianami PKB dla tego samego roku. Zatem skoro zmiany PKB ujemnie autokorelują, to jeśli w jednym roku są wyższe, to w następnym roku będą niższe lub ujemne, a z tego wynika, że w tym drugim roku zmiana ROE też będzie średnio biorąc niższa lub ujemna.
Zmiana luki PKB w 2025 najprawdopodobniej będzie lekko ujemna, co sugeruje wzrost ROE w 2026. Usuwając stałą z modelu, która w tym przypadku raczej przeszkadza, dostajemy:
> modelRoe <- zmianyRoe_ts2 ~ zmianyLuka_ts2 - 1
> summary(lm(modelRoe))
Call:
lm(formula = modelRoe)
Residuals:
Min 1Q Median 3Q Max
-10.803 -0.155 0.425 1.994 4.410
Coefficients:
Estimate Std. Error t value Pr(>|t|)
zmianyLuka_ts2 -2.161 0.451 -4.79 0.00015 ***
---
Signif. codes: 0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1
Residual standard error: 3.22 on 18 degrees of freedom
Multiple R-squared: 0.561, Adjusted R-squared: 0.536
F-statistic: 23 on 1 and 18 DF, p-value: 0.000146
I teraz prognoza:> predict.lm(lm(modelRoe), newdata = data.frame(zmianyLuka_ts2 = zmianyLuka[length(zmianyLuka)]))
1
0.359036
Na 2026 prognoza ROE = 20,3 + 0,36 = 20,7%.
Uzyskaliśmy więc prognozę ROE na poziomie ok. 21%. Skoro stopa zwrotu koreluje dodatnio z ROE, to możemy się spodziewać w sumie wzrostu S&P 500 jeszcze w 2026...
Nazwałbym osiągnięciem to co uzyskałem, bo nie jest to mechaniczny model oparty na ARMA. Żaden system AI czegoś podobnego nie zbuduje. Wymaga namysłu, wiedzy i prawdziwego rozumowania ekonomicznego. Ale nie znaczy to, że z ARMA można zupełnie zrezygnować. Zawsze warto podeprzeć się paroma modelami, bo prognoza z kilku perspektyw jest bardziej obiektywna.
Autoregresja i średnia krocząca
Najpierw jednak trzeba sprawdzić rozkład zmian ROE. Właściwie powinienem to sprawdzić na samym początku, ale normalność nie ma aż takiego znaczenia w przypadku zwykłej regresji liniowej. Jednak tam gdzie stosuje się metodę największej wiarygodności, lepiej dostosować odpowiednio dane do rozkładu albo rozkład do danych. Robimy test Shapiro-Wilka:
> shapiro.test(zmianyRoe)
Shapiro-Wilk normality test
data: zmianyRoe
W = 0.8992, p-value = 0.00102
Czyli rozkład jest daleki od normalnego. Popatrzmy w ogóle na te zmiany w końcu:
Wygląda to tak, jakby wariancja się zmieniła Wykonajmy gqtest i hmctest na samych zmianach:
> modelRoe <- zmianyRoe_ts ~ 1
> gqtest(modelRoe)
Goldfeld-Quandt test
data: modelRoe
GQ = 2.002, df1 = 21, df2 = 21, p-value = 0.0598
alternative hypothesis: variance increases from segment 1 to 2
> hmctest(modelRoe)
Harrison-McCabe test
data: modelRoe
HMC = 0.3332, p-value = 0.056
Rzeczywiście jest blisko heteroskedastyczności, ale mimo wszystko brak normalności jest obiektywnie bardziej prawdopodobny. Idziemy więc do auto.arima i tam dostosujemy dane.
Aby znormalizować niegaussowskie szeregi w auto.arima stosujemy transformację Boxa-Coxa. Są tu dwa argumenty do użycia: lambda oraz biasadj. Pierwszy to parametr transformacji. Domyślnie ustawiony na NULL, a teraz ustawiamy go na "auto". Drugi to parametr odwrotnej transformacji. Jeżeli ustawimy go na FALSE, to estymatory dopasowania oraz prognozy - jeżeli lambda nie jest NULL - są medianą (przekształcenie monotoniczne funkcji nie zmienia jej wartości środkowej, ale średnią owszem). Żeby sprowadzić medianę do średniej, potrzebna jest korekta - to robi biasadj. Możemy ustawić biasadj = TRUE, jeśli chcemy mieć średnią, czyli wartość oczekiwaną. Istotne, żeby w takim przypadku używać samego ROE jako zmiennej prognozowanej, dlatego że transformacja Boxa-Coxa działa tylko na dodatnich wartościach.
Użyję kryterium AIC. BIC okazało się za surowe - wskazało błądzenie losowe.
> roe_ts <- ts(roe, start = 1981) > arima_model <- auto.arima(roe, + ic = "aic", max.p = 10, max.q = 10, + stepwise = FALSE, approximation = FALSE, lambda = "auto", biasadj = TRUE) > arima_model Series: roe ARIMA(0,1,2) with drift Box Cox transformation: lambda= 1.18272 Coefficients: ma1 ma2 drift -0.365 -0.268 0.516 s.e. 0.145 0.147 0.322 sigma^2 = 32: log likelihood = -137.31 AIC=282.62 AICc=283.65 BIC=289.76 > prognoza <- forecast(arima_model, h=1)$mean > prognoza Time Series: Start = 46 End = 46 Frequency = 1 out 19.9176 > roe_ts <- ts(roe, start = 1981) > arima_model <- auto.arima(roe_ts, + ic = "aic", max.p = 10, max.q = 10, + stepwise = FALSE, approximation = FALSE, lambda = "auto", biasadj = TRUE) > arima_model Series: roe_ts ARIMA(0,1,2) with drift Box Cox transformation: lambda= 1.18272 Coefficients: ma1 ma2 drift -0.365 -0.268 0.516 s.e. 0.145 0.147 0.322 sigma^2 = 32: log likelihood = -137.31 AIC=282.62 AICc=283.65 BIC=289.76 > prognoza <- forecast(arima_model, h=1)$mean > prognoza Time Series: Start = 2026 End = 2026 Frequency = 1 out 19.9176
Optymalny model to MA(2) i prognozuje on lekki spadek ROE w 2026 do poziomu ok. 20%. Gdybyśmy pominęli transformację, wartość niewiele by się zmieniła, bo dostalibyśmy równo 20%.
W zasadzie nie musimy nawet zastanawiać się nad tym czy transformować czy nie, tylko od razu użyć z tego samego pakietu modelu BATS (akronim od: Box-Cox transform, ARMA errors, Trend, and Seasonal components), który automatycznie sprawdza czy transformować dane i jeśli potrzeba to robi; poza tym wyławia trend, sezonowość i ARMA w resztach.
> roe_model_bats <- bats(roe_ts, biasadj = TRUE)
> prognoza <- forecast(roe_model_bats, 1)$mean
> prognoza
Time Series:
Start = 2026
End = 2026
Frequency = 1
[1] 19.6489
Tak samo jak poprzednio, jeśli biasadj = FALSE (domyślnie), dostaniemy medianę. Właściwie to w ogóle można pominąć ten argument, bo mediana często jest lepsza od średniej dla niegaussowskich rozkładów (zob. ten wpis).
BATS nie jest to bardziej zautomatyzowana wersja auto.arima, tylko bardziej złożony model w przestrzeni stanów (state space model). Stanem może być średni poziom ROE, nachylenie trendu albo pewna cykliczność. Jak sama nazwa wskazuje, to stan, który obowiązuje w danym czasie, a więc w następnych okresach może się zmienić. Mimo to współczynniki ARMA pozostają stałe. Intuicyjnie: jeżeli mamy tłumioną sinusoidę, to funkcja jest stała, w tym zawiera parametr tłumienia; stanem jest wtedy amplituda (wielkość ruchu), który się zmniejsza wraz z każdym okresem.
W końcu można połączyć model czysto ekonomiczny z ARMA, czyli użyć ARMAX. Do auto.arima wpisujemy argument xreg, który będzie zmianami luki PKB. Powrócę więc też do zmian ROE - i to od 2006 r., bo, jak pamiętamy, regresja z luką do tego roku była niestabilna. Oznacza to, że lepiej wtedy nie transformować danych, bo występują ujemne wartości. Muszą wystarczyć surowe:
> arima_model <- auto.arima(zmianyRoe_ts2,
+ ic = "bic", max.p = 10, max.q = 10,
+ stepwise = FALSE, approximation = FALSE, xreg = zmianyLuka_ts2)
> arima_model
Series: zmianyRoe_ts2
Regression with ARIMA(0,1,1) errors
Coefficients:
ma1 xreg
-0.619 -2.478
s.e. 0.197 0.381
sigma^2 = 10.7: log likelihood = -46.07
AIC=98.14 AICc=99.86 BIC=100.81
> prognoza <- forecast(arima_model, h=1, xreg = zmianyLuka_ts2[length(zmianyLuka_ts2)])$mean
> prognoza
Time Series:
Start = 2026
End = 2026
Frequency = 1
[1] 1.29567
Nie ma znaczenia którego kryterium użyję - daje to samo. W sumie prognoza jest mocno dodatnia. Na 2026 mamy 20,3 + 1,3 = 21,6%. Pamiętać oczywiście trzeba, że model powstał w oparciu o bardzo małą próbę (ledwo 19 obserwacji), więc nie jest to szczególnie wiarygodna wartość. Raczej traktować ją należy jak drogowskaz.
Podsumowując, mechaniczne metody prognozują delikatny spadek ROE, ale efekt ekonomiczny zmienia ten kierunek na dodatni. I wstępnie dodaje giełdzie amerykańskiej pretekstu do dalszych zwyżek.