├── BotData.db ├── DayLog.csv ├── FindTrendingStocks.py ├── README.md ├── TradeBot.py ├── algo.py ├── config.py └── trading.py /BotData.db: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kevc528/TradingBot/ef914a4e06f601f377e0b4e61b3432d6d69de869/BotData.db -------------------------------------------------------------------------------- /DayLog.csv: -------------------------------------------------------------------------------- 1 | symbol,time,price,upperBand,lowerBand,bandWidth,rsi,upperSlope,realSlope,lowerSlope,upperI,realI,lowerI,action 2 | -------------------------------------------------------------------------------- /FindTrendingStocks.py: -------------------------------------------------------------------------------- 1 | from bs4 import BeautifulSoup 2 | import requests 3 | 4 | # scrapes for stocks off of yahoo finance trending 5 | # def findTrendingStocks(): 6 | 7 | # url = 'https://finance.yahoo.com/trending-tickers' 8 | # resp = requests.get(url) 9 | # html = resp.content 10 | # soup = BeautifulSoup(html, 'html.parser') 11 | # tr_tags = soup.find_all('tr') 12 | # td_tags = [tag.find('td') for tag in tr_tags] 13 | 14 | # symbols = [tag.find('a').text for tag in td_tags if tag != None and '=' not in tag.find('a').text and '^' not in tag.find('a').text] 15 | # print('run scrape') 16 | # fObj = open('watchlist.csv', 'w') 17 | # for s in symbols: 18 | # fObj.write(s + '\n') 19 | # fObj.close() 20 | 21 | # scrapes for stocks off of high momentum charts 22 | # def findTrendingStocks(): 23 | 24 | # url = 'https://tradingstockalerts.com/PremiumAlerts/Momentum' 25 | # resp = requests.get(url) 26 | # html = resp.content 27 | # soup = BeautifulSoup(html, 'html.parser') 28 | # table = soup.find('table', {'id' : 'TABLE_1'}) 29 | # tr_tags = table.find_all('tr')[1:] 30 | # td_tags = [tag.find_all('td')[1] for tag in tr_tags] 31 | 32 | # symbols = [tag.text.strip() for tag in td_tags if tag != None and '=' not in tag.text.strip() and '^' not in tag.text.strip()] 33 | # print('run scrape') 34 | # fObj = open('watchlist.csv', 'w') 35 | # for s in symbols: 36 | # fObj.write(s + '\n') 37 | # fObj.close() 38 | 39 | # scrapes most popular stocks from financial content 40 | def findTrendingStocks(): 41 | 42 | symbols = [] 43 | 44 | def processPage(page): 45 | url = page 46 | resp = requests.get(url) 47 | html = resp.content 48 | soup = BeautifulSoup(html, 'html.parser') 49 | symbolCol = soup.find_all('td', {'class' : 'last col_symbol'}) 50 | return [tag.text.strip() for tag in symbolCol] 51 | 52 | symbols += processPage('http://markets.financialcontent.com/stocks/stocks/dashboard/mostactive') 53 | symbols += processPage('http://markets.financialcontent.com/stocks/stocks/dashboard/mostactive?CurrentPage=1') 54 | symbols += processPage('http://markets.financialcontent.com/stocks/stocks/dashboard/mostactive?CurrentPage=2') 55 | print('run scrape') 56 | fObj = open('watchlist.csv', 'w') 57 | for s in symbols: 58 | fObj.write(s + '\n') 59 | fObj.close() 60 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # TradingBot 2 | Stock trading bot using Alpaca API 3 | 4 | Data gathered from the bot will be stored in the database, BotData.db 5 | 6 | ### Setup 7 | To setup the bot, you will have to obtain API keys from the Alpaca API 8 | 9 | Once the keys are obtained, go into config.py and change the API keys 10 | ~~~~ 11 | API_KEY = 'your-api-key' 12 | SECRET_KEY = 'your-secret-key' 13 | ~~~~~~~~ 14 | 15 | ### Running Commands 16 | Navigate to the directory and run the command 17 | ~~~~ 18 | python TradeBot.py 19 | ~~~~~~~~ 20 | -------------------------------------------------------------------------------- /TradeBot.py: -------------------------------------------------------------------------------- 1 | from trading import * 2 | from algo import * 3 | from FindTrendingStocks import findTrendingStocks 4 | from datetime import datetime 5 | from datetime import date 6 | import pytz 7 | import time 8 | import csv 9 | import sqlite3 10 | import pandas as pd 11 | 12 | # prepare the trading bot by getting the portfolio, and getting the watchlist 13 | initPortfolio() 14 | findTrendingStocks() 15 | getWatchList() 16 | 17 | tz_NY = pytz.timezone('America/New_York') 18 | 19 | now = datetime.now(tz_NY) 20 | start = now.replace(hour=9, minute=30, second=0, microsecond=0) 21 | 22 | # checks time for stock market opening and runs the cycle function continuously 23 | while start <= now and (datetime.now(tz_NY).hour) < 16: 24 | cycle() 25 | 26 | # stores day's data into a SQL database 27 | con = sqlite3.connect('BotData.db') 28 | df = pd.read_csv('DayLog.csv') 29 | df.to_sql(str(date.today()), con, if_exists = 'append', index = False) 30 | 31 | # resets files for the next day 32 | fObj = open('watchlist.csv', 'w') 33 | fObj.close() 34 | 35 | fObj = open('DayLog.csv', 'w') 36 | fObj.write('symbol,time,price,upperBand,lowerBand,bandWidth,rsi,upperSlope,\ 37 | realSlope,lowerSlope,upperI,realI,lowerI,action') 38 | fObj.close() 39 | -------------------------------------------------------------------------------- /algo.py: -------------------------------------------------------------------------------- 1 | import pandas as pd 2 | import numpy as np 3 | import matplotlib.pyplot as plt 4 | import math 5 | from pandas_datareader import data as web 6 | from datetime import datetime, timedelta 7 | from FindTrendingStocks import findTrendingStocks 8 | import datetime as dt 9 | from sklearn.linear_model import LinearRegression 10 | 11 | # portfolio stored as a dict for stock to number of stocks 12 | portfolio = {} 13 | 14 | # obtain the portfolio of stocks currently holding 15 | def initPortfolio(): 16 | try: 17 | fObj = open('portfolio.csv') 18 | for line in fObj: 19 | if line != '': 20 | lineSplit = line.strip().split(',') 21 | portfolio[lineSplit[0]] = lineSplit[1] 22 | fObj.close() 23 | except OSError: 24 | fObj = open('portfolio.csv', 'w') 25 | fObj.close() 26 | 27 | # max num of stocks in portfolio 28 | MAX_SIZE = 30 29 | 30 | # returns up, low, width of 20 day bollinger bands 31 | def find20BBBounds(symbol): 32 | df = web.DataReader(symbol, data_source = 'yahoo', 33 | start = datetime.now() - timedelta(days = 40), end = datetime.now()) 34 | df['20 Day MA'] = df['Adj Close'].rolling(window = 20).mean() 35 | df['20 Day STD'] = df['Adj Close'].rolling(window = 20).std().apply( 36 | lambda x: x * (math.sqrt(19) / math.sqrt(20))) 37 | df['Upper Band'] = df['20 Day MA'] + 2 * df['20 Day STD'] 38 | df['Lower Band'] = df['20 Day MA'] - 2 * df['20 Day STD'] 39 | return (df.iloc[-1]['Upper Band'], df.iloc[-1]['Lower Band'], 40 | (df.iloc[-1]['Upper Band'] - df.iloc[-1]['Lower Band']) / df.iloc[-1]['20 Day MA']) 41 | 42 | # returns up, low, width of 10 day bollinger bands 43 | def find10BBBounds(symbol): 44 | df = web.DataReader(symbol, data_source = 'yahoo', 45 | start = datetime.now() - timedelta(days = 40), end = datetime.now()) 46 | df['10 Day MA'] = df['Adj Close'].rolling(window = 10).mean() 47 | df['10 Day STD'] = df['Adj Close'].rolling(window = 10).std().apply( 48 | lambda x: x * (math.sqrt(9) / math.sqrt(10))) 49 | df['Upper Band'] = df['10 Day MA'] + 1.5 * df['10 Day STD'] 50 | df['Lower Band'] = df['10 Day MA'] - 1.5 * df['10 Day STD'] 51 | return (df.iloc[-1]['Upper Band'], df.iloc[-1]['Lower Band'], 52 | (df.iloc[-1]['Upper Band'] - df.iloc[-1]['Lower Band']) / df.iloc[-1]['10 Day MA']) 53 | 54 | # uses linear regression to find the best fit lines for bands and price 55 | def findEquations(symbol): 56 | df = web.DataReader(symbol, data_source = 'yahoo', 57 | start = datetime.now() - timedelta(days = 90), end = datetime.now()) 58 | df['20 Day MA'] = df['Adj Close'].rolling(window = 20).mean() 59 | df['20 Day STD'] = df['Adj Close'].rolling(window = 20).std().apply( 60 | lambda x: x * (math.sqrt(19) / math.sqrt(20))) 61 | df['Upper Band'] = df['20 Day MA'] + 2 * df['20 Day STD'] 62 | df['Lower Band'] = df['20 Day MA'] - 2 * df['20 Day STD'] 63 | df = df.reset_index() 64 | df['Date'] = pd.to_datetime(df['Date']) 65 | df['Date']= df['Date'].map(dt.datetime.toordinal) 66 | df['Date'] = df['Date'] - df.iloc[-5]['Date'] 67 | prevChart = df.iloc[-5:] 68 | 69 | BBModel = LinearRegression() 70 | normalizedX = prevChart['Date'].values.reshape(-1, 1) 71 | normalizedY1 = prevChart['Upper Band'].values.reshape(-1, 1) 72 | BBModel.fit(normalizedX, normalizedY1) 73 | 74 | RTModel = LinearRegression() 75 | normalizedY2 = prevChart['Adj Close'].values.reshape(-1, 1) 76 | RTModel.fit(normalizedX, normalizedY2) 77 | 78 | BBModel2 = LinearRegression() 79 | normalizedY3 = prevChart['Lower Band'].values.reshape(-1, 1) 80 | BBModel2.fit(normalizedX, normalizedY3) 81 | 82 | return (BBModel.coef_[0][0], RTModel.coef_[0][0], BBModel2.coef_[0][0], 83 | BBModel.intercept_[0], RTModel.intercept_[0], BBModel2.intercept_[0]) 84 | 85 | # plotting bounds using 10 day bollinger bands method, for testing 86 | def plotBBBounds10(symbol): 87 | df = web.DataReader(symbol, data_source = 'yahoo', 88 | start = datetime.now() - timedelta(days = 100), end = datetime.now()) 89 | df['10 Day MA'] = df['Adj Close'].rolling(window = 10).mean() 90 | df['10 Day STD'] = df['Adj Close'].rolling(window = 10).std().apply( 91 | lambda x: x * (math.sqrt(9) / math.sqrt(10))) 92 | df['Upper Band'] = df['10 Day MA'] + 1.5 * df['10 Day STD'] 93 | df['Lower Band'] = df['10 Day MA'] - 1.5 * df['10 Day STD'] 94 | df[['Adj Close', '10 Day MA', 'Upper Band', 'Lower Band']].plot(figsize=(12,6)) 95 | 96 | plt.style.use('fivethirtyeight') 97 | fig = plt.figure(figsize=(12,6)) 98 | ax = fig.add_subplot(111) 99 | x_axis = df.index.get_level_values(0) 100 | 101 | ax.fill_between(x_axis, df['Upper Band'], df['Lower Band'], color='grey') 102 | 103 | ax.plot(x_axis, df['Adj Close'], color='blue', lw=2) 104 | ax.plot(x_axis, df['10 Day MA'], color='black', lw=2) 105 | 106 | ax.set_title('10 Day Bollinger Band For ' + symbol) 107 | ax.set_xlabel('Date (Year/Month)') 108 | ax.set_ylabel('Price(USD)') 109 | 110 | ax.legend() 111 | plt.show() 112 | 113 | # plotting bounds using 20 day bollinger bands method, for testing 114 | def plotBBBounds20(symbol): 115 | df = web.DataReader(symbol, data_source = 'yahoo', 116 | start = datetime.now() - timedelta(days = 200), end = datetime.now()) 117 | df['20 Day MA'] = df['Adj Close'].rolling(window = 20).mean() 118 | df['20 Day STD'] = df['Adj Close'].rolling(window = 20).std().apply( 119 | lambda x: x * (math.sqrt(19) / math.sqrt(20))) 120 | df['Upper Band'] = df['20 Day MA'] + 2 * df['20 Day STD'] 121 | df['Lower Band'] = df['20 Day MA'] - 2 * df['20 Day STD'] 122 | df[['Adj Close', '20 Day MA', 'Upper Band', 'Lower Band']].plot(figsize=(12,6)) 123 | 124 | plt.style.use('fivethirtyeight') 125 | fig = plt.figure(figsize=(12,6)) 126 | ax = fig.add_subplot(111) 127 | x_axis = df.index.get_level_values(0) 128 | 129 | ax.fill_between(x_axis, df['Upper Band'], df['Lower Band'], color='grey') 130 | 131 | ax.plot(x_axis, df['Adj Close'], color='blue', lw=2) 132 | ax.plot(x_axis, df['20 Day MA'], color='black', lw=2) 133 | 134 | ax.set_title('20 Day Bollinger Band For ' + symbol) 135 | ax.set_xlabel('Date (Year/Month)') 136 | ax.set_ylabel('Price(USD)') 137 | 138 | # need to add legend 139 | ax.legend() 140 | plt.show() 141 | 142 | # finds the RSI for a stock 143 | def findRSI(symbol): 144 | df = web.DataReader(symbol, data_source = 'yahoo', 145 | start = datetime.now() - timedelta(days = 30), end = datetime.now()) 146 | df['Gain'] = df['Adj Close'] - df['Open'] 147 | current = df.iloc[-1] 148 | df = df.iloc[-15:-1] 149 | dfGain = df[df['Gain'] >= 0] 150 | dfLose = df[df['Gain'] < 0] 151 | avgUp = dfGain['Gain'].sum()/14 152 | avgDown = dfLose['Gain'].sum()/14 * -1 153 | currGain = 0 154 | currLoss = 0 155 | if current['Gain'] > 0: 156 | currGain = current['Gain'] 157 | else: 158 | currLoss = abs(current['Gain']) 159 | avgUp = (avgUp * 13 + currGain)/14 160 | avgDown = (avgDown * 13 + currLoss)/14 161 | RS = avgUp/avgDown 162 | RSI = 100 - 100/(RS + 1) 163 | return RSI 164 | 165 | # gets current price of stock, a little bit behind real time 166 | def getPrice(symbol): 167 | price = web.DataReader(symbol, data_source = 'yahoo', 168 | start = datetime.now() - timedelta(days = 5), end = datetime.now()) 169 | price = price.iloc[-1]['Close'] 170 | return price 171 | 172 | # decides what to do with the stock: buy, sell or hold 173 | def decide(symbol): 174 | fObj = open('DayLog.csv', 'a') 175 | upperBand, lowerBand, bandWidth = find10BBBounds(symbol) 176 | x, y, bandWidth = find20BBBounds(symbol) 177 | rsi = findRSI(symbol) 178 | price = web.DataReader(symbol, data_source = 'yahoo', 179 | start = datetime.now() - timedelta(days = 5), end = datetime.now()) 180 | price = price.iloc[-1]['Close'] 181 | upperSlope, realSlope, lowerSlope, upperI, realI, lowerI = findEquations(symbol) 182 | nextDayUp = upperSlope + upperI 183 | nextDayReal = realSlope + realI 184 | nextDayLow = lowerSlope + lowerI 185 | 186 | if len(portfolio) >= MAX_SIZE: 187 | action = 'portfolio size large' 188 | elif (abs((upperSlope - realSlope)/upperSlope) < 0.2 and upperSlope > 0 and 189 | abs((upperI - realI)/upperI) < 0.02 and rsi > 20 and rsi < 60): 190 | print('Upward Slope Buy ' + symbol) 191 | action = 'buy' 192 | elif ((realSlope - lowerSlope)/lowerSlope) < 0.2 and abs((lowerI - realI)/lowerI) < 0.02: 193 | print('Downward Slope Sell ' + symbol) 194 | action = 'sell' 195 | elif price < lowerBand and rsi < 35 and (nextDayReal - nextDayLow)/abs(nextDayLow) > 0.01: 196 | print('Low relative price buy ' + symbol) 197 | action = 'buy' 198 | elif price > upperBand and rsi > 70 and abs((nextDayUp - nextDayReal)/nextDayUp) > 0.01: 199 | print('High relative price sell ' + symbol) 200 | action = 'sell' 201 | elif bandWidth < 0.05 and rsi < 35 and rsi > 20: 202 | print('Expecting surge buy ' + symbol) 203 | action = 'buy' 204 | elif bandWidth < 0.05 and rsi > 70: 205 | print('Expecting drop sell ' + symbol) 206 | action = 'sell' 207 | elif bandWidth > 0.3 and rsi > 70: 208 | print('Surge dying down sell ' + symbol) 209 | action = 'sell' 210 | else: 211 | action = 'hold' 212 | if symbol in portfolio and action == 'buy': 213 | action = 'already in portfolio' 214 | if symbol not in portfolio and action == 'sell': 215 | action = 'not in portfolio' 216 | line = '\n' + symbol + ',' + str(datetime.now()) + ',' + str(price) + ',' + str(upperBand) + ',' + \ 217 | str(lowerBand) + ',' + str(bandWidth) + ',' + str(rsi) + ',' + str(upperSlope) + ',' + \ 218 | str(realSlope) + ',' + str(lowerSlope) + ',' + str(upperI) + ',' + str(realI) +',' + str(lowerI) + \ 219 | ',' + str(action) 220 | fObj.write(line) 221 | fObj.close() 222 | return action -------------------------------------------------------------------------------- /config.py: -------------------------------------------------------------------------------- 1 | API_KEY = '' 2 | SECRET_KEY = '' -------------------------------------------------------------------------------- /trading.py: -------------------------------------------------------------------------------- 1 | import pandas_datareader.data as web 2 | import pandas as pd 3 | from FindTrendingStocks import findTrendingStocks 4 | from algo import decide 5 | from datetime import datetime 6 | import alpaca_trade_api as tradeapi 7 | import requests 8 | import json 9 | from config import * 10 | from algo import * 11 | import os 12 | import time 13 | import csv 14 | from pandas_datareader._utils import RemoteDataError 15 | 16 | # alpaca api information, used for requests 17 | BASE_URL = 'https://paper-api.alpaca.markets' 18 | ACCOUNT_URL = '{}/v2/account'.format(BASE_URL) 19 | ORDERS_URL = '{}/v2/orders'.format(BASE_URL) 20 | HEADERS = {'APCA-API-KEY-ID' : API_KEY, 'APCA-API-SECRET-KEY' : SECRET_KEY} 21 | 22 | # returns json of information on the account, like equity, buying power, etc. 23 | def getAccountInfo(): 24 | r = requests.get(ACCOUNT_URL, headers = HEADERS) 25 | return json.loads(r.content) 26 | 27 | # creates an order for either buy or sell 28 | def create_order(symbol, qty, side, type, time_in_force): 29 | data = { 30 | 'symbol' : symbol, 31 | 'qty' : qty, 32 | 'side' : side, 33 | 'type' : type, 34 | 'time_in_force' : time_in_force 35 | } 36 | try: 37 | r = requests.post(ORDERS_URL, json = data, headers = HEADERS) 38 | response = json.loads(r.content) 39 | return json.loads(r.content) 40 | except NameError as e: 41 | print(e) 42 | 43 | # get stocks on the watchlist and portfolio and save them in a set to determine 44 | # which stocks to watch 45 | def getWatchList(): 46 | trending = [] 47 | fObj = open('watchlist.csv') 48 | for line in fObj: 49 | if line != '': 50 | trending.append(line.strip()) 51 | watchlist = list(portfolio.keys()) + trending 52 | watchlist = set(watchlist) 53 | return watchlist 54 | 55 | # updates the portfolio.csv file after transactions 56 | def updatePortfolio(): 57 | fObj = open('portfolio.csv', 'w') 58 | for key in portfolio: 59 | fObj.write(str(key) + ',' + str(portfolio[key]) + '\n') 60 | fObj.close() 61 | 62 | # cycle function that will repeat (loop), gets stock data and decides what to do 63 | def cycle(): 64 | watchlist = getWatchList() 65 | for symbol in watchlist: 66 | try: 67 | move = decide(symbol) 68 | if move == 'sell': 69 | create_order(symbol, portfolio[symbol], 'sell', 'market', 'gtc') 70 | print('Sold ' + symbol + ' at ' + str(getPrice(symbol))) 71 | del portfolio[symbol] 72 | updatePortfolio() 73 | elif move == 'buy': 74 | if float(getAccountInfo()['buying_power']) < 3000: 75 | continue; 76 | create_order(symbol, 3000 // getPrice(symbol), 'buy', 'market', 'gtc') 77 | print('Bought ' + symbol + ' at ' + str(getPrice(symbol))) 78 | portfolio[symbol] = 3000 // getPrice(symbol) 79 | updatePortfolio() 80 | else: 81 | continue; 82 | except RemoteDataError as e: 83 | print(e) 84 | except KeyError as a: 85 | print(a) 86 | --------------------------------------------------------------------------------