To co się obecnie dzieje w świecie AI, szczególnie LLM (Large Language Model), przyprawia o zawrót głowy. Tempo rozwoju tej technologii jest oszałamiające, zarówno wertykalnie jak i horyzontalnie. To znaczy serwisy branżowe co chwilę donoszą o nowych funkcjach lub poprawionej efektywności jakiegoś modelu, a jednocześnie można korzystać z coraz większej ilości narzędzi do różnych celów. Dla gigantów z S&P 500 rozwój ich własnych AI to priorytet. Dla inwestorów oczywiście najważniejsze to pytanie o ich zyskowność, a więc czy zwrot będzie odpowiedni w stosunku do kosztów. Technologia ta pozwala zarówno zmniejszać koszty (zastępować ludzi) jak i zwiększać przychody (firmy kupują AI zamiast zatrudniać), jednak jej utrzymanie kosztuje bardzo dużo. Sprzęt to jedno, ale istnieje też ryzyko niepowodzenia. Nie chodzi tu nawet o realne możliwości danego modelu, ale o to, że konkurencja może przejąć klientów. Np. Microsoft i Google (czy Alphabet - choć ta nazwa jest taka jakaś...) wydadzą miliardy dolarów na AI (w 2025 jest to odpowiednio 65 i 60 mld dol - źródło), a w tym samym czasie ktoś trzeci "niszczy system", przejmuje klientów, a giganci przepalają fortunę.
Pytanie więc brzmi: jak może wyglądać wzrost EPS S&P 500 w kolejnych 5-10 latach? I teraz ciekawa sprawa: wydawałoby się, że będzie to powyżej średniej; ale okazuje się, że średni wzrost od 1981 do 2022 r. wynosi 15%. Dopiero mediana daje bardziej umiarkowany, choć nadal bardzo wysoki, bo 10%. A takie są mniej więcej oczekiwania co do kolejnych lat. J.P. Morgan szacuje wzrost 13-15% na lata 2026-2027 (źródło), a Goldman Sachs 12% w 2026 i 10% w kolejnym (źródło).
Sam Standard and Poor's szacuje jednak ogromny wzrost ok. 20% na 2026 (źródło). Biorąc pod uwagę, że mamy do czynienia ciągle z hossą, przyjmę ten optymistyczny scenariusz, natomiast na 2027 średnią z wyżej wymienionych instytucji 14 i 10%, czyli 12%.
Skłania to do pytania o lokalny szczyt samego impetu zysków. Coraz wolniejsze zmiany, a w końcu spadek zysku oznaczałoby występowanie cykliczności. Aby to sprawdzić najlepiej użyć periodogramu lub analizy spektralnej.
W R najłatwiejszą funkcją (w sensie najmniejszej liczby ustawień) jest spectrum():
spectrum(x, method = c("pgram", "ar"))Z metod mamy do wyboru periodogram lub spektrum dla modelu autoregresyjnego. Zaletą tego pierwszego jest to, że stanowi metodę nieparametryczną - nie nakłada specjalnych warunków na sygnał (chociaż musi być stacjonarny). Jego wadą jest jednak duża wrażliwość na wielkość próby, wykres jest poszarpany (duża wariancja) i piki mogą się układać przypadkowo niezależnie od wielkości próby (estymator jest niezgodny). Zaletą tego drugiego jest gładkość i stabilność (estymator zgodny), ale posiada dużą wadę: sygnał musi być liniowym procesem autoregresyjnym.
Pomimo prostoty to spectrum() jest tylko nakładką (wrapper) na odpowiednio spec.pgram() i spec.ar(). Lepiej ich używać, bo od razu widać jakie są dodatkowe argumenty i można nimi manipulować. Tak więc dla periodogramu najlepiej użyć:
spec.pgram(x, spans = NULL, kernel, taper = 0.1,
pad = 0, fast = TRUE, demean = FALSE, detrend = TRUE,
plot = TRUE, na.action = na.fail, ...)
Dzięki kalibracji spans, kernel i taper możemy zminimalizować przytoczone wyżej wady surowego periodogramu.
Na ten moment zwrócę tylko uwagę na demean i detrend. Już samo detrend = TRUE oznacza usunięcie zarówno średniej jak i (liniowego) trendu, dlatego demean (usunięcie średniej) jest zbędne. Jeśli używamy czystego EPS, to jednak detrend nie wystarczy, bo usuwa tylko zwykły trend liniowy, a EPS rośnie przecież nieliniowo, powiedzmy wykładniczo (link do danych):
Po zlogarytmowaniu:
freq_values <- pg$freqspec_values <- pg$specidx_max <- which.max(spec_values)Szukaną częstotliwością odpowiadającą maksimum oznaczymy fr_max i będzie to:> fr_max <-freq_values[which.max(spec_values)] > fr_max [1] 0.125
> cyc <- 1/fr_max
> cyc
[1] 8A więc dostaliśmy "magiczną" liczbę 8 lat - tyle wynosi pełen cykl EPS: wzrost + spadek.
Kolejnym krokiem jest użycie wykrytej cykliczności do budowy filtra Butterwortha (FB). Dotychczas używałem go w gretlu, ale teraz zrobię to w R, w bardziej zaawansowany sposób. Użyję pakietu gsignal, w którym potrzebujemy dwóch funkcji: butter() oraz filtfilt(). Pierwsza oblicza współczynniki filtra, a druga podstawia je bezpośrednio do filtra.
Najważniejsze argumenty w funkcji butter() to rząd filtra oraz graniczna częstotliwość (znormalizowana częstotliwość odcięcia). Pozostałe argumenty dotyczą typu FB (w większości przypadków interesuje nas dolnoprzepustowy, to jest domyślne ustawienie), rodzaju płaszczyzny (wybór między filtrem cyfrowym a analogowym, interesuje nas zawsze cyfrowy - domyślne ustawienie) oraz czysto techniczne ustawienie formatu wyniku (tu zawsze wybieramy "Sos", skrót od Second-order sections).
Chociaż pakiet ma ciekawe funkcje optymalizujące zarówno rząd filtra jak i częstość odcięcia, to dla naszych potrzeb wystarczy dotychczasowa wiedza, że ustawiamy rząd filtra = 2, natomiast znormalizowana częstotliwość odcięcia jest to 2*f_cut / fs, gdzie f_cut - częstotliwość odcięcia odpowiadająca maksimum periodogramu plus pewna nadwyżka (która bywa potrzebna, żeby uwzględnić niestabilność cyklu), fs to częstotliwość próbkowania (por. z opisem funkcji SciPy, który jest pierwowzorem). W naszym przypadku dane są roczne, więc pobieramy 1 na rok, tj. fs = 1, tak że zostaje 2*f_cut. To mnożenie przez 2 wydaje się być związane z twierdzeniem Nyquista o próbkowaniu. Zgodnie z tym twierdzeniem, aby sygnał został odtworzony bez zniekształceń, częstotliwość próbkowania musi być co najmniej dwa razy większa od częstotliwości odcięcia. Filtrowanie jest podobną operacją do próbkowania, dlatego można traktować taką częstość jako odpowiednią do zachowania sygnału. Od razu jednak dodam, że trochę zgaduję, bo może być tak, że to tylko przyjęta konwencja konstrukcji tej funkcji.
Żeby to zilustrować, pokażę jak wygląda FB z częstością odcięcia bez normalizacji, a jak wygląda po normalizacji. Przykład dla sinus z szumem:
# --- Parametry sygnału ---
fs <- 100 # częstotliwość próbkowania w Hz
t <- seq(0, 5, by=1/fs)
f_sin <- 5 # częstotliwość sinusa w Hz
# --- Sygnał sinusoidalny + biały szum ---
set.seed(123)
x <- sin(2*pi*f_sin*t) + rnorm(length(t), sd=0.5)
# --- Widmo sygnału ---
pg <- spec.pgram(x, log="no", plot=FALSE)
freq_values <- pg$freq # jednostki Nyquista [0,0.5]
spec_values <- pg$spec
idx_max <- which.max(spec_values)
fr_max <- freq_values[idx_max] # główny pik
# --- Dwie wersje częstotliwości odcięcia ---
fr_cut1 <- fr_max # zwykłe
fr_cut2 <- fr_cut1*2 # "podwójne" dla efektu wizualnego
# --- Filtry Butterwortha z gsignal ---
order_filtr <- 2
coef1 <- butter(order_filtr, fr_cut1, type="low", output="Sos")
coef2 <- butter(order_filtr, fr_cut2, type="low", output="Sos")
x_filt1 <- filtfilt(coef1, x)
x_filt2 <- filtfilt(coef2, x)
# --- Porównanie na wykresie ---
plot(t, x, type='l', col='gray', main='Sygnał sinus + szum')
lines(t, x_filt1, col='blue', lwd=2)
lines(t, x_filt2, col='red', lwd=2)
legend("topright", legend=c("Oryginalny", "fr_cut", "fr_cut*2"),
col=c("gray","blue","red"), lwd=2)
Coefficients:
ar1 ar2 ar3 ma1 ma2
2.106 -2.001 0.770 1.857 0.926
s.e. 0.111 0.175 0.107 0.101 0.082





