Wstęp

Inwestowanie to proces alokacji kapitału w celu osiągnięcia zysków lub wzrostu wartości finansowej. Jest to działanie, które obejmuje świadome podejmowanie decyzji dotyczących lokowania środków w różnorodne aktywa finansowe, takie jak akcje, obligacje, nieruchomości czy instrumenty pochodne, w nadziei na generowanie zwrotów z zainwestowanego kapitału. Inwestycje są kluczowym elementem zarządzania finansami osobistymi, instytucjonalnymi czy korporacyjnymi, a skuteczne podejście do inwestowania wymaga zrozumienia różnych klas aktywów, ryzyka inwestycyjnego i strategii alokacji portfela[1].

Harry Markowitz, amerykański ekonomista, stworzył w latach 50. XX wieku innowacyjną teorię portfela, która zmieniła sposób, w jaki inwestorzy podchodzą do dywersyfikacji. Model Markowitza, zwany także teorią portfela efektywnego, zakłada, że inwestorzy powinni wybierać portfele inwestycyjne, które oferują najwyższy poziom oczekiwanego zwrotu przy określonym poziomie ryzyka lub minimalnym poziomie ryzyka przy określonym poziomie oczekiwanego zwrotu[2].

Centralnym punktem tej teorii jest pojęcie dywersyfikacji, czyli rozproszenia kapitału między różne aktywa w celu zminimalizowania ryzyka. Model Markowitza pomaga inwestorom znaleźć optymalny punkt na krzywej efektywnej alokacji kapitału, uwzględniając jednocześnie oczekiwane zwroty i ryzyko poszczególnych aktywów oraz ich wzajemne korelacje.

Dywersyfikacja jest strategią inwestycyjną, której celem jest ograniczenie ryzyka poprzez rozproszenie kapitału na różne rodzaje aktywów. Inwestorzy starają się unikać koncentracji swojego portfela tylko w jednym rodzaju instrumentów finansowych czy branży, co pomaga zminimalizować wpływ potencjalnych strat związanych z jednym konkretnym rynkiem lub sektorem. Dzięki dywersyfikacji inwestycje są rozłożone, co może chronić portfel przed nagłymi spadkami wartości poszczególnych aktywów [3].

W praktyce, inwestorzy korzystający z modelu Markowitza starają się znaleźć balans między maksymalizacją potencjalnych zysków a minimalizacją ryzyka, tworząc portfele, które są zoptymalizowane pod kątem efektywności. Model ten stanowi fundament dla wielu współczesnych teorii zarządzania portfelem inwestycyjnym i pomaga inwestorom podejmować bardziej świadome decyzje dotyczące alokacji kapitału[4].

Cel projektu

Celem niniejszego projektu jest optymalizacja portfela inwestycyjnego za pomocą narzędzi analitycznych, takich jak model Markowitza jako efektywne zarządzanie kapitałem inwestycyjnym w celu osiągnięcia optymalnej kombinacji aktywów. Głównym celem jest znalezienie proporcji alokacji kapitału, które umożliwią inwestorowi maksymalizację oczekiwanego zwrotu przy danym poziomie akceptowalnego ryzyka lub minimalizację ryzyka przy określonym oczekiwanym zwrocie.

W ramach tego projektu przeprowadzono:

  • Analizę różnych aktywów inwestycyjnych, oceniając ich historyczne dane dotyczące zwrotów, ryzyka i korelacji.

  • Implementację Modelu Markowitza który bierze pod uwagę dane dotyczące oczekiwanego zwrotu, ryzyka i korelacji aktywów, aby wygenerować optymalne proporcje alokacji kapitału i poziom efektywności wskaźnikiem Sharpa.

  • Wygenerowanie wykresu możliwości inwestycyjnych który identyfikuje kombinację aktywów, maksymalizującą oczekiwany zwrot przy zachowaniu akceptowalnego poziomu ryzyka.

  • Analizę Wyników oraz ich wpływu na oczekiwany zwrot i ryzyko portfela, umożliwiająca inwestorowi lepsze zrozumienie zoptymalizowanej strategii.

W efekcie projekt ten stanowić może wiedzę dla inwestorów w podejmowaniu bardziej precyzyjnych decyzji inwestycyjnych, opartych na danych i modelowaniu matematycznym, co może przełożyć się na lepszą efektywność alokacji kapitału w ich portfelu inwestycyjnym.

Aby osiągnąć zamierzony zostały wykorzystane następujące pakiety programu R:

library(quantmod)     # ściąganie danych z portalu Yahoo Finance
library(dplyr)        # manipulowanie tabelami
library(ggplot2)      # tworzenie wykresów
library(moments)      # obliczanie kurtozy
library(knitr)        # tworzenie tabel
library(corrplot)     # diagram korelacji
library(xts)          # obsługa szeregów czasowych
library(tidyr)        # konwersja tabel szerokich na długie
library(lpSolve)      # pakiet wykorzystywany przy wykresie wag na granicy efektywnej

Zakres danych

Analizowane spółki pochodzą z rynku amerykańskiego notowane na giełdzie S&P 500. Źródłem danych jest serwis Yahoo Finance z którego zostały pobrane niniejsze dane potrzebne do analizy.

Pierwszą spółką jest kalifornijskie przedsiębiorstwo informatyczne Cisco. Główna siedziba przedsiębiorstwa znajduje się w Kalifornii w samym sercu Doliny krzemowej. Obecnie niniejsza spółka wytwarza i sprzedaje rozwiązania z zakresu sprzętu sieciowego, telekomunikacyjnego oraz bezpieczeństwa sieciowego. Polskie oddziały przedsiębiorstwa znajdują się w Warszawie oraz Krakowie.

Drugim analizowanym przedsiębiorstwem jest przedsiębiorstwo Eaton Corporation którego głównym zakresem oferowanych produktów są systemy zarządzania energią elektryczną, komponenty hydrauliczne i pneumatyczne dla producentów maszyn ciężkich i lotnictwa. Sprzedaż produktów przedsiębiorstwa obejmuje ponad 150 krajów na całym świecie. Polskie jednostki przedsiębiorstwa znajdują się w Bielsku-Białej oraz w Tczewie w których wytwarzane są podzespoły dla potrzeb sektora automotive.

Procter & Gamble jest globalną grupą kapitałową zlokalizowaną w Cincinnati w Stanach zjednoczonych. Głównymi produktami przedsiębiorstwa są dobra konsumpcyjne, głównie kosmetyki i środki higieny osobistej. W grupy wchodzą marki jak ambi pur, Pampers, Oral-B, Ariel, Pantene Pro-V, always itp.

Jako okres badawczy zdecydowano się wybrać dzienne akcje notowań spółek od 31 grudnia 2013 roku do 01 stycznia 2024 roku

Wykorzystując pakiet quantmod wskazując tzw. “tickery” określamy interesujące nas akcje. Następnie wskazując zakres danych i źródło pobrania możemy wygenerować połączenie do danych historycznych portalu Yahoo Finance bezpośrednio z R.

options(scipen = 999)                                 # wyłączenie notacji naukowej

ticker <- c("CSCO", "ETN", "PG")                      # Wybór kodów konkretnej akcji

getSymbols(ticker, src = 'yahoo',
           from = "2013-12-31", to = "2024-01-01")    # Wybór interesującego nas zakresu notowań
## [1] "CSCO" "ETN"  "PG"

Przy tworzeniu połączenia i przeglądaniu bazy danych, kierujemy się wyborem interesującej nas kolumny. W ramach tego projektu postanowiliśmy skoncentrować się na cenie Adjusted Close, która reprezentuje skorygowane wartości cen zamknięcia akcji. Korekta ta uwzględnia różnice związane między innymi z wypłatą dywidendy w określonych dniach. W naszej bazie danych, kolumna Adjusted Close znajduje się na szóstej pozycji, co stanowi kluczowy element naszej analizy.

# Wybór kolumny Adjusted Close pełniącej role skorygowanych zamknięć obejmujących dywidendy
CISCO <-CSCO[,6]
EATON <-ETN[,6]
PROCTER <-PG[,6]

dane <- merge(CISCO, EATON, PROCTER) # Tworzenie ramki danych szeregów czasowych

# Zmiana nazw kolumn
new_column_names <- c("CISCO", "EATON", "PROCTER")
colnames(dane) <- new_column_names


# Wyświetlamy liczbę obserwacji
liczba_wierszy <- nrow(dane)
cat("Liczba obserwacji:", liczba_wierszy, "\n")
## Liczba obserwacji: 2517

Na przestrzeni określonego okresu R wygenerował dla nas 2517 obserwacji dotyczących wybranych akcji. W trakcie tego procesu dokonano zmiany nazw kolumn w celu ułatwienia manipulacji danymi na kolejnych etapach projektu.

Analogiczny okres zostanie wskazany celem pobrania notowań pełnego indeksu S&P 500.

ticker2 <- c("^GSPC")                                 # Wybór kodów konkretnej akcji

getSymbols(ticker2, src = 'yahoo',
           from = "2013-12-31", to = "2024-01-01")    # Wybór interesującego nas zakresu notowań
## [1] "GSPC"

Mając wskazany okres liczący 2517 obserwacji robimy to samo co dla naszego zestawu akcji wcześniej.

# Wybór kolumny Adjusted Close pełniącej role skorygowanych zamknięć obejmujących dywidendy

SP500 <-GSPC[,6]

# Zmiana nazw kolumn
new_column_names <- c("SP500")
colnames(SP500) <- new_column_names

# Wyświetlamy liczbę obserwacji
liczba_wierszy <- nrow(SP500)
cat("Liczba obserwacji:", liczba_wierszy, "\n")
## Liczba obserwacji: 2517

Dane indeksu również zawierają łącznie 2517 obserwacji co wskazuje na bogaty zbiór danych. Teraz można przejść do etapu jakim jest przygotowanie danych pod analizę.

Przygotowanie danych

Przygotowanie danych to etap analizy danych, absorbujący najwięcej czasu. Zazwyczaj obejmuje ono procesy czyszczenia danych, dokonywania przekształceń, tworzenia nowych zmiennych oraz wybierania odpowiednich metod analizy. Niniejszy rozdział koncentruje się na przygotowaniu zbioru akcji przed przystąpieniem do modelowania i późniejszej analizy.

Wartości puste

Wartości pustę stanowią pozycje w ramce danych które zawierają wartości NA czyli wartości puste (nie mylić z zero). Celem identyfikacji wykorzystując do tego R możemy określić czy nasze ramki danych dla akcji oraz indeksu zawierają puste pozycje. Ten etap jest jednym z etapów początkowych przy każdym punkcie przygotowania danych.

puste_kolumny <- colSums(is.na(dane))

# Suma pustych obserwacji w całym dataframe
suma_pustych <- sum(is.na(dane))

# Wyświetlenie wyników
print("Liczba pustych obserwacji w każdej kolumnie:")
## [1] "Liczba pustych obserwacji w każdej kolumnie:"
print(puste_kolumny)
##   CISCO   EATON PROCTER 
##       0       0       0
print("Suma pustych obserwacji w całym dataframe:")
## [1] "Suma pustych obserwacji w całym dataframe:"
print(suma_pustych)
## [1] 0

Na podstawie wygenerowanego wyniku stwierdzono brak pozycji pustych dla akcji notowań z giełdy S&P 500. Wynik ten wskazuje, że w zbiorze każda linijka ma określoną wartość.

To samo zostało zrobione celem identyfikacji w drugim zbiorze jaki stanowi notowania indeksu S&P 500.

puste_kolumny <- colSums(is.na(SP500))

# Suma pustych obserwacji w całym dataframe
suma_pustych <- sum(is.na(SP500))

# Wyświetlenie wyników
print("Liczba pustych obserwacji w każdej kolumnie:")
## [1] "Liczba pustych obserwacji w każdej kolumnie:"
print(puste_kolumny)
## SP500 
##     0
print("Suma pustych obserwacji w całym dataframe:")
## [1] "Suma pustych obserwacji w całym dataframe:"
print(suma_pustych)
## [1] 0

W drugim zbiorze danych nie występują puste wartości, co otwiera przed nami możliwość pełnego wykorzystania danych na kolejnych etapach analizy.

Prezentacja danych

Celem prezentacji zaprezentowane notowania każdych akcji oraz indeksu na wykresie liniowym za pomocą pakietu ggplot2. Do każdego wykresu szeregu czasowego uwzględniono linie trendu aby pokazać jak w analizowanym okresie wyglądały zmiany cen akcji.

Notowania <-merge(dane,SP500)
Notowania <-data.frame(Data = index(Notowania), coredata(Notowania))

CISCO

ggplot(Notowania, aes(x = Data, y = CISCO)) +
  geom_line(color = "blue") +  # Linia liniowa
  stat_smooth(method = "lm", se = FALSE, color = "red") +
  theme_bw() +
  labs(y = "Wartość akcji CISCO [USD]")

EATON

ggplot(Notowania, aes(x = Data, y = EATON)) +
  geom_line(color = "blue") +  # Linia liniowa
  stat_smooth(method = "lm", se = FALSE, color = "red")+
  theme_bw()+
  labs(y = "Wartość akcji EATON [USD]")

PROCTER

ggplot(Notowania, aes(x = Data, y = PROCTER)) +
  geom_line(color = "blue") +  # Linia liniowa
  stat_smooth(method = "lm", se = FALSE, color = "red")+
  theme_bw() +
  labs(y = "Wartość akcji PROCTER [USD]")

S&P500

ggplot(Notowania, aes(x = Data, y = SP500)) +
  geom_line(color = "blue") +  # Linia liniowa
  stat_smooth(method = "lm", se = FALSE, color = "red")+
  theme_bw()+
  labs(y = "Wartość akcji S&P 500 [USD]")

Wykresy potwierdzają, że wszystkie akcje w analizowanym okresie odnotowały wzrosty. W celu uzyskania dodatkowych wniosków i szczegółowej oceny, skorzystano z wykresów pudełkowych, aby zanalizować rozkłady tych zmiennych.

Wykresy pudełkowe

# Zmienność  akcji

y <- data.frame(Dzien = index(dane), coredata(dane))
y_long <- pivot_longer(y, cols = -Dzien, names_to = "Akcje", values_to = "Adjusted")

ggplot(y_long, aes(x = Akcje, y = Adjusted, fill = Akcje)) + 
  geom_boxplot() +
  stat_summary(fun = "mean", geom = "point", shape = 8,
               size = 2, color = "white") +
  theme_linedraw()

Na powyższym wykresie, wykorzystując funkcje pakietu ggplot2, zauważalny jest istotny fakt dotyczący akcji EATON. Te akcje wykazują najwyższe wartości w porównaniu do pozostałych zestawów danych. Warto zauważyć większe rozproszenie wartości w przypadku EATON, szczególnie w porównaniu do akcji PROCTER. Dodatkowo, na wykresach oznaczono białą gwiazdką punkt średniej dla każdej spółki. Na podstawie tych oznaczeń można stwierdzić, że akcje PROCTER osiągają lepsze wyniki w porównaniu do EATON.

Ciemną linią zaznaczono medianę dla każdej spółki. Zauważamy, że mimo mniejszego rozproszenia wartości, akcje PROCTER osiągają wyższą medianę w porównaniu do akcji EATON.

Najmniejsze wartości w wykresie pudełkowym prezentuje przedsiębiorstwo CISCO.

Wyciągnijmy zatem z danych wartości liczbowe celem dogłębnej analizy wykorzystując do tego przekształcenia z pakietu dplyr.

dane2<-data.frame(index =index(dane), coredata(dane)) # konwersja danych na data frame

dane2 <- dane2 %>%
  select(CISCO,EATON,PROCTER)

knitr::kable(summary(dane2, digits = 3, format.args = list(big.mark = ",",
  scientific = FALSE)))
CISCO EATON PROCTER
Min. :15.6 Min. : 38.6 Min. : 53.6
1st Qu.:22.9 1st Qu.: 57.1 1st Qu.: 67.1
Median :37.5 Median : 71.4 Median : 81.4
Mean :35.4 Mean : 93.4 Mean : 97.1
3rd Qu.:45.9 3rd Qu.:133.7 3rd Qu.:128.6
Max. :59.7 Max. :240.8 Max. :156.2

Na podstawie przedstawionej tabeli stwierdzono że to akcje PROCTER wyceniane są wyżej. Wartości takie jak średnia, mediana oraz pierwszy kwartyl dla akcji Procter osiągneły najwyższy wskaźnik. Sytuacja się zmienia w przypadku obserwacji najwyższych wartości gdzie to akcje EATON wskazały w obserwowanym okresie najwyższą cenę.

Na podstawie tabeli można potwierdzić że akcje przedsiębiorstwa CISCO są stabilne gdzie cene za akcje wahają się w granicach około 30 USD - 60 USD.

Obliczanie stopy zwrotu

Kolejnym etapem dla zbioru akcji wymagane jest obliczenie stopy zwrotu czyli zysku dla inwestora. Do obliczenia zysku wykorzystywane będą logarytmiczne stopy zwrotu które obliczane są za pomocą wzoru:

\(r_{t} =\ln(\frac{P_{t}}{P_{t-1}})=lnP_{t}-lnP_{t-1}\)

Gdzie: \(r_{t}\) - logarytmiczna stopa zwrotu w okresie t,

\(P_{t}\), \(P_{t-1}\) - cena akcji w momencie t, t-1.

Do tego ponownie wykorzystamy R wyliczając w pierwszej kolejności logarytmy każdej zmiennej a następnie różnice.

# Obliczenie logarytmicznej stopy zwrotu
stopy_l<-diff(log(dane))
stopy_l<-na.omit(stopy_l)

To samo robimy dla zbioru indeksu SP500. Po drodze wykorzystujemy kod na.omit celem usunięcia pustej wartości początkowej.

# Obliczenie logarytmicznej stopy zwrotu
stopy_sp<-diff(log(SP500))
stopy_sp<-na.omit(stopy_sp)

Mając wyliczone stopy zwrotu możemy zaprezentować zmienność stóp dla każdej akcji oraz rozkład. Do tego wykorzystano przekształcenia tabeli szerokiej na długą za pomocą pakietu tidyr oraz graficzne pokazanie zmiennosci pakietem ggplot2.

# Zmienność stóp zwrotu

x <- data.frame(Dzien = index(stopy_l), coredata(stopy_l))

x_long <- pivot_longer(x, cols = -Dzien, names_to = "Akcje", values_to = "Zwrot")


ggplot(x_long, aes(Dzien, Zwrot, color = Akcje)) +
  geom_line() +
  facet_grid(Akcje ~ .) +
  theme_bw() +
  scale_y_continuous(labels = scales::percent_format())

ggplot(x_long, aes(Zwrot))+
  geom_histogram(color="black", fill="lightblue")+
  facet_wrap(~Akcje)+
  scale_x_continuous(labels = scales::percent_format())

Wśród wszystkich przedsiębiorstw można dostrzec podobne zyski dla inwestorów z poszczególnych akcji. Jest zauważalne, że po 2020 roku akcje wszystkich trzech spółek doświadczyły zmiany w wariancji, co było rezultatem okresu pandemii, mającego istotny wpływ na rynek kapitałowy.

Analizując histogramy, można zauważyć, że akcje firmy Procter charakteryzowały się większą liczbą wartości zbliżonych do zera. Wyższe zyski można natomiast zaobserwować między innymi w akcjach Eaton oraz Cisco.

Aby lepiej zrozumieć sytuację, warto przyjrzeć się podstawowym wskaźnikom tych akcji za pomocą statystyk opisowych.

Statystyki opisowe

Statystyki opisowe to zestaw metod i wskaźników służących do organizacji, podsumowywania i prezentowania danych numerycznych w sposób zwięzły, umożliwiający zrozumienie istotnych cech zbioru danych. Obejmują takie miary jak średnia, mediana, odchylenie standardowe i kwartyle, które pomagają opisać rozkład i charakterystyki danych statystycznych.

Celem analizy wykorzystano podstawowe wskaźniki jakimi są średnia, mediana, odchylenie standardowe, kurtoza, skośność, wartość minimalna, wartość maksymalna oraz licznik.

Analizie poddano zbiór wartości stóp logarytmicznych akcji Cisco, Eaton i Procter.

statystyki_opisowe <- function(x) {
  x <-x[!is.na(x)]
  m <- mean(x) * 100
  md <- median(x)* 100
  s <- sd(x)* 100
  w <-var(x)* 100
  k <- kurtosis(x)
  sk <- moments::skewness(x)
  mn <- min(x)* 100
  mx <- max(x)* 100
  d <- length(x)

  return(c(
    "Srednia [%]" = m,
    "Mediana [%]" = md,
    "Odchylenie standardowe [%]" = s,
    "Wariancja [%]" = w,
    "Kurtoza" = k,
    "Skosnosc" = sk,
    "Minimum [%]" = mn,
    "Maksimum [%]" = mx,
    "Licznik" = d
  ))
}

df<-as.data.frame(stopy_l)

statystyka <-data.frame(
    "CISCO" = statystyki_opisowe(df$CISCO),
    "EATON" = statystyki_opisowe(df$EATON),
    "PROCTER" = statystyki_opisowe(df$PROCTER)
  )

knitr::kable(statystyka, digits = 3, format.args = list(big.mark = ",",
  scientific = FALSE))
CISCO EATON PROCTER
Srednia [%] 0.045 0.056 0.035
Mediana [%] 0.060 0.063 0.058
Odchylenie standardowe [%] 1.577 1.730 1.147
Wariancja [%] 0.025 0.030 0.013
Kurtoza 15.185 17.164 15.212
Skosnosc -0.657 0.163 0.003
Minimum [%] -14.769 -12.747 -9.143
Maksimum [%] 12.552 20.916 11.341
Licznik 2,516.000 2,516.000 2,516.000

Siła korelacji

Korelacja to miara statystyczna, która opisuje stopień związku między dwiema zmiennymi. Wartość korelacji mieści się między -1 a 1, gdzie:

  • 1 oznacza doskonałą dodatnią korelację (obie zmienne rosną razem),
  • -1 oznacza doskonałą ujemną korelację (jedna zmienna maleje, gdy druga rośnie),
  • 0 oznacza brak korelacji (zmienne nie wykazują związku).

Korelacja nie implikuje przyczynowości, ale jedynie mierzy siłę i kierunek związku między zmiennymi.

# Obliczenie lsiły korelacji
dane_kor<-merge(stopy_l,stopy_sp)

Korelacja <- cor(dane_kor)

corrplot(
  Korelacja,
  method = "number",       # metoda wizualizacji
  shade.col = NA,         # kolor linii cienia
  tl.col = "black",       # kolor etykiety tekstu
  tl.srt = 45,            # rotacja etykiety tekstu
  addCoef.col = "black",  # kolor współczynników
  order = "original"      # metoda sortowania
)

Na podstawie przeprowadzonej analizy korelacji można zauważyć, że akcje przedsiębiorstwa EATON wykazują istotne skorelowanie z rynkiem. W przypadku wzrostów na rynku obserwuje się równoczesne wzrosty w akcjach tego przedsiębiorstwa. Analogicznie, akcje przedsiębiorstwa CISCO również wykazują podobne zjawisko interakcji z rynkiem.

Warto zaznaczyć, że akcje przedsiębiorstwa PROCTER wykazują najmniejsze skorelowanie z rynkiem, co oznacza, że ich zmiany nie są bezpośrednio związane z ogólnymi trendami na rynku. Niemniej jednak, obserwuje się pewne pośrednie wzrosty w przypadku wzrostów na rynku, co sugeruje, że istnieje pewien stopień wpływu rynku na te akcje.

Jeśli spojrzymy na same akcje między sobą, to można zauważyć niewielką siłę korelacji. Szczególnie istotna wydaje się para akcji CISCO-EATON, gdzie siła korelacji Pearsona osiąga wartość 0,54, co świadczy o umiarkowanej zależności między tymi dwoma instrumentami finansowymi.

Wskaźniki Alpha i Beta

W kontekście rynku kapitałowego, wskaźniki alpha i beta są używane w modelu Capital Asset Pricing Model (CAPM), który pomaga ocenić ryzyko i zwrot z inwestycji. Oto ich krótka charakteryzacja:

Alpha (α):

Alpha mierzy nadzwyczajną stopę zwrotu aktywa finansowego (np. akcji) w porównaniu do oczekiwanej stopy zwrotu wynikającej z modelu CAPM. Pozytywna wartość alpha wskazuje, że aktywo osiągnęło lepszy wynik niż zakładał model, podczas gdy wartość negatywna wskazuje wynik gorszy.

Beta (β):

Beta mierzy wrażliwość aktywa finansowego na ruchy rynku ogólnego, zazwyczaj reprezentowanego przez indeks, takie jak S&P 500. Beta równa 1 oznacza, że aktywo reaguje w podobny sposób jak rynek. Beta większa niż 1 wskazuje na większą wrażliwość na ruchy rynku, natomiast beta mniejsza niż 1 oznacza mniejszą wrażliwość.

tabela_wspolczynnikow <- data.frame(
  Akcje = c("CISCO", "EATON", "PROCTER"),
  Beta = c(
    cov(dane_kor$CISCO, dane_kor$SP500) / var(dane_kor$SP500),
    cov(dane_kor$EATON, dane_kor$SP500) / var(dane_kor$SP500),
    cov(dane_kor$PROCTER, dane_kor$SP500) / var(dane_kor$SP500)
  ),
  Alpha = c(
    coef(lm(dane_kor$CISCO ~ dane_kor$SP500))[1],
    coef(lm(dane_kor$EATON ~ dane_kor$SP500))[1],
    coef(lm(dane_kor$PROCTER ~ dane_kor$SP500))[1]
  ))
knitr::kable(tabela_wspolczynnikow, digits = 5, format.args = list(big.mark = ",",
  scientific = FALSE))
Akcje Beta Alpha
CISCO 0.98801 0.00007
EATON 1.13621 0.00013
PROCTER 0.57180 0.00013
  • Cisco:

Beta: 0.98801

Alpha: 0.00007

Beta wynosi 0.98801, co oznacza, że akcje CISCO są mniej ryzykowne niż średnia na rynku. Jeżeli rynek rośnie, można oczekiwać, że akcje CISCO wzrosną w podobnym stopniu, ale z mniejszym ryzykiem. Alpha wynosi 0.00007, co sugeruje, że w przypadku oczekiwanego zwrotu na rynku, akcje CISCO mają niewielką przewagę nad oczekiwaniami, co może być interpretowane jako dodatkowy zysk w porównaniu do rynkowej normy.

  • Eaton:

Beta: 1.13621

Alpha: 0.00013

Beta wynosi 1.13621, co wskazuje, że akcje EATON są bardziej ryzykowne niż średnia na rynku. W przypadku wzrostów na rynku, można oczekiwać, że akcje EATON wzrosną bardziej niż przeciętna akcja na rynku, ale z większym ryzykiem. Alpha wynosi 0.00013, co oznacza, że akcje EATON mają niewielką przewagę nad oczekiwaniami rynkowymi.

  • Procter:

Beta: 0.57180

Alpha: 0.00013

Beta wynosi 0.57180, co sugeruje, że akcje PROCTER są mniej ryzykowne niż przeciętne akcje na rynku. Wzrosty na rynku będą miały mniejszy wpływ na akcje PROCTER, ale również z mniejszym ryzykiem. Alpha wynosi 0.00013, co wskazuje na niewielką przewagę nad oczekiwaniami rynkowymi, co może być interpretowane jako dodatkowy zysk w porównaniu do rynkowej normy.

Optymalizacja portfela inwestycyjnego

W celu optymalizacji portfela inwestycyjnego, przystąpimy do pierwszej fazy analizy, gdzie wyliczymy oczekiwany zysk z poszczególnych akcji. Następnie skoncentrujemy się na obliczeniu kowariancji między tymi akcjami, co pozwoli nam zrozumieć ich wzajemne relacje w kontekście ryzyka. W kolejnym etapie stworzymy zbiór możliwości inwestycyjnych, mając na uwadze trzy spółki z rynku SP500. Celem tego procesu będzie zidentyfikowanie najbardziej optymalnego portfela, który uwzględnić będzie równowagę pomiędzy oczekiwanym zyskiem a minimalizacją ryzyka.

# Obliczenie oczekiwanej stopy zwrotu na podstawie średniej każej z kolumn

srednia <-colMeans(stopy_l)
print(srednia)
##        CISCO        EATON      PROCTER 
## 0.0004468814 0.0005629663 0.0003479464

Spośród wymienionych akcji, na podstawie średnich można oczekiwać najwyższych zysków z akcji EATON. Warto zaznaczyć, że mimo korzystnych tendencji na wykresach dla firmy PROCTER, akcje tej spółki charakteryzują się najniższą średnią w całym zakresie obserwacji dla logarytmicznych zwrotów. Średnia dla spółki CISCO jest natomiast umiejscowiona pośrodku.

Wyliczenie kowariancji

Kowariancja to miara statystyczna, która opisuje, jak dwie zmienne zmieniają się razem. W kontekście finansów, kowariancja między dwoma akcjami wskazuje, czy ich ceny poruszają się w tym samym kierunku (kowariancja dodatnia), czy w przeciwnych kierunkach (kowariancja ujemna). Jednak warto zauważyć, że sama kowariancja nie jest normalizowana, co oznacza, że jej wartość nie jest standaryzowana i może przyjmować różne zakresy.

Wzór na kowariancję (Cov) między dwiema zmiennymi X i Y to:

\(Cov(X,Y) = \frac{\sum_{n=1}^{n} (X_{i}-\overline{X})(Y_{i}-\overline{Y}) }{n-1}\)

\(COV_{roczna} = COV_{dzienna}*252\) [5]

# Obliczenie Kowariancji

kowariancja <-cov(stopy_l) * 252
print(kowariancja)
##              CISCO      EATON    PROCTER
## CISCO   0.06265725 0.03708832 0.02030791
## EATON   0.03708832 0.07539660 0.01959929
## PROCTER 0.02030791 0.01959929 0.03317930

Kowariancja między akcjami CISCO a EATON wynosi 0.03708834, co wskazuje na pewien stopień równoczesnych zmian cen obu akcji. Niemniej jednak, ze względu na względnie niską wartość, korelacja ta nie jest bardzo silna.

Kowariancja między akcjami CISCO a PROCTER wynosi 0.02030792, co sugeruje pewien stopień równoczesnych zmian cen obu akcji. Jednak wartość ta jest niższa niż dla pary CISCO-EATON, co wskazuje na słabszą zależność między tymi dwoma akcjami.

Kowariancja między akcjami EATON a PROCTER wynosi 0.01959932, co wskazuje na pewien stopień równoczesnych zmian cen obu akcji. Jednak, podobnie jak w przypadku pary CISCO-PROCTER, kowariancja ta jest stosunkowo niska.

Wytyczne dla portfela inwestycyjnego

Mając wyliczone średni zysk oraz kowariancje natszedł czas budowy i optymalizacji portfela inwestycyjnego składającego się z trzech akcji rynku S&P 500: - CISCO - EATON - PROCTER.

Na podstawie poniższego algorytmu w języku R, zostały stworzone puste wektory, w których będą przechowywane dane do obliczeń związanych z 500 000 kombinacjami portfela. Celem tych obliczeń jest znalezienie portfela, który: - jest jednoelementowy - najbardziej efektywny - poddany dywersyfikacji prostej - najmniejszego ryzyka - przynoszący najwyższy zysk.

# Wyznaczenie liczby możliwości inwestycyjnych

num_port <- 500000

# Macierz do przechowywania wag

Wagi <- matrix(nrow = num_port,
                  ncol = length(ticker))

# Wektor do przechodwywania poziomu zysku

port_returns <- vector('numeric', length = num_port)


# Wektor dla odchylenia standardowego

port_risk <- vector('numeric', length = num_port)

# Wektor dla współczynnika Sharpa

sharpe_ratio <- vector('numeric', length = num_port)

Obliczanie wag

Do wyliczeń przez R poziomu zysku oraz ryzyka zostały wykorzystane następujące wzory[6]:

\(i_{p} = \sum_{i=1}^{n} w_{i}E(i_{i})\)

\(\sigma_{p} = \sqrt{\sum_{i=1}^{n} w_{i}^{2} \sigma_{i}^{2} + 2\sum_{i=1}^{n-1} \sum_{j=i+1}^{n} w_{i} w_{j}Cov_{ij}}\)

\(\sum_{i=1}^{n}w_{i}=1\)

\(S_{i}=\frac{R_{i} - R_{f}}{\sigma_{i}}\)

gdzie:

\(i_{p}\) - oczekiwana stopa zwrotu

\(w_{i}\) - waga aktywa

\(\sigma_{p}\) - ryzyko portfela

\(Cov_{ij}\) - kowariancja aktywa i, j

\(S_{i}\) - wskaźnik Sharpa

\(R_{f}\) - stopa wolna od ryzyka

\(R_{i}\) - stopa zwrotu portfela

Następnie utworzono funkcje która dokonuje obliczeń a wyniki kieruje do utworzonych wcześniej wektorów.

for (i in seq_along(port_returns)) {
  
# WarunEK dla punktu dywersyfikacji prostej
  if (i == 1) {
    wts <- rep(1/length(ticker), length(ticker))
  }
# Warunek dla portfeli jednoelementowych
  else if (i <= length(ticker) + 1) {
    wts <- rep(0, length(ticker))
    wts[i - 1] <- 1
  }
# Warunki dla pozostałych kombinacji wag
  else {
    wts <- runif(length(ticker))
    wts <- wts / sum(wts)
  }
  
# Wprowadzenie wag do macierzy
  Wagi[i, ] <- wts

# Obliczenie oczekiwanej stopy zwrotu
  port_ret <- sum(wts * srednia)
  port_ret <- ((port_ret + 1)^252) - 1
  
  # Wprowadzenie wyników do macierzy
  port_returns[i] <- port_ret
  
  # Obliczenie poziomu ryzyka
  port_sd <- sqrt(t(wts) %*% (kowariancja %*% wts))
  
  # Wprowadzenie danych do maciezy
  port_risk[i] <- port_sd
  
  # Obliczenie współczynnika Sharpe'a
  rf <- 0.05 # Założono 5% stopy wolnej od ryzyka
  sr <- (port_ret - rf) / port_sd
  
  # Wprowadzenie wyników do macierzy
  sharpe_ratio[i] <- sr
}

Tworzenie portfela

Obliczone wektory zostały następnie wprowadzone do tabeli w celu pozyskania informacji dotyczących kombinacji instrumentów oraz ich zysku, ryzyka i wskaźnika Sharpe.

# Tworzenie tabeli portfela

Portfel <- tibble(Zysk = port_returns,
                           Ryzyko = port_risk,
                           Sharp = sharpe_ratio)


# Zmiana macierzy wag na data frame
Wagi <- as.data.frame(Wagi)

# Łączenie danych w jedną tabelę
Portfel <- as.data.frame(cbind(Wagi, Portfel))
head(Portfel*100)
##          V1         V2        V3      Zysk   Ryzyko    Sharp
## 1  33.33333  33.333333  33.33333 12.078452 19.00948 37.23644
## 2 100.00000   0.000000   0.00000 11.917182 25.03143 27.63399
## 3   0.00000 100.000000   0.00000 15.237796 27.45844 37.28469
## 4   0.00000   0.000000 100.00000  9.162481 18.21519 22.85171
## 5  31.11729   6.746392  62.13632 10.415087 17.39941 31.12225
## 6  38.75186   6.950577  54.29756 10.637605 17.77046 31.72459

Mając utworzone 500 000 kombinacji kolejnym etapem jest identyfikacja poszukianych portfeli czyli: portfela najbardziej efektywnego, portfela minimalnego ryzyka, portfela maksymalnego zysku, portfeli jednoelementowych oraz portfela poddanego dywersyfikacji prostej.

Identyfikacja optymalnych kombinacji

# Wyznaczenie portfeli
max_sharp <- Portfel[Portfel$Sharp == max(Portfel$Sharp), ] * 100
min_ryzyko <- Portfel[Portfel$Ryzyko == min(Portfel$Ryzyko), ] * 100
max_zysk <- Portfel[Portfel$Zysk == max(Portfel$Zysk), ] * 100
p1 <- Portfel[Portfel$V1 == 1, ] * 100
p2 <- Portfel[Portfel$V2 == 1, ] * 100
p3 <- Portfel[Portfel$V3 == 1, ] * 100
dp <- Portfel[Portfel$V1 == 1/3, ] * 100

combined_data <- rbind(max_sharp, min_ryzyko, max_zysk, p1, p2, p3, dp)
names(combined_data)[1:3] <- c("CISCO", "EATON", "PROCTER")
rownames(combined_data) <- c("Maksymalny Sharp [%]", "Minimalne ryzyko [%]", "Maksymalny zysk [%]", 
                              "Portfel CISCO [%]", "Portfel EATON [%]", "Portfel PROCTER [%]", 
                              "Dywersyfikacja prosta [%]")

knitr::kable(combined_data, digits = 2, format.args = list(big.mark = ",",
  scientific = FALSE))
CISCO EATON PROCTER Zysk Ryzyko Sharp
Maksymalny Sharp [%] 18.30 60.47 21.24 13.31 21.44 38.76
Minimalne ryzyko [%] 16.57 12.39 71.03 10.35 17.14 31.23
Maksymalny zysk [%] 0.00 100.00 0.00 15.24 27.46 37.28
Portfel CISCO [%] 100.00 0.00 0.00 11.92 25.03 27.63
Portfel EATON [%] 0.00 100.00 0.00 15.24 27.46 37.28
Portfel PROCTER [%] 0.00 0.00 100.00 9.16 18.22 22.85
Dywersyfikacja prosta [%] 33.33 33.33 33.33 12.08 19.01 37.24

Na podstawie przedstawionej tabeli konstruując portfel dzieląc kapitał w proporcjach ok 18% w akcje Cisco, ok 60% w Eaton oraz ok 21% w Procter szacuje się zyskiem w wysokości około 13,30% oraz ryzykiem w wysokości ponad 21%. Niniejszy portfel osiąga wskaźnik Sharpa na poziomie 38,76% co czyni tą kombinacje najbardziej efektywną.

Portfel minimalnego ryzyka, w którym szacuje się poziom 17,14% osiąga się dzieląc kapitał między CISCO, EATON i PROCTER w proporcjach 16%/12%/72%. Szacuje się zysk w wysokości 10,35%, natomiast wskaźnik Sharpa osiąga wartość 31,21%

Chcąc maksymalizować zysk należy lokować kapitał na 100% akcji EATON. Dla takiego portfela osiągnie się 15,24% zysku natomiast powyższa kombinacja obarczona jest ryzykiem wynoszacym 27,46%.

Dywersyfikacja prosta czyli rozłożenie kapitału równomiernie wskazuje zysk na poziomie 12,08% natomiast ryzyko szacowane jest w wysokości 19,01%.

Wykres możliwości inwestycyjnych

Wszystkie możliwe kombinacje zostały przedstawione na wykresie punktowym. Celem tego wykresu jest dostarczenie wizualnej prezentacji różnych kombinacji portfela, ukazując stosunek oczekiwanego zysku do akceptowalnego poziomu ryzyka.

ggplot(Portfel, aes(Ryzyko, Zysk, color = Sharp)) +
  geom_point() +
  
  geom_point(data = Portfel[Portfel$Sharp == max(Portfel$Sharp), ], aes(Ryzyko, Zysk), color = "violet", size = 3) +
  geom_point(data = Portfel[Portfel$Ryzyko == min(Portfel$Ryzyko), ], aes(Ryzyko, Zysk), color = "salmon", size = 3) +
  geom_point(data = Portfel[Portfel$Zysk == max(Portfel$Zysk), ], aes(Ryzyko, Zysk), color = "green", size = 4) +
  geom_point(data = Portfel[Portfel$V1 == 1, ], aes(Ryzyko, Zysk), color = "red", size = 3) +
  geom_point(data = Portfel[Portfel$V2 == 1, ], aes(Ryzyko, Zysk), color = "red", size = 2) +
  geom_point(data = Portfel[Portfel$V3 == 1, ], aes(Ryzyko, Zysk), color = "red", size = 3) +
  
  # Dodaj etykiety dla dodatkowych punktów
  geom_text(data = Portfel[Portfel$Sharp == max(Portfel$Sharp), ], aes(label = "MAX EFEKTYWNOŚĆ"), vjust = -0.5, color = "violet", size = 3) +
  geom_text(data = Portfel[Portfel$Ryzyko == min(Portfel$Ryzyko), ], aes(label = "MIN RYZYKA"), vjust = -1, hjust = -0.01, color = "salmon", size = 3) +
  geom_text(data = Portfel[Portfel$Zysk == max(Portfel$Zysk), ], aes(label = "MAX ZYSKU"), hjust = 1.3, color = "green", size = 3) +
  geom_text(data = Portfel[Portfel$V1 == 1, ], aes(label = "100% CISCO"), vjust = -1, color = "red", size = 3) +
  geom_text(data = Portfel[Portfel$V2 == 1, ], aes(label = "100% EATON"), vjust = -0.5, hjust = 1, color = "red", size = 3) +
  geom_text(data = Portfel[Portfel$V3 == 1, ], aes(label = "100% PROCTER"), vjust = -0.5, color = "red", size = 3) +
  
  theme_bw() +
  scale_color_gradient(name = "Wskaźnik Sharpa") +
  scale_y_continuous(labels = scales::percent) +
  scale_x_continuous(labels = scales::percent) +
  
  labs(y = "Oczekiwany zysk [%]", x = "Poziom ryzyka [%]", title = "Zbiór możliwości inwestycyjnych")

Warto zauważyć że portfel najbardziej efektywny oznaczony jest fioletowym punktem. Portfel minimalnej wariancji oznaczono kolorem pomarańczowym, natomiast kolorem zielonym oznaczono portfel najwyższego zysku. Kolor czerwony stanowią portfele jednoelementowe.

Granica efektywna

Granica efektywna w modelu Markowitza to koncepcja, która opisuje zbiór wszystkich możliwych kombinacji portfela inwestycyjnego, oferujących najwyższy poziom oczekiwanego zysku przy danym poziomie ryzyka lub minimalny poziom ryzyka przy danym oczekiwanym zysku. Granica ta przedstawiana jest na wykresie punktowym, gdzie każdy punkt odpowiada innej kombinacji alokacji kapitału pomiędzy różnymi aktywami.

Celem inwestora jest maksymalizacja oczekiwanego zysku przy danym poziomie akceptowalnego ryzyka lub minimalizacja ryzyka przy określonym oczekiwanym zysku. Granica efektywna umożliwia inwestorowi wybór optymalnego portfela, który zapewnia najkorzystniejszy stosunek ryzyka do zysku, uwzględniając różne kombinacje aktywów w portfelu. Model Markowitza zakłada, że inwestorzy podejmują decyzje inwestycyjne, biorąc pod uwagę nie tylko oczekiwany zysk, ale także poziom ryzyka związany z danym portfelem.

Poniżej wykorzystano algorytm R celem identyfikacji 2000 portfeli z granicy efektywnej celem późniejszej prazentacji na wykresie zbioru możliwości inwestycyjnych.

# Wyznaczenie granicy efektywnej

find_highest_return_portfolio <- function(risk_level, Portfele) {
  highest_return_portfolio <- Portfel[Portfel$Ryzyko <= risk_level, ]
  highest_return_portfolio <- highest_return_portfolio[highest_return_portfolio$Zysk == max(highest_return_portfolio$Zysk), ]
  return(highest_return_portfolio)
}

# Określamy poziomy ryzyka
risk_levels <- seq(min(Portfel$Ryzyko), max(Portfel$Ryzyko), length.out = 2000)

# Znajdujemy portfele na granicy efektywnej dla każdego poziomu ryzyka
efficient_portfolios <- lapply(risk_levels, function(risk_level) {
  find_highest_return_portfolio(risk_level, Portfele)
})

# Konwertujemy wyniki do jednego ramki danych
Granica_efektywna <- do.call(rbind, efficient_portfolios)
head(Granica_efektywna*100)
##              V1       V2       V3     Zysk   Ryzyko    Sharp
## 249386 16.57395 12.39296 71.03310 10.35260 17.13955 31.22953
## 473989 16.66294 13.87491 69.46214 10.44366 17.14469 31.75131
## 496191 17.13912 14.27426 68.58663 10.48067 17.14979 31.95762
## 391352 16.29915 15.16582 68.53502 10.51089 17.15461 32.12486
## 490980 16.77410 15.40744 67.81846 10.53844 17.15999 32.27532
## 85677  16.61161 15.83382 67.55457 10.55949 17.16473 32.38905

Wykres granicy efektywnej

Wyliczając zbiór portfeli z granicy efektywnej zaprezentowano powyższe portfele na wykresie poniżej. Celem inwestora jest dobierać kombinacje aktywów w taki sposób aby portfele znajdowały się na granicy oznaczonej kolorem pomarańczowym.

ggplot(Portfel, aes(Ryzyko, Zysk, color = Sharp)) +
  geom_point() +
  geom_point(data = Granica_efektywna, aes(x = Ryzyko, y = Zysk), color = "orange", size = 2.5) +
  theme_bw() +
  scale_color_gradient(name = "Wskaźnik Sharpa") +
  scale_y_continuous(labels = scales::percent_format()) +
  scale_x_continuous(labels = scales::percent_format()) +
  labs(y = "Oczekiwany zysk [%]", x = "Poziom ryzyka [%]", title = "Zbiór możliwości inwestycyjnych", subtitle = "Granica efektywna")

Rozkład wag w granicy efektywnej

Na zakończenie wykorzystano pakiet fPortfolio do zbudowania wykresu pokazujący kombinacje wag w granicy efektywnej. Wykres zaprezentowano poniżej.

library(timeSeries)
library(fPortfolio)
x <- as.timeSeries(stopy_l)

# Estymacja macierzy kowariancji
x <- covEstimator(x)

# Specyfikacja portfela
shortSpec <- portfolioSpec()

# Ustawienie solvera
setSolver(shortSpec) <- "solveRshortExact"

# Tworzenie frontu portfela
shortFrontier <- portfolioFrontier(x, constraints = "LongOnly")

# Wykres wagi portfela
weightsPlot(shortFrontier, labels = TRUE, col = NULL)

Wykres potwierdza fakt że chcąć osiągać najlepsze zyski należy dobierać aktywa tak aby w większości procent kapitału lokować na akcjach EATON oraz PROCTER Akcje przedsiębiorstwa CISCO na podstawie wykresu powyżej stanowią najmniejszy udział dla wskazanego zysku aczkolwiek posiadając je w portfelu osiąga się dywersyfikacje ryzyka osiągając portfel na wysokości granicy efektywnej.

Podsumowanie

Projekt optymalizacji portfela inwestycyjnego w języku R, oparty na modelu Markowitza, skupił się na analizie 500 000 możliwych kombinacji aktywów. Głównym celem było znalezienie najefektywniejszego portfela, uwzględniającego równowagę między ryzykiem a zyskiem. Zastosowany model pozwolił zidentyfikować optymalny portfel, portfele jednoelementowe, portfel o najniższym ryzyku i o najwyższym zysku. Projekt dostarczył cennej wiedzy na temat różnorodności strategii inwestycyjnych, stanowiąc solidny fundament dla dalszych analiz i podejmowania świadomych decyzji inwestycyjnych.

Otrzymane wyniki projektu nie tylko pozwoliły zidentyfikować optymalne rozwiązania inwestycyjne, ale także dostarczyły istotnych wniosków na temat dynamiki rynku finansowego. Wnioski te stanowią cenny punkt wyjścia do dalszych badań nad strategiami inwestycyjnymi oraz służą jako narzędzie wspierające podejmowanie bardziej świadomych decyzji na rynku kapitałowym.

Przedstawiony projekt ma jedynie charakter demonstracyjny w zakresie analizy portfelowej przy użyciu języka R. W żadnym wypadku nie stanowi on rekomendacji ani sugestii odnośnie do decyzji inwestycyjnych. Jego celem jest jedynie przedstawienie możliwości analizy portfelowej w kontekście używania języka R, a wszelkie wnioski czy obserwacje powinny być traktowane wyłącznie jako element edukacyjny.