Objective: Write a script to autonomously buy and sell stocks depending on historical stock data in order to fund future projects
Goals:
Low latency
Low risk (through portfolio diversification)
Exception handling and self correction
The objective of the main script is to call all of the accompanying scripts as needed.
Main Script Pseudo Code
buyList = [] #list of stocks to buy on the next trading day
buyWeights = [] #theoretical change in price based on the model
sellList = [] #list of stocks to sell on the next trading day
ownedList = [] #list of stocks currently owned
while True:
if weekend:
train the model on new stocks
elif weekday and trading hours:
execute appropriate trades
else:
use the model to determine the change in price for each trained stock
Before trading stocks with actual money, the algorithmic trading bot must first be verified using backtesting. Backtesting is the idea of recording trades on a spreadsheet instead of actually executing real orders. In this case, the backtesting script records data to a spreadsheet. It records not only the trades, but also the imaginary account's balance and any anomalies (such as the script attempting to purchase stocks after hours or overdrawing the account).
In this case, Robinhood is used for trades, and therefore a script had to be written to interface with the broker.
Both the backtesting and robinhood scripts were written with the same function names and operations. Therefore, as soon as backtesting is done, it is simple to change one line of code in the main script (importing the robinhood module instead of the backtest module) to go live.
Backtest and Robinhood Script Pseudo Code
def getOwned(tickerList):
return list of stocks that are in tickerList as well as the portfolio - this allows stocks not handled by the portfolio to be in the script
def getChange():
return the difference in portfolio value between yesterday and today
def getPending():
return a boolean indicating if there are any pending orders that have not executed
def balance():
return liquid balance in account
def buy(ticker, quantity):
purchase stock
if stock purchase does not go through in 5 seconds, cancel the order and throw an exception
def sell(ticker, quantity):
purchase stock
if stock purchase does not go through in 5 seconds, cancel the order and throw an exception
This is the script that trains an LSTM model based on historical stock data. This utilizes a module contained in Tensorflow called Keras for the machine learning and the Alpha Vantage API for getting historical stock data. The most challenging part was tuning the hyperparameters to ensure that the model did not overtrain with the training set. In order to safeguard against overfitting, there is a validation set that is separated from the historical data at the beginning of the script. This way, when the model is trained, it has no access to "future data".
Machine Learning Script Pseudo Code
def getData(ticker):
return historical open, high, low, close, and volume data in a pandas dataframe
def train(tickerList):
train the model for each stock and save a python pickle file
def predict(tickerList):
open appropriate pickle file and predict the change in price based on previously trained model
return a pandas dataframe with each stock in ascending order of predicted change in price
Once a list of stocks to buy are identified, the number of each stock to buy must be determined to optimize profit and minimize risk. This is a much more challenging question than it seems, but can be solved using Constrained Mixed-Integer Linear Programming. The system is constrained in the sense that the linear combination of the stock prices and stock quantities cannot exceed the account balance (that is, the dot product between the stock price and quantity must be less than or equal to the liquid cash in the account). Furthermore, no one stock should account for more than 10% of the portfolio (which can also be stated as the price of the stock times the quantity of that stock divided by the total value of the portfolio should be less than 0.1). Finally, the linear combination of the change in stock price and quantity of each stock must be optimized (that is, the dot product of the quantity of each stock and change in price of each stock should be maximized).
Optimization Script Pseudo Code
def optimize(currentPrice, predChange, accountBalance):
m = GEKKO() #GEKKO is a python optimization library, and m is an object
m.Equation(constraint 1)
m.Equation(constraint 2)
...
m.Equation(constraint n)
m.Obj(objective) #this is the function to maximize
m. solve()
return m.x #x is a vector containing the optimized quantity of each stock
In case something were to go wrong, this module allows any other script to send an email. This allows the user to manually make changes using the Robinhood app if needed and/or terminate the script.
Message Script Pseudo Code
def sendMsg(message):
prepare message object
prepare server
send email
terminate server