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:
faire un peu de feature engineering que nous avons vu dans le dernier cours
nous verrons l’importance de normaliser les variables à une échelle comparable
nous allons chercher les paramètres optimaux de notre régression-SGD
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.