Skip to content

Instantly share code, notes, and snippets.

@yungookim
Created April 23, 2018 01:14
Show Gist options
  • Save yungookim/cd5311ac24b55e1f6ca6fdde3ac34248 to your computer and use it in GitHub Desktop.
Save yungookim/cd5311ac24b55e1f6ca6fdde3ac34248 to your computer and use it in GitHub Desktop.
Weekend Exercise. Forex EURUSD 15 minute interval price prediction Part 1
Display the source blob
Display the rendered blob
Raw
{"cells":[{"metadata":{"_uuid":"399b276fbc421991fbe996691c1a195c5e5f48e2","_cell_guid":"e439dbc8-3c95-4c1c-ab53-e9d208a5d79d"},"cell_type":"markdown","source":"# Introduction\n\nThis notebook trains a LSTM model that predicts the bid price of **EURUSD** 15 minutes in the future by looking at last five hours of data. While there is no requirement for the input to be contiguous, it's been empirically observed that having the contiguous input does improve the accuracy of the model. I suspect that having *day of the week* and *hour of the day* as the features mitigates some of the seasonality and contiguousness problems.\n\n**Disclaimer**: This exercise has been carried out using a small sample data which only contains 14880 samples (2015-12-29 00:00:00 to 2016-05-31 23:45:00) and lacks ASK prices. Which restricts the ability for the model to approach a better accuracy. \n\n**Improvements**\n* To tune the model further, I would recommend having at least 5 years worth of data, have ASK price (so that you can compute the spread), and increasing the epoch to 3000.\n* Adding more cross-axial features. Such as *spread*.\n* If you are looking into classification approach (PASS, BUY, SELL), consider adding some technical indicators that is more sensitive to more recent data. \n* Consider adding non-numerical data, e.g. news, Tweets. The catch is that you have to get the data under one minute for trading, otherwise the news will be reflected before you even make a trade. If anybody knows how to get the news streamed really fast, please let me know.\n\n**Credits** : Dave Y. Kim, Mahmoud Elsaftawy,"},{"metadata":{"_uuid":"cc3143d11be64e4b26d7b3e264f271f1c4ccd904","_cell_guid":"901455c8-d5fd-4dfb-86a6-44d15a3acc6f","trusted":false,"collapsed":true},"cell_type":"code","source":"import numpy as np\nimport pandas as pd\nimport matplotlib.pyplot as plt\nimport seaborn as sns\nfrom sklearn.preprocessing import MinMaxScaler\n\nfrom subprocess import check_output\nprint(check_output([\"ls\", \"../input\"]).decode(\"utf8\"))","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"3995058fb77ae366c7b3be525af2862b5007c60b","collapsed":true,"_cell_guid":"52b27b36-fbc4-4e45-a220-c664ab6dfbc7","trusted":false},"cell_type":"code","source":"# Load sample data\ndf = pd.read_csv('../input/EURUSD_15m_BID_sample.csv')","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"8f705cac308b99530a90f830e803854e7b26e595","_cell_guid":"ec9e6dba-442c-488d-92d0-916e7b6703a2","trusted":false,"collapsed":true},"cell_type":"code","source":"df.count()","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"2c6600772d5ae8b592c9fef7f29dc93832589a7e","_cell_guid":"d381458f-2693-4d23-8d79-62fefa1e8ca6","trusted":false,"collapsed":true},"cell_type":"code","source":"df.index.min(), df.index.max()","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"aa4d293f227bb0a3edf0faa9e277fa9ee089a517","collapsed":true,"_cell_guid":"4aaaa570-2155-4c96-9abd-78c74ed89016","trusted":false},"cell_type":"code","source":"# FULL DATA (takes too long)\n# df = pd.read_csv('../input/EURUSD_15m_BID_01.01.2010-31.12.2016.csv')","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"305dfff7460dabfae7ed2e6ac3f5f5266615d46f","_cell_guid":"4a7283d8-564f-40c4-b636-3587fa6990bf","trusted":false,"collapsed":true},"cell_type":"code","source":"# Rename bid OHLC columns\ndf.rename(columns={'Time' : 'timestamp', 'Open' : 'open', 'Close' : 'close', \n 'High' : 'high', 'Low' : 'low', 'Close' : 'close', 'Volume' : 'volume'}, inplace=True)\ndf['timestamp'] = pd.to_datetime(df['timestamp'], infer_datetime_format=True)\ndf.set_index('timestamp', inplace=True)\ndf = df.astype(float)\ndf.head()","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"cb5608ff930146ffd1c1364f9f973c867d471f7c","collapsed":true,"_cell_guid":"b1b4d74a-6137-418f-93a4-f5a2e74a359a","trusted":false},"cell_type":"code","source":"# Add additional features\ndf['hour'] = df.index.hour\ndf['day'] = df.index.weekday\ndf['week'] = df.index.week\ndf['momentum'] = df['volume'] * (df['open'] - df['close'])\ndf['avg_price'] = (df['low'] + df['high'])/2\ndf['range'] = df['high'] - df['low']\ndf['ohlc_price'] = (df['low'] + df['high'] + df['open'] + df['close'])/4\ndf['oc_diff'] = df['open'] - df['close']\n\n# Cannot add ASK related features, which will limit the accuracy of the model","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"8f0cc48c0973272649732410963aa52a20c720eb","collapsed":true,"_cell_guid":"d45ca935-7e02-462d-a046-8b587c7df6d8","trusted":false},"cell_type":"code","source":"# Add PCA as a feature instead of for reducing the dimensionality. This improves the accuracy a bit.\nfrom sklearn.decomposition import PCA\n\ndataset = df.copy().values.astype('float32')\npca_features = df.columns.tolist()\n\npca = PCA(n_components=1)\ndf['pca'] = pca.fit_transform(dataset)","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"c69167f965e53056ffa92b60870793061736bc24","_cell_guid":"7eeb62b1-a682-484b-9ba6-5b024ae28f64","trusted":false,"collapsed":true},"cell_type":"code","source":"import matplotlib.colors as colors\nimport matplotlib.cm as cm\nimport pylab\n\nplt.figure(figsize=(10,5))\nnorm = colors.Normalize(df['ohlc_price'].values.min(), df['ohlc_price'].values.max())\ncolor = cm.viridis(norm(df['ohlc_price'].values))\nplt.scatter(df['ohlc_price'].values, df['pca'].values, lw=0, c=color, cmap=pylab.cm.cool, alpha=0.3, s=1)\nplt.title('ohlc_price vs pca')\nplt.show()\n\nplt.figure(figsize=(10,5))\nnorm = colors.Normalize(df['volume'].values.min(), df['volume'].values.max())\ncolor = cm.viridis(norm(df['volume'].values))\nplt.scatter(df['volume'].values, df['pca'].values, lw=0, c=color, cmap=pylab.cm.cool, alpha=0.3, s=1)\nplt.title('volume vs pca')\nplt.show()\n\nplt.figure(figsize=(10,5))\nnorm = colors.Normalize(df['ohlc_price'].values.min(), df['ohlc_price'].values.max())\ncolor = cm.viridis(norm(df['ohlc_price'].values))\nplt.scatter(df['ohlc_price'].shift().values, df['pca'].values, lw=0, c=color, cmap=pylab.cm.cool, alpha=0.3, s=1)\nplt.title('ohlc_price - 15min future vs pca')\nplt.show()\n\nplt.figure(figsize=(10,5))\nnorm = colors.Normalize(df['volume'].values.min(), df['volume'].values.max())\ncolor = cm.viridis(norm(df['volume'].values))\nplt.scatter(df['volume'].shift().values, df['pca'].values, lw=0, c=color, cmap=pylab.cm.cool, alpha=0.3, s=1)\nplt.title('volume - 15min future vs pca')\nplt.show()","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"3e329d43a6e9a833b177a765743ec9249e73c3ca","_cell_guid":"2eb47cc3-400c-4a04-bc44-9e07023b02c9"},"cell_type":"markdown","source":"As observed above, using PCA shows data seperability that somehwat clusters the data into different price groups."},{"metadata":{"_uuid":"af5d192a1bc1c6291102c544fb77eb7c47f53f73","_cell_guid":"b20fc15f-a4b8-40f4-8796-5e21d3426680","trusted":false,"collapsed":true},"cell_type":"code","source":"df.head()","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"f1688fe116bbf8663dbbf657cfe20369486d42c7","collapsed":true,"_cell_guid":"176318f8-cfd4-4830-9f63-beefad162b62","trusted":false},"cell_type":"code","source":"def create_dataset(dataset, look_back=20):\n dataX, dataY = [], []\n for i in range(len(dataset)-look_back-1):\n a = dataset[i:(i+look_back)]\n dataX.append(a)\n dataY.append(dataset[i + look_back])\n return np.array(dataX), np.array(dataY)","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"ba4f8e7e577eb30fa512736e7824ed559d1137d9","_cell_guid":"c8b936e0-7ad5-4c8a-8d4e-4203d85c7032"},"cell_type":"markdown","source":"# Doing a bit of features analysis"},{"metadata":{"_uuid":"e3308cb07121307ce717d48b98b16af5b0abf760","scrolled":true,"_cell_guid":"0e13ad25-0da4-49cc-8362-36252ae487ce","trusted":false,"collapsed":true},"cell_type":"code","source":"colormap = plt.cm.inferno\nplt.figure(figsize=(15,15))\nplt.title('Pearson correlation of features', y=1.05, size=15)\nsns.heatmap(df.corr(), linewidths=0.1, vmax=1.0, square=True, cmap=colormap, linecolor='white', annot=True)\nplt.show()\n\nplt.figure(figsize=(15,5))\ncorr = df.corr()\nsns.heatmap(corr[corr.index == 'close'], linewidths=0.1, vmax=1.0, square=True, cmap=colormap, linecolor='white', annot=True);","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"f9a4346fd0c044ad064ed3f9b9fd87f726324a6e","collapsed":true,"_cell_guid":"2efea531-9b62-4c16-9deb-741a64c9661a","trusted":false},"cell_type":"code","source":"from sklearn.ensemble import RandomForestRegressor\n\n# Scale and create datasets\ntarget_index = df.columns.tolist().index('close')\ndataset = df.values.astype('float32')\n\n# Scale the data\nscaler = MinMaxScaler(feature_range=(0, 1))\ndataset = scaler.fit_transform(dataset)\n\n# Set look_back to 20 which is 5 hours (15min*20)\nX, y = create_dataset(dataset, look_back=1)\ny = y[:,target_index]\nX = np.reshape(X, (X.shape[0], X.shape[2]))","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"960c97c1020c8072d2346c5afeeb1d2723f4463b","collapsed":true,"_cell_guid":"577a77e6-4cac-4d0f-8970-17b06cb731f7","trusted":false},"cell_type":"code","source":"forest = RandomForestRegressor(n_estimators = 100)\nforest = forest.fit(X, y)","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"66b675557f9e0b68358219cc20a6d62a8cd198d0","_cell_guid":"7f0cf340-e285-4241-955b-21f6b4119a41","trusted":false,"collapsed":true},"cell_type":"code","source":"importances = forest.feature_importances_\nstd = np.std([forest.feature_importances_ for forest in forest.estimators_], axis=0)\nindices = np.argsort(importances)[::-1]\n\ncolumn_list = df.columns.tolist()\nprint(\"Feature ranking:\")\nfor f in range(X.shape[1]-1):\n print(\"%d. %s %d (%f)\" % (f, column_list[indices[f]], indices[f], importances[indices[f]]))\n\n# Plot the feature importances of the forest\nplt.figure(figsize=(20,10))\nplt.title(\"Feature importances\")\nplt.bar(range(X.shape[1]), importances[indices],\n color=\"salmon\", yerr=std[indices], align=\"center\")\nplt.xticks(range(X.shape[1]), indices)\nplt.xlim([-1, X.shape[1]])\nplt.show()","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"1592f57021b81981bc5519eddd3566f9b7540ccc","_cell_guid":"b893fc2d-dbfb-4c4e-9bd4-b6577958482f"},"cell_type":"markdown","source":"# Exploration"},{"metadata":{"_uuid":"97417479a9326253b1855daf84f4f6fef1b316d9","_cell_guid":"d25cb089-f944-4ce8-a415-ebef26e084a6","trusted":false,"collapsed":true},"cell_type":"code","source":"ax = df.plot(x=df.index, y='close', c='red', figsize=(40,10))\nindex = [str(item) for item in df.index]\nplt.fill_between(x=index, y1='low',y2='high', data=df, alpha=0.4)\nplt.show()\n\np = df[:200].copy()\nax = p.plot(x=p.index, y='close', c='red', figsize=(40,10))\nindex = [str(item) for item in p.index]\nplt.fill_between(x=index, y1='low', y2='high', data=p, alpha=0.4)\nplt.title('zoomed, first 200')\nplt.show()","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"f028f6ae47c668625c842e047a774054df9c0d46","collapsed":true,"_cell_guid":"ff8d4234-7c71-4ade-a13a-c5c75bdc8c95","trusted":false},"cell_type":"code","source":"# Scale and create datasets\ntarget_index = df.columns.tolist().index('close')\nhigh_index = df.columns.tolist().index('high')\nlow_index = df.columns.tolist().index('low')\ndataset = df.values.astype('float32')\n\n# Scale the data\nscaler = MinMaxScaler(feature_range=(0, 1))\ndataset = scaler.fit_transform(dataset)\n\n# Create y_scaler to inverse it later\ny_scaler = MinMaxScaler(feature_range=(0, 1))\nt_y = df['close'].values.astype('float32')\nt_y = np.reshape(t_y, (-1, 1))\ny_scaler = y_scaler.fit(t_y)\n \n# Set look_back to 20 which is 5 hours (15min*20)\nX, y = create_dataset(dataset, look_back=20)\ny = y[:,target_index]","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"31ff89b4610099a47252b518de87befa8346779f","collapsed":true,"_cell_guid":"ab9d8796-dd89-4c3a-8425-e54860acf920","trusted":false},"cell_type":"code","source":"# Set training data size\n# We have a large enough dataset. So divid into 98% training / 1% development / 1% test sets\ntrain_size = int(len(X) * 0.99)\ntrainX = X[:train_size]\ntrainY = y[:train_size]\ntestX = X[train_size:]\ntestY = y[train_size:]","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"8bdd1ca1a5ad7c2e1e0fcecc7f0746ddb433933d","_cell_guid":"8d2615a0-32df-4998-9b83-f41d3e50ed62","trusted":false,"collapsed":true},"cell_type":"code","source":"from keras.models import Sequential\nfrom keras.layers import Dense, Dropout, Activation, Input, LSTM, Dense\n\n# create a small LSTM network\nmodel = Sequential()\nmodel.add(LSTM(20, input_shape=(X.shape[1], X.shape[2]), return_sequences=True))\nmodel.add(LSTM(20, return_sequences=True))\nmodel.add(LSTM(10, return_sequences=True))\nmodel.add(Dropout(0.2))\nmodel.add(LSTM(4, return_sequences=False))\nmodel.add(Dense(4, kernel_initializer='uniform', activation='relu'))\nmodel.add(Dense(1, kernel_initializer='uniform', activation='relu'))\n\nmodel.compile(loss='mean_squared_error', optimizer='adam', metrics=['mae', 'mse'])\nprint(model.summary())","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"6345784d591a2d522311dc56c8ade6c55abe569c","scrolled":false,"_cell_guid":"3a2e81fa-f958-4ab7-9f9d-4677148a5529","trusted":false,"collapsed":true},"cell_type":"code","source":"# Save the best weight during training.\nfrom keras.callbacks import ModelCheckpoint\ncheckpoint = ModelCheckpoint(\"weights.best.hdf5\", monitor='val_mean_squared_error', verbose=1, save_best_only=True, mode='min')\n\n# Fit\ncallbacks_list = [checkpoint]\nhistory = model.fit(trainX, trainY, epochs=200, batch_size=500, verbose=0, callbacks=callbacks_list, validation_split=0.1)","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"e77270be0899ae276da48515fae05303eb0d7f9b","_cell_guid":"3bd87749-6336-41f8-9e8d-28b2af668da7","trusted":false,"collapsed":true},"cell_type":"code","source":"epoch = len(history.history['loss'])\nfor k in list(history.history.keys()):\n if 'val' not in k:\n plt.figure(figsize=(40,10))\n plt.plot(history.history[k])\n plt.plot(history.history['val_' + k])\n plt.title(k)\n plt.ylabel(k)\n plt.xlabel('epoch')\n plt.legend(['train', 'test'], loc='upper left')\n plt.show()","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"1c73b5d4ab0969215eb3e7c18ab27e0e2545d382","_cell_guid":"90839462-0395-4116-a13b-101fc1c2bc5f","trusted":false,"collapsed":true},"cell_type":"code","source":"min(history.history['val_mean_absolute_error'])","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"b8233417663b4590f6e4d378d43c3fe198bf287b","_cell_guid":"f2304754-a7cf-4c62-a5fe-666fec2397cf"},"cell_type":"markdown","source":"As seen from the above, the model seems to have converged nicely, but the mean absolute error on the development data remains at ~0.003X which means the model is unusable in practice. Ideally, we want to get ~0.0005. Let's go back to the best weight, and decay the learning rate while retraining the model"},{"metadata":{"_uuid":"b088bcb4e082a9c2791d414c9e16f67ff2ca959a","_cell_guid":"5b028aca-1359-4653-9b17-fe49e31f2079","trusted":false,"collapsed":true},"cell_type":"code","source":"# Baby the model a bit\n# Load the weight that worked the best\nmodel.load_weights(\"weights.best.hdf5\")\n\n# Train again with decaying learning rate\nfrom keras.callbacks import LearningRateScheduler\nimport keras.backend as K\n\ndef scheduler(epoch):\n if epoch%2==0 and epoch!=0:\n lr = K.get_value(model.optimizer.lr)\n K.set_value(model.optimizer.lr, lr*.9)\n print(\"lr changed to {}\".format(lr*.9))\n return K.get_value(model.optimizer.lr)\nlr_decay = LearningRateScheduler(scheduler)\n\ncallbacks_list = [checkpoint, lr_decay]\nhistory = model.fit(trainX, trainY, epochs=int(epoch/3), batch_size=500, verbose=0, callbacks=callbacks_list, validation_split=0.1)","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"6ad515300117e7ffce8169db1afbdd296db4a04f","_cell_guid":"5f4b4826-71ce-4a78-8964-d3e6c8ae2cf7","trusted":false,"collapsed":true},"cell_type":"code","source":"epoch = len(history.history['loss'])\nfor k in list(history.history.keys()):\n if 'val' not in k:\n plt.figure(figsize=(40,10))\n plt.plot(history.history[k])\n plt.plot(history.history['val_' + k])\n plt.title(k)\n plt.ylabel(k)\n plt.xlabel('epoch')\n plt.legend(['train', 'test'], loc='upper left')\n plt.show()","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"202a1e3bb06e25b77af5029ae347bcf502dce4c2","_cell_guid":"fac2ed68-5e46-4068-87bf-743287e80082","trusted":false,"collapsed":true},"cell_type":"code","source":"min(history.history['val_mean_absolute_error'])","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"3fbda13306965b59f682eb2d2f7604663f907e6b","_cell_guid":"1c71e7b2-a5fa-4c96-983c-e8736f41e193"},"cell_type":"markdown","source":"The variance should have improved slightly. However, unless the mean absolute error is not small enough. The model is still not an usable model in practice. This is mainly due to only using the sample data for training and limiting epoch to a few hundreds."},{"metadata":{"_uuid":"70d5d8ec1b68813cf6a8cd4250da9caf2746d094","_cell_guid":"862ba33b-6935-4777-b9e4-29a1fc3259b6"},"cell_type":"markdown","source":"# Visually compare the delta between the prediction and actual (scaled values)"},{"metadata":{"_uuid":"c9bbaade0cc059ec3e63dfabf182fcd9c3b7c885","scrolled":false,"_cell_guid":"759644d5-1a1b-4916-93c5-75e9c2f5b8a2","trusted":false,"collapsed":true},"cell_type":"code","source":"from sklearn.metrics import mean_squared_error, mean_absolute_error\n\n# Benchmark\nmodel.load_weights(\"weights.best.hdf5\")\n\npred = model.predict(testX)\n\npredictions = pd.DataFrame()\npredictions['predicted'] = pd.Series(np.reshape(pred, (pred.shape[0])))\npredictions['actual'] = testY\npredictions = predictions.astype(float)\n\npredictions.plot(figsize=(20,10))\nplt.show()\n\npredictions['diff'] = predictions['predicted'] - predictions['actual']\nplt.figure(figsize=(10,10))\nsns.distplot(predictions['diff']);\nplt.title('Distribution of differences between actual and prediction')\nplt.show()\n\nprint(\"MSE : \", mean_squared_error(predictions['predicted'].values, predictions['actual'].values))\nprint(\"MAE : \", mean_absolute_error(predictions['predicted'].values, predictions['actual'].values))\npredictions['diff'].describe()","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"90fb470b95817dc394005f294787f5a809943e26","_cell_guid":"48e036a6-f1f1-4d07-aae9-40ff7b1fc34a"},"cell_type":"markdown","source":"# Compare the unscaled values and see if the prediction falls within the Low and High"},{"metadata":{"scrolled":false,"collapsed":true,"_uuid":"0cbcf799bfec016f80f0ffcfe7a66a19eedb1b7b","_cell_guid":"c93f72cc-aff8-4e48-b022-87deab5a929e","trusted":false},"cell_type":"code","source":"pred = model.predict(testX)\npred = y_scaler.inverse_transform(pred)\nclose = y_scaler.inverse_transform(np.reshape(testY, (testY.shape[0], 1)))\npredictions = pd.DataFrame()\npredictions['predicted'] = pd.Series(np.reshape(pred, (pred.shape[0])))\npredictions['close'] = pd.Series(np.reshape(close, (close.shape[0])))\n\np = df[-pred.shape[0]:].copy()\npredictions.index = p.index\npredictions = predictions.astype(float)\npredictions = predictions.merge(p[['low', 'high']], right_index=True, left_index=True)\n\nax = predictions.plot(x=predictions.index, y='close', c='red', figsize=(40,10))\nax = predictions.plot(x=predictions.index, y='predicted', c='blue', figsize=(40,10), ax=ax)\nindex = [str(item) for item in predictions.index]\nplt.fill_between(x=index, y1='low', y2='high', data=p, alpha=0.4)\nplt.title('Prediction vs Actual (low and high as blue region)')\nplt.show()\n\npredictions['diff'] = predictions['predicted'] - predictions['close']\nplt.figure(figsize=(10,10))\nsns.distplot(predictions['diff']);\nplt.title('Distribution of differences between actual and prediction ')\nplt.show()\n\ng = sns.jointplot(\"diff\", \"predicted\", data=predictions, kind=\"kde\", space=0)\nplt.title('Distributtion of error and price')\nplt.show()\n\n# predictions['correct'] = (predictions['predicted'] <= predictions['high']) & (predictions['predicted'] >= predictions['low'])\n# sns.factorplot(data=predictions, x='correct', kind='count')\n\nprint(\"MSE : \", mean_squared_error(predictions['predicted'].values, predictions['close'].values))\nprint(\"MAE : \", mean_absolute_error(predictions['predicted'].values, predictions['close'].values))\npredictions['diff'].describe()","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"935ca5504302a194d0af986410681213ea3fe5bc"},"cell_type":"markdown","source":"The above references an opinion and is for information purposes only. It is not intended to be investment advice. Seek a duly licensed professional for investment advice."}],"metadata":{"language_info":{"name":"python","version":"3.6.4","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kernelspec":{"display_name":"Python 3","language":"python","name":"python3"}},"nbformat":4,"nbformat_minor":1}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment