Trading by Prediction#

import numpy as np
import pandas as pd
from sklearn.preprocessing import LabelEncoder
import joblib
import matplotlib.pyplot as plt

# load our modules
import sys
sys.path.append("../../")
import lstm
import profit

Load Data#

We again consider the Tesla stock of the latest 90 days.

stocks: pd.DataFrame = pd.read_csv("../../data/stocks.csv", index_col=0, parse_dates=True)
company = "TSLA"
stock = stocks.query(f"Company == '{company}'").drop(columns=["Company", "Sector"])
num_days = 90
stock = stock[-num_days:]
stock
Open High Low Close Volume
Date
2022-06-27 249.366669 252.070007 242.566666 244.919998 89178300
2022-06-28 244.483337 249.970001 232.343338 232.663330 90391200
2022-06-29 230.500000 231.173340 222.273331 228.490005 82897200
2022-06-30 224.509995 229.456665 218.863327 224.473328 94600500
2022-07-01 227.000000 230.229996 222.119995 227.263336 74460300
... ... ... ... ... ...
2022-10-26 219.399994 230.600006 218.199997 224.639999 85012500
2022-10-27 229.770004 233.809998 222.850006 225.089996 61638800
2022-10-28 225.399994 228.860001 216.350006 228.520004 69152400
2022-10-31 226.190002 229.850006 221.940002 227.539993 61554300
2022-11-01 234.050003 237.399994 227.279999 227.820007 62566500

90 rows × 5 columns

Load Trained Model#

Recall that to use our PricePredictor, we need a feature encoder.

# set up feature encoder for the price predictor
feature_encoder = LabelEncoder()
feature_encoder.fit(lstm.ALL_FEATURES)
LabelEncoder()

Pass the encoder to the class attribute:

lstm.PricePredictor.feature_encoder = feature_encoder

Convert Pandas DataFrame to NumPy array:

# select the features of interest in data frame and transform it to NumPy array
stock = stock[lstm.ALL_FEATURES]
ts = stock[feature_encoder.classes_].to_numpy()

Load our trained model with joblib module:

Note

In fact, Scikit-Learn recommends to use joblib to load the models instead of pickle module.

price_predictor = joblib.load("../../models/TSLA-3-day-predictor.pkl")
price_predictor
PricePredictor(features=['Open', 'Volume', 'Close'], hidden_size=16,
               num_days_ago=400, pred_len=3, seq_len=30)

Information About the Stock Price of Future 1, 2 or 3 Days#

In this notebook, we choose to refer the predicted closing price the day after tomorrow. (num_days_ahead equals 2.)

num_days_ahead = 2

For each day, what the model does is that it takes into consideration all past days before the current day and predict the closing price N days from now. (N equals num_days_ahead. Since the model we have loaded can predict at most 3 days ahead, N can only be 1, 2 or 3.)

# use price predictor to predict future prices
sliding_prices = price_predictor.predict(ts)
future_prices = sliding_prices[1:, num_days_ahead-1]

# compare current and future closing prices
stock = stock.copy()[["Close"]]
stock["Future"] = future_prices

stock
Close Future
Date
2022-06-27 244.919998 245.929047
2022-06-28 232.663330 243.220215
2022-06-29 228.490005 237.126190
2022-06-30 224.473328 231.133057
2022-07-01 227.263336 229.120163
... ... ...
2022-10-26 224.639999 219.918671
2022-10-27 225.089996 223.878922
2022-10-28 228.520004 226.528946
2022-10-31 227.539993 227.581192
2022-11-01 227.820007 229.530670

90 rows × 2 columns

Strategy#

The strategy is rather straight forward.

  • If the future price drops, and we are holding some shares, then we must sell all of it.

  • If the future price rises, and we don’t have any shares, then it is a good time to buy.

# Find buying and selling dates
buy_dates = []
sell_dates = []
is_holding_shares = False
for date, row in stock.iterrows():
    if is_holding_shares:
        # price will go down, sell it!
        if row["Future"] < row["Close"]:
            sell_dates.append(date)
            is_holding_shares = False
    else:
        # price will go up, buy!
        if row["Future"] > row["Close"]:
            buy_dates.append(date)
            is_holding_shares = True

# convert to Pandas `DatetimeIndex`
buy_dates = pd.DatetimeIndex(buy_dates)
sell_dates = pd.DatetimeIndex(sell_dates)

buy_dates, sell_dates
(DatetimeIndex(['2022-06-27', '2022-07-11', '2022-07-18', '2022-07-20',
                '2022-07-25', '2022-08-01', '2022-08-11', '2022-08-17',
                '2022-08-26', '2022-09-02', '2022-09-13', '2022-09-20',
                '2022-09-29', '2022-10-05', '2022-10-07', '2022-10-14',
                '2022-10-20', '2022-10-24', '2022-10-31'],
               dtype='datetime64[ns]', freq=None),
 DatetimeIndex(['2022-07-05', '2022-07-14', '2022-07-19', '2022-07-21',
                '2022-07-29', '2022-08-10', '2022-08-12', '2022-08-23',
                '2022-09-01', '2022-09-06', '2022-09-19', '2022-09-26',
                '2022-10-04', '2022-10-06', '2022-10-12', '2022-10-17',
                '2022-10-21', '2022-10-25'],
               dtype='datetime64[ns]', freq=None))

We now visualize the buying and selling dates:

# plot the last 90 days
plt.figure(figsize=(12, 4))
df = stock.copy()
df["Buy"] = np.nan
df.loc[buy_dates, "Buy"] = df.loc[buy_dates, "Close"]
df["Sell"] = np.nan
df.loc[sell_dates, "Sell"] = df.loc[sell_dates, "Close"]
plt.plot(df["Close"], label="Stock Price", color="k", alpha=0.5)
plt.scatter(
    df.index, df["Buy"],
    marker="^", color="r", zorder=2,
    label="Buy"
)
plt.scatter(
    df.index, df["Sell"],
    marker="v", color="g", zorder=2,
    label="Sell"
)
plt.legend()
plt.grid()
plt.show()
../_images/trading-by-prediction_24_0.png

As we can see, this strategy suggests to trade more often than that of the moving average approach. And, it tends to seize every opportunity to increase the profit.

Profit#

Use the calc_profit function from our profit module to calculate the profit rate:

profit_rate = profit.calc_profit(
    stock, 
    buy_dates, sell_dates, 
    start_date="2022-06-27"
)

print(f"Profit rate: {100 * profit_rate:.2f}%")
Profit rate: 17.05%

Recall the profit rate with moving average strategy is 9.19%. Hence, the strategy proposed in this section is somewhat better.