Kontynuujemy serię gościnnych wpisów na temat ,,R i finanse”. W tym wpisie zajmiemy się podstawowymi metodami przetwarzania danych finansowych. W szczególności pokażemy jak, mając dane ceny zamknięcia, konstruować proste i logarytmiczne stopy zwrotu oraz jak testować ich normalność.
Marcin Pitera
Używając funkcji yahooSeries, wczytajmy dane dzienne dla AAPLE (Apple), MSFT (Microsoft) oraz CSCO (Cisco) w okresie od 01.01.2014 do 31.12.2014 (o tym, jak wczytywać takie dane pisaliśmy w poprzednim wpisie). Dla uproszczenia, zamiast rozważać całe dane OHLC, zajmiemy się tylko dopasowanymi cenami zamknięcia. Aby wybrać odpowiednie kolumny (w końcu wczytaliśmy dane w formacie OHLC), najłatwiej użyć standardowej funkcji grepl do wyrażeń regularnych.
require(fImport); require(quantmod); require(xts) stocks<-c("AAPL","MSFT","CSCO") prices<-as.xts(yahooSeries(stocks,from="2014-01-01",to="2014-12-31")) prices<-prices[,grep("Adj.Close", colnames(prices))] names(prices)<-stocks
Narysujmy wykresy cen zamknięcia.
par(mfrow=c(3,1)) for (i in names(prices)){plot.xts(prices[,i],main=i)}
Jeżeli chcemy przeprowadzić analizę statystyczną korzystając z naszych danych, wygodną rzeczą byłoby posiadanie próbki prostej, tzn. próbki niezależnych obserwacji o tym samym rozkładzie, nazywaną też próbką i.i.d. (ang. independent and identically distributed). Pierwszą rzeczą jaką zazwyczaj się robi, aby sprawdzić czy założenie to jest spełnione, jest zbadanie zależności liniowej między obserwacjami. Służą do tego funkcje autokorelacji oraz korelacji krzyżowej. W R możemy skorzystać na przykład z funkcji acf lub ccf .
acf(prices)
Jak wyraźnie widać, ceny zamknięcia nie spełniają założenia niezależności. Aby temu zaradzić (przynajmniej częściowo), najczęściej stosuje się modele ARMA (ang. autoregressive moving average model) lub rozważa szeregi stóp zwrotu (ang. rate of return). Skupimy się na tym drugim podejściu rozważając dwa najpopularniejsze typu stóp zwrotu. [Wskazówka: jak dopasować model AR/ARMA? Na przykład za pomocą funkcji ar/arima.. o tym będzie w innym wpisie.]
Niech oznacza cenę danej spółki w dniu t. Wtedy, prosta stopa zwrotu (ang. simple return) tej spółki w dniu t wyraża się wzorem
. Logarytmiczna stopa zwrotu (ang. logarithmic return, log-return) w dniu t wyraża się natomiast wzorem
. [Wskazówka: To, którą stopę zwrotu wybierzemy, zależy od tego co chcemy osiągnąć. Ogólnie rzecz biorąc, proste stopy zwrotu dobrze agregują się między sobą, tzn. łatwo z nich uzyskiwać stopy zwrotu dla portfeli. Z drugiej strony, logarytmiczne stopy zwrotu dobrze agregują się w czasie, tzn. łatwo na przykład z dziennych stóp przejść na stopy miesięczne. Dlatego też używa się ich dla czasu ciągłego i czasami nazywa ciągłymi stopami zwrotu (ang. continuous returns). Oczywiście z jednych stóp stosunkowo łatwo przejść na drugie. Do tego, dla małych wartości, przy krótkich okresach czasu, stopy te są sobie bliskie.]
W R stopy zwrotu można uzyskać korzystając na przykład z funkcji ROC z biblioteki TTR (inne przykładowe funkcje: returns z timeSeries, Return.calculate z PerformanceAnalytics). Dokonajmy tego przekształcenia i zobaczmy wykres, które powstał dla prostych stóp zwrotu. (dla stóp logarytmicznych wykres ten jest bardzo podobny; można też to zrobić ręcznie – np. dla logarytmicznych stóp zwrotu wystarczy użyć funkcji diff oraz log. Pamiętajmy jednak, że działamy na obiektach typu Time Series, a nie zwykłych tablicach)
#simple returns returns<-ROC(prices,type="discrete",na.pad=F) #log-returns #returns<-ROC(prices,type="continuous",na.pad=F) par(mfrow=c(3,1)) for (i in names(returns)){plot.xts(returns[,i],main=i)}
Dla pewności używamy jeszcze raz funkcji acf.
acf(returns)
Przy okazji warto podkreślić jedną rzecz, która jest często pomijana. Z powyższego rysunki nie możemy stwierdzić, iż stopy zwrotu są próbką prostą, gdyż funkcja autokorelacji mierzy tylko zależność liniową. Częstym powodem braku niezależności jest występowanie efektu grupowania zmienności (ang. volatility clustering effect). Wtedy (zazwyczaj) używa się filtrów typu GARCH, ale o tym będziemy mówić innym razem. Do tego, jeżeli mamy do czynienia z wielowymiarowym szeregiem czasowym, badanie korelacji krzyżowych jest ważnym elementem analizy. Jeżeli na przykład porównujemy stopy zwrotu indeksu WIG oraz NASDAQ 100, w danym dniu, to różne godziny funkcjonowania giełd powodują, iż ceny zamknięcia są z różnych czasów. W związku z tym funkcja korelacji krzyżowych często nie zeruje się. Mając to na uwadze, załóżmy, iż nasza próbka jest prosta i sprawdźmy, czy pochodzi z rozkładu normalnego.
Najpierw sprawdźmy, jak wygląda histogram oraz (wygładzona) empiryczna funkcja gęstości stóp zwrotu AAPL.
return1<-returns[,"AAPL"] par(mfrow=c(1,2)) hist(return1,breaks=21,main="Histogram of returns") plot(density(return1),main="Emp. density of returns")
W klasycznym podejściu do analizy finansowej zakładamy, iż próbka nasza pochodzi z rozkładu normalnego [Wskazówka: w praktyce, szczególnie po kryzysie finansowym 2007-2008, odchodzi się od tego założenia i od razu używa np. rozkładu t-studenta]. Dopasujmy więc parametry średniej oraz odchylenia do naszej próbki i porównajmy gęstość teoretyczną z gęstością empiryczną. Mimo, że wzory na estymatory (punktowe) dla naszej próbki są ogólnie znane, zastosujemy funkcję fitdistr z pakietu MASS [Wskazówka: przy jej pomocy można estymować parametry dla wielu rodzin rozkładów]. Zróbmy też standardowy QQ Plot, aby zobaczyć, jak dobre jest dopasowanie.
require(MASS) return1.fit<-fitdistr(return1, "normal")$estimate x<-seq(-0.1,0.1,length=100) hx<-dnorm(x, mean=return1.fit[1], sd=return1.fit[2]) par(mfrow=c(1,2)) plot(density(return1),main="Emp. density vs estimate") lines(x,hx, col="red",lwd=2) qqnorm(return1); qqline(return1)
Jak można się było spodziewać, dopasowanie, szczególnie w ogonach rozkładu, nie jest zbyt dobre. Aby potwierdzić, iż próbka ta nie pochodzi z rozkładu normalnego, wykonajmy dwa testy statystyczne. [Wskazówka: aby szybko przerobić szereg czasowy na zwykły wektor, wygodne jest użycie funkcji takich jak coredata.]
#Shapiro-Wilk Normality Test shapiro.test(coredata(return1)) #Kolmogorov-Smirnov Test ks.test(return1,"pnorm",return1.fit[1],return1.fit[2])
Mała wartość p-value wyraźnie sugeruje, iż pochodzenie próbki z rozkładu normalnego jest mało prawdopodobne. Wspomnieliśmy wcześniej, że lepiej jest używać innych rodzin rozkładów, takich jak na przykład rozkład t-studenta. Tym jednak zajmiemy się innym razem. Przy okazji warto wspomnieć, iż im dłuższy okres dla stóp zwrotu, tym hipoteza normalności jest bardziej prawdopodobna, aczkolwiek nie jest to reguła (Dlaczego tak się dzieje? Może być to na przykład wniosek z Centralnego Twierdzenia Granicznego.. ale o tym też w innym wpisie).
Jeżeli szukasz więcej informacji na temat finansowych szeregów czasowych, testowania ich normalności, efektu grupowania zmienności, czy innych tzw. faktów stylizowanych (ang. stylized facts), to bardzo polecam świetny artykuł Ramy Conta: “Empirical properties of asset returns: stylized facts and statistical issues” (Quantitative Finance, Vol. 1, 2001, pp. 223-236).
Możesz również wpisać w google “stylized facts stock returns”. Poniżej prezentujemy wybrane ciekawe linki:
- http://quantivity.wordpress.com/2011/02/21/why-log-returns/
- http://www.portfolioprobe.com/2010/10/04/a-tale-of-two-returns/
- http://mathbabe.org/2011/08/30/why-log-returns/
- http://quant.stackexchange.com/questions/4160/discrete-returns-versus-log-returns-of-assets
- http://stats.stackexchange.com/questions/124404/why-monthly-stock-returns-instead-of-daily-returns-in-multiple-regressions
Ciekawy wpis, aczkolwiek mam problem z replikacja wynikow poniewaz pierszy wiersz (2014-01-02) nie zawiera danych (NA).
W konsekwencji poczawszy od
plot(density(return1),main=”Emp. density of returns”)
kolejne linijki kodu nie moga byc wykonane (missing data etc.)
Ma ktos podobny problem?
Hej! Dzięki za informację. Faktycznie, w skrypcie był mały błąd. Trzeba dodać do funkcji ROC parametr na.pad=F (wpis poprawiony). W dużym skrócie, mając dane, nie da się wyliczyć pierwszej stopy zwrotu, gdyż nie masz poprzedniej wartości ceny. Czasami warto zostawiać te pola (np. wpisując tam NA), aby wielkość tablicy się nie zmieniła.
Super, dzieki za wyjasnienie!