Przepowiednie wyroczni z Omaha

Warren Buffett jest jednym z najbardziej znanych inwestorów na świecie. Uczeń słynnego Benjamina Grahama nosi przydomek „wyrocznia z Omaha”. Zawdzięcza go trafnym decyzjom inwestycyjnym, jakie podejmował na przestrzeni ostatnich kilkudziesięciu lat.

Większość wspomnianych decyzji odnosi się do Berkshire Hathaway, którym Buffett zarządza. Holding ten regularnie osiąga świetnie wyniki i z łatwością „pokonuje” rynek. Jako CEO, Buffett co roku dzieli się z akcjonariuszami otwartymi listami. Ich treść podsumowuje miniony rok i odnosi się do perspektyw na kolejne lata. Zatem być może właśnie w nich jest zawarta przynajmniej część recepty na sukces.


  1. Wstęp.
  2. Cel projektu
  3. Założenia dotyczące projektu
  4. Opis użytych danych
  5. Wczytanie słowników
  6. Pobranie i przygotowanie danych
  7. Eksploracyjna analiza danych
  8. Weryfikacja hipotez
  9. Podsumowanie

Wstęp

Jako zapalony inwestor natknąłem się kiedyś na analizę sentymentu listów Buffeta. Zgrabne podsumowanie pokazywało, jak nastrój danego listu odnosi się do wahań rynkowych w danym roku. Wtedy po raz pierwszy pomyślałem, że może warto by było spojrzeć na ten sam temat, ale z nieco innej perspektywy i sprawdzić kilka dodatkowych hipotez.

Szalenie mnie ciekawi, jaki był sentyment poszczególnych listów. Dodatkowo chciałbym sprawdzić, czy istnieje jakakolwiek zależność, pomiędzy wspomnianym sentymentem a wynikami, jakie osiągała amerykańska giełda i Berkshire Hathaway.

Cel projektu

Celem ogólnym projektu jest zbadanie sentymentu listów publikowanych przez Warrena Buffeta na stronie Berkshire Hathaway. Celem szczegółowym jest zbadanie czterech hipotez:

Hipoteza 1
Dzięki niej chciałbym odpowiedzieć na pytanie: czy sentyment listów jest skorelowany z wynikami S&P 500 z minionego roku?

  • H0: Nie ma korelacji pomiędzy sentymentem listów a wynikami indeksu S&P 500 z minionego roku.
  • H1: Istnieje korelacja pomiędzy sentymentem listów a wynikami indeksu S&P 500 z minionego roku.

Hipoteza 2
Dzięki niej chciałbym odpowiedzieć na pytanie dotyczące przyszłości: czy sentyment listów jest skorelowany z wynikami S&P 500 z kolejnego roku (chodzi o rok, w którym to list był publikowany)?

  • H0: Nie ma korelacji pomiędzy sentymentem listów a wynikami indeksu S&P 500 z danego roku.
  • H1: Istnieje korelacja pomiędzy sentymentem listów a wynikami indeksu S&P 500 z danego roku.

Hipoteza 3
Dzięki niej chciałbym odpowiedzieć na pytanie: czy sentyment listów jest skorelowany z wynikami, jakie osiągnął Berkshire Hathaway w minionym roku?

  • H0: Nie ma korelacji pomiędzy sentymentem listów a wynikami giełdowymi Berkshire Hathaway z minionego roku.
  • H1: Istnieje korelacja pomiędzy sentymentem listów a wynikami giełdowymi Berkshire Hathaway z minionego roku .

Hipoteza 4
Dzięki niej chciałbym odpowiedzieć na pytanie: czy sentyment listów jest skorelowany z wynikami, jakie osiągnął Berkshire Hathaway w kolejnym roku?

  • H0: Nie istnieje korelacja pomiędzy sentymentem listów a wynikami giełdowymi Berkshire Hathaway z danego roku.
  • H1: Istnieje korelacja pomiędzy sentymentem listów a wynikami giełdowymi Berkshire Hathaway z danego roku.

W mojej ocenie szczególnie interesująca jest hipoteza dotycząca korelacji sentymentu listu i przyszłych wahań kursu S&P 500. Jeśli udałoby się ją potwierdzić, to oznaczałoby to, można wnioskować, że Warrent Buffet przewidywał nastroje panujące na amerykańskiej giełdzie).

Założenia dotyczące projektu

Naiwnie zakładam, że S&P 500 obrazuje sytuację dla całego rynku amerykańskiego, co oczywiście nie musi być prawdą. Nie mniej indeks ten często jest „benchmarkiem” w różnego typu analizach rynkowych.

Do zbadania sentymentu listów użyję analizy sentymentu bing, opracowanej przez Bing Liu z University of Illinois w Chicago. To podejście do analizy sentymentu można opisać za pomocą następujących kroków:

  1. Wyczyszczenie tekstu poprzez usunięcie wszystkich liczb, niepotrzebnych znaków i „stopwords-ów”.
  2. Podzielenie dokumentu na zbiór unikatowych słów.
  3. Określenie sentymentu każdego słowa: negatywny, neutralny, pozytywny.
  4. Wyznaczenie współczynnika sentymentu tekstu, który jest wyrażony jako: (liczba słów pozytywnych – liczba słów negatywnych) / (liczba wszystkich słów).

Bing Liu udostępnił słownik wyrazów pozytywnych i negatywnych, z którego będę korzystać. Zawiera on 6790 wyrazów (2006 pozytywnych, 4784 negatywnych).

Opis użytych danych

Do przeprowadzenia analizy użyję następujących danych:

  • Listów Warrena Buffetta do akcjonariuszy Berkshire Hathaway. Są dostępne na oficjalnej stronie internetowej BH. Dla przykładu: list widniejący na podstronie „2016”:
    • podsumowuje rok 2016,
    • mówi o perspektywach na rok 2017,
    • został opublikowany w lutym 2017 roku.
  • Wahań kursu Berkshire Hathaway na przestrzeni ostatnich 40 lat.
  • Wahań kursu indeksu S&P 500 (dla tych którzy nie wiedzą: S&P 500 jest indeksem giełdy amerykańskiej. W jego skład wchodzi 500 przedsiębiorstw o największej kapitalizacji, notowanych na New York Stock Exchange i NASDAQ. Powinien on zatem odzwierciedlać ogólny stan amerykańskiego rynku w danym roku).
  • Słownika wyrazów pozytywnych.
  • Słownika wyrazów negatywnych.
  • Słownika „stopwords-ów”.

Wczytanie słówników

Wczytuję słowniki wyrazów pozytywnych i negatywnych.

pos = open("data\\positive.txt","r")
pos = list(pos.read().split('\n'))

neg = open("data\\negative.txt","r")
neg = list(neg.read().split('\n'))

Wczytuję słowniki stopwords z dwóch bibliotek dostępnych w Python: many_stop_wordsstop_words.

# many_stop_words
stop_words_1 = list(many_stop_words.get_stop_words('en'))
# stop_words
stop_words_2 = get_stop_words('en')        
# łączę oba zbiory
stop_words_3 = stop_words_1 + stop_words_2

Pobranie i przygotowanie danych

Listy są umieszczane na stronie w dwóch formatach: pdf i html. Muszę zatem zdefiniować dwie funkcje.

Funkcja służąca do pobierania listów w formacie html:

def download_letters_html(start_year):
    a = []
    for x in range(0,30):
        year = start_year+x
        print('Pobieram list: ' + str(year))
        if year > 2001:
            break
        # prepare url
        if(year < 1998):
            url = 'http://www.berkshirehathaway.com/letters/' + str(year) + '.html'
        elif((year > 1997) & (year < 2000)):
            url = 'http://www.berkshirehathaway.com/letters/' + str(year) + 'htm.html'
        elif((year > 1999) & (year < 2002)):
            url = 'http://www.berkshirehathaway.com/' + str(year) + 'ar/' + str(year) + 'letter.html'
        elif(year==2002):
            break
        # download website        
        page = requests.get(url)
        tree = html.fromstring(page.content)        
        a.append([year,tree])
    return a

Funkcja służąca do pobierania listów w formacie pdf:

def download_letters_pdf(start_year):
    a = []
    for x in range(0,30):
        year = start_year+x
        print('Pobieram list: ' + str(year))    
        url = "http://www.berkshirehathaway.com/letters/" + str(year) + "ltr.pdf"
        pdf = pdfx.PDFx(url)    
        text = pdf.get_text()
        a.append([year,text])
        if(year==2016):
            break
    return a

Po pobraniu danych, będę musiał zbadać ich sentyment. W tym celu definiuję następującą funkcję:

def analyze_letters(letters):
    ret = []
    words = []
    if(letters[0][0]>2000):
        for letter in letters:    
            blob = TextBlob(letter[1])
            year = letter[0]
            print('Analizuję sentyment listu: ' + str(year))
            num_pos_words = 0
            num_neg_words = 0
            num_neu_words = 0
            for word in blob.words:
                # check if the word is really a word
                if(word not in stop_words_3 and word.isalnum() and word.isalpha() and len(word) > 1):
                    if(word in pos):
                        num_pos_words = num_pos_words + 1
                        sentiment = 'positive'
                    elif(word in neg):
                        num_neg_words = num_neg_words + 1
                        sentiment = 'negative'
                    else:
                        num_neu_words = num_neu_words + 1
                        sentiment = 'neutral'
                    words.append([year,word,sentiment])
            pred_year = year+1
            ret.append([pred_year, num_pos_words, num_neg_words, num_neu_words])
    else:                
        for letter in letters:
            year = letter[0]
            blob = TextBlob('')
            print('Analizuję sentyment listu: ' + str(year))
            sentiment = ''
            for x in letter[1].itertext():
                blob_n = TextBlob(x)
                blob = blob + blob_n
            num_pos_words = 0
            num_neg_words = 0
            num_neu_words = 0
            for word in blob.words:
                if(word not in stop_words_3 and word.isalnum() and word.isalpha() and len(word) > 1):
                    if(word in pos):
                        num_pos_words = num_pos_words + 1
                    elif(word in neg):
                        num_neg_words = num_neg_words + 1 
                    else:
                        num_neu_words = num_neu_words + 1
                        sentiment = 'neutral'
                    words.append([year,word,sentiment])
            pred_year = year+1
            ret.append([pred_year, num_pos_words, num_neg_words, num_neu_words])
    return ret, words

Funkcja zawiera jedną „duzą” instrukcję warunkową if, która służy do podzielenia listów o różnych formatach (html, pdf). W zależności of formatu listu, nieco inaczej wygląda jego analiza.

Mając zdefiniowane wszystkie niezbędne funkcje mogę przejść do ich wywołania.

# html letters
letters_html = download_letters_html(1977)
letters_sent_html, words_1 = analyze_letters(letters_html)
# pdf letter
letters_pdf = download_letters_pdf(2003)
letters_sent_pdf, words_2 = analyze_letters(letters_pdf)

Powyższy proces na moim komputerze trwa ok 7-8 minut. Po jego zakończeniu otrzymuję dwa obiekty typu listletters_sent_htmlletters_sent_pdf. W nich zapisane są wszystkie niezbędne dane do obliczenia sentymentu poszczególnych listów. Wczytuję je następnie do dataframe’a.

df = pd.DataFrame(letters_sent_html, columns=['Release_Year', 'Num_Pos_Words', 'Num_Neg_Words', 'Num_Neu_Words'])
df = df.append(pd.DataFrame(letters_sent_pdf, columns=df.columns), ignore_index=True)
df.head()
   Release_Year  Num_Pos_Words  Num_Neg_Words  Num_Neu_Words
0          1978             69             39           1332
1          1979            135             63           1701
2          1980            182            122           2562
3          1981            165            147           2975
4          1982            144            127           2673

UWAGA: w dalszej analizie pomijam dane z roku 2003. Niestety ale przy próbie pobieranie listu z 2003 roku otrzymywałem błąd z biblioteki pdfx.

Do pełni szczęścia brakuje mi jeszcze danych dotyczących wahań kursów Berkshire Hathaway i S&P 500. Dodaję je zatem ręcznie:

s_and_p__this_year = [0.064,0.182,0.323,-0.05,0.214,0.224,0.061,0.316,0.186,0.051,0.166,0.317,-0.031,0.305,0.076,0.101,0.013,0.376,0.23,0.334,0.286,0.21,-0.091,-0.119,-0.221,0.109,0.049,0.158,0.055,-0.37,0.265,0.151,0.021,0.16,0.324,0.137,0.014,0.0954,0.1952]
b_h__this_year = [0.145,1.025,0.328,0.318,0.384,0.69,-0.027,0.937,0.142,0.046,0.593,0.846,-0.231,0.356,0.298,0.389,0.250,0.574,0.062,0.349,0.522,-0.199,0.266,0.065,-0.038,0.043,0.008,0.2410,0.2870,-0.3180,0.0270,0.2140,-0.047,0.168,0.3270,0.270,-0.125,0.23,0.2137]
s_and_p__last_year = [-0.074,0.064,0.182,0.323,-0.05,0.214,0.224,0.061,0.316,0.186,0.051,0.166,0.317,-0.031,0.305,0.076,0.101,0.013,0.376,0.23,0.334,0.286,0.21,-0.091,-0.119,-0.221,0.109,0.049,0.158,0.055,-0.37,0.265,0.151,0.021,0.16,0.324,0.137,0.014,0.0954]
b_h__last_year = [0.468, 0.145,1.025,0.328,0.318,0.384,0.69,-0.027,0.937,0.142,0.046,0.593,0.846,-0.231,0.356,0.298,0.389,0.250,0.574,0.062,0.349,0.522,-0.199,0.266,0.065,-0.038,0.043,0.008,0.2410,0.2870,-0.3180,0.0270,0.2140,-0.047,0.168,0.3270,0.270,-0.125,0.23]
df['b_h__this_year'] = b_h__this_year
df['s_and_p__this_year'] = s_and_p__this_year
df['b_h__last_year'] = b_h__last_year
df['s_and_p__last_year'] = s_and_p__last_year

Teraz, gdy mam już wszystkie dane, mogę przejść do obliczenia współczynnika sentymentu.

df['coeff'] = (df.Num_Pos_Words-df.Num_Neg_Words)/(df.Num_Pos_Words+df.Num_Neg_Words+df.Num_Neu_Words)

Teraz całość wygląda następująco:

   Release_Year  Num_Pos_Words  Num_Neg_Words  Num_Neu_Words     coeff
0          1978             69             39           1332  0.020833
1          1979            135             63           1701  0.037915
2          1980            182            122           2562  0.020935
3          1981            165            147           2975  0.005476
4          1982            144            127           2673  0.005774

Powyższy proces zajmuje ok. 10 min, dlatego zapisuję jego wyniki do pliku csv.

# small backup
df.to_csv("data_frame.csv")

Eksploracyjna analiza danych

W tym punkcie wykonam analizę poszczególnych zmiennych, zaczynając od współczynnika sentymentu. Sprawdźmy zatem jak zmieniał się on na przestrzeni lat.

sns.set(font_scale=1.3, style="whitegrid")
f, ax = plt.subplots(figsize=(9, 15))
df['Release_Year'] = df.Release_Year.astype("category")
sns.barplot(x="coeff", y="Release_Year", data=df, color='#e64845')
ax.set(xlim=(-0.01, 0.05), ylabel="", title='Sentyment listów wg. roku', xlabel='Sentyment')
plt.show()

Proszę zwróć uwagę na sentyment listów opublikowanych w roku 2002 i 2009. Mówi Ci to coś? 🙂 Tak, tak… rok 2001 to pęknięcie bańki internetowej (z ang. dot-com bubble). Rok 2008 to kryzys zapoczątkowany w USA, poprzez zapaść na rynku pożyczek hipotecznych (sytuacja ta została doskonale opisana w filmie Big short. Szczerze polecam 🙂 ). Hipoteza dotycząca korelacji pomiędzy sentyentem listów i sytuacją na rynku w minionym roku wydaje się być bardzo prawdopodobna. Za wcześnie jednak by ją potwierdzić. Potrzebuję na to twardych dowodów.

Teraz pora na analizę rozkładów poszczególnym zmiennych. Ma ona na celu zweryfikowanie „normalności” rozkładu (pod kątem analizy korelacji) i wyłapanie wartości odstających.

Dosyć równomierny rozkład. Lekka skośność. Żadnych wartości odstających.

Dwie obserwacje odstające. Prawoskośność.

Trzy obserwacje odstające. Prawoskośność.

Jedna obserwacja odstająca. Lewoskośność.

Dwie obserwacje odstające. Lewoskośność.

Sprawdzę jeszcze jak wyglądają histogramy dla poszczwególnych zmiennych. Dodatkowo zweryfikuję zależności pomiędzy poszczególnymi zmiennymi na wykresie punktowym.

Sentyment vs wahania kursu Berkshire Hathaway w minionym roku

Widać niewielką korelację i kilka obserwacji odstających.

Sentyment vs wahania kursu Berkshire Hathaway w danym roku

Korelacja niemalże niewidoczna.

Sentyment vs wahania indexu S&P 500 w minionym roku

Dosyć wysoka korelacja. Jesli któraś z hipotez zostanie potwierdzona to stawiam, że to będzie ta 🙂

Sentyment vs wahania indexu S&P 500 w danym roku

Niewielka korelacja.

Test normalności rozkładów

Zanim przejdę dalej muszę jeszcze sprawdzić testem statystycznych rozkłady zmiennych. Zgodnie z dokumentacją sciPy: „This function tests null hypothesis that a sample comes from a normal distribution„. Hipoteza zerowa mówi, że rozkład badanej próby jest rozkładem normalnym. Zbieram zatem dowody na odrzucenie hipotezy. Jako alfa wyznaczam klasyczną wartość 0,05.

stats.normaltest(df.coeff)[1] > 0.05
stats.normaltest(df.b_h__last_year)[1] > 0.05
stats.normaltest(df.b_h__this_year)[1] > 0.05
stats.normaltest(df.s_and_p__last_year)[1] > 0.05
stats.normaltest(df.s_and_p__this_year)[1] > 0.05

Wszędzie tam, gdzie p-value jest większy od 0.05, mam do czynienia z rozkładem normalnym. Wyniki wykonywania powyższego kodu wyglądają następująco:

True
True
True
True
False

A więc tylko w ostatnim przypadku mogę być pewny, że dane nie pochodzą z rozkładu normalnego. W dalszej analizie nie mogę zatem użyć korelacji Pearsona, gdyż ma ona założenie mówiące o normalności rozkładu obu analizowanych zmiennych. Jako zamiennika użyję korelacji rang Spearmana. Czemu analiza korelacji rang Spearmana? Powody są dwa:

  1. Nie ma ona założenia dotyczącego normalności rozkładów.
  2. Nie jest ona wrażliwa na wartości odstające. Korelacja Pearsona jest wrażliwa na występowanie obserwacji odsatjących (musiałbym usuwać „outliery”, a przy tak małej próbie oznaczałoby to ubytek na poziomie ok. 20%. Nie mogę sobie na to pozwolić).

Analiza korelacji rang Spearmana

Przechodzę teraz do badania korelacji pomiędzy zmiennymi.

cor = pd.DataFrame(stats.spearmanr(df[["coeff", "b_h__last_year", "b_h__this_year", "s_and_p__last_year", "s_and_p__this_year"]])[0], columns = df[["coeff", "b_h__last_year", "b_h__this_year", "s_and_p__last_year", "s_and_p__this_year"]].columns, index=df[["coeff", "b_h__last_year", "b_h__this_year", "s_and_p__last_year", "s_and_p__this_year"]].columns)
                       coeff  b_h__last_year  b_h__this_year  \
coeff               1.000000        0.382186       -0.137247   
b_h__last_year      0.382186        1.000000       -0.047166   
b_h__this_year     -0.137247       -0.047166        1.000000   
s_and_p__last_year  0.454656        0.541700        0.018421   
s_and_p__this_year  0.140688       -0.001619        0.587247   

                    s_and_p__last_year  s_and_p__this_year  
coeff                         0.454656            0.140688  
b_h__last_year                0.541700           -0.001619  
b_h__this_year                0.018421            0.587247  
s_and_p__last_year            1.000000            0.050810  
s_and_p__this_year            0.050810            1.000000

Widać relatywnie wysoką korelację pomiędzy współczynnikiem coeff i zmiennymi: b_h__last_year, s_and_p__last_year. Kolejne przesłanki wskazóją zatem na korelację sentymentu listów Buffeta z wynikami S&P 500 i BH z przeszłości.

Weryfikacja hipotez

By zweryfikować hipotezy posłużę się wartościami p-value z korelacji rang Spearmana. Każdorazowo, wartość p_value < alfa oznaczać będzie odrzucenie hipotezy zerowej i przyjęcie hipotezy alternatywnej H1. Jako alfa przyjmuję poziom 0.05. Buduję maciej wartości p-value dla badanych zmiennych.

p_values = pd.DataFrame(stats.spearmanr(df[["coeff", "b_h__last_year", "b_h__this_year", "s_and_p__last_year", "s_and_p__this_year"]])[1], columns = df[["coeff", "b_h__last_year", "b_h__this_year", "s_and_p__last_year", "s_and_p__this_year"]].columns, index=df[["coeff", "b_h__last_year", "b_h__this_year", "s_and_p__last_year", "s_and_p__this_year"]].columns)
                       coeff  b_h__last_year  b_h__this_year  \
coeff               0.000000        0.016349        0.404747   
b_h__last_year      0.016349        0.000000        0.775547   
b_h__this_year      0.404747        0.775547        0.000000   
s_and_p__last_year  0.003640        0.000369        0.911374   
s_and_p__this_year  0.392954        0.992193        0.000085   

                    s_and_p__last_year  s_and_p__this_year  
coeff                         0.003640            0.392954  
b_h__last_year                0.000369            0.992193  
b_h__this_year                0.911374            0.000085  
s_and_p__last_year            0.000000            0.758705  
s_and_p__this_year            0.758705            0.000000

Hipoteza 1

  • H0: Nie ma korelacji pomiędzy sentymentem listów a wynikami indeksu S&P 500 z minionego roku.
  • H1: Istnieje korelacja pomiędzy sentymentem listów a wynikami indeksu S&P 500 z minionego roku.

Z maksymalnym prawdopodobiestwem popełnienia błędu, równym 0.3% odrzucam H0 i przyjmuję H1. Istnieje korelacja pomiędzy sentymentem listów, a wynikami indeksu S&P 500 z minionego roku.

Hipoteza 2

  • H0: Nie ma korelacji pomiędzy sentymentem listów a wynikami indeksu S&P 500 z danego roku.
  • H1: Istnieje korelacja pomiędzy sentymentem listów a wynikami indeksu S&P 500 z danego roku.

Nie znalazłem dowodów na odrzucenie H0. Przyjmuję zatem, że nie ma korelacji pomiędzy sentymentem listów, a wynikami indeksu S&P 500 z danego roku.

Hipoteza 3

  • H0: Nie ma korelacji pomiędzy sentymentem listów a wynikami giełdowymi Berkshire Hathaway z minionego roku.
  • H1: Istnieje korelacja pomiędzy sentymentem listów a wynikami giełdowymi Berkshire Hathaway z minionego roku.

Z maksymalnym prawdopodobiestwem popełnienia błędu, równym 1.6% odrzucam H0 i przyjmuję H1. Istnieje korelacja pomiędzy sentymentem listów, a wynikami giełdowymi Berkshire Hathaway z minionego roku.

Hipoteza 4
Dzięki niej chciałbym odpowiedzieć na pytanie: czy sentyment listów jest skorelowany z wynikami, jakie osiągnął Berkshire Hathaway w kolejnym roku?

  • H0: Nie istnieje korelacja pomiędzy sentymentem listów a wynikami giełdowymi Berkshire Hathaway z danego roku.
  • H1: Istnieje korelacja pomiędzy sentymentem listów a wynikami giełdowymi Berkshire Hathaway z danego roku.

Nie znalazłem dowodów na odrzucenie H0. Przyjmuję zatem, że nie ma korelacji pomiędzy sentymentem listów, a wynikami giełdowymi Berkshire Hathaway z danego roku.

Podsumowanie

Na podstawie powyższych danych i przeprowadzonej analizy odrzuciłem obie zerowe hipotezy dotyczących przeszłości. Niestety nie udało się odrzucić żadnej z zerowych hipotez, które dotyczyły przyszłości. Gdyby tak się stało, to mógłbym w pewnym stopniu przewidywać zachowania amerykańskiej giełdy (lub BH) w roku 2018. Podstawą do tego byłby sentyment listu Warrena Buffetta, który zostanie opublikowany za około miesiąc. W moim przypadku mogę jedynie wnioskować w przeciwną stronę. Patrząc na wyniki osiągane zarówno przez S&P 500, jak i Berkshire Hathaway w 2017, należy się spodziewać, że sentyment najbliższego listu będzie bardzo pozytywny 🙂

Wszystkie pliki utworzone przy projekcie można znaleźć na moim GitHub’ie.
Link do analizy, która była inspiracją tego projektu: klik.

photo: artsfon.com

Podobał Ci się ten artykuł?

Jeśli tak, to zarejestruj się, by otrzymywać informacje o nowych wpisach. Dodatkowo w prezencie wyślę Ci bezpłatny poradnik 🙂

Bądź pierwszy, który skomentuje ten wpis!

Dodaj komentarz

Twój adres email nie zostanie opublikowany.


*