Added support for persistence indicator

Changed from years to months
Added function to scrape Yahoo Finance for indicator data
Moved generic functions to Functions.py
Added function to scrape websites for stocks
Attempted to alleviate problem of async function
This commit is contained in:
Andrew Dinh 2019-03-01 11:06:09 -08:00
parent 531c41862a
commit 6366453f63
4 changed files with 609 additions and 185 deletions

3
.gitignore vendored
View File

@ -1,5 +1,6 @@
__pycache__/ __pycache__/
test/ test/
.vscode/ .vscode/
requests_cache.sqlite *.sqlite
README.html README.html
*stocks.txt

View File

@ -1,6 +1,5 @@
# Python file for general functions # Python file for general functions
def getNearest(items, pivot): def getNearest(items, pivot):
return min(items, key=lambda x: abs(x - pivot)) return min(items, key=lambda x: abs(x - pivot))
@ -58,6 +57,77 @@ def fromCache(r):
return return
def getJoke():
import requests
import requests_cache
with requests_cache.disabled():
'''
f = requests.get('https://official-joke-api.appspot.com/jokes/random').json()
print('')
print(f['setup'])
print(f['punchline'], end='\n\n')
'''
headers = {'Accept': 'application/json',
'User-Agent': 'fund-indicators (https://github.com/andrewkdinh/fund-indicators)'}
f = requests.get('https://icanhazdadjoke.com/', headers=headers).json()
print('')
print(f['joke'])
def hasNumbers(inputString):
return any(char.isdigit() for char in inputString)
def checkPackages(listOfPackages):
import importlib.util
import sys
packagesInstalled = True
packages = listOfPackages
for i in range(0, len(packages), 1):
package_name = packages[i]
spec = importlib.util.find_spec(package_name)
if spec is None:
print(
package_name +
" is not installed\nPlease enter 'pip install -r requirements.txt' to install all required packages")
packagesInstalled = False
return packagesInstalled
def checkPythonVersion():
import platform
#print('Checking Python version')
i = platform.python_version()
r = i.split('.')
k = float(''.join((r[0], '.', r[1])))
if k < 3.3:
print('Your Python version is', i,
'\nIt needs to be greater than version 3.3')
return False
else:
print('Your Python version of', i, 'is good')
return True
def isConnected():
import socket # To check internet connection
try:
# connect to the host -- tells us if the host is actually reachable
socket.create_connection(("www.andrewkdinh.com", 80))
print('Internet connection is good')
return True
except OSError:
# pass
print("No internet connection!")
return False
def fileExists(file):
import os.path
return os.path.exists(file)
def main(): def main():
exit() exit()

678
main.py
View File

@ -4,22 +4,26 @@
# Python 3.6.7 # Python 3.6.7
# Required # Required
from concurrent.futures import ThreadPoolExecutor as PoolExecutor from bs4 import BeautifulSoup
import requests import requests
import json import json
import datetime import datetime
import Functions import Functions
import numpy as np import numpy as np
import re
import os.path
# Required for linear regression # Required for linear regression
import matplotlib.pyplot as plt import matplotlib.pyplot as plt
import sys import sys
# Optional # Optional
from concurrent.futures import ThreadPoolExecutor as PoolExecutor
import time
import random
import requests_cache import requests_cache
requests_cache.install_cache( requests_cache.install_cache(
'requests_cache', backend='sqlite', expire_after=43200) # 12 hours 'cache', backend='sqlite', expire_after=43200) # 12 hours
# API Keys # API Keys
apiAV = 'O42ICUV58EIZZQMU' apiAV = 'O42ICUV58EIZZQMU'
@ -59,19 +63,20 @@ API Keys:
class Stock: class Stock:
# GLOBAL VARIABLES # GLOBAL VARIABLES
timeFrame = 0 timeFrame = 0 # Months
riskFreeRate = 0 riskFreeRate = 0
indicator = '' indicator = ''
# BENCHMARK VALUES # BENCHMARK VALUES
benchmarkDates = [] benchmarkDates = []
benchmarkCloseValues = [] benchmarkCloseValues = []
benchmarkAverageAnnualReturn = 0 benchmarkAverageMonthlyReturn = 0
benchmarkStandardDeviation = 0 benchmarkStandardDeviation = 0
# INDICATOR VALUES # INDICATOR VALUES
indicatorCorrelation = [] indicatorCorrelation = []
indicatorRegression = [] indicatorRegression = []
persTimeFrame = 0
def __init__(self): def __init__(self):
# BASIC DATA # BASIC DATA
@ -84,8 +89,8 @@ class Stock:
self.closeValuesMatchBenchmark = [] self.closeValuesMatchBenchmark = []
# CALCULATED RETURN # CALCULATED RETURN
self.averageAnnualReturn = 0 self.averageMonthlyReturn = 0
self.annualReturn = [] self.monthlyReturn = []
self.sharpe = 0 self.sharpe = 0
self.sortino = 0 self.sortino = 0
self.treynor = 0 self.treynor = 0
@ -161,7 +166,7 @@ class Stock:
json_data = f.text json_data = f.text
loaded_json = json.loads(json_data) loaded_json = json.loads(json_data)
if len(loaded_json) == 1 or f.status_code != 200: if len(loaded_json) == 1 or f.status_code != 200 or len(loaded_json) == 0:
print("Alpha Vantage not available") print("Alpha Vantage not available")
return 'Not available' return 'Not available'
@ -268,6 +273,15 @@ class Stock:
allDates[j] = Functions.stringToDate(allDates[j]) allDates[j] = Functions.stringToDate(allDates[j])
datesAndCloseList[0] = allDates datesAndCloseList[0] = allDates
# Determine if close value list has value of zero
# AKA https://www.alphavantage.co/query?function=TIME_SERIES_DAILY_ADJUSTED&symbol=RGN&outputsize=full&apikey=O42ICUV58EIZZQMU
for i in datesAndCloseList[1]:
if i == 0:
print('Found close value of 0. This is likely something like ticker RGN (Daily Time Series with Splits and Dividend Events)')
print('Removing', self.name,
'from list of stocks to ensure compability later')
return 'Not available'
return datesAndCloseList return datesAndCloseList
def datesAndCloseFitTimeFrame(self): def datesAndCloseFitTimeFrame(self):
@ -280,8 +294,8 @@ class Stock:
closeValues.append(self.allCloseValues[i]) closeValues.append(self.allCloseValues[i])
firstDate = datetime.datetime.now().date() - datetime.timedelta( firstDate = datetime.datetime.now().date() - datetime.timedelta(
days=self.timeFrame*365) days=self.timeFrame*30)
print('\n', self.timeFrame, ' years ago: ', firstDate, sep='') print('\n', self.timeFrame, ' months ago: ', firstDate, sep='')
closestDate = Functions.getNearest(dates, firstDate) closestDate = Functions.getNearest(dates, firstDate)
if closestDate != firstDate: if closestDate != firstDate:
print('Closest date available for', self.name, ':', closestDate) print('Closest date available for', self.name, ':', closestDate)
@ -306,23 +320,23 @@ class Stock:
return datesAndCloseList2 return datesAndCloseList2
def calcAverageAnnualReturn(self): # pylint: disable=E0202 def calcAverageMonthlyReturn(self): # pylint: disable=E0202
# averageAnnualReturn = (float(self.closeValues[len(self.closeValues)-1]/self.closeValues[0])**(1/(self.timeFrame)))-1 # averageMonthlyReturn = (float(self.closeValues[len(self.closeValues)-1]/self.closeValues[0])**(1/(self.timeFrame)))-1
# averageAnnualReturn = averageAnnualReturn * 100 # averageMonthlyReturn = averageMonthlyReturn * 100
averageAnnualReturn = sum(self.annualReturn)/self.timeFrame averageMonthlyReturn = sum(self.monthlyReturn)/self.timeFrame
print('Average annual return:', averageAnnualReturn) print('Average monthly return:', averageMonthlyReturn)
return averageAnnualReturn return averageMonthlyReturn
def calcAnnualReturn(self): def calcMonthlyReturn(self):
annualReturn = [] monthlyReturn = []
# Calculate annual return in order from oldest to newest # Calculate monthly return in order from oldest to newest
annualReturn = [] monthlyReturn = []
for i in range(0, self.timeFrame, 1): for i in range(0, self.timeFrame, 1):
firstDate = datetime.datetime.now().date() - datetime.timedelta( firstDate = datetime.datetime.now().date() - datetime.timedelta(
days=(self.timeFrame-i)*365) days=(self.timeFrame-i)*30)
secondDate = datetime.datetime.now().date() - datetime.timedelta( secondDate = datetime.datetime.now().date() - datetime.timedelta(
days=(self.timeFrame-i-1)*365) days=(self.timeFrame-i-1)*30)
# Find closest dates to firstDate and lastDate # Find closest dates to firstDate and lastDate
firstDate = Functions.getNearest(self.dates, firstDate) firstDate = Functions.getNearest(self.dates, firstDate)
@ -333,7 +347,7 @@ class Stock:
'which is after the given time frame.') 'which is after the given time frame.')
return 'Not available' return 'Not available'
# Get corresponding close values and calculate annual return # Get corresponding close values and calculate monthly return
for i in range(0, len(self.dates), 1): for i in range(0, len(self.dates), 1):
if self.dates[i] == firstDate: if self.dates[i] == firstDate:
firstClose = self.closeValues[i] firstClose = self.closeValues[i]
@ -341,13 +355,12 @@ class Stock:
secondClose = self.closeValues[i] secondClose = self.closeValues[i]
break break
annualReturnTemp = (secondClose/firstClose)-1 monthlyReturnTemp = (secondClose/firstClose)-1
annualReturnTemp = annualReturnTemp * 100 monthlyReturnTemp = monthlyReturnTemp * 100
annualReturn.append(annualReturnTemp) monthlyReturn.append(monthlyReturnTemp)
print('Annual return over the past', # print('Monthly return over the past', self.timeFrame, 'months:', monthlyReturn)
self.timeFrame, 'years:', annualReturn) return monthlyReturn
return annualReturn
def calcCorrelation(self, closeList): def calcCorrelation(self, closeList):
correlation = np.corrcoef( correlation = np.corrcoef(
@ -357,32 +370,32 @@ class Stock:
def calcStandardDeviation(self): def calcStandardDeviation(self):
numberOfValues = self.timeFrame numberOfValues = self.timeFrame
mean = self.averageAnnualReturn mean = self.averageMonthlyReturn
standardDeviation = ( standardDeviation = (
(sum((self.annualReturn[x]-mean)**2 for x in range(0, numberOfValues, 1)))/(numberOfValues-1))**(1/2) (sum((self.monthlyReturn[x]-mean)**2 for x in range(0, numberOfValues, 1)))/(numberOfValues-1))**(1/2)
print('Standard Deviation:', standardDeviation) print('Standard Deviation:', standardDeviation)
return standardDeviation return standardDeviation
def calcDownsideDeviation(self): def calcDownsideDeviation(self):
numberOfValues = self.timeFrame numberOfValues = self.timeFrame
targetReturn = self.averageAnnualReturn targetReturn = self.averageMonthlyReturn
downsideDeviation = ( downsideDeviation = (
(sum(min(0, (self.annualReturn[x]-targetReturn))**2 for x in range(0, numberOfValues, 1)))/(numberOfValues-1))**(1/2) (sum(min(0, (self.monthlyReturn[x]-targetReturn))**2 for x in range(0, numberOfValues, 1)))/(numberOfValues-1))**(1/2)
print('Downside Deviation:', downsideDeviation) print('Downside Deviation:', downsideDeviation)
return downsideDeviation return downsideDeviation
def calcKurtosis(self): def calcKurtosis(self):
numberOfValues = self.timeFrame numberOfValues = self.timeFrame
mean = self.averageAnnualReturn mean = self.averageMonthlyReturn
kurtosis = (sum((self.annualReturn[x]-mean)**4 for x in range( kurtosis = (sum((self.monthlyReturn[x]-mean)**4 for x in range(
0, numberOfValues, 1)))/((numberOfValues-1)*(self.standardDeviation ** 4)) 0, numberOfValues, 1)))/((numberOfValues-1)*(self.standardDeviation ** 4))
print('Kurtosis:', kurtosis) print('Kurtosis:', kurtosis)
return kurtosis return kurtosis
def calcSkewness(self): def calcSkewness(self):
numberOfValues = self.timeFrame numberOfValues = self.timeFrame
mean = self.averageAnnualReturn mean = self.averageMonthlyReturn
skewness = (sum((self.annualReturn[x]-mean)**3 for x in range( skewness = (sum((self.monthlyReturn[x]-mean)**3 for x in range(
0, numberOfValues, 1)))/((numberOfValues-1)*(self.standardDeviation ** 3)) 0, numberOfValues, 1)))/((numberOfValues-1)*(self.standardDeviation ** 3))
print('Skewness:', skewness) print('Skewness:', skewness)
return skewness return skewness
@ -394,26 +407,26 @@ class Stock:
return beta return beta
def calcAlpha(self): def calcAlpha(self):
alpha = self.averageAnnualReturn - \ alpha = self.averageMonthlyReturn - \
(Stock.riskFreeRate+((Stock.benchmarkAverageAnnualReturn - (Stock.riskFreeRate+((Stock.benchmarkAverageMonthlyReturn -
Stock.riskFreeRate) * self.beta)) Stock.riskFreeRate) * self.beta))
print('Alpha:', alpha) print('Alpha:', alpha)
return alpha return alpha
def calcSharpe(self): def calcSharpe(self):
sharpe = (self.averageAnnualReturn - Stock.riskFreeRate) / \ sharpe = (self.averageMonthlyReturn - Stock.riskFreeRate) / \
self.standardDeviation self.standardDeviation
print('Sharpe Ratio:', sharpe) print('Sharpe Ratio:', sharpe)
return sharpe return sharpe
def calcSortino(self): def calcSortino(self):
sortino = (self.averageAnnualReturn - self.riskFreeRate) / \ sortino = (self.averageMonthlyReturn - self.riskFreeRate) / \
self.downsideDeviation self.downsideDeviation
print('Sortino Ratio:', sortino) print('Sortino Ratio:', sortino)
return sortino return sortino
def calcTreynor(self): def calcTreynor(self):
treynor = (self.averageAnnualReturn - Stock.riskFreeRate)/self.beta treynor = (self.averageMonthlyReturn - Stock.riskFreeRate)/self.beta
print('Treynor Ratio:', treynor) print('Treynor Ratio:', treynor)
return treynor return treynor
@ -484,6 +497,210 @@ class Stock:
sys.stdout.flush() sys.stdout.flush()
plt.close() plt.close()
def scrapeYahooFinance(self):
# Determine if ETF, Mutual fund, or stock
print('Determining if Yahoo Finance has data for', self.name, end=": ")
url = ''.join(('https://finance.yahoo.com/quote/',
self.name, '?p=', self.name))
if requests.get(url).history:
print('No')
return 'Not available'
else:
print('Yes')
stockType = ''
url2 = ''.join(('https://finance.yahoo.com/lookup?s=', self.name))
print('Sending request to:', url2)
raw_html = requests.get(url2).text
soup2 = BeautifulSoup(raw_html, 'html.parser')
# Type (Stock, ETF, Mutual Fund)
r = soup2.find_all(
'td', attrs={'class': 'data-col4 Ta(start) Pstart(20px) Miw(30px)'})
t = soup2.find_all('a', attrs={'class': 'Fw(b)'}) # Name and class
z = soup2.find_all('td', attrs={
'class': 'data-col1 Ta(start) Pstart(10px) Miw(80px)'}) # Name of stock
listNames = []
for i in t:
if len(i.text.strip()) < 6:
listNames.append(i.text.strip())
for i in range(0, len(listNames), 1):
if listNames[i] == self.name:
break
r = r[i].text.strip()
z = z[i].text.strip()
print('Name:', z)
if r == 'ETF':
stockType = 'ETF'
elif r == 'Stocks':
stockType = 'Stock'
elif r == 'Mutual Fund':
stockType = 'Fund'
else:
print('Could not determine fund type')
return 'Not available'
print('Type:', stockType)
if Stock.indicator == 'Expense Ratio':
if stockType == 'Stock':
print(
self.name, 'is a stock, and therefore does not have an expense ratio')
return 'Not available'
url = ''.join(('https://finance.yahoo.com/quote/',
self.name, '?p=', self.name))
# https://finance.yahoo.com/quote/SPY?p=SPY
print('Sending request to:', url)
raw_html = requests.get(url).text
soup = BeautifulSoup(raw_html, 'html.parser')
r = soup.find_all('span', attrs={'class': 'Trsdu(0.3s)'})
if r == []:
print('Something went wrong with scraping expense ratio')
return('Not available')
if stockType == 'ETF':
for i in range(len(r)-1, 0, -1):
s = r[i].text.strip()
if s[-1] == '%':
break
elif stockType == 'Fund':
count = 0 # Second in set
for i in range(0, len(r)-1, 1):
s = r[i].text.strip()
if s[-1] == '%' and count == 0:
count += 1
elif s[-1] == '%' and count == 1:
break
if s[-1] == '%':
expenseRatio = float(s.replace('%', ''))
else:
print('Something went wrong with scraping expense ratio')
return 'Not available'
print(str(expenseRatio) + '%')
return expenseRatio
elif Stock.indicator == 'Market Capitalization':
url = ''.join(('https://finance.yahoo.com/quote/',
self.name, '?p=', self.name))
# https://finance.yahoo.com/quote/GOOGL?p=GOOGL
raw_html = requests.get(url).text
soup = BeautifulSoup(raw_html, 'html.parser')
r = soup.find_all(
'span', attrs={'class': 'Trsdu(0.3s)'})
if r == []:
print('Something went wrong with scraping market capitalization')
return 'Not available'
marketCap = 0
for t in r:
s = t.text.strip()
if s[-1] == 'B':
print(s, end='')
s = s.replace('B', '')
marketCap = float(s) * 1000000000 # 1 billion
break
elif s[-1] == 'M':
print(s, end='')
s = s.replace('M', '')
marketCap = float(s) * 1000000 # 1 million
break
elif s[-1] == 'K':
print(s, end='')
s = s.replace('K', '')
marketCap = float(s) * 1000 # 1 thousand
break
if marketCap == 0:
print('\nSomething went wrong with scraping market capitalization')
return 'Not available'
marketCap = int(marketCap)
print(' =', marketCap)
return marketCap
elif Stock.indicator == 'Turnover':
if stockType == 'Stock':
print(self.name, 'is a stock, and therefore does not have turnover')
return 'Not available'
if stockType == 'Fund':
url = ''.join(('https://finance.yahoo.com/quote/',
self.name, '?p=', self.name))
# https://finance.yahoo.com/quote/SPY?p=SPY
print('Sending request to', url)
raw_html = requests.get(url).text
soup = BeautifulSoup(raw_html, 'html.parser')
r = soup.find_all(
'span', attrs={'class': 'Trsdu(0.3s)'})
if r == []:
print('Something went wrong without scraping turnover')
return 'Not available'
turnover = 0
for i in range(len(r)-1, 0, -1):
s = r[i].text.strip()
if s[-1] == '%':
turnover = float(s.replace('%', ''))
break
if stockType == 'ETF':
url = ''.join(('https://finance.yahoo.com/quote/',
self.name, '/profile?p=', self.name))
# https://finance.yahoo.com/quote/SPY/profile?p=SPY
print('Sending request to', url)
raw_html = requests.get(url).text
soup = BeautifulSoup(raw_html, 'html.parser')
r = soup.find_all(
'span', attrs={'class': 'W(20%) D(b) Fl(start) Ta(e)'})
if r == []:
print('Something went wrong without scraping turnover')
return 'Not available'
turnover = 0
for i in range(len(r)-1, 0, -1):
s = r[i].text.strip()
if s[-1] == '%':
turnover = float(s.replace('%', ''))
break
if turnover == 0:
print('Something went wrong with scraping turnover')
return 'Not available'
print(str(turnover) + '%')
return turnover
def indicatorManual(self):
indicatorValueFound = False
while indicatorValueFound == False:
if Stock.indicator == 'Expense Ratio':
indicatorValue = str(
input(Stock.indicator + ' for ' + self.name + ' (%): '))
elif Stock.indicator == 'Persistence':
indicatorValue = str(
input(Stock.indicator + ' for ' + self.name + ' (years): '))
elif Stock.indicator == 'Turnover':
indicatorValue = str(input(
Stock.indicator + ' for ' + self.name + ' in the last ' + str(Stock.timeFrame) + ' years: '))
elif Stock.indicator == 'Market Capitalization':
indicatorValue = str(
input(Stock.indicator + ' of ' + self.name + ': '))
else:
print('Something is wrong. Indicator was not found. Ending program.')
exit()
if Functions.strintIsFloat(indicatorValue) == True:
indicatorValueFound = True
return float(indicatorValue)
else:
print('Please enter a number')
def calcPersistence(self):
persistenceFirst = (sum(self.monthlyReturn[i] for i in range(
0, Stock.persTimeFrame, 1))) / Stock.persTimeFrame
persistenceSecond = self.averageMonthlyReturn
persistence = persistenceSecond-persistenceFirst
print('Change in average monthly return:', persistence)
return persistence
def datesToDays(dates): def datesToDays(dates):
days = [] days = []
@ -496,69 +713,22 @@ def datesToDays(dates):
return days return days
def isConnected():
import socket # To check internet connection
#print('Checking internet connection')
try:
# connect to the host -- tells us if the host is actually reachable
socket.create_connection(("www.andrewkdinh.com", 80))
print('Internet connection is good')
return True
except OSError:
# pass
print("No internet connection!")
return False
def checkPackages():
import importlib.util
import sys
packagesInstalled = True
packages = ['requests', 'numpy']
for i in range(0, len(packages), 1):
package_name = packages[i]
spec = importlib.util.find_spec(package_name)
if spec is None:
print(
package_name +
" is not installed\nPlease type in 'pip install -r requirements.txt' to install all required packages")
packagesInstalled = False
return packagesInstalled
def checkPythonVersion():
import platform
#print('Checking Python version')
i = platform.python_version()
r = i.split('.')
k = ''.join((r[0], '.', r[1]))
k = float(k)
if k < 3.3:
print('Your Python version is', i,
'\nIt needs to be greater than version 3.3')
return False
else:
print('Your Python version of', i, 'is good')
return True
def benchmarkInit(): def benchmarkInit():
# Treat benchmark like stock # Treat benchmark like stock
benchmarkTicker = '' benchmarkTicker = ''
while benchmarkTicker == '':
benchmarks = ['S&P500', 'DJIA', 'Russell 3000', 'MSCI EAFE'] benchmarks = ['S&P500', 'DJIA', 'Russell 3000', 'MSCI EAFE']
benchmarksTicker = ['SPY', 'DJIA', 'VTHR', 'EFT'] benchmarksTicker = ['SPY', 'DJIA', 'VTHR', 'EFT']
print('\nList of benchmarks:') print('\nList of benchmarks:')
for i in range(0, len(benchmarks), 1): for i in range(0, len(benchmarks), 1):
print(str(i+1) + '. ' + print(str(i+1) + '. ' +
benchmarks[i] + ' (' + benchmarksTicker[i] + ')') benchmarks[i] + ' (' + benchmarksTicker[i] + ')')
while benchmarkTicker == '':
benchmark = str(input('Please choose a benchmark from the list: ')) benchmark = str(input('Please choose a benchmark from the list: '))
# benchmark = 'SPY' # TESTING # benchmark = 'SPY' # TESTING
if Functions.stringIsInt(benchmark) == True: if Functions.stringIsInt(benchmark) == True:
if int(benchmark) <= len(benchmarks): if int(benchmark) <= len(benchmarks) and int(benchmark) > 0:
benchmarkInt = int(benchmark) benchmarkInt = int(benchmark)
benchmark = benchmarks[benchmarkInt-1] benchmark = benchmarks[benchmarkInt-1]
benchmarkTicker = benchmarksTicker[benchmarkInt-1] benchmarkTicker = benchmarksTicker[benchmarkInt-1]
@ -586,34 +756,182 @@ def benchmarkInit():
def stocksInit(): def stocksInit():
listOfStocks = [] listOfStocks = []
print('\nThis program can analyze stocks (GOOGL), mutual funds (VFINX), and ETFs (SPY)')
print('For simplicity, all of them will be referred to as "stock"')
found = False
while found == False:
print('\nMethods:')
method = 0
methods = ['Read from a file', 'Enter manually',
'U.S. News popular funds (~35)', 'Kiplinger top-performing funds (50)', 'TheStreet top-rated mutual funds (20)']
for i in range(0, len(methods), 1):
print(str(i+1) + '. ' + methods[i])
while method == 0 or method > len(methods):
method = str(input('Which method? '))
if Functions.stringIsInt(method) == True:
method = int(method)
if method == 0 or method > len(methods):
print('Please choose a valid method')
else:
method = 0
print('Please choose a number')
print('')
if method == 1:
defaultFiles = ['.gitignore', 'LICENSE', 'main.py', 'Functions.py',
'README.md', 'requirements.txt', 'cache.sqlite', '_test_runner.py'] # Added by repl.it for whatever reason
stocksFound = False
print('Files in current directory (not including default files): ')
listOfFilesTemp = [f for f in os.listdir() if os.path.isfile(f)]
listOfFiles = []
for files in listOfFilesTemp:
if files[0] != '.' and any(x in files for x in defaultFiles) != True:
listOfFiles.append(files)
for i in range(0, len(listOfFiles), 1):
if listOfFiles[i][0] != '.':
print(str(i+1) + '. ' + listOfFiles[i])
while stocksFound == False:
fileName = str(input('What is the file number/name? '))
if Functions.stringIsInt(fileName) == True:
if int(fileName) < len(listOfFiles)+1 and int(fileName) > 0:
fileName = listOfFiles[int(fileName)-1]
print(fileName)
if Functions.fileExists(fileName) == True:
listOfStocks = []
file = open(fileName, 'r')
n = file.read()
file.close()
s = re.findall(r'[^,;\s]+', n)
for i in s:
if str(i) != '' and Functions.hasNumbers(str(i)) == False:
listOfStocks.append(str(i).upper())
stocksFound = True
else:
print('File not found')
for i in range(0, len(listOfStocks), 1):
stockName = listOfStocks[i].upper()
listOfStocks[i] = Stock()
listOfStocks[i].setName(stockName)
for k in listOfStocks:
print(k.name, end=' ')
print('\n' + str(len(listOfStocks)) + ' stocks total')
elif method == 2:
isInteger = False isInteger = False
while isInteger == False: while isInteger == False:
temp = input('\nNumber of stocks to analyze (2 minimum): ') temp = input('\nNumber of stocks to analyze (2 minimum): ')
isInteger = Functions.stringIsInt(temp) isInteger = Functions.stringIsInt(temp)
if isInteger == True: if isInteger == True:
if int(temp) >= 2:
numberOfStocks = int(temp) numberOfStocks = int(temp)
else:
print('Please type a number greater than or equal to 2')
isInteger = False
else: else:
print('Please type an integer') print('Please type an integer')
# numberOfStocks = 5 # TESTING i = 0
# print('How many stocks would you like to analyze? ', numberOfStocks) while i < numberOfStocks:
print('\nThis program can analyze stocks (GOOGL), mutual funds (VFINX), and ETFs (SPY)')
print('For simplicity, all of them will be referred to as "stock"\n')
# listOfGenericStocks = ['googl', 'aapl', 'vfinx', 'tsla', 'vthr']
for i in range(0, numberOfStocks, 1):
print('Stock', i + 1, end=' ') print('Stock', i + 1, end=' ')
stockName = str(input('ticker: ')) stockName = str(input('ticker: '))
# stockName = listOfGenericStocks[i] if stockName != '' and Functions.hasNumbers(stockName) == False:
# print(':', stockName)
stockName = stockName.upper() stockName = stockName.upper()
listOfStocks.append(stockName) listOfStocks.append(stockName)
listOfStocks[i] = Stock() listOfStocks[i] = Stock()
listOfStocks[i].setName(stockName) listOfStocks[i].setName(stockName)
i += 1
else:
print('Invalid ticker')
elif method == 3:
listOfStocks = []
url = 'https://money.usnews.com/funds/mutual-funds/most-popular'
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.98 Safari/537.36'}
print('Sending request to', url)
f = requests.get(url, headers=headers)
Functions.fromCache(f)
raw_html = f.text
soup = BeautifulSoup(raw_html, 'html.parser')
file = open('usnews-stocks.txt', 'w')
r = soup.find_all(
'span', attrs={'class': 'text-smaller text-muted'})
for k in r:
print(k.text.strip(), end=' ')
listOfStocks.append(k.text.strip())
file.write(str(k.text.strip()) + '\n')
file.close()
for i in range(0, len(listOfStocks), 1):
stockName = listOfStocks[i].upper()
listOfStocks[i] = Stock()
listOfStocks[i].setName(stockName)
print('\n' + str(len(listOfStocks)) + ' mutual funds total')
elif method == 4:
listOfStocks = []
url = 'https://www.kiplinger.com/tool/investing/T041-S001-top-performing-mutual-funds/index.php'
headers = {
'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.109 Safari/537.36'}
print('Sending request to', url)
f = requests.get(url, headers=headers)
Functions.fromCache(f)
raw_html = f.text
soup = BeautifulSoup(raw_html, 'html.parser')
file = open('kiplinger-stocks.txt', 'w')
r = soup.find_all('a', attrs={'style': 'font-weight:700;'})
for k in r:
print(k.text.strip(), end=' ')
listOfStocks.append(k.text.strip())
file.write(str(k.text.strip()) + '\n')
file.close()
for i in range(0, len(listOfStocks), 1):
stockName = listOfStocks[i].upper()
listOfStocks[i] = Stock()
listOfStocks[i].setName(stockName)
print('\n' + str(len(listOfStocks)) + ' mutual funds total')
elif method == 5:
listOfStocks = []
url = 'https://www.thestreet.com/topic/21421/top-rated-mutual-funds.html'
headers = {
'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.109 Safari/537.36'}
print('Sending request to', url)
f = requests.get(url, headers=headers)
Functions.fromCache(f)
raw_html = f.text
soup = BeautifulSoup(raw_html, 'html.parser')
file = open('thestreet-stocks.txt', 'w')
r = soup.find_all('a')
for k in r:
if len(k.text.strip()) == 5:
n = re.findall(r'^/quote/.*\.html', k['href'])
if len(n) != 0:
print(k.text.strip(), end=' ')
listOfStocks.append(k.text.strip())
file.write(str(k.text.strip()) + '\n')
file.close()
for i in range(0, len(listOfStocks), 1):
stockName = listOfStocks[i].upper()
listOfStocks[i] = Stock()
listOfStocks[i].setName(stockName)
print('\n' + str(len(listOfStocks)) + ' mutual funds total')
if len(listOfStocks) < 2:
print('Please choose another method')
else:
found = True
return listOfStocks return listOfStocks
@ -638,6 +956,16 @@ def asyncData(benchmark, listOfStocks):
('https://www.quandl.com/api/v3/datasets/USTREASURY/LONGTERMRATES.json?api_key=', apiQuandl)) ('https://www.quandl.com/api/v3/datasets/USTREASURY/LONGTERMRATES.json?api_key=', apiQuandl))
urlList.append(url) urlList.append(url)
# Yahoo Finance
for i in range(0, len(listOfStocks), 1):
url = ''.join(('https://finance.yahoo.com/quote/',
listOfStocks[i].name, '?p=', listOfStocks[i].name))
urlList.append(url)
for i in range(0, len(listOfStocks), 1):
url = ''.join(
('https://finance.yahoo.com/lookup?s=', listOfStocks[i].name))
urlList.append(url)
# Send async requests # Send async requests
print('\nSending async requests (Assuming Alpha Vantage is first choice)') print('\nSending async requests (Assuming Alpha Vantage is first choice)')
with PoolExecutor(max_workers=3) as executor: with PoolExecutor(max_workers=3) as executor:
@ -648,6 +976,8 @@ def asyncData(benchmark, listOfStocks):
def sendAsync(url): def sendAsync(url):
time.sleep(random.randrange(0, 2))
print('Sending request to', url)
requests.get(url) requests.get(url)
return return
@ -656,18 +986,19 @@ def timeFrameInit():
isInteger = False isInteger = False
while isInteger == False: while isInteger == False:
print( print(
'\nPlease enter the time frame in years (<10 years recommended):', end='') '\nPlease enter the time frame in months (<60 months recommended):', end='')
temp = input(' ') temp = input(' ')
isInteger = Functions.stringIsInt(temp) isInteger = Functions.stringIsInt(temp)
if isInteger == True: if isInteger == True:
years = int(temp) if int(temp) > 1:
months = int(temp)
else:
print('Please enter a number greater than 1')
isInteger = False
else: else:
print('Please type an integer') print('Please type an integer')
# years = 5 # TESTING timeFrame = months
# print('Years:', years)
timeFrame = years
return timeFrame return timeFrame
@ -725,17 +1056,17 @@ def returnMain(benchmark, listOfStocks):
print('Getting risk-free rate from current 10-year treasury bill rates', end='\n\n') print('Getting risk-free rate from current 10-year treasury bill rates', end='\n\n')
Stock.riskFreeRate = riskFreeRate() Stock.riskFreeRate = riskFreeRate()
print(benchmark.name, end='\n\n') print(benchmark.name, end='\n\n')
benchmark.annualReturn = Stock.calcAnnualReturn(benchmark) benchmark.monthlyReturn = Stock.calcMonthlyReturn(benchmark)
if benchmark.annualReturn == 'Not available': if benchmark.monthlyReturn == 'Not available':
print('Please use a lower time frame\nEnding program') print('Please use a lower time frame\nEnding program')
exit() exit()
benchmark.averageAnnualReturn = Stock.calcAverageAnnualReturn(benchmark) benchmark.averageMonthlyReturn = Stock.calcAverageMonthlyReturn(benchmark)
benchmark.standardDeviation = Stock.calcStandardDeviation(benchmark) benchmark.standardDeviation = Stock.calcStandardDeviation(benchmark)
# Make benchmark data global # Make benchmark data global
Stock.benchmarkDates = benchmark.dates Stock.benchmarkDates = benchmark.dates
Stock.benchmarkCloseValues = benchmark.closeValues Stock.benchmarkCloseValues = benchmark.closeValues
Stock.benchmarkAverageAnnualReturn = benchmark.averageAnnualReturn Stock.benchmarkAverageMonthlyReturn = benchmark.averageMonthlyReturn
Stock.benchmarkStandardDeviation = benchmark.standardDeviation Stock.benchmarkStandardDeviation = benchmark.standardDeviation
i = 0 i = 0
@ -755,15 +1086,16 @@ def returnMain(benchmark, listOfStocks):
benchmarkMatchDatesAndCloseValues = temp[1] benchmarkMatchDatesAndCloseValues = temp[1]
# Calculate everything for each stock # Calculate everything for each stock
listOfStocks[i].annualReturn = Stock.calcAnnualReturn(listOfStocks[i]) listOfStocks[i].monthlyReturn = Stock.calcMonthlyReturn(
if listOfStocks[i].annualReturn == 'Not available': listOfStocks[i])
if listOfStocks[i].monthlyReturn == 'Not available':
print('Removing', listOfStocks[i].name, 'from list of stocks') print('Removing', listOfStocks[i].name, 'from list of stocks')
del listOfStocks[i] del listOfStocks[i]
if len(listOfStocks) == 0: if len(listOfStocks) == 0:
print('No stocks to analyze. Ending program') print('No stocks fit time frame. Ending program')
exit() exit()
else: else:
listOfStocks[i].averageAnnualReturn = Stock.calcAverageAnnualReturn( listOfStocks[i].averageMonthlyReturn = Stock.calcAverageMonthlyReturn(
listOfStocks[i]) listOfStocks[i])
listOfStocks[i].correlation = Stock.calcCorrelation( listOfStocks[i].correlation = Stock.calcCorrelation(
listOfStocks[i], benchmarkMatchDatesAndCloseValues[1]) listOfStocks[i], benchmarkMatchDatesAndCloseValues[1])
@ -787,6 +1119,9 @@ def returnMain(benchmark, listOfStocks):
print('\nNumber of stocks from original list that fit time frame:', print('\nNumber of stocks from original list that fit time frame:',
len(listOfStocks)) len(listOfStocks))
if len(listOfStocks) < 2:
print('Cannot proceed to the next step. Exiting program.')
exit()
def indicatorInit(): def indicatorInit():
@ -795,17 +1130,16 @@ def indicatorInit():
listOfIndicators = ['Expense Ratio', listOfIndicators = ['Expense Ratio',
'Market Capitalization', 'Turnover', 'Persistence'] 'Market Capitalization', 'Turnover', 'Persistence']
print('\n', end='') print('\n', end='')
while indicatorFound == False:
print('List of indicators:') print('List of indicators:')
for i in range(0, len(listOfIndicators), 1): for i in range(0, len(listOfIndicators), 1):
print(str(i + 1) + '. ' + listOfIndicators[i]) print(str(i + 1) + '. ' + listOfIndicators[i])
while indicatorFound == False:
indicator = str(input('Choose an indicator from the list: ')) indicator = str(input('Choose an indicator from the list: '))
# indicator = 'expense ratio' # TESTING # indicator = 'expense ratio' # TESTING
if Functions.stringIsInt(indicator) == True: if Functions.stringIsInt(indicator) == True:
if int(indicator) <= 4: if int(indicator) <= 4 and int(indicator) > 0:
indicator = listOfIndicators[int(indicator)-1] indicator = listOfIndicators[int(indicator)-1]
indicatorFound = True indicatorFound = True
else: else:
@ -819,7 +1153,7 @@ def indicatorInit():
break break
if indicatorFound == False: if indicatorFound == False:
print('Please choose an indicator from the list') print('Please choose an indicator from the list\n')
return indicator return indicator
@ -878,12 +1212,14 @@ def plot_regression_line(x, y, b, i):
plt.plot(x, y_pred, color="g") plt.plot(x, y_pred, color="g")
# putting labels # putting labels
listOfReturnStrings = ['Average Annual Return', listOfReturnStrings = ['Average Monthly Return',
'Sharpe Ratio', 'Sortino Ratio', 'Treynor Ratio', 'Alpha'] 'Sharpe Ratio', 'Sortino Ratio', 'Treynor Ratio', 'Alpha']
plt.title(Stock.indicator + ' and ' + listOfReturnStrings[i]) plt.title(Stock.indicator + ' and ' + listOfReturnStrings[i])
if Stock.indicator == 'Expense Ratio': if Stock.indicator == 'Expense Ratio' or Stock.indicator == 'Turnover':
plt.xlabel(Stock.indicator + ' (%)') plt.xlabel(Stock.indicator + ' (%)')
elif Stock.indicator == 'Persistence':
plt.xlabel(Stock.indicator + ' (Difference in average monthly return)')
else: else:
plt.xlabel(Stock.indicator) plt.xlabel(Stock.indicator)
@ -894,7 +1230,7 @@ def plot_regression_line(x, y, b, i):
# function to show plot # function to show plot
plt.show(block=False) plt.show(block=False)
for i in range(2, 0, -1): for i in range(3, 0, -1):
if i == 1: if i == 1:
sys.stdout.write('Keeping plot open for ' + sys.stdout.write('Keeping plot open for ' +
str(i) + ' second \r') str(i) + ' second \r')
@ -909,45 +1245,50 @@ def plot_regression_line(x, y, b, i):
plt.close() plt.close()
def indicatorMain(listOfStocks): def persistenceTimeFrame():
Stock.indicator = indicatorInit() print('\nTime frame you chose was', Stock.timeFrame, 'months')
print(Stock.indicator, end='\n\n') persTimeFrameFound = False
while persTimeFrameFound == False:
persistenceTimeFrame = str(
input('Please choose how many months to measure persistence: '))
if Functions.stringIsInt(persistenceTimeFrame) == True:
if int(persistenceTimeFrame) > 0 and int(persistenceTimeFrame) < Stock.timeFrame - 1:
persistenceTimeFrame = int(persistenceTimeFrame)
persTimeFrameFound = True
else:
print('Please choose a number between 0 and',
Stock.timeFrame, end='\n')
else:
print('Please choose an integer between 0 and',
Stock.timeFrame, end='\n')
# indicatorValuesGenericExpenseRatio = [2.5, 4.3, 3.1, 2.6, 4.2] # TESTING return persistenceTimeFrame
def indicatorMain(listOfStocks):
print('\n' + str(Stock.indicator) + '\n')
listOfStocksIndicatorValues = [] listOfStocksIndicatorValues = []
for i in range(0, len(listOfStocks), 1): for i in range(0, len(listOfStocks), 1):
indicatorValueFound = False print(listOfStocks[i].name)
while indicatorValueFound == False: if Stock.indicator != 'Persistence':
if Stock.indicator == 'Expense Ratio': listOfStocks[i].indicatorValue = Stock.scrapeYahooFinance(
indicatorValue = str( listOfStocks[i])
input(Stock.indicator + ' for ' + listOfStocks[i].name + ' (%): '))
elif Stock.indicator == 'Persistence':
indicatorValue = str(
input(Stock.indicator + ' for ' + listOfStocks[i].name + ' (years): '))
elif Stock.indicator == 'Turnover':
indicatorValue = str(input(
Stock.indicator + ' for ' + listOfStocks[i].name + ' in the last ' + str(Stock.timeFrame) + ' years: '))
elif Stock.indicator == 'Market Capitalization':
indicatorValue = str(
input(Stock.indicator + ' of ' + listOfStocks[i].name + ': '))
else: else:
print('Something is wrong. Indicator was not found. Ending program.') listOfStocks[i].indicatorValue = Stock.calcPersistence(
exit() listOfStocks[i])
print('')
if Functions.strintIsFloat(indicatorValue) == True: if listOfStocks[i].indicatorValue == 'Not available':
listOfStocks[i].indicatorValue = float(indicatorValue) listOfStocks[i].indicatorValue = Stock.indicatorManual(
indicatorValueFound = True listOfStocks[i])
else:
print('Please enter a number')
# listOfStocks[i].indicatorValue = indicatorValuesGenericExpenseRatio[i] # TESTING
listOfStocksIndicatorValues.append(listOfStocks[i].indicatorValue) listOfStocksIndicatorValues.append(listOfStocks[i].indicatorValue)
listOfReturns = [] # A list that matches the above list with return values [[averageAnnualReturn1, aAR2, aAR3], [sharpe1, sharpe2, sharpe3], etc.] listOfReturns = [] # A list that matches the above list with return values [[averageMonthlyReturn1, aAR2, aAR3], [sharpe1, sharpe2, sharpe3], etc.]
tempListOfReturns = [] tempListOfReturns = []
for i in range(0, len(listOfStocks), 1): for i in range(0, len(listOfStocks), 1):
tempListOfReturns.append(listOfStocks[i].averageAnnualReturn) tempListOfReturns.append(listOfStocks[i].averageMonthlyReturn)
listOfReturns.append(tempListOfReturns) listOfReturns.append(tempListOfReturns)
tempListOfReturns = [] tempListOfReturns = []
for i in range(0, len(listOfStocks), 1): for i in range(0, len(listOfStocks), 1):
@ -974,9 +1315,8 @@ def indicatorMain(listOfStocks):
Stock.indicatorCorrelation = calcIndicatorCorrelation( Stock.indicatorCorrelation = calcIndicatorCorrelation(
listOfIndicatorValues, listOfReturns) listOfIndicatorValues, listOfReturns)
listOfReturnStrings = ['Average Annual Return', listOfReturnStrings = ['Average Monthly Return',
'Sharpe Ratio', 'Sortino Ratio', 'Treynor Ratio', 'Alpha'] 'Sharpe Ratio', 'Sortino Ratio', 'Treynor Ratio', 'Alpha']
print('\n', end='')
for i in range(0, len(Stock.indicatorCorrelation), 1): for i in range(0, len(Stock.indicatorCorrelation), 1):
print('Correlation with ' + Stock.indicator.lower() + ' and ' + print('Correlation with ' + Stock.indicator.lower() + ' and ' +
listOfReturnStrings[i].lower() + ': ' + str(Stock.indicatorCorrelation[i])) listOfReturnStrings[i].lower() + ': ' + str(Stock.indicatorCorrelation[i]))
@ -992,23 +1332,29 @@ def indicatorMain(listOfStocks):
def main(): def main():
# Test internet connection
internetConnection = isConnected()
if not internetConnection:
return
# Check that all required packages are installed # Check that all required packages are installed
packagesInstalled = checkPackages() packagesInstalled = Functions.checkPackages(
['numpy', 'requests', 'bs4', 'requests_cache'])
if not packagesInstalled: if not packagesInstalled:
return exit()
else: else:
print('All required packages are installed') print('All required packages are installed')
# Check python version is above 3.3 # Check python version is above 3.3
pythonVersionGood = checkPythonVersion() pythonVersionGood = Functions.checkPythonVersion()
if not pythonVersionGood: if not pythonVersionGood:
return return
# Test internet connection
internetConnection = Functions.isConnected()
if not internetConnection:
return
else:
Functions.getJoke()
# Functions.getJoke()
# Choose benchmark and makes it class Stock # Choose benchmark and makes it class Stock
benchmark = benchmarkInit() benchmark = benchmarkInit()
# Add it to a list to work with other functions # Add it to a list to work with other functions
@ -1017,10 +1363,16 @@ def main():
# Asks for stock(s) ticker and makes them class Stock # Asks for stock(s) ticker and makes them class Stock
listOfStocks = stocksInit() listOfStocks = stocksInit()
# Determine time frame [Years, Months] # Determine time frame (Years)
timeFrame = timeFrameInit() timeFrame = timeFrameInit()
Stock.timeFrame = timeFrame # Needs to be a global variable for all stocks Stock.timeFrame = timeFrame # Needs to be a global variable for all stocks
# Choose indicator
Stock.indicator = indicatorInit()
# Choose time frame for initial persistence
if Stock.indicator == 'Persistence':
Stock.persTimeFrame = persistenceTimeFrame()
# Send async request to AV for listOfStocks and benchmark # Send async request to AV for listOfStocks and benchmark
asyncData(benchmark, listOfStocks) asyncData(benchmark, listOfStocks)

View File

@ -1,3 +1,4 @@
requests~=2.21.0 requests~=2.21.0
numpy~=1.15.4 numpy~=1.15.4
beautifulsoup4~=4.7.1
requests-cache~=0.4.13 # NOT REQUIRED requests-cache~=0.4.13 # NOT REQUIRED