{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"pygments_lexer":"ipython3","nbconvert_exporter":"python","version":"3.6.4","file_extension":".py","codemirror_mode":{"name":"ipython","version":3},"name":"python","mimetype":"text/x-python"}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"# About this tutorial\n\nThe purpose of this tutorial is to provide a simple and practical example of applying machine learning in time series analysis. You can use it as a skeleton for your first time series forecasting project and tweak parts of it for achieving higher performance. I'm assuming familiarity with general machine learning but not necessarily with time series forecasting nor stock price prediction.\n\nAs an example dataset, we'll use the [G-Research Crypto Forecasting](https://www.kaggle.com/competitions/g-research-crypto-forecasting) competition data. Unfortunately I was too late to participate to the competition but when browsing through the top solutions I found it interesting how similar many of them were. I found that `LightGBM` was very popular and different scores probably resulted mostly from different feature engineering. We'll present here a simplified project in the same style for you to see how machine learning can be applied to predict crypto/stock prices! Here's a description of the dataset:\n* 14 cryptoassets, e.g. Bitcoin, Ethereum, Cardano\n* 1 minute historical prices (open, high, low, close)\n* Almost 4 years of data, starting at 2018\n\nThe goal is to predict market returns of these assets 15 minutes in the future. So, in principle, if one had a model that could do this consistently you could use it to make 15 minute trades going either long or short depending on the prediction and make money in the long run. We should immediately temper our expectations: it is unlikely that we can find models with amazing predictive powers. Instead we should be happy if we can predict returns in the right direction on average, then this model would be profitable over many trades.\n\nIn this tutorial, we'll simplify the problem by only considering Bitcoin prices instead of the total set of cryptoassets. This way we can reduce hardware requirements while still being able to get our point across.\n\nWithout further ado, let's get to work!","metadata":{}},{"cell_type":"markdown","source":"We need to import some standard libraries used to work with data.","metadata":{}},{"cell_type":"code","source":"import pandas as pd\nimport numpy as np\nimport matplotlib.pyplot as plt\nimport seaborn as sns\n\ncolors = sns.color_palette()\nsns.set_theme()","metadata":{"execution":{"iopub.status.busy":"2022-10-28T17:22:05.396511Z","iopub.execute_input":"2022-10-28T17:22:05.397253Z","iopub.status.idle":"2022-10-28T17:22:06.366252Z","shell.execute_reply.started":"2022-10-28T17:22:05.397128Z","shell.execute_reply":"2022-10-28T17:22:06.364832Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# What is a time series\n\nA time series is a series of measurements where each data point has an associated time index. That is, each measurement has been made at some particular time. The world is full of such data: weather measurements, electricity usage, and exchange rates are just some examples. Often the quantity of interest is measured with a uniform time interval. We are working with such a dataset now: prices are recorded each minute (assuming no missing data) for the four year duration starting at 2018. This is not necessarily true though: for example, stock market data has non-uniform time steps because stocks are not traded during the night. In this regard the crypto market is more convenient since it trades around the clock. One can think of a time series as a sequence\n$$\\{Y_t, Y_{t-1}, Y_{t-2}, \\ldots\\} \\ .$$\nThe challenge is then to predict future values of the series using a finite set of past observations. Notice that in this case we actually have multiple time series in the dataset since it records four quantities as a discrete function of time: open, high, low, and close prices. The difference to a typical (non-time series) regression/classification task is that the samples are far from independent: samples close in time are often correlated with each other (autocorrelation). Also you have to be careful not to leak data from \"the future\" to your training procedure which need to be taken into account for example in cross validation.","metadata":{}},{"cell_type":"markdown","source":"# Data format\n\nPrice data is usually presented in the OHLC format. The letters stand for\n* O(pen) = Opening price for the time period\n* H(igh) = Highest price during the period\n* L(ow) = Lowest price during the period\n* C(lose) = Closing price at the end of the period\nThe period in this case is 1 minute, but generally used time periods in charts are e.g. 15 minute, 1 hour, 1 day. The point of this format is that in a liquid market there probably has occurred a very large number of trades in the time period. It is not very useful to work with the exact prices of all of those trades. Instead, we compress all of that information in the above four numbers giving a summary of the price action.\n\n![ohlc.png](attachment:8e3ac87f-6a6f-4302-aaba-fc9f7c968e5f.png)\n\nOHLC is often visualized as a candle as above. The body of the candle corresponds to the opening and closing prices. The color tells which edge is which: if close is higher than open (price went up), then the candle is green so that open is the lower edge of the candle body and close is the upper one. If close is lower than open (price went down) then the candle body is red. The wicks above and below the candle body denote the highest and lowest prices during the period.","metadata":{},"attachments":{"8e3ac87f-6a6f-4302-aaba-fc9f7c968e5f.png":{"image/png":"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"}}},{"cell_type":"markdown","source":"# Loading the data\n\nIn the dataset, the files we are interested in are `train.csv` (asset price data) and `asset_details.csv` (connects asset IDs to asset names).","metadata":{}},{"cell_type":"code","source":"TRAIN_CSV = '../input/g-research-crypto-forecasting/train.csv'\nASSET_CSV = '../input/g-research-crypto-forecasting/asset_details.csv'","metadata":{"execution":{"iopub.status.busy":"2022-10-28T17:22:06.368351Z","iopub.execute_input":"2022-10-28T17:22:06.368686Z","iopub.status.idle":"2022-10-28T17:22:06.374426Z","shell.execute_reply.started":"2022-10-28T17:22:06.368656Z","shell.execute_reply":"2022-10-28T17:22:06.372668Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"First, we look in the `asset_details.csv` to find which `Asset_ID` corresponds to which `Asset_Name`.","metadata":{}},{"cell_type":"code","source":"asset_details = pd.read_csv(ASSET_CSV)\nasset_details[['Asset_ID', 'Asset_Name']].sort_values('Asset_ID')","metadata":{"execution":{"iopub.status.busy":"2022-10-28T17:22:06.376517Z","iopub.execute_input":"2022-10-28T17:22:06.377119Z","iopub.status.idle":"2022-10-28T17:22:06.421883Z","shell.execute_reply.started":"2022-10-28T17:22:06.377072Z","shell.execute_reply":"2022-10-28T17:22:06.420763Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"In this tutorial, we'll consider only BTC since it is the asset with the highest market cap.","metadata":{}},{"cell_type":"code","source":"BTC_ID = 1","metadata":{"execution":{"iopub.status.busy":"2022-10-28T17:22:06.426332Z","iopub.execute_input":"2022-10-28T17:22:06.426842Z","iopub.status.idle":"2022-10-28T17:22:06.433606Z","shell.execute_reply.started":"2022-10-28T17:22:06.426791Z","shell.execute_reply":"2022-10-28T17:22:06.432308Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Here's the price data for BTC as given in `train.csv`.","metadata":{}},{"cell_type":"code","source":"df = pd.read_csv(TRAIN_CSV, usecols=['timestamp', 'Asset_ID', 'Open', 'High', 'Low', 'Close'])\ndf = df.loc[df.Asset_ID == BTC_ID] # Drop price data for everything but BTC\ndf = df.drop(columns=['Asset_ID']) # Drop Asset_ID since it's useless (each row is BTC)\ndf.timestamp = pd.to_datetime(df.timestamp, unit='s', origin='unix') # Convert Unix timestamps to datetime\ndf = df.set_index('timestamp') # Use timestamp as index\ndf","metadata":{"execution":{"iopub.status.busy":"2022-10-28T17:22:06.435408Z","iopub.execute_input":"2022-10-28T17:22:06.436356Z","iopub.status.idle":"2022-10-28T17:23:05.395196Z","shell.execute_reply.started":"2022-10-28T17:22:06.436308Z","shell.execute_reply":"2022-10-28T17:23:05.393991Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Plotting the data\n\nLet's see what the data looks like.","metadata":{}},{"cell_type":"code","source":"df.Close.iloc[::1000].plot(figsize=(15, 5), color=colors[0], title='BTC/USD')","metadata":{"execution":{"iopub.status.busy":"2022-10-28T17:23:05.396905Z","iopub.execute_input":"2022-10-28T17:23:05.398034Z","iopub.status.idle":"2022-10-28T17:23:05.979846Z","shell.execute_reply.started":"2022-10-28T17:23:05.397998Z","shell.execute_reply":"2022-10-28T17:23:05.978346Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Missing values\n\nLuckily, we don't have missing values in the columns we selected.","metadata":{}},{"cell_type":"code","source":"df.isna().sum()","metadata":{"execution":{"iopub.status.busy":"2022-10-28T17:23:05.981387Z","iopub.execute_input":"2022-10-28T17:23:05.981799Z","iopub.status.idle":"2022-10-28T17:23:06.006703Z","shell.execute_reply.started":"2022-10-28T17:23:05.981747Z","shell.execute_reply":"2022-10-28T17:23:06.005520Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Missing time stamps\n\nWe can notice that the time index has gaps. The vast majority of time steps are separated by 1 minute (like they should) but some have larger gaps, even up to 24 minutes. This means, that for whatever reason, price data has not been collected for each minute in the time span of the data set.","metadata":{}},{"cell_type":"code","source":"(df.index[1:] - df.index[:-1]).value_counts()","metadata":{"execution":{"iopub.status.busy":"2022-10-28T17:23:06.008113Z","iopub.execute_input":"2022-10-28T17:23:06.009341Z","iopub.status.idle":"2022-10-28T17:23:06.060117Z","shell.execute_reply.started":"2022-10-28T17:23:06.009293Z","shell.execute_reply":"2022-10-28T17:23:06.059203Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"This is a problem because time series models often assume that the data $\\{Y_t, Y_{t-1}, Y_{t-2}, \\ldots\\}$ are all separated by a constant time difference. We'll work around the problem by filling in the missing time steps with forward filling (values of new time steps are filled with values from the last time step that has valid data).","metadata":{}},{"cell_type":"code","source":"df = df.reindex(index=pd.date_range(df.index[0], df.index[-1], freq='1min'), method='ffill')","metadata":{"execution":{"iopub.status.busy":"2022-10-28T17:23:06.061576Z","iopub.execute_input":"2022-10-28T17:23:06.062216Z","iopub.status.idle":"2022-10-28T17:23:06.537026Z","shell.execute_reply.started":"2022-10-28T17:23:06.062181Z","shell.execute_reply":"2022-10-28T17:23:06.535916Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Now all time steps are separated by 1 minute.","metadata":{}},{"cell_type":"code","source":"(df.index[1:] - df.index[:-1]).value_counts()","metadata":{"execution":{"iopub.status.busy":"2022-10-28T17:23:06.541210Z","iopub.execute_input":"2022-10-28T17:23:06.541594Z","iopub.status.idle":"2022-10-28T17:23:06.602658Z","shell.execute_reply.started":"2022-10-28T17:23:06.541561Z","shell.execute_reply":"2022-10-28T17:23:06.601441Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Creating the target variable\n\nThe `train.csv` file has a target variable already, but we won't use it now: it is simply the 15 minute log returns of the asset with the average market returns subtracted. However, since we are using only BTC instead of the full set of cryptoassets we don't have the average market returns part. So we take our target variable is simply the 15 minute log returns of BTC.\n\nThe *log return* at lag $s$ is\n$$\n\\text{log return}_s(t) = \\log \\frac{\\text{Close}_{t+s}}{\\text{Close}_t} \\ .\n$$","metadata":{}},{"cell_type":"code","source":"df['Target'] = np.log(df.Close.shift(-15, freq='min') / df.Close)\ndf","metadata":{"execution":{"iopub.status.busy":"2022-10-28T17:23:06.604438Z","iopub.execute_input":"2022-10-28T17:23:06.604888Z","iopub.status.idle":"2022-10-28T17:23:08.151328Z","shell.execute_reply.started":"2022-10-28T17:23:06.604844Z","shell.execute_reply":"2022-10-28T17:23:08.147478Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"This introduces some missing values at the end because for the last 15 minutes, we obviously cannot know the 15 minute returns in the future.","metadata":{}},{"cell_type":"code","source":"df.isna().sum()","metadata":{"execution":{"iopub.status.busy":"2022-10-28T17:23:08.156290Z","iopub.execute_input":"2022-10-28T17:23:08.158617Z","iopub.status.idle":"2022-10-28T17:23:08.210609Z","shell.execute_reply.started":"2022-10-28T17:23:08.158578Z","shell.execute_reply":"2022-10-28T17:23:08.209737Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"To deal with this, we'll simply drop those last values.","metadata":{}},{"cell_type":"code","source":"df = df.dropna()","metadata":{"execution":{"iopub.status.busy":"2022-10-28T17:23:08.212390Z","iopub.execute_input":"2022-10-28T17:23:08.212714Z","iopub.status.idle":"2022-10-28T17:23:08.422114Z","shell.execute_reply.started":"2022-10-28T17:23:08.212684Z","shell.execute_reply":"2022-10-28T17:23:08.420647Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"The target variable looks like you would expect, noise around 0 (hopefully with some structure).","metadata":{}},{"cell_type":"code","source":"df.Target.iloc[::1000].plot(figsize=(15, 5), color=colors[0], title='Target')","metadata":{"execution":{"iopub.status.busy":"2022-10-28T17:23:08.423843Z","iopub.execute_input":"2022-10-28T17:23:08.424182Z","iopub.status.idle":"2022-10-28T17:23:14.564814Z","shell.execute_reply.started":"2022-10-28T17:23:08.424151Z","shell.execute_reply":"2022-10-28T17:23:14.563372Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Feature engineering\n\nThe performance of the model is mostly dependent on the features one uses so this is the part you spend most time experimenting on. Here, we'll start off with simple features such as lagged returns, hour/month/year, moving averages, etc.","metadata":{}},{"cell_type":"code","source":"lags = [15, 30, 60, 60 * 2, 60 * 4, 60 * 24] # In minutes: 15min, 30min, 1h, 2h, 4h, 24h\nmas = [15, 60, 60 * 24, 60 * 24 * 7]         # In minutes: 15min, 1h, 1d, 7d\n\ndef create_features(df):\n    df = df.copy()\n    # Compute log-returns for various lags\n    for lag in lags:\n        df[f'log_return_{lag}'] = np.log(df.Close.shift(lag, freq='min') / df.Close)\n    # Moving averages (how price is closing w.r.t. moving averages)\n    for ma in mas:\n        df[f'log_close_ma_{ma}'] = np.log(df.Close / df.Close.rolling(window=ma).mean())\n    # Log difference between Low and High (a measure of volatility)\n    df['log_high/low'] = np.log(df.High / df.Low)\n    # Features relating to time\n    df['hour'] = df.index.hour\n    df['dayofweek'] = df.index.dayofweek\n    df['month'] = df.index.month\n    df['year'] = df.index.year\n    # One can invent many more other indicators\n    # The new dataset has some NaNs in the first 7d time indices because of the moving averages so we'll drop those rows\n    df = df.dropna()\n    return df","metadata":{"execution":{"iopub.status.busy":"2022-10-28T17:23:14.566567Z","iopub.execute_input":"2022-10-28T17:23:14.566949Z","iopub.status.idle":"2022-10-28T17:23:14.576281Z","shell.execute_reply.started":"2022-10-28T17:23:14.566912Z","shell.execute_reply":"2022-10-28T17:23:14.574872Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"df = create_features(df)\ndf","metadata":{"execution":{"iopub.status.busy":"2022-10-28T17:23:14.578033Z","iopub.execute_input":"2022-10-28T17:23:14.578410Z","iopub.status.idle":"2022-10-28T17:23:24.546364Z","shell.execute_reply.started":"2022-10-28T17:23:14.578377Z","shell.execute_reply":"2022-10-28T17:23:24.544719Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Define the columns which we use as features (`FEATURES`) during prediction of the target column (`TARGET`).","metadata":{}},{"cell_type":"code","source":"FEATURES = ['log_return_15', 'log_return_30', 'log_return_60', 'log_return_120', 'log_return_240', 'log_return_1440', 'log_close_ma_15', 'log_close_ma_60', 'log_close_ma_1440', 'log_close_ma_10080', 'log_high/low', 'hour', 'dayofweek', 'month', 'year']\nTARGET = 'Target'","metadata":{"execution":{"iopub.status.busy":"2022-10-28T17:23:24.547963Z","iopub.execute_input":"2022-10-28T17:23:24.548349Z","iopub.status.idle":"2022-10-28T17:23:24.554143Z","shell.execute_reply.started":"2022-10-28T17:23:24.548316Z","shell.execute_reply":"2022-10-28T17:23:24.553312Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Test with linear regression\n\nIt is often a good idea to start with a simple model to establish a baseline for performance. We'll start simply with linear regression.","metadata":{}},{"cell_type":"code","source":"from sklearn.linear_model import LinearRegression\nfrom sklearn.preprocessing import StandardScaler","metadata":{"execution":{"iopub.status.busy":"2022-10-28T17:23:24.555831Z","iopub.execute_input":"2022-10-28T17:23:24.556545Z","iopub.status.idle":"2022-10-28T17:23:24.775046Z","shell.execute_reply.started":"2022-10-28T17:23:24.556510Z","shell.execute_reply":"2022-10-28T17:23:24.773893Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Split to train/validation. Leave a gap of a week in between.","metadata":{}},{"cell_type":"code","source":"df.Close.loc[:'2021-03'].iloc[::1000].plot(figsize=(15, 5), label='train', color=colors[0], title='BTC/USD train/val split')\ndf.Close.loc['2021-04-08':].iloc[::1000].plot(label='val', color=colors[1])\nplt.legend()","metadata":{"execution":{"iopub.status.busy":"2022-10-28T17:23:24.776696Z","iopub.execute_input":"2022-10-28T17:23:24.777352Z","iopub.status.idle":"2022-10-28T17:23:32.194126Z","shell.execute_reply.started":"2022-10-28T17:23:24.777313Z","shell.execute_reply":"2022-10-28T17:23:32.192956Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"X_train = df[FEATURES].loc[:'2021-03'].to_numpy()\ny_train = df[TARGET].loc[:'2021-03'].to_numpy()\nX_val = df[FEATURES].loc['2021-04-08':].to_numpy()\ny_val = df[TARGET].loc['2021-04-08':].to_numpy()","metadata":{"execution":{"iopub.status.busy":"2022-10-28T17:23:32.197787Z","iopub.execute_input":"2022-10-28T17:23:32.198150Z","iopub.status.idle":"2022-10-28T17:23:32.492212Z","shell.execute_reply.started":"2022-10-28T17:23:32.198117Z","shell.execute_reply":"2022-10-28T17:23:32.491079Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"scaler = StandardScaler()\nX_train_scaled = scaler.fit_transform(X_train)\nX_val_scaled = scaler.transform(X_val)","metadata":{"execution":{"iopub.status.busy":"2022-10-28T17:23:32.493701Z","iopub.execute_input":"2022-10-28T17:23:32.494041Z","iopub.status.idle":"2022-10-28T17:23:32.843627Z","shell.execute_reply.started":"2022-10-28T17:23:32.494010Z","shell.execute_reply":"2022-10-28T17:23:32.842483Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"reg = LinearRegression()\nreg.fit(X_train_scaled, y_train)\ny_pred = reg.predict(X_val_scaled)","metadata":{"execution":{"iopub.status.busy":"2022-10-28T17:23:32.845182Z","iopub.execute_input":"2022-10-28T17:23:32.845634Z","iopub.status.idle":"2022-10-28T17:23:33.688788Z","shell.execute_reply.started":"2022-10-28T17:23:32.845589Z","shell.execute_reply":"2022-10-28T17:23:33.686060Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"print('Correlation between prediction and ground truth:', np.corrcoef(y_val, y_pred)[0, 1])","metadata":{"execution":{"iopub.status.busy":"2022-10-28T17:23:33.690510Z","iopub.execute_input":"2022-10-28T17:23:33.691226Z","iopub.status.idle":"2022-10-28T17:23:33.705646Z","shell.execute_reply.started":"2022-10-28T17:23:33.691180Z","shell.execute_reply":"2022-10-28T17:23:33.704048Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"The result is not great. Hopefully we can improve with a more sophisticated model.","metadata":{}},{"cell_type":"markdown","source":"# Cross validation\n\nFor the LightGBM regressor, we'll adopt a better validation scheme: cross validation. A more robust validation method helps when tuning hyperparameters and crafting new features to have a more reliable idea of the effect of changes.\n\nSince we are working with a time series we have to be a bit more careful than normal with our cross validation setup. Here's what the typical $K$-fold cross validation would look like.","metadata":{}},{"cell_type":"code","source":"from sklearn.model_selection import KFold\n\nn_splits = 6\nfolds = KFold(n_splits=n_splits)\n\nfig, axs = plt.subplots(n_splits, 1, figsize=(15, 5 * n_splits), sharex=True)\nfor split_idx, (train_idx, val_idx) in enumerate(folds.split(df[FEATURES])):\n    # Train set is not necessarily contiguous. We'll plot the parts separately so the figure looks nicer.\n    if split_idx > 0:\n        df.Close.iloc[train_idx[train_idx < val_idx[0]]].iloc[::1000].plot(ax=axs[split_idx], color=colors[0], label='train')\n    if split_idx < n_splits - 1:\n        df.Close.iloc[train_idx[train_idx > val_idx[-1]]].iloc[::1000].plot(ax=axs[split_idx], color=colors[0], label='train')\n    df.Close.iloc[val_idx].iloc[::1000].plot(ax=axs[split_idx], color=colors[1], label='val')\n    axs[split_idx].set_ylabel('Price ($)')\nplt.legend()","metadata":{"execution":{"iopub.status.busy":"2022-10-28T17:23:33.708145Z","iopub.execute_input":"2022-10-28T17:23:33.708655Z","iopub.status.idle":"2022-10-28T17:24:13.411247Z","shell.execute_reply.started":"2022-10-28T17:23:33.708607Z","shell.execute_reply":"2022-10-28T17:24:13.410053Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Clearly this doesn't make much sense because we are breaking the causal structure of the data: a time series is not simply a bag of observations but those observations have a temporal relationship to each other. Every split except the last one would train on *future* values of the series and then measure performance on the validation set which is in its past. Surely this is not a good thing to do!\n\nA clear example would be this: suppose you are predicting the stock price of a company for the next month. If you somehow knew what the price was *two* months from now, surely that would give you some information about price movements *one* month from now.\n\nA better way to do cross validation with temporal data is to take different subsequences of the series, split the sequence in two and train on the first part while validating on the last part. `sklearn` has a class to do an appropriate split `TimeSeriesSplit`. In addition to doing time respecting splits, we choose to leave a gap of 1 week between train and validation splits. This is because without a gap, the last part of the train split might be unrealistically informative about the very first part of validation. We'll also set a fixed size for the validation sets because we prefer the train set to be significantly larger than the validation set. Without fixed validation size, for example, in the first split the train set would be the same size as the validation which is not exactly what we want.","metadata":{}},{"cell_type":"code","source":"from sklearn.model_selection import TimeSeriesSplit\n\ntss = TimeSeriesSplit(n_splits=n_splits, gap=60 * 24 * 7, test_size=len(df) // (n_splits + 2))\n\nfig, axs = plt.subplots(n_splits, 1, figsize=(15, 5 * n_splits), sharex=True, sharey=True)\nfor split_idx, (train_idx, val_idx) in enumerate(tss.split(df[FEATURES])):\n    df.Close.iloc[train_idx].iloc[::1000].plot(ax=axs[split_idx], color=colors[0], label='train')\n    df.Close.iloc[val_idx].iloc[::1000].plot(ax=axs[split_idx], color=colors[1], label='val')\n    axs[split_idx].set_ylabel('Price ($)')\nplt.legend()","metadata":{"execution":{"iopub.status.busy":"2022-10-28T17:24:13.413112Z","iopub.execute_input":"2022-10-28T17:24:13.413966Z","iopub.status.idle":"2022-10-28T17:24:44.115125Z","shell.execute_reply.started":"2022-10-28T17:24:13.413918Z","shell.execute_reply":"2022-10-28T17:24:44.113733Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"This looks much nicer: validation is always to the future of train, train is significantly bigger than validation, and there is a gap between train/validation (even though it is not visible in the plot).\n\nAn another consideration is the number of splits. In general it is better to have more splits because then you will lower the variance of your performance estimates. This is a balancing act though: more splits needs more compute time and slows you down. Also a larger number of splits might reduce the number of samples in individual splits, making training harder. This can happen if your splits do not overlap, for example if at each split we moved the start of the training set to the right so that the same training data doesn't appear in multiple splits. Right now we allow overlapping splits like you can see in the above figures: previous training sets are strict subsets of the later ones. This has the advantage that each split has a good amount of training samples.\n\nNow, we'll start training our model!","metadata":{}},{"cell_type":"markdown","source":"# LightGBM regressor\n\n`LightGBM` is a popular gradient boosting framework intended to be more efficient than `XGBoost`. Both use decision trees as base learners and are commonly used in Kaggle competitions (and data science more generally). In the GResearch Crypto Forecasting competition, many of the top solutions used `LightGBM`. Therefore give `LightGBM` a try in this tutorial as well!","metadata":{}},{"cell_type":"code","source":"import lightgbm as lgb","metadata":{"execution":{"iopub.status.busy":"2022-10-28T21:04:52.681420Z","iopub.execute_input":"2022-10-28T21:04:52.682484Z","iopub.status.idle":"2022-10-28T21:04:52.686830Z","shell.execute_reply.started":"2022-10-28T21:04:52.682438Z","shell.execute_reply":"2022-10-28T21:04:52.685787Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"In the competition, submissions were scored by how well the predictions correlated with actual market returns. We'll use correlation as a metric as well. First we need a function to compute it.","metadata":{}},{"cell_type":"code","source":"def corr(y_preds, eval_data):\n    '''\n    Metric for use during training, gives the correlation between\n    predicted target and true value.\n    This is supposed to return a tuple:\n    (Name of metric, value of metric, whether higher is better)\n    '''\n    y = eval_data.label\n    corr = np.corrcoef(y_preds, y)[0, 1]\n    return 'corr', corr, True","metadata":{"execution":{"iopub.status.busy":"2022-10-28T20:50:21.962224Z","iopub.execute_input":"2022-10-28T20:50:21.962985Z","iopub.status.idle":"2022-10-28T20:50:21.968993Z","shell.execute_reply.started":"2022-10-28T20:50:21.962946Z","shell.execute_reply":"2022-10-28T20:50:21.967797Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Then we will to set some parameters for our `LightGBM` model.","metadata":{}},{"cell_type":"code","source":"params = {\n    'objective': 'regression',     # We are doing regression\n    'metric': 'rmse',              # Optimize RMSE\n    'learning_rate': 0.05,         # Learning rate for gradient boosting\n    'max_depth': 5,                # Max depth for regression trees\n    'num_leaves': 5,               # Number of leaves in a single decision tree\n    'verbose': -1                  # Print errors and warnings only\n}","metadata":{"execution":{"iopub.status.busy":"2022-10-28T17:24:45.195277Z","iopub.execute_input":"2022-10-28T17:24:45.195711Z","iopub.status.idle":"2022-10-28T17:24:45.204163Z","shell.execute_reply.started":"2022-10-28T17:24:45.195677Z","shell.execute_reply":"2022-10-28T17:24:45.202921Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"This function simply runs cross validation over the provided folds. That is, for each fold we train a `LightGBM` model and record the root mean squared error and correlation of predictions for both training and validation sets. These metrics are finally returned with the trained models, one for each fold.","metadata":{}},{"cell_type":"code","source":"def cv(folds, params):\n    # Record rmse and corr for each fold\n    train_rmse, train_corr, val_rmse, val_corr = [], [], [], []\n    # Collect the trained models for each fold\n    models = []\n    for split_idx, (train_idx, val_idx) in enumerate(folds.split(df[FEATURES])):\n        print('Current fold:', split_idx + 1, '/', n_splits)\n        # Locate train and validation sets for this fold\n        train_set = lgb.Dataset(df[FEATURES].iloc[train_idx], df.Target.iloc[train_idx])\n        val_set = lgb.Dataset(df[FEATURES].iloc[val_idx], df.Target.iloc[val_idx])\n        # Training the model\n        model = lgb.train(\n            params=params,\n            train_set=train_set,\n            valid_sets=[train_set, val_set],\n            valid_names=['train', 'val'],\n            num_boost_round=5000,              # Number of boosting iterations (if we don't stop early)\n            feval=corr,                        # Measure correlation in addition to rmse\n            callbacks=[\n                lgb.early_stopping(50),        # Stop training if metrics don't improve in 50 iterations\n                lgb.log_evaluation(100)        # Print metrics every 100 iterations\n            ]\n        )\n        # Record results from this fold\n        train_rmse.append(model.best_score['train']['rmse'])\n        train_corr.append(model.best_score['train']['corr'])\n        val_rmse.append(model.best_score['val']['rmse'])\n        val_corr.append(model.best_score['val']['corr'])\n        models.append(model)\n        print('==============================================================')\n    print('Average over folds:')\n    print(f'\\ttrain\\'s rmse = {np.mean(train_rmse):.5f} ± {np.std(train_rmse):.5f}, corr = {np.mean(train_corr):.5f} ± {np.std(train_corr):.5f}')\n    print(f'\\tval\\'s   rmse = {np.mean(val_rmse):.5f} ± {np.std(val_rmse):.5f}, corr = {np.mean(val_corr):.5f} ± {np.std(val_corr):.5f}')\n    return train_rmse, train_corr, val_rmse, val_corr, models\n    ","metadata":{"execution":{"iopub.status.busy":"2022-10-28T21:05:06.830118Z","iopub.execute_input":"2022-10-28T21:05:06.830525Z","iopub.status.idle":"2022-10-28T21:05:06.842052Z","shell.execute_reply.started":"2022-10-28T21:05:06.830491Z","shell.execute_reply":"2022-10-28T21:05:06.840961Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Run our cross validated training code.","metadata":{}},{"cell_type":"code","source":"train_rmse, train_corr, val_rmse, val_corr, models = cv(tss, params)","metadata":{"execution":{"iopub.status.busy":"2022-10-28T17:24:45.223043Z","iopub.execute_input":"2022-10-28T17:24:45.223610Z","iopub.status.idle":"2022-10-28T17:25:25.147946Z","shell.execute_reply.started":"2022-10-28T17:24:45.223575Z","shell.execute_reply":"2022-10-28T17:25:25.146907Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"It is interesting to study which of our features were most useful during training. One way to measure the relative importance of features is to count how many times a feature is used in a model. Here we plot the feature importances for each of our models (one for each CV-split).","metadata":{}},{"cell_type":"code","source":"def plot_importance(cv_boosters):\n    for split_idx in range(n_splits):\n        max_importance = cv_boosters[split_idx].feature_importance().max()\n        lgb.plot_importance(cv_boosters[split_idx], title=f'Feature importance, split {split_idx + 1} / {n_splits}', xlim=(0, max_importance + 2))","metadata":{"execution":{"iopub.status.busy":"2022-10-28T17:25:25.152075Z","iopub.execute_input":"2022-10-28T17:25:25.154681Z","iopub.status.idle":"2022-10-28T17:25:25.162409Z","shell.execute_reply.started":"2022-10-28T17:25:25.154636Z","shell.execute_reply":"2022-10-28T17:25:25.161228Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"plot_importance(models)","metadata":{"execution":{"iopub.status.busy":"2022-10-28T17:25:25.165890Z","iopub.execute_input":"2022-10-28T17:25:25.166466Z","iopub.status.idle":"2022-10-28T17:25:27.041364Z","shell.execute_reply.started":"2022-10-28T17:25:25.166433Z","shell.execute_reply":"2022-10-28T17:25:27.040246Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"We can see that according to these importance measures, `log_high/low` maybe the best feature. On the other hand, the `dayofweek` and `year` features seem not to be very useful for our models. This information about feature importance can be valuable for engineering good features!","metadata":{}},{"cell_type":"markdown","source":"# LightGBM regressor with automatic parameter tuning (Optuna)\n\nHyperparameter tuning is an important part of making your model perform well. How to choose those parameters though and how did we choose them in our previous experiment? Well, I made a few quick experiments and simply selected something but let's be honest: this is barely better than pulling values out of thin air. There is no strict theory behind the choice of hyperparameters, so if we need to resort to trial-and-error let's at least try to automate the process. This is where hyperparameter optimization frameworks step in: their job is to search the space of hyperparameters in an automated way. I chose to go with `Optuna` which has integration for `LightGBM` so it is really easy to use.","metadata":{}},{"cell_type":"code","source":"from optuna.integration.lightgbm import LightGBMTunerCV","metadata":{"execution":{"iopub.status.busy":"2022-10-28T21:05:12.064585Z","iopub.execute_input":"2022-10-28T21:05:12.065024Z","iopub.status.idle":"2022-10-28T21:05:12.071136Z","shell.execute_reply.started":"2022-10-28T21:05:12.064988Z","shell.execute_reply":"2022-10-28T21:05:12.069665Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"This time we let `Optuna` run experiments with different hyperparameter values to find us parameters that work as well as possible.","metadata":{}},{"cell_type":"code","source":"params = {\n    'objective': 'regression',\n    'metric': 'rmse',\n    'verbose': -1\n}\n\ntrain_set = lgb.Dataset(df[FEATURES], df.Target)\n\ntuner = LightGBMTunerCV(\n    params,\n    train_set,\n    folds=tss,\n    feval=corr,\n    callbacks=[lgb.early_stopping(50), lgb.log_evaluation(100)],\n    return_cvbooster=True\n)\ntuner.run()\nprint('Best score: ', tuner.best_score)\nprint('Best params:')\nfor key, value in tuner.best_params.items():\n    print(f'\\t{key}: {value}')","metadata":{"execution":{"iopub.status.busy":"2022-10-28T21:05:31.819174Z","iopub.execute_input":"2022-10-28T21:05:31.819634Z","iopub.status.idle":"2022-10-28T21:27:07.760033Z","shell.execute_reply.started":"2022-10-28T21:05:31.819596Z","shell.execute_reply":"2022-10-28T21:27:07.758021Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Here's the performance of the best model found by `Optuna`.","metadata":{}},{"cell_type":"code","source":"train_rmse, train_corr, val_rmse, val_corr, models = cv(tss, tuner.best_params)","metadata":{"execution":{"iopub.status.busy":"2022-10-28T21:27:57.347884Z","iopub.execute_input":"2022-10-28T21:27:57.349183Z","iopub.status.idle":"2022-10-28T21:28:34.792853Z","shell.execute_reply.started":"2022-10-28T21:27:57.349137Z","shell.execute_reply":"2022-10-28T21:28:34.791795Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"plot_importance(models)","metadata":{"execution":{"iopub.status.busy":"2022-10-28T21:30:32.502378Z","iopub.execute_input":"2022-10-28T21:30:32.503731Z","iopub.status.idle":"2022-10-28T21:30:34.318655Z","shell.execute_reply.started":"2022-10-28T21:30:32.503674Z","shell.execute_reply":"2022-10-28T21:30:34.317352Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Conclusion\n\nIn this tutorial we gave a practical example of using `LightGBM` for time series forecasting and achieved significantly higher performance compared to a linear regression baseline on our dataset. We used Bitcoin price data as an example, but similar approaches should be applicable to other datasets as well. The important points are to have a cross validation scheme in place to guide you in the tuning of your model. Largest improvements in performance originate from your hand crafted features so you should spend time on developing those using feature importance metrics as your guide.\n\nI hope you found this tutorial helpful!","metadata":{}},{"cell_type":"code","source":"","metadata":{},"execution_count":null,"outputs":[]}]}