# Financial Asset Recommender: Profitability Predication

## Introduction

This notebook is aimed to serve as an introduction to the creation of a recommender that operates on an open dataset of stock market prices in order to recommend profitable assets. It covers the data download and processing steps, the calculation of the features fed into the prediction model, and finally a simple model that predicts the profitability of assets to rank them. 

Before we get started, we need to confgure where we are to store the dataset and models produced. If you are running this notebook locally, then any folder on your machine should be fine. If you are working within a container, you may need to change the directory to a mounted writable file system.


```python
# Local Mode
storageDIR = "dataset" # creates a dataset directory in the same folder as the notebook

# Container Mode
#storageDIR = "/tmp/dataset" # creates a dataset directory in the /tmp/ directory for the container
```

## Dataset

Different types of financial asset recommendation system use different types of data to prodice their recommendations. This approach is known as Profitability Prediction, where assets that are predicted to gain significant value over the following year are recommended. This type of approach uses past pricing data, i.e. the price for different assets over time to identify pricing trends and hence future profitable assets. Hence, as input we need a dataset that for a range of assets contains their price history over time.

For illustration, in this notebook we will use an open dataset, although you may wish to swap this for data from your market of choice. The dataset used here is the Huge Stock Market Dataset compiled by Boris Marjanovic, and is publically available <b><a href='https://www.kaggle.com/borismarjanovic/price-volume-data-for-all-us-stocks-etfs'>here</a>.</b>

The dataset comprises historical price and volume data for all US-based stocks and ETFs trading on the NYSE, NASDAQ, and NYSE markets, and runs up to the last quarter of 2017. For each financial asset (stock or ETF), it contains a series of price entries describing the market price of that asset on different days. Each entry is comprised of:
 - Date: The date of the pricing data 
 - Open: Opening price for that day
 - High: The maximum price for that day
 - Low: The minimum price for that day
 - Close: The closing price for that day
 - Volume: The amount of the asset that is traded 
 - OpenInt: The total number of outstanding contracts held by market participants

## Downloading the Dataset

This dataset can be downloaded through the <b><a href='https://github.com/Kaggle/kaggle-api'>Kaggle API</a></b>. Users should make an account on the Kaggle website and download an API token in order to access this dataset on their local machine. This is available from the Account section of the Kaggle user profile.

To use the Kaggle API, we first install the package through pip. We then export the username and the API key from the aforementioned token, and use the download command to fetch and unzip the dataset. For our current experiment we will use only the set of Stocks.

(The below commands can also be entered directly into your terminal, without the ! prefix.)


```python
import os
os.environ["KAGGLE_USERNAME"] = "yourKaggleUsername"
os.environ["KAGGLE_KEY"] = "yourKaggleAPIKey"

!pip install kaggle
from kaggle.api.kaggle_api_extended import KaggleApi
api = KaggleApi()
api.authenticate()
api.dataset_download_files('borismarjanovic/price-volume-data-for-all-us-stocks-etfs', path=storageDIR)
print("Download Complete")
```

    Requirement already satisfied: kaggle in c:\programdata\anaconda3\lib\site-packages (1.5.12)
    Requirement already satisfied: certifi in c:\programdata\anaconda3\lib\site-packages (from kaggle) (2019.11.28)
    Requirement already satisfied: urllib3 in c:\programdata\anaconda3\lib\site-packages (from kaggle) (1.25.8)
    Requirement already satisfied: requests in c:\programdata\anaconda3\lib\site-packages (from kaggle) (2.22.0)
    Requirement already satisfied: tqdm in c:\programdata\anaconda3\lib\site-packages (from kaggle) (4.42.1)
    Requirement already satisfied: python-slugify in c:\programdata\anaconda3\lib\site-packages (from kaggle) (5.0.2)
    Requirement already satisfied: python-dateutil in c:\programdata\anaconda3\lib\site-packages (from kaggle) (2.8.1)
    Requirement already satisfied: six>=1.10 in c:\programdata\anaconda3\lib\site-packages (from kaggle) (1.14.0)
    Requirement already satisfied: idna<2.9,>=2.5 in c:\programdata\anaconda3\lib\site-packages (from requests->kaggle) (2.8)
    Requirement already satisfied: chardet<3.1.0,>=3.0.2 in c:\programdata\anaconda3\lib\site-packages (from requests->kaggle) (3.0.4)
    Requirement already satisfied: text-unidecode>=1.3 in c:\programdata\anaconda3\lib\site-packages (from python-slugify->kaggle) (1.3)
    Download Complete


## Extracting and Loading the Dataset

After downloading the dataset we need to extract the files and then convert it into a Pandas DataFrame, which is somewhat like a large data table that makes the raw data easier to analyse. 


```python
import zipfile
import pandas as pd
import numpy as np
import glob, os, random, math

# Unzip the Dataset
with zipfile.ZipFile(storageDIR+"/price-volume-data-for-all-us-stocks-etfs.zip", 'r') as zip_ref:
    zip_ref.extractall(storageDIR)

# Replace this with your dataset path
path = storageDIR+'/Stocks/' 
all_files = glob.glob(os.path.join(path, "*.us.txt"))
dfs = []

# Iterating through files and only using non-empty files
for f in all_files:
    if os.path.getsize(f) > 0:
        df = pd.read_csv(f) 
        df['Stock'] = f.split('/')[-1].split('.')[0]
        dfs.append(df)
full_kaggle_df = pd.concat(dfs)

print("Dataset Extraction and Loading as Dataframe Complete")
```

    Dataset Extraction and Loading as Dataframe Complete


## Filtering the Dataset

Pandas allows us to perform manipulations on the pricing data so that we can extract only what we need for training the model. For the purpose of our illustration, we will only use pricing data from 2016 and 2017, where we consider 2016 as the 'past' and hence we can use that period for learning what assets are likely to be profitable, and 2017 as the future (which we will use to test how well our model performs later). In effect, for our experiment here we can consider an the date of asset recommendation to be the 1st of January 2017. 

Lets first filter the dataset to only hold data from the dates we care about:


```python
# We only require the date, stock name, and closing price for that day
full_kaggle_df = full_kaggle_df[['Date', 'Close', 'Stock']]
full_kaggle_df['Date'] = pd.to_datetime(full_kaggle_df['Date'])
full_kaggle_df['year'] = full_kaggle_df['Date'].dt.year
full_kaggle_df['month'] = full_kaggle_df['Date'].dt.month
full_kaggle_df['day'] = full_kaggle_df['Date'].dt.day

# Selecting only that data from either 2016 or 2017
full_kaggle_df = full_kaggle_df[(full_kaggle_df['year'] >= 2016)].drop(columns=['year', 'month', 'day'])
full_kaggle_df['Date'] = full_kaggle_df['Date'].dt.strftime('%Y-%m-%d')

print("Dataset Filtering Complete")
```

## Weekly Price Change Calculation

Now that we have our data, we need to convert it into a form that makes it easier to work with. In this case, we can convert pricing data over time into weekly price changes for each asset. This makes the data more closely match what we want as output, while also enabling data normalization over the daily price points (which can help avoid outliers and in cases where particular days are missing in the data). In particular, we find the closing price for each stock as per the Monday of each week. We calculate the weekly return of each stock after aggregating close prices weekly. This is defined as the percentage change in the weekly prices and is represented as follows:

\begin{equation}
WeeklyChange = \frac{Current \ closing \ price \ - \ Preceding \ closing \ price}{Preceding \ closing \ price}
\end{equation}

Note that this step can take some time...



```python
stocks = full_kaggle_df['Stock'].unique().tolist()
pricedfs = []
for s in stocks:
    df = full_kaggle_df[full_kaggle_df['Stock'] == s]
    df['Date'] = pd.to_datetime(df['Date']) - pd.to_timedelta(7, unit='d')
    
    #Grouping close prices weekly
    df = df.groupby(['Stock', pd.Grouper(key='Date', freq='W-MON')])['Close'].nth(-1).reset_index().sort_values('Date')

    #Calculating percentage change in asset prices as returns
    df['returns'] = df['Close'].pct_change()
    df['returns'] = df['returns'].replace([np.inf, -np.inf], np.nan)
    df['returns'] = df['returns'].fillna(df['returns'].mean())
    
    df = df.iloc[1:]
    pricedfs.append(df)
    
print("Dataset Price Changes Extracted")
```

    C:\ProgramData\Anaconda3\lib\site-packages\ipykernel_launcher.py:5: SettingWithCopyWarning: 
    A value is trying to be set on a copy of a slice from a DataFrame.
    Try using .loc[row_indexer,col_indexer] = value instead
    
    See the caveats in the documentation: https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy
      """


Below we show a sample DataFrame with weekly returns calculated.


```python
pricedfs[0].head()
```




<div>
<style scoped>
    .dataframe tbody tr th:only-of-type {
        vertical-align: middle;
    }

    .dataframe tbody tr th {
        vertical-align: top;
    }

    .dataframe thead th {
        text-align: right;
    }
</style>
<table border="1" class="dataframe">
  <thead>
    <tr style="text-align: right;">
      <th></th>
      <th>Stock</th>
      <th>Date</th>
      <th>Close</th>
      <th>returns</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <th>1</th>
      <td>Stocks\a</td>
      <td>2016-01-04</td>
      <td>37.278</td>
      <td>-0.067560</td>
    </tr>
    <tr>
      <th>2</th>
      <td>Stocks\a</td>
      <td>2016-01-11</td>
      <td>36.463</td>
      <td>-0.021863</td>
    </tr>
    <tr>
      <th>3</th>
      <td>Stocks\a</td>
      <td>2016-01-18</td>
      <td>36.952</td>
      <td>0.013411</td>
    </tr>
    <tr>
      <th>4</th>
      <td>Stocks\a</td>
      <td>2016-01-25</td>
      <td>37.031</td>
      <td>0.002138</td>
    </tr>
    <tr>
      <th>5</th>
      <td>Stocks\a</td>
      <td>2016-02-01</td>
      <td>34.193</td>
      <td>-0.076638</td>
    </tr>
  </tbody>
</table>
</div>



We then filter the dataframes with 2016 pricing data for training purposes and those with 9 months of 2017 pricing data for testing, and take a random sample of 1000 assets from the filtered subset to speed up the experiment.


```python
# Filtering those dataframes with 2016 pricing data for training
newdfs_training = []

for d in pricedfs:
    d['Date'] = pd.to_datetime(d['Date'])
    d['year'] = d['Date'].dt.year
    d['month'] = d['Date'].dt.month
    d['day'] = d['Date'].dt.day

    d = d[(d['year'] == 2016)].drop(columns=['year', 'month', 'day'])
    d['Date'] = d['Date'].dt.strftime('%Y-%m-%d')
    if d.shape[0] >= 52:
        newdfs_training.append(d)
        
#Randomly sampling assets
random.seed(20)
newdfs_training = random.sample(newdfs_training, 1000)

# ==============================================================
# Filtering those dataframes with 2017 pricing data for testing

newdfs_test = []

for d in pricedfs:
    d['Date'] = pd.to_datetime(d['Date'])
    d['year'] = d['Date'].dt.year
    d['month'] = d['Date'].dt.month
    d['day'] = d['Date'].dt.day

    d = d[(d['year'] == 2017) & ((d['month'] != 10) & (d['month'] != 11) & 
                                 (d['month'] != 12))].drop(columns=['year', 'month', 'day'])
    d['Date'] = d['Date'].dt.strftime('%Y-%m-%d')
    if d.shape[0] >= 39:
        newdfs_test.append(d)
        
#Randomly sampling assets
random.seed(20)
newdfs_test = random.sample(newdfs_test, 1000)

print("Dataset Divided into Training and Test Sets")
```

## Feature Creation for the Model

Now that we have the pricing data in a more useful form, we can now convert that data into additional indicators that a machine learned model can use for identifying patterns/trends. In effect, we want to capture how the price for an asset changed in the recent past, for use as indicators for future performance (of course past performance is not always a good indicator, and more advanced approaches may mix in other sources of evidence here). We convert the pricing data into three main indicator (feature) types:

<ul>
    <li> Average closing price </li>
    <li> Return on Investment </li>
    <li> Volatility </li>
</ul>

These are calculated over the last 3, 6, and 9 months from the point in time at which recommendations are requested.

Returns are calculated as follows over each specified period:

\begin{equation}
Return On Investment = \frac{Final \ closing \ price \ - \ Initial \ closing \ price}{Initial \ closing \ price}
\end{equation}

Volatility represent the risk of a stock as expressed by its fluctuations, and is expressed as the standard deviation of the returns of the stock. As our returns are calculated weekly, we also ensure our volatilities are converted into 3-month, 6-month, and 9-month estimates.

\begin{equation}
Volatility = \sqrt{\frac{1}{N-1} \sum_{i=1}^N (Return_i - \overline{Return})^2} * \sqrt{Number \ of \ weeks}
\end{equation}



```python

def calculate_features(newdfs):
    dfs = []
    for d in newdfs:
        d['Date'] = pd.to_datetime(d['Date'])
        df3m = d.set_index('Date').last('3M')
        d['avg_price_3m'] = df3m['Close'].mean()
        d['volatility_3m'] = df3m['returns'].std() * math.sqrt(13.0357)
        d['returns_3m'] = (df3m.tail(1)['Close'].values[0] - df3m.head(1)['Close'].values[0])/df3m.head(1)['Close'].values[0]

        df6m = d.set_index('Date').last('6M')
        d['avg_price_6m'] = df6m['Close'].mean()
        d['volatility_6m'] = df6m['returns'].std() * math.sqrt(26.0715)
        d['returns_6m'] = (df6m.tail(1)['Close'].values[0] - df6m.head(1)['Close'].values[0])/df6m.head(1)['Close'].values[0]

        df9m = d.set_index('Date').last('9M')
        d['avg_price_9m'] = df9m['Close'].mean()
        d['volatility_9m'] = df9m['returns'].std() * math.sqrt(39.1072)
        d['returns_9m'] = (df9m.tail(1)['Close'].values[0] - df9m.head(1)['Close'].values[0])/df9m.head(1)['Close'].values[0]

        d = d.head(1)
        dfs.append(d)

    all_feature_dfs = pd.concat(dfs)
    all_feature_dfs = all_feature_dfs.drop(columns=['Date', 'Close', 'returns'])
    all_feature_dfs.dropna(inplace=True)
    return all_feature_dfs


training_data = calculate_features(newdfs_training)

# Enter your training dataset path here
training_data.to_csv(storageDIR+'/Kaggle-2016-Training-Final.csv')

test_data = calculate_features(newdfs_test)

# Enter your test dataset path here
test_data.to_csv(storageDIR+'/Kaggle-2017-Test-Final.csv')

print("Dataset Pricing Trend Features Extracted")

```

    Dataset Pricing Trend Features Extracted


## Profitability prediction model - Random forest regression

After having curated our training and testing sets, we proceed to use a simple machine learning model to predict the 9-month profitability of assets from the test dataset. The model we choose is a Random Forest model. This is imported from the Scikit-Learn library, which is a commonly used package for introductory machine learning experiments and houses a wide variety of models. A random forest regression model is an ensemble model, which means that it combines the predictions of multiple machine learning models in order to improve prediction accuracy and avoid overfitting. A random forest model aggregates decision trees and takes the average of their predictions or probabilities for classes; decision trees are supervised models that attempt to infer simple decision rules from the features and use these to make predictions.

Random forests introduce randomness from two other sources, namely that each tree is built with a random sample from the training set, and that the splitting of nodes during tree construction is either found from all features or from a random subset of the maximum permitted number of features. These sources of randomness are intended to minimize the impact of each singular decision tree's sensitivity. 

Here, we use the training and test data we just prepared and apply a scaler to them so that their values are standardized for prediction. We then apply a random forest regressor model with 500 estimators (decision trees) to calculate predicted returns over a 9-month period. 


```python
from sklearn.ensemble import RandomForestRegressor
from sklearn.preprocessing import StandardScaler

# Using precomputed data
training_data = pd.read_csv(storageDIR+'/Kaggle-2016-Training-Final.csv')
test_data = pd.read_csv(storageDIR+'/Kaggle-2017-Test-Final.csv')

train_instrument_ids = training_data['Stock']
returns_9m = training_data['returns_9m']
training_data = training_data.drop(columns=['Stock', 'returns_9m'])

test_instrument_ids = test_data['Stock']
test_returns_9m = test_data['returns_9m']
test_vol_9m = test_data['volatility_9m']
test_data = test_data.drop(columns=['Stock', 'returns_9m'])

# Scaling data values
scaler = StandardScaler()
scaled_data = scaler.fit_transform(training_data)

scaler = StandardScaler()
scaled_test = scaler.fit_transform(test_data)

# Prediction
model = RandomForestRegressor(n_estimators=500)
model.fit(scaled_data, returns_9m)
pred = model.predict(scaled_test)

pred_df = pd.DataFrame(pred)
pred_df = pred_df.rename(columns={0: 'Predicted returns'})
pred_df['Stock'] = pd.Series(test_instrument_ids)
pred_df['Actual returns (9M)'] = test_returns_9m
pred_df['Actual Volatility (9M)'] = test_vol_9m
```

## Model Effectiveness Evaluation

After the prediction of returns, we proceed onto the evaluation of our model. We can do so by means of more classical metrics that assess the difference between actual and predicted values; we can also look at the aggregate profitability and volatility of the recommended assets over the test period as a measure of suitability. 


```python
pred_df = pred_df.sort_values(by='Predicted returns', ascending=False)
pred_df.head(10)
```




<div>
<style scoped>
    .dataframe tbody tr th:only-of-type {
        vertical-align: middle;
    }

    .dataframe tbody tr th {
        vertical-align: top;
    }

    .dataframe thead th {
        text-align: right;
    }
</style>
<table border="1" class="dataframe">
  <thead>
    <tr style="text-align: right;">
      <th></th>
      <th>Predicted returns</th>
      <th>Stock</th>
      <th>Actual returns (9M)</th>
      <th>Actual Volatility (9M)</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <th>298</th>
      <td>2.704236</td>
      <td>Stocks\zgnx</td>
      <td>2.050980</td>
      <td>1.940495</td>
    </tr>
    <tr>
      <th>224</th>
      <td>2.287230</td>
      <td>Stocks\abeo</td>
      <td>1.933884</td>
      <td>0.860671</td>
    </tr>
    <tr>
      <th>506</th>
      <td>2.263269</td>
      <td>Stocks\arwr</td>
      <td>1.354497</td>
      <td>0.631877</td>
    </tr>
    <tr>
      <th>429</th>
      <td>2.251566</td>
      <td>Stocks\sppi</td>
      <td>2.081023</td>
      <td>0.620670</td>
    </tr>
    <tr>
      <th>398</th>
      <td>2.226679</td>
      <td>Stocks\wprt</td>
      <td>1.488189</td>
      <td>0.726520</td>
    </tr>
    <tr>
      <th>402</th>
      <td>2.224329</td>
      <td>Stocks\cyrx</td>
      <td>1.966049</td>
      <td>0.636090</td>
    </tr>
    <tr>
      <th>150</th>
      <td>2.199163</td>
      <td>Stocks\ensv</td>
      <td>0.037037</td>
      <td>0.849338</td>
    </tr>
    <tr>
      <th>705</th>
      <td>2.107315</td>
      <td>Stocks\xxii</td>
      <td>2.140152</td>
      <td>0.738352</td>
    </tr>
    <tr>
      <th>772</th>
      <td>2.102616</td>
      <td>Stocks\qure</td>
      <td>0.761017</td>
      <td>0.604679</td>
    </tr>
    <tr>
      <th>513</th>
      <td>2.093745</td>
      <td>Stocks\sify</td>
      <td>0.942658</td>
      <td>0.496918</td>
    </tr>
  </tbody>
</table>
</div>



In the above table, we are showing the top 10 stocks that were predicted to be profitable. The last two columns report the actual return on investment after 9 months and asset volitility. Note that a return value of 1.5 means a 150% return on investment. 

We notice that predicted returns for the top stocks are exceedingly high, which is not ordinary. However, we can also see that the actual returns for these stocks are similar for several of these instances, i.e. the model is not wrong in predicting these as profitable investments in the short term. However, we can also see that the volitility fo these stocks is very high, i.e. these are 'high-risk' assets that may subsequently crash in price. 

We can also analyse the statistics for this predictions across the dataset.


```python
pred_df.describe()
```




<div>
<style scoped>
    .dataframe tbody tr th:only-of-type {
        vertical-align: middle;
    }

    .dataframe tbody tr th {
        vertical-align: top;
    }

    .dataframe thead th {
        text-align: right;
    }
</style>
<table border="1" class="dataframe">
  <thead>
    <tr style="text-align: right;">
      <th></th>
      <th>Predicted returns</th>
      <th>Actual returns (9M)</th>
      <th>Actual Volatility (9M)</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <th>count</th>
      <td>1000.000000</td>
      <td>1000.000000</td>
      <td>1000.000000</td>
    </tr>
    <tr>
      <th>mean</th>
      <td>0.291667</td>
      <td>0.117710</td>
      <td>0.279499</td>
    </tr>
    <tr>
      <th>std</th>
      <td>0.545248</td>
      <td>0.440275</td>
      <td>0.270453</td>
    </tr>
    <tr>
      <th>min</th>
      <td>-0.858322</td>
      <td>-0.998589</td>
      <td>0.008209</td>
    </tr>
    <tr>
      <th>25%</th>
      <td>0.019777</td>
      <td>-0.027597</td>
      <td>0.108639</td>
    </tr>
    <tr>
      <th>50%</th>
      <td>0.199883</td>
      <td>0.073051</td>
      <td>0.210807</td>
    </tr>
    <tr>
      <th>75%</th>
      <td>0.463422</td>
      <td>0.214930</td>
      <td>0.364167</td>
    </tr>
    <tr>
      <th>max</th>
      <td>2.704236</td>
      <td>7.232558</td>
      <td>3.707981</td>
    </tr>
  </tbody>
</table>
</div>



Here we are able to see the average returns and volatility of the entire test set. 

We can also examine the predicted returns and volatility over the top 1, 5, 10, and 20 stocks, ranked by their predicted profitability. This will serve to provide an idea of how financially feasible our recommendations are, and whether the recommended stocks are an improvement to the average returns of the test set.


```python

ret_vol = pd.DataFrame({'Number of assets': [1, 5, 10, 20], 'Mean returns (9M), in %': [pred_df.head(1)['Actual returns (9M)'].mean(),
                        pred_df.head(5)['Actual returns (9M)'].mean(), 
                        pred_df.head(10)['Actual returns (9M)'].mean(),
                        pred_df.head(20)['Actual returns (9M)'].mean()], 
                        'Mean volatility (9M), in %': [pred_df.head(1)['Actual Volatility (9M)'].mean(), 
                                                pred_df.head(5)['Actual Volatility (9M)'].mean(),
                                                pred_df.head(10)['Actual Volatility (9M)'].mean(),
                                                pred_df.head(20)['Actual Volatility (9M)'].mean()]
                       })
ret_vol['Mean returns (9M), in %'] *= 100
ret_vol['Mean volatility (9M), in %'] *= 100

ret_vol
```




<div>
<style scoped>
    .dataframe tbody tr th:only-of-type {
        vertical-align: middle;
    }

    .dataframe tbody tr th {
        vertical-align: top;
    }

    .dataframe thead th {
        text-align: right;
    }
</style>
<table border="1" class="dataframe">
  <thead>
    <tr style="text-align: right;">
      <th></th>
      <th>Number of assets</th>
      <th>Mean returns (9M), in %</th>
      <th>Mean volatility (9M), in %</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <th>0</th>
      <td>1</td>
      <td>205.098039</td>
      <td>194.049514</td>
    </tr>
    <tr>
      <th>1</th>
      <td>5</td>
      <td>178.171489</td>
      <td>95.604667</td>
    </tr>
    <tr>
      <th>2</th>
      <td>10</td>
      <td>147.554883</td>
      <td>81.056093</td>
    </tr>
    <tr>
      <th>3</th>
      <td>20</td>
      <td>123.460761</td>
      <td>77.108316</td>
    </tr>
  </tbody>
</table>
</div>



The returns and volatility for the top stocks, ranked by predicted returns, are far higher than their averages across the test set. This indicates that ranking assets by their predicted returns can produce some highly profitable but risk-laden investment recommendations, which might be suitable for aggressive investors. However, it remains to be seen how much of this is owed to fluctuations and outliers in the data, and perhaps even if there are better ways to capture the returns and volatility of the dataset.

Next, we look at the differences between the actual and predicted returns.


```python
import matplotlib.pyplot as plt
import seaborn as sns

plt.figure(figsize=(10,10))
sns.regplot(pred_df['Actual returns (9M)'], pred_df['Predicted returns'])
plt.show()
```


```python
plt.figure(figsize=(10,10))
plt.plot(pred_df['Actual returns (9M)'] - pred_df['Predicted returns'] ,marker='o', linestyle='', c='crimson')
plt.xlabel('Observations', fontsize=15)
plt.ylabel('Difference between true and predicted returns', fontsize=15)
plt.show()
```

Lastly, we can examine the mean absolute error and mean squared error of the predictions. As these can be quite dependent on the dataset and problem in question, we also assume a simple baseline, by taking the median of all stock returns from the test dataset. We then compare the results of applying these metrics to the baseline and our predictor model.


```python
from sklearn.metrics import mean_absolute_error, mean_squared_error


testlist = pd.Series([test_returns_9m.median()]*1000)

mae_mse = pd.DataFrame({'Metric': ['MAE', 'MSE'], 
                        'Baseline (median return)': [mean_absolute_error(pred, testlist),
                                                     mean_squared_error(pred, testlist)], 
                       'Random forest regression': [mean_absolute_error(pred, test_returns_9m), 
                                                   mean_squared_error(pred, test_returns_9m)] 
                       })

mae_mse['Percentage difference'] = (mae_mse['Random forest regression'] - 
                                    mae_mse['Baseline (median return)'])/mae_mse['Baseline (median return)']


mae_mse
```




<div>
<style scoped>
    .dataframe tbody tr th:only-of-type {
        vertical-align: middle;
    }

    .dataframe tbody tr th {
        vertical-align: top;
    }

    .dataframe thead th {
        text-align: right;
    }
</style>
<table border="1" class="dataframe">
  <thead>
    <tr style="text-align: right;">
      <th></th>
      <th>Metric</th>
      <th>Baseline (median return)</th>
      <th>Random forest regression</th>
      <th>Percentage difference</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <th>0</th>
      <td>MAE</td>
      <td>0.394475</td>
      <td>0.259137</td>
      <td>-0.343083</td>
    </tr>
    <tr>
      <th>1</th>
      <td>MSE</td>
      <td>0.344792</td>
      <td>0.200128</td>
      <td>-0.419568</td>
    </tr>
  </tbody>
</table>
</div>



We can see from this that the random forest model presents an improvement (reduction) in both MAE and MSE.


```python

```
