Niedawno, bo ok. 2 miesiące temu, Kainos wszedł na londyńską giełdę. W codziennych rozmowach w pracy czasami pojawia się temat aktualnych zmian ceny akcji i tego, czy można jakoś je przewidzieć. Ta sytuacja spowodowała, że postanowiłem wykonać prosty eksperyment polegający na próbie wykorzystania machine learning (uczenie maszynowe) do predykcji wzrostów, bądź spadków ceny spółek giełdowych. W tym wpisie podzielę się z Wami szczegółami przeprowadzenia tego eksperymentu oraz jego wynikami.

Ogólna idea

Na początek postanowiłem znaleźć dane dotyczące notowań giełdowych spółek z warszawskiej Giełdy Papierów Wartościowych. Odpowiednie dane można znaleźć na stronie domu maklerskiego BZWBK: http://www.dmbzwbk.pl/inwestowanie-online/inwestor-online/narzedzia-inwestycyjne/pliki-at/pliki-at.html. Po pobraniu pliku bazy dziennej, można znaleźć tam notowania każdej spółki z GPW w oddzielnym pliku *.csv. W moim eksperymencie postanowiłem przeanalizować ceny akcji jednej z tych spółek (CD Projekt). Odpowiadający tej firmie plik zawiera zestaw wpisów, z których każdy odpowiada jednemu dniu notowań. Zwróćcie uwagę na pojedynczy rekord, w którym cena akcji na zamknięciu zawarta jest w szóstej kolumnie.

Program, który napisałem do wykonania tego zadania podejmie próbę nauki, by przewidzieć jaki będzie wzrost (bądź spadek) ceny akcji w kolejnym dniu, znając wzrosty (spadki) z ostatnich 20 sesji giełdowych (ok. 1 miesiąca). Do zakodowania tego programu wykorzystałem nowoczesny język programowania Scala wraz z biblioteką Apache Spark.

Spark jest silnikiem ogólnego przeznaczenia do przetwarzania danych z naciskiem na przetwarzanie równoległe zarówno na wielu wątkach jak i maszynach. Spark można określić jako następcę biblioteki Apache Hadoop, którą często przewyższa pod względem wydajnościowym (choć może to dotyczyć szczególnych zastosowań), ale także pod względem wygody stosowania we własnych programach. Spark został napisany w języku Scala i to dla niego udostępnia najbardziej podstawowy zestaw interfejsów API. Wygoda użycia jest nie do przecenienia! Wykorzystałem także dodatkowy moduł Sparka, zwany MLlib, który umożliwia zastosowanie gotowych implementacji algorytmów typu machine learning.

Ale dość już zbędnego owijania w bawełnę. Przejdę do tego, co programiści lubią najbardziej, czyli kodu.

Szczegóły implementacji

Na początek przestawię zestaw klasy, z których będę korzystał w tym programie.

imports

Pierwszą rzeczą która jest konieczna do wykonania jest utworzenie konfiguracji i kontekstu biblioteki Spark. W tym miejscu decydujemy o nazwie programu i sposobu wykonywania zadań. Program ten będzie programem lokalnym, wykonywanym na lokalnej maszynie.

ctxt

Konieczne będzie wczytanie pliku *.csv z notowaniami. Po podaniu odpowiedniej ścieżki, ta linia pobierze wszystkie linie z tego pliku.

file

Kolejnym krokiem jest wyciągniecie ceny na zamknięciu sesji giełdowej. Znajduje się ona w szóstej kolumnie, więc ze wszystkich wartości oddzielonych przecinkiem, interesuje nas wartość na indeksie piątym.

prices

Właściwie najłatwiej będzie nam pracować nie na nominalnych cenach, lecz na procentowym wzroście ceny w odniesieniu do poprzedniego dnia. W tym celu łączymy ceny w sąsiadujące pary oraz obliczamy procentowy wzrost.

growth

Z poprzednio uzyskanej listy wzrostów dziennych potrzeba teraz przygotować dane do nauki. (Przypominam: będziemy starać się przewidzieć wzrost/spadek ceny znając 20 poprzednich wzrostów/spadków). Tworzymy odpowiednie listy i zapisujemy je w strukturze klasy LabeledPoint, która reprezentuje 2 wartości:

  • label – aktualny wzrost / spadek
  • features – lista ostatnich 20 wzrostów / spadków

Uzyskana lista obiektów klasy LabeledPoint jest zestawem danych przygotowanych do uczenia maszynowego.

labeled

Aby skorzystać z algorytmów zaimplementowanych w MLlib, konieczne jest zastosowanie innej struktury danych niż standardowa lista pochodząca Scali. Spark w swoim API operuje na kolekcjach RDD (resilient, distributed dataset) który reprezentuje niezmienny zestaw danych rozproszonych na pewną liczbę wątków / maszyn. (Niezmienny – oznacza, że próbując go zmieniać, będziemy tworzyli transformacje, które przygotują nowy obiekt RDD zamiast modyfikować istniejący.

rdd

Dla celów uczenia maszynowego, konieczne będzie rozdzielenie tego zestawu danych na dwie części – zestaw danych do nauki oraz zestaw testowy (służący do oceny skuteczności nauki). W tym celu dzielę obecny RDD w proporcji 7:3.

training-test

Do nauki wykorzystałem poniższe parametry. Dobrałem je doświadczalnie – odniosłem wrażenie, że stosunkowo dobrze algorytm działał na tych danych właśnie z tak dobranymi parametrami. Znaczenie parametrów jest ściśle związane ze szczegółami metody uczenia maszynowego, więc na dziś pominę te, uważam, dość skomplikowane wyjaśnienia.

params-small

Wykorzystam teraz API modułu MLlib oraz posiadane już dane treningowe, by nauczyć model regresji liniowej z wykorzystaniem metody optymalizacji gradientu stochastycznego:

model

Nauczony w ten sposób model, można już stosować do predykcji wzrostu cen akcji. Przeprowadzę taka operacje na każdym elemencie ze zbioru testowego, by możliwe było porównanie wartości obliczonej („score”) z wartością znaną wcześniej („label”).

scores-small

Na koniec konieczne będzie jeszcze obliczenie błędu średniokwadratowego, który będzie stanowił mierzalną skuteczność/dokładność predykcji.

mse

Cały program ma więc jedynie 40 linii kodu :) Pełna zawartość dostępna jest tutaj.

Wyniki eksperymentu

No cóż… ostatecznie uruchamiając ten program wiele razy, udało mi się obniżyć błąd średniokwadratowy do okolicy… 4. I to w najlepszym przypadku. Szkoda, bo to zdecydowanie zbyt dużo by realnie przewidywać wzrost cen. Przy takim błędzie na danych testowych, przewidywany wzrost / spadek różniłby się na ogół o ponad 2 punkty procentowe od realnej wartości!

Możliwe sposoby do poprawy wyniku, które przychodzą mi do głowy to:

  • wykorzystanie innych danych (np. o wolumenie transakcji)
  • wykorzystanie innego, bardziej skomplikowanego modelu (np. sieci neuronowych?)
  • wykorzystanie potęgowych wartości – także obecnie wykorzystywanych danych – jest to typowy sposób do powiększenia „mocy” modelu regresji liniowej

A może Wy macie jakieś lepsze pomysły, jak zmienić ten program? Proszę, napiszcie o  wszystkim w komentarzu!

Stosunkowo słaby wynik eksperymentu udowadnia jak trudne jest przewidywanie zachowania rynków finansowych. Najwyraźniej nawet sztuczna inteligencja nie daje sobie z tym rady.

Mówi się trudno :) Dziś się nie udało, ale będę szukał okazji do dalszych eksperymentów i kiedyś na pewno uda mi się dokonać przełomowego odkrycia!