Jak poprawnie skalować ceny akcji dla sieci neuronowych?

W tym artykule chcę zwrócić na nagminny błąd pojawiający się w artykułach o prognozowaniu cen akcji za pomocą sieci neuronowych. Błąd ten można spotkać m. in. w tym artykule w serwisie medium i w tym artykule w serwisie towardsdatascience.

Wiadomym jest, że sieci neuronowe preferują wyskalowane dane wejściowe, jednak skalowanie szeregów czasowych(a szeregi cen akcji są szeregami czasowymi) wcale nie jest takie oczywiste jakby się mogło wydawać i nie można tego dokonać za pomocą MinMaxScalera z pythonowego pakietu Scikit Learn co jest nagminne w artykułach w sieci.

To co jest właściwie problemem?

Aby zobrazować ten problem weźmy może na tapet cenę Assecopolu(ticker ACP) i go przeskalujmy za pomocą MinMaxScalera:

from sklearn.preprocessing import MinMaxScaler
sc = MinMaxScaler(feature_range = (0, 1))
stock["ScaledCloseLookAhead"] = sc.fit_transform(stock["Close"].values.reshape(-1,1))

Dla uproszczenia kodu pominąłem wczytywanie danych.

Po takim przeskalowaniu otrzymujemy taki oto wykres:

Wykres ceny ACP przeskalowanej za pomocą MinMaxScalera

Porównajmy to z rzeczywistym wykresem ceny:

Wykres ceny ACP

Na pierwszy rzut oka można stwierdzić: “no i gitara, mieliśmy ceny w zakresie od ok. 5 PLN za akcję do ok. 95 PLN, a my przeskalowaliśmy sobie je do zakresu od 0 do 1 czyli tak jak lubią sieci neuronowe”.

Problemem jest to, że skalowaliśmy ceny przeszłe za pomocą cen z przyszłości. Chodzi o to, że cena zamknięcia np. z 10 grudnia 1999 była skalowana za pomocą ceny z 3 marca 2002 co jest błędem w przypadku analizy szeregów czasowych ponieważ 10 grudnia 1999 nie znaliśmy ceny nawet z 11 grudnia więc nie mogliśmy tych cen wykorzystać do skalowania dostępnych danych. Ten błąd nazywany jest zjawiskiem lookahead.

Zjawisko lookahead

Najprościej mówiąc zjawisko lookahead polega na użyciu danych do analizy, które nie były znane w danym momencie przez co na predykcję przyszłych wartości szeregu mają wpływ przyszłe wartości szeregu co nie powinno mieć miejsca. Właśnie po to analizujemy szeregi czasowe żeby poznać ich przyszłe wartości.

Co można z tym zrobić?

Aby rozwiązać ten problem musimy po prostu użyć wartości przeszłych do skalowania szeregu czasowego. Można do tego celu użyć np. kroczącej średniej i kroczącego odchylenia standardowego. Przykładowa implementacja takiego podejścia może wyglądać następująco:

def StockScaler(series, column, period):
    series = series.to_frame()
    series["Avg"] = series[column].rolling(window=period).mean()
    series["Std"] = series[column].rolling(window=period).std()
    series["Scaled" + column] = (series[column] - series["Avg"]) / series["Std"]
    return series["Scaled" + column]

stock["ScaledClose"] = StockScaler(stock["Close"], "Close", 10)

W powyższym przykładzie od aktualnej ceny odejmujemy średnią kroczącą z 10 okresów, otrzymana wartość jest następnie dzielona przez kroczące odchylenie standardowe z takiej samej ilości okresów. Po takiej operacji otrzymujemy taki wykres:

Poprawnie przeskalowany wykres ceny ACP

Nie wygląda to zbyt ładnie, sprawdźmy jak to wygląda dla ostatnich stu sesji:

Poprawnie przeskalowany wykres ceny ACP, 100 ostatnich sesji

Teraz wykres przeskalowanej ceny wcale nie przypomina oryginalnego wykresu ceny. Warto zwrócić uwagę, że otrzymany szereg czasowy możemy uznać za szereg stacjonarny tj. w długim okresie jego średnia wynosi ok. 0, a odchylenie standardowe ok. 1(czyli są stałe) co może być istotne dla niektórych analityków:

Odchylenie standardowe kroczące i średnia kroczące dla przeskalowanej ceny ACP

Tak przeskalowany szereg możemy wykorzystać jako dane wejściowe do sieci neuronowej.

Ten wpis został opublikowany w kategorii Analiza danych, Artykuły. Dodaj zakładkę do bezpośredniego odnośnika.

Dodaj komentarz

Twój adres e-mail nie zostanie opublikowany. Wymagane pola są oznaczone *