Niektórzy zwrócili niedawno uwagę, że na S&P 500 występuje tzw. krzyż śmierci, czyli 50-dniowa średnia krocząca przecinająca od góry 200-dniową średnią kroczącą:
Na ten sygnał zwrócił m.in. portal xyz.pl , dodając jednak notkę, że analitycy firmy LPL Financial zaobserwowali, że historycznie w ciągu kolejnego miesiąca indeks spadał średnio niecały 1%, a w ciągu 3 miesięcy nawet rósł. Mowa jednak o medianie, a więc średnia i dominanta mogły się sporo różnić. Podanie samej mediany jest niewystarczające.
Sprawdźmy jak to rzeczywiście wyglądało dotychczas. Zaczniemy tak jak ta firma od 1950 r. Trzeba precyzyjnie określić warunki. Nie wystarczy, że SMA50 przetnie SMA200 z góry w dół, bo za chwilę może jeszcze zawrócić i średnie mogą się znowu dotknąć albo w ogóle SMA50 może przebić z dołu w górę. Jeśli taka sytuacja dzieje się w np. w ciągu tygodnia, to sygnału nie będzie. Tak więc określamy minimalne okno tego zdarzenia. Przyjmę cały tydzień, co oznacza 3 kroki do tyłu i 3 kroki do przodu od momentu przecięcia. Taki układ warunków jest już wystarczający, chociaż może nie być przekonujący, bo nawet po przecięciu się obie średnie mogą rosnąć. Tak na intuicję wydaje się, że powinny spadać. Z drugiej strony już sama nazwa "krzyż" sugeruje dowolne przecięcie, np. rosnącej SM200 i spadkowej SMA50. Z powodu tej różnicy w interpretacji krzyża śmierci, zrobimy 2 sprawdziany:
1) Są warunki: (a) SMA50 <= SMA200, (b) SMA50[t-3] > SMA200[t-3], (c) SMA50[t+3] < SMA200[t+3] ,
2) To samo co p. (1) i dodatkowo warunek, że obie średnie spadają.
Ostatnia sprawa to sam okres przyszłej stopy zwrotu. Sprawdzimy 1-3 miesięczne zwroty do przodu. Za 1 miesiąc przyjmiemy 21 dni, czyli 21 sesji.
Ad 1) Kod w R:
# 1. Pobieramy dane S&P500, ładujemy pakiety, np. xts.
# 2. Przekształcamy ceny na logarytmy
logCena <- log(sp500)
# 3. Obliczamy 50-dniową i 200-dniową średnią kroczącą
sma50 <- SMA(logCena, n = 50)
sma200 <- SMA(logCena, n = 200)
# 4. Ustalamy wartość przesunięcia (krok) dla warunków – tu krok = 3
krok <- 3
# Warunki dla sygnału
war1 <- sma50 <= sma200
war2 <- lag(sma50, k = krok) > lag(sma200, k = krok) # SMA50[t-krok] > SMA200[t-krok]
war3 <- lag(sma50, k = -krok) < lag(sma200, k = -krok) # SMA50[t+krok] < SMA200[t+krok]
war4 <- TRUE
# 5. Tworzymy wektor sygnału: 1, gdy wszystkie warunki są spełnione, NA w przeciwnym przypadku
sygnal <- ifelse(war1 & war2 & war3 & war4, 1, 0)
sygnal <- na.omit(sygnal)
datySygnalow <- index(sygnal)
# Przypisujemy daty wszystkich sygnałów
print("Daty sygnałów (pełny szereg):")
print(datySygnalow)
# 6. Teraz dodajemy warunek, aby wyłapać jedynie moment przejścia z 0 na 1.
# To pozwala zachować tylko pierwszy dzień pojawienia się sygnału.
sygnal_krok1 <- lag(sygnal, k = 1) # przesunięcie danych o jeden dzień do tyłu (opóźnione)
sygnal_krok1[is.na(sygnal_krok1)] <- 0 # traktujemy pierwszy dzień jako 0
unikalneDatySygnalow <- which(sygnal_krok1 == 0 & sygnal == 1)
unikalneDatySygnalow <- datySygnalow[unikalneDatySygnalow]
print("Unikalne daty sygnału (przejście z 0 na 1):")
print(unikalneDatySygnalow)
# 7. Obliczamy dzienne logarytmiczne stopy zwrotu
logZwrot <- na.omit(diff(logCena))
# 8. Używamy rollapply do sumowania logarytmicznych stóp zwrotu na horyzoncie 21 sesji,
# ale dnia następnego po sygnale
oknoPrzyszlosci <- 21
logZwrot_suma <- lag(rollapply(logZwrot,
width = oknoPrzyszlosci,
FUN = sum,
align = "left"), -1)
# 9. Przekształcamy sumy logarytmicznych stóp zwrotu na zwykłe stopy zwrotu: exp(sum) - 1
miesZwrot <- exp(logZwrot_suma) - 1
# 10. Dopasowujemy daty unikalnych sygnałów do obliczonych miesięcznych stóp zwrotu
wyniki <- data.frame(Date = unikalneDatySygnalow,
MonthlyReturn = as.numeric(miesZwrot[unikalneDatySygnalow]))
wyniki <- na.omit(wyniki)
print("Wyniki (data unikalnego sygnału + miesięczna stopa zwrotu):")
print(wyniki)
mean(wyniki$MonthlyReturn)
# 11. Rysujemy histogram miesięcznych stóp zwrotu dla wyłapanych sygnałów
h <- hist(wyniki$MonthlyReturn,
breaks = 7,
col = "lightblue",
border = "black",
main = "Histogram miesięcznych stóp zwrotu",
xlab = "Stopa zwrotu",
ylab = "Liczba sygnałów",
freq = NULL)
# 12. Przekształcamy liczebności na prawdopodobieństwa
czestosc <- h$counts / sum(h$counts)
# 13. Ustawiamy układ dwóch wykresów oraz marginesy
par(mfrow = c(2, 1), mar = c(2, 5, 2.2, 3))
slupkiZwrotow <- 100 * round(h$mids, 3)
# 14. Rysujemy wykres słupkowy prawdopodobieństwa 1-miesięcznych stóp zwrotu
b <- barplot(czestosc,
names.arg = slupkiZwrotow,
col = "lightblue",
border = "black",
main = "Prawdopodobieństwo 1-miesięcznych stóp zwrotu",
xlab = "",
ylab = "Prawdopodobieństwo",
yaxt = "n",
space = 0,
cex.main = 0.9)
# 15. Dodajemy oś Y z wartościami prawdopodobieństwa
axis(2, at = round(czestosc, 2), las = 2, cex = 0.7)
# 16. Zaznaczamy dominantę prawdopodobieństwa linią pionową
abline(v = b[which.max(czestosc)],
col = "red",
lty = 2,
lwd = 2)
# 17. Rysujemy wykres słupkowy skumulowanego prawdopodobieństwa
par(mar = c(4, 5, 1.5, 3))
barplot(cumsum(czestosc),
names.arg = slupkiZwrotow,
col = "lightblue",
border = "black",
main = "Skumulowane prawdopodobieństwo 1-miesięcznych stóp zwrotu",
xlab = "",
ylab = "Prawdopodobieństwo",
yaxt = "n",
space = 0,
cex.main = 0.9)
# 18. Dodajemy oś Y z wartościami skumulowanego prawdopodobieństwa
axis(2, at = round(cumsum(czestosc), 2), las = 2, cex = 0.7)
# 19. Dodajemy opis osi X
mtext(text = "Stopa zwrotu, 1-miesięczna (%)", side = 1, padj = 3.5)
# 20. Zaznaczamy dominantę skumulowanego prawdopodobieństwa linią poziomą
abline(h = cumsum(czestosc)[which.max(czestosc)],
col = "red",
lty = 2,
lwd = 2)
# 21. Zaznaczamy dominantę pojedynczego prawdopodobieństwa linią pionową
abline(v = b[which.max(czestosc)],
col = "red",
lty = 2,
lwd = 2)
W kolejnych dwóch miesiącach po krzyżu śmierci dominowały wzrosty z medianą 2,5%, ale znów skośność powoduje duże ryzyko spadków.
> unikalneDatySygnalow [1] "1953-05-11" "1957-09-26" "1960-02-15" "1962-05-08" "1965-07-22" "1968-02-27" "1969-06-23" [8] "1977-03-03" "1980-04-22" "1984-02-06" "1987-11-04" "1990-09-07" "1994-04-19" "2000-10-30" [15] "2010-07-02" "2011-08-12" "2015-08-28" "2016-01-11" "2018-12-07" "2020-03-27" "2025-04-14"
Brak komentarzy:
Prześlij komentarz