Prédire l’indice DIJA

Dans cet exercice, nous allons essayer de prédire l’indice boursier Dow Jones Industrial Average (abrégé en DJIA) en utilisant la régression linéaire par SGD. Pour ce faire, nous allons:

  1. faire un peu de feature engineering que nous avons vu dans le dernier cours

  2. nous verrons l’importance de normaliser les variables à une échelle comparable

  3. nous allons chercher les paramètres optimaux de notre régression-SGD

  4. nous comparerons nos prédictions avec les vraies valeurs

Afin d’acquérir les données, nous allons utiliser le package yfinance. Sinon vous pouvez télécharger les données brutes directement de mon répertoire de données GitHub.

features engineering

Importons quelques librairies dont nous aurons besoin

import yfinance as yf
import pandas as pd
import numpy as np

Allons chercher les données historiques sur 30 ans, soit de 1990-01-01 à 2020-12-31.

djia_df = yf.download('DJIA', 
                      start='1990-01-01', 
                      end='2020-12-31', 
                      progress=False,)
Open High Low Close Adj Close Volume
Date
1990-01-02 2753.199951 2811.649902 2732.510010 2810.149902 2810.149902 162070000
1990-01-03 2810.149902 2834.040039 2786.260010 2809.729980 2809.729980 192330000
1990-01-04 2809.729980 2821.459961 2766.419922 2796.080078 2796.080078 177000000
1990-01-05 2796.080078 2810.149902 2758.110107 2773.250000 2773.250000 158530000
1990-01-08 2773.250000 2803.969971 2753.409912 2794.370117 2794.370117 140110000

Note

La librarie yfinance ne fonctionne pas sur google colab. Dans ce cas, utilisez `pd.read_csv(‘paths’)

djia_df=pd.read_csv('https://raw.githubusercontent.com/nmeraihi/data/master/DIJA_raw_1990-01-01_2020-12-31.csv')

Maintenant que nous avons les données pour travailler, nettoyons un peut les données etr créons quelques variables comme nous l’avons appris dans la section interaction

D’abord créons un nouveau data frame appelé df_new dans lequel nous allons insérer les variables que nous avons besoin à partir du data frame original djia_df.

df_new = pd.DataFrame()
df_new['Dates']=np.array(djia_df.index.values)

Gardons 2 variables que nous allons travaillons;

df_new['Close'] = djia_df['Close'].values
df_new['Volume']=djia_df['Volume'].values

Calculons la moyenne mobile (5, 30 et 365) jours. Pour ce faire, nous devons utiliser la fonction rolling avec le nombre de jours correspondants

Moyenne mobile 5-30-365 jours

Calculons maintenant les ratios de prix (5/30), (5/365) et (30/365) jours.

df_new['avg_price_5'] = pd.Series(df_new['Close']).rolling(window=5).mean().shift(1)
df_new['avg_price_30'] = pd.Series(df_new['Close']).rolling(window=21).mean().shift(1)
df_new['avg_price_365'] = pd.Series(df_new['Close']).rolling(window=252).mean().shift(1)
df_new['avg_volume_5'] = pd.Series(df_new['Volume']).rolling(window=5).mean().shift(1)
df_new['avg_volume_30'] = pd.Series(df_new['Volume']).rolling(window=21).mean().shift(1)
df_new['avg_volume_365'] = pd.Series(df_new['Volume']).rolling(window=252).mean().shift(1)

Écart-type mobile 5-30-365 jours

Appliquons la même chose sur le volume de transactions.

df_new['std_price_5'] = pd.Series(df_new['Close']).rolling(window=5).std().shift(1)
df_new['std_price_30'] = pd.Series(df_new['Close']).rolling(window=21).std().shift(1)
df_new['std_price_365'] = pd.Series(df_new['Close']).rolling(window=252).std().shift(1)
df_new['std_volume_5'] = pd.Series(df_new['Volume']).rolling(window=5).std().shift(1)
df_new['std_volume_30'] = pd.Series(df_new['Volume']).rolling(window=21).std().shift(1)
df_new['std_volume_365'] = pd.Series(df_new['Volume']).rolling(window=252).std().shift(1)

Ratios

Créons quelques ratios de moyennes et d’écart-type sur 5, 30 et 365 jours.

df_new['ratio_avg_price_5_30'] = df_new['avg_price_5'] / df_new['avg_price_30']
df_new['ratio_avg_price_5_365'] = df_new['avg_price_5'] / df_new['avg_price_365']
df_new['ratio_avg_price_30_365'] = df_new['avg_price_30'] / df_new['avg_price_365']

df_new['ratio_avg_volume_5_30'] = df_new['avg_volume_5'] / df_new['avg_volume_30']
df_new['ratio_avg_volume_5_365'] = df_new['avg_volume_5'] / df_new['avg_volume_365']
df_new['ratio_avg_volume_30_365'] = df_new['avg_volume_30'] / df_new['avg_volume_365']

df_new['ratio_std_price_5_30'] = df_new['std_price_5'] / df_new['std_price_30']
df_new['ratio_std_price_5_365'] = df_new['std_price_5'] / df_new['std_price_365']
df_new['ratio_std_price_30_365'] = df_new['std_price_30'] / df_new['std_price_365']

df_new['ratio_std_volume_5_30'] = df_new['std_volume_5'] / df_new['std_volume_30']
df_new['ratio_std_volume_5_365'] = df_new['std_volume_5'] / df_new['std_volume_365']
df_new['ratio_std_volume_30_365'] = df_new['std_volume_30'] / df_new['std_volume_365']

Taux de rendement:

Calculons le taux de rendement; $\(R=\frac{V_{f}-V_{i}}{V_{i}}\)$

df_new['return_1'] = ((df_new['Close'] - df_new['Close'].shift(1)) / df_new['Close'].shift(1)).shift(1)
df_new['return_5'] = ((df_new['Close'] - df_new['Close'].shift(5)) / df_new['Close'].shift(5)).shift(1)
df_new['return_30'] = ((df_new['Close'] - df_new['Close'].shift(21)) / df_new['Close'].shift(21)).shift(1)
df_new['return_365'] = ((df_new['Close'] - df_new['Close'].shift(252)) / df_new['Close'].shift(252)).shift(1)

Ensuite les ratios sur les taux de rendement

df_new['moving_avg_5'] = pd.Series(df_new['return_1']).rolling(window=5).mean()
df_new['moving_avg_30'] = pd.Series(df_new['return_1']).rolling(window=21).mean()
df_new['moving_avg_365'] = pd.Series(df_new['return_1']).rolling(window=252).mean()

Les valeurs NaN

Maintenant, supprimons les observations NaN car nous avons assez de données

df_new = df_new.dropna(axis=0)

Répartition des données

Maintenant nous avons un semble de données de 7558 observations et 34 variables.

df_new.shape
(7558, 34)

Nous allons supprimer la variable data, mais pour le moment gardons la pour séparer les données en données d’entrainement et de test.

Gardons les données jusqu’au 31 décembre 2014 pour entrainer notre modèle

import datetime

X_train = df_new[df_new['Dates']<datetime.datetime(2014, 12, 31, 0, 0)]

Maintenant, gardons la variable Close comme variable réponse que nous allons justement tenter de prédire ses valeurs pour la période à partir du 01 janvier 2015.

y_train=X_train['Close']

Bien évidemment, il faut supprimer la variable réponse Close de notre ensemble d’entraînement. Mais aussi la variable Dates qui ne nous sert pas vraiment.

X_train=X_train.drop(['Dates', 'Close'], axis=1)
Volume avg_price_5 avg_price_30 avg_price_365 avg_volume_5 avg_volume_30 avg_volume_365 std_price_5 std_price_30 std_price_365 ... ratio_std_volume_5_30 ratio_std_volume_5_365 ratio_std_volume_30_365 return_1 return_5 return_30 return_365 moving_avg_5 moving_avg_30 moving_avg_365
6295 3043950000 17593.625781 17701.891369 16740.940689 4.887896e+09 3.939822e+09 3.351236e+09 368.743506 262.682482 521.087525 ... 1.277171 1.699865 1.330961 0.008685 0.045318 0.013570 0.107163 0.008962 0.000683 0.000427
6296 1416980000 17784.685937 17712.087054 16747.804021 4.504950e+09 3.898276e+09 3.351999e+09 260.463962 271.110829 526.546973 ... 1.550715 2.118145 1.365915 0.003604 0.055967 0.012022 0.106143 0.010987 0.000609 0.000424
6297 1735230000 17919.354297 17722.197080 16754.441565 3.799872e+09 3.816796e+09 3.352433e+09 120.357157 279.095101 532.121018 ... 1.848204 2.937019 1.589121 0.000335 0.038794 0.011916 0.102256 0.007679 0.000604 0.000410
6298 2452360000 17974.466406 17733.567150 16760.686923 3.206242e+09 3.737858e+09 3.351453e+09 101.081430 287.790458 538.088084 ... 1.789822 3.093629 1.728456 0.001303 0.015500 0.013403 0.095500 0.003085 0.000674 0.000385
6299 2440280000 18021.152344 17743.590030 16766.876686 2.403608e+09 3.723910e+09 3.353038e+09 36.233166 294.814065 543.769556 ... 0.733509 1.289917 1.758557 -0.000857 0.013110 0.011806 0.094658 0.002614 0.000599 0.000382

5 rows × 32 columns

Nous avons un ensemble de données de 6047 observations et 32 variables explicatives

X_train.shape
(6047, 32)
y_train.shape
(6047,)

Travaillons maintenant les données test;

X_test = df_new[df_new['Dates']>=datetime.datetime(2014, 12, 31, 0, 0)]
y_test=X_test['Close']
X_test=X_test.drop(['Dates', 'Close'], axis=1)
X_test.shape
(1511, 32)
y_test.shape
(1511,)

Normalisation des variables

Utilisant les package StandardScaler afin de normaliser les variables en supprimant la moyenne et en mettant à l’échelle la variance.

from sklearn.preprocessing import StandardScaler
scaler = StandardScaler()
scaler.fit(X_train)
X_scaled_train = scaler.transform(X_train)
X_scaled_test = scaler.transform(X_test)

Régression-SGD

Fixons les paramètres \(\alpha\) et \(\eta\) de notre grille de recherche des paramètres comme nous l’avons vu dans la section.

param_grid = {
    "alpha": [1e-5, 3e-5, 1e-4],
    "eta0": [0.01, 0.03, 0.1],
}

Maintenant, appliquons la régression-SGD. Nous allons chercher les paramètres optimaux dans la grille de recherche GridSearchCV

from sklearn.linear_model import SGDRegressor
lr = SGDRegressor(max_iter=1000)

from sklearn.model_selection import GridSearchCV
grid_search = GridSearchCV(lr, param_grid, cv=5, scoring='neg_mean_absolute_error')
grid_search.fit(X_scaled_train, y_train)

print(grid_search.best_params_)

lr_best = grid_search.best_estimator_

predictions_lr = lr_best.predict(X_scaled_test)
from sklearn.metrics import mean_squared_error
print('MSE: {0:.3f}'.format(mean_squared_error(y_test, predictions_lr)))
{'alpha': 1e-05, 'eta0': 0.03}
MSE: 185159.665

Traçons maintenant un graphique qui nous montre la courbe des valeurs Close prédites vs les vraies valeurs de notre ensemble de données test.