diff --git a/.gitignore b/.gitignore index b254bd6..293b5a3 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,6 @@ __pycache__/ *.pyc quickstart.py creds.json +test/ +.vscode/ +listGoogle.py \ No newline at end of file diff --git a/ExpenseRatio.py b/ExpenseRatio.py index 3d14d05..f6d6df0 100644 --- a/ExpenseRatio.py +++ b/ExpenseRatio.py @@ -5,35 +5,24 @@ ''' Asks user for expense ratio of stock (I don't think there's an API for expense ratios) Runs corrrelation study (I'm not sure if I want another class for this or not) -''' +''' import numpy #import urllib2, re from urllib.request import urlopen import re +class ExpenseRatio: + def __init__(self): + + def main(): # For testing purposes - ''' + ''' a = [1,2,3] b = [2,4,6] c = numpy.corrcoef(a, b)[0, 1] print(c) - ''' - #http://finance.yahoo.com/q/pr?s=spy+profile - stockSymbols = [ "VDIGX", "VFIAX" ] - expenses = [ [ "Fund", "Most Recent Expense Ratio" ] ] - for stockSymbol in stockSymbols: - page = urlopen("http://finance.yahoo.com/q/pr?s=" + stockSymbol + "+profile" ) - data = str(page.read()) - row = re.findall("Annual Report Expense Ratio.*?", data) - if len(row) > 0: - ER = re.findall("(\d+\.\d+).*?", row[0] )[0] - expenses.append( [ stockSymbol, ER ] ) - else: - print(stockSymbol, "does not appear to be a fund with an expense ratio") - print("\n".join( i[0] + "," + i[1] for i in expenses)) - - + ''' if __name__ == "__main__": main() diff --git a/Functions.py b/Functions.py new file mode 100644 index 0000000..1ec3db4 --- /dev/null +++ b/Functions.py @@ -0,0 +1,24 @@ +# Python file for general functions +class Functions: + def getNearest(items, pivot): + return min(items, key=lambda x: abs(x - pivot)) + def stringToDate(date): + from datetime import datetime + + #datetime_object = datetime.strptime('Jun 1 2005 1:33PM', '%b %d %Y %I:%M%p') + datetime_object = datetime.strptime(date, '%Y-%m-%d').date() + return(datetime_object) + ''' + dateSplit = date.split('-') + year = int(dateSplit[0]) + month = int(dateSplit[1]) + day = int(dateSplit[2]) + datetime_object = datetime.date(year, month, day) + ''' + return datetime_object + +def main(): + exit() + +if __name__ == "__main__": + main() diff --git a/README.md b/README.md index d696909..eead752 100644 --- a/README.md +++ b/README.md @@ -4,9 +4,11 @@ A project to determine indicators of overperforming mutual funds. This project is written in Python and will examine market capitalization, persistence, turnover, and expense ratios. ### Prerequisites -```sh -$ pip install requests -$ pip install numpy -``` + +`$ pip install -r requirements.txt` + +or + +`$ pip install requests` Created by Andrew Dinh from Dr. TJ Owens Gilroy Early College Academy diff --git a/StockData.py b/StockData.py index 0d6cff7..e2a90a9 100644 --- a/StockData.py +++ b/StockData.py @@ -29,20 +29,19 @@ Daily Requests = 20,000 Symbol Requests = 500 ''' -import requests, json, socket -import importlib.util, sys # To check whether a package is installed +import requests, json from datetime import datetime -class Stock: +class StockData: - def __init__(self, newName = '', newFirstLastDates = [], newAbsFirstLastDates = [], newFinalDatesAndClose = [], newAllLists = []): + def __init__(self, newName = '', newAbsFirstLastDates = [], newFinalDatesAndClose = [], newFinalDatesAndClose2 = [],newAllLists = []): self.name = newName # Name of stock - self.firstLastDates = newFirstLastDates # Dates that at least 2 sources have (or should it be all?) - Maybe let user decide self.absFirstLastDates = newAbsFirstLastDates # Absolute first and last dates from all sources - self.finalDatesAndClose = newFinalDatesAndClose # All available dates + self.finalDatesAndClose = newFinalDatesAndClose # All available dates with corresponding close values + self.finalDatesAndClose2 = newFinalDatesAndClose2 # After some consideration, I decided to keep what I had already done here and make a new list that's the same except dates are in datetime format self.allLists = newAllLists ''' - Format: + Format: # List from each source containing: [firstDate, lastDate, allDates, values, timeFrame] # firstDate & lastDate = '2018-12-18' (year-month-date) allDates = ['2018-12-17', '2018-12-14'] (year-month-date) @@ -59,9 +58,18 @@ class Stock: def setName(self, newName): self.name = newName - - def getAllLists(self): + def returnName(self): + return self.name + def returnAllLists(self): return self.allLists + def returnAbsFirstLastDates(self): + return self.absFirstLastDates + def returnAllLists(self): + return self.allLists + def returnFinalDatesAndClose(self): + return self.finalDatesAndClose + def returnFinalDatesAndClose2(self): + return self.finalDatesAndClose2 def getIEX(self): url = ''.join(('https://api.iextrading.com/1.0/stock/', self.name, '/chart/5y')) @@ -133,9 +141,17 @@ class Stock: def getAV(self): listAV = [] - url = ''.join(('https://www.alphavantage.co/query?function=TIME_SERIES_MONTHLY&symbol=', self.name, '&apikey=', apiAV)) + #url = ''.join(('https://www.alphavantage.co/query?function=TIME_SERIES_MONTHLY&symbol=', self.name, '&apikey=', apiAV)) # https://www.alphavantage.co/query?function=TIME_SERIES_MONTHLY&symbol=MSFT&apikey=demo + + #url = ''.join(('https://www.alphavantage.co/query?function=TIME_SERIES_DAILY&symbol=', self.name, '&outputsize=full&apikey=', apiAV)) + # https://www.alphavantage.co/query?function=TIME_SERIES_DAILY&symbol=MSFT&outputsize=full&apikey=demo + + url = ''.join(('https://www.alphavantage.co/query?function=TIME_SERIES_DAILY_ADJUSTED&symbol=', self.name, '&outputsize=full&apikey=', apiAV)) + # https://www.alphavantage.co/query?function=TIME_SERIES_DAILY_ADJUSTED&symbol=MSFT&outputsize=full&apikey=demo + print("\nSending request to:", url) + print("(This will take a while)") f = requests.get(url) json_data = f.text loaded_json = json.loads(json_data) @@ -148,9 +164,9 @@ class Stock: return 'Not available' #print(loaded_json['Monthly Time Series']) - monthlyTimeSeries = loaded_json['Monthly Time Series'] + dailyTimeSeries = loaded_json['Time Series (Daily)'] #print(monthlyTimeSeries) - listOfDates = list(monthlyTimeSeries) + listOfDates = list(dailyTimeSeries) #print(listOfDates) firstDate = listOfDates[-1] @@ -171,8 +187,9 @@ class Stock: values = [] for i in range(0, len(listOfDates), 1): temp = listOfDates[i] - loaded_json2 = monthlyTimeSeries[temp] - value = loaded_json2['4. close'] + loaded_json2 = dailyTimeSeries[temp] + #value = loaded_json2['4. close'] + value = loaded_json2['5. adjusted close'] values.append(value) listAV.append(values) #print(listOfDates[0]) @@ -424,13 +441,31 @@ class Stock: # Want lists from most recent to oldest, comment this out if you don't want that finalDates = list(reversed(finalDates)) - finalClose = list(reversed(finalClose)) + finalClose = list(reversed(finalClose)) finalDatesAndClose.append(finalDates) finalDatesAndClose.append(finalClose) return finalDatesAndClose + def datetimeDates(self): + finalDatesAndClose2 = [] + finalDatesAndClose = self.finalDatesAndClose + finalDatesStrings = finalDatesAndClose[0] + finalClose = finalDatesAndClose[1] + finalDates = [] + + from Functions import Functions + for i in range(0, len(finalDatesStrings), 1): + temp = Functions.stringToDate(finalDatesStrings[i]) + finalDates.append(temp) + #print(finalDates) + + finalDatesAndClose2.append(finalDates) + finalDatesAndClose2.append(finalClose) + return(finalDatesAndClose2) + def is_connected(): + import socket # To check internet connection try: # connect to the host -- tells us if the host is actually # reachable @@ -442,6 +477,7 @@ class Stock: return False def main(self): + import importlib.util, sys # To check whether a package is installed packages = ['requests'] for i in range(0, len(packages), 1): @@ -451,16 +487,18 @@ class Stock: print(package_name +" is not installed\nPlease type in 'pip install -r requirements.txt' to install all required packages") # Test internet connection - internetConnection = Stock.is_connected() + internetConnection = StockData.is_connected() if internetConnection == False: return listOfFirstLastDates = [] self.allLists = [] + print('\nNOTE: Only IEX and Alpha Vantage support adjusted returns') + # IEX print("\nIEX") - listIEX = Stock.getIEX(self) + listIEX = StockData.getIEX(self) #print(listIEX) if listIEX != 'Not available': listOfFirstLastDates.append((listIEX[0], listIEX[1])) @@ -468,7 +506,7 @@ class Stock: # Alpha Vantage print("\nAlpha Vantage (AV)") - listAV = Stock.getAV(self) + listAV = StockData.getAV(self) #print(listAV) if listAV != 'Not available': listOfFirstLastDates.append((listAV[0], listAV[1])) @@ -477,38 +515,44 @@ class Stock: # COMMENTED OUT FOR NOW B/C LIMITED ''' print("\nTiingo") - listTiingo = Stock.getTiingo(self) + print("NOTE: Tiingo does not return adjusted returns!!") + listTiingo = StockData.getTiingo(self) #print(listTiingo) if listTiingo != 'Not available': listOfFirstLastDates.append((listTiingo[0], listTiingo[1])) self.allLists.append(listTiingo) ''' - + #print(self.allLists) #print(listOfFirstLastDates) if (len(self.allLists) > 0): print("\n") - print(len(self.allLists), "available sources for", self.name) - self.absFirstLastDates = Stock.getFirstLastDate(self, listOfFirstLastDates) + print(len(self.allLists), "available source(s) for", self.name) + self.absFirstLastDates = StockData.getFirstLastDate(self, listOfFirstLastDates) print("\nThe absolute first date with close values is:", self.absFirstLastDates[0]) print("The absolute last date with close values is:", self.absFirstLastDates[1]) print("\nCombining dates and averaging close values") - self.finalDatesAndClose = Stock.getFinalDatesAndClose(self) # Returns [List of Dates, List of Corresponding Close Values] + self.finalDatesAndClose = StockData.getFinalDatesAndClose(self) # Returns [List of Dates, List of Corresponding Close Values] #print("All dates available:", self.finalDatesAndClose[0]) #print("All close values:\n", self.finalDatesAndClose[1]) finalDates = self.finalDatesAndClose[0] finalClose = self.finalDatesAndClose[1] print(len(finalDates), "unique dates:", finalDates[len(finalDates)-1], "...", finalDates[0]) print(len(finalClose), "close values:", finalClose[len(finalClose)-1], "...", finalClose[0]) + + print("\nConverting list of final dates to datetime") + self.finalDatesAndClose2 = StockData.datetimeDates(self) + #print(self.finalDatesAndClose2[0][0]) + else: print("No sources have data for", self.name) def main(): # For testing purposes stockName = 'spy' - stock1 = Stock(stockName) + stock1 = StockData(stockName) print("Finding available dates and close values for", stock1.name) - Stock.main(stock1) + StockData.main(stock1) if __name__ == "__main__": main() diff --git a/StockReturn.py b/StockReturn.py new file mode 100644 index 0000000..44e36bb --- /dev/null +++ b/StockReturn.py @@ -0,0 +1,141 @@ +# ExpenseRatio.py +# Andrew Dinh +# Python 3.6.7 +# Description: +''' +Calculates return for each stock from the lists from ExpenseRatio.py +listOfReturn = [Unadjsted Return, Sharpe Ratio, Sortino Ratio, Treynor Ratio, Jensen's Alpha] +''' + +from StockData import StockData +import datetime +from Functions import Functions + +class Return: + def __init__(self, newListOfReturn = [], newTimeFrame = [], newBeta = 0, newStandardDeviation = 0, newNegativeStandardDeviation = 0, newMarketReturn = 0, newSize = 0, newSizeOfNeg = 0, newFirstLastDates = [], newAllLists = [], newAbsFirstLastDates = ''): + self.listOfReturn = newListOfReturn + self.timeFrame = newTimeFrame # [year, months (30 days)] + self.beta = newBeta + self.standardDeviation = newStandardDeviation + self.negativeStandardDeviation = newNegativeStandardDeviation + self.marketReturn = newMarketReturn + self.size = newSize + self.sizeOfNeg = newSizeOfNeg + self.firstLastDates = newFirstLastDates + + def getFirstLastDates(self, stock): + firstLastDates = [] + timeFrame = self.timeFrame + firstDate = datetime.datetime.now() - datetime.timedelta(days=timeFrame[0]*365) + firstDate = firstDate - datetime.timedelta(days=timeFrame[1]*30) + firstDate = ''.join((str(firstDate.year),'-', str(firstDate.month), '-', str(firstDate.day))) + + lastDate = StockData.returnAbsFirstLastDates(stock)[1] + #print(lastDate) + firstLastDates.append(firstDate) + firstLastDates.append(lastDate) + return firstLastDates + + def getFirstLastDates2(self, stock): + finalDatesAndClose = StockData.returnFinalDatesAndClose(stock) + finalDatesAndClose2 = StockData.returnFinalDatesAndClose2(stock) + firstDate = self.firstLastDates[0] + lastDate = self.firstLastDates[1] + finalDates = finalDatesAndClose[0] + + firstDateExists = False + lastDateExists = False + for i in range(0, len(finalDates), 1): + if finalDates[i] == str(firstDate): + firstDateExists = True + elif finalDates[i] == lastDate: + lastDateExists = True + i = len(finalDates) + + if firstDateExists == False: + print("Could not find first date. Changing first date to closest date") + tempDate = Functions.stringToDate(firstDate) # Change to datetime + print('Original first date:', tempDate) + #tempDate = datetime.date(2014,1,17) + newFirstDate = Functions.getNearest(finalDatesAndClose2[0], tempDate) + print('New first date:', newFirstDate) + firstDate = str(newFirstDate) + + if lastDateExists == False: + print("Could not find final date. Changing final date to closest date") + tempDate2 = Functions.stringToDate(lastDate) # Change to datetime + print('Original final date:', tempDate2) + #tempDate2 = datetime.date(2014,1,17) + newLastDate = Functions.getNearest(finalDatesAndClose2[0], tempDate2) + print('New final date:', newLastDate) + lastDate = str(newLastDate) + + firstLastDates = [] + firstLastDates.append(firstDate) + firstLastDates.append(lastDate) + return firstLastDates + + def getUnadjustedReturn(self, stock): + finalDatesAndClose = StockData.returnFinalDatesAndClose(stock) + finalDatesAndClose2 = StockData.returnFinalDatesAndClose2(stock) + firstDate = self.firstLastDates[0] + lastDate = self.firstLastDates[1] + finalDates = finalDatesAndClose[0] + finalClose = finalDatesAndClose[1] + + for i in range(0, len(finalDates), 1): + if finalDates[i] == str(firstDate): + firstClose = finalClose[i] + elif finalDates[i] == lastDate: + lastClose = finalClose[i] + i = len(finalDates) + + print('Close values:', firstClose, '...', lastClose) + unadjustedReturn = float(lastClose/firstClose) + unadjustedReturn = unadjustedReturn * 100 + return unadjustedReturn + +# def getBeta(self, timeFrame): + +# def getStandardDeviation(self, timeFrame): + + def main(self, stock): + # Find date to start from and last date + self.timeFrame = [] + self.listOfReturn = [] + + print("\nPlease enter a time frame in years: ", end='') + #timeFrameYear = int(input()) + timeFrameYear = 5 + print(timeFrameYear) + self.timeFrame.append(timeFrameYear) + print("Please enter a time frame in months (30 days): ", end='') + #timeFrameMonth = int(input()) + timeFrameMonth = 0 + print(timeFrameMonth) + self.timeFrame.append(timeFrameMonth) + #print(self.timeFrame) + self.firstLastDates = Return.getFirstLastDates(self, stock) + print('Dates: ', self.firstLastDates) + + print('\nMaking sure dates are within list...') + self.firstLastDates = Return.getFirstLastDates2(self, stock) + print('New dates: ', self.firstLastDates) + + print('\nGetting unadjusted return') + unadjustedReturn = Return.getUnadjustedReturn(self, stock) + self.listOfReturn.append(unadjustedReturn) + print(self.listOfReturn[0]) + print(self.listOfReturn[0]/timeFrameYear, '%') + +def main(): + stockName = 'spy' + stock1 = StockData(stockName) + print("Finding available dates and close values for", stock1.name) + StockData.main(stock1) + + stock1Return = Return() + Return.main(stock1Return, stock1) + +if __name__ == "__main__": + main() diff --git a/main.py b/main.py index dc8fe5e..ccf3692 100644 --- a/main.py +++ b/main.py @@ -1,7 +1,7 @@ # main.py # Andrew Dinh # Python 3.6.1 -# Description: +# Description: ''' Asks users for mutual funds/stocks to compare Asks to be compared (expense ratio, turnover, market capitalization, or persistence) @@ -15,7 +15,7 @@ Gives correlation value using equation at the end (from 0 to 1) FIRST TESTING WITH EXPENSE RATIO ''' -from StockData import Stock +from StockData import StockData listOfStocks = [] numberOfStocks = int(input("How many stocks or mutual funds would you like to analyze? ")) @@ -23,48 +23,54 @@ for i in range(0, numberOfStocks, 1): print("Stock", i+1, ": ", end='') stockName = str(input()) listOfStocks.append(i) - listOfStocks[i] = Stock() + listOfStocks[i] = StockData() listOfStocks[i].setName(stockName) #print(listOfStocks[i].name) sumOfListLengths = 0 for i in range(0, numberOfStocks, 1): print(listOfStocks[i].name) - Stock.main(listOfStocks[i]) + StockData.main(listOfStocks[i]) # Count how many stocks are available - temp = Stock.getAllLists(listOfStocks[i]) + temp = StockData.returnAllLists(listOfStocks[i]) sumOfListLengths = sumOfListLengths + len(temp) if sumOfListLengths == 0: - print("No sources have stock data for given stocks") + print("No sources have data for given stocks") + exit() -else: - #print(listOfStocks[0].name, listOfStocks[0].absFirstLastDates, listOfStocks[0].finalDatesAndClose) - indicatorFound = False - while indicatorFound == False: - print("\n1. Expense Ratio\n2. Asset Size\n3. Turnover\n4. Persistence\nWhich indicator would you like to look at? ", end='') - indicator = str(input()) - indicatorFound = True +# Find return over time using either Jensen's Alpha, Sharpe Ratio, Sortino Ratio, or Treynor Ratio +#from StockReturn import Return - if indicator == 'Expense Ratio' or indicator == '1' or indicator == 'expense ratio': - print('\nExpense Ratio') - elif indicator == 'Asset Size' or indicator == '2' or indicator == 'asset size': - print('\nAsset Size') +# Runs correlation or regression study +#print(listOfStocks[0].name, listOfStocks[0].absFirstLastDates, listOfStocks[0].finalDatesAndClose) +indicatorFound = False +while indicatorFound == False: + print("\n1. Expense Ratio\n2. Asset Size\n3. Turnover\n4. Persistence\nWhich indicator would you like to look at? ", end='') + indicator = str(input()) + indicatorFound = True - elif indicator == 'Turnover' or indicator == '3' or indicator == 'turnover': - print('\nTurnover') + if indicator == 'Expense Ratio' or indicator == '1' or indicator == 'expense ratio': + #from ExpenseRatio import ExpenseRatio + print('\nExpense Ratio') - elif indicator == 'Persistence' or indicator == '4' or indicator == 'persistence': - print('\nPersistence') + elif indicator == 'Asset Size' or indicator == '2' or indicator == 'asset size': + print('\nAsset Size') - else: - indicatorFound = False - print('\nInvalid input, please enter indicator again') + elif indicator == 'Turnover' or indicator == '3' or indicator == 'turnover': + print('\nTurnover') + + elif indicator == 'Persistence' or indicator == '4' or indicator == 'persistence': + print('\nPersistence') + + else: + indicatorFound = False + print('\nInvalid input, please enter indicator again') ''' stockName = 'IWV' stock1 = Stock(stockName) print("Finding available dates and close values for", stock1.name) -Stock.main(stock1) -''' \ No newline at end of file +StockData.main(stock1) +'''