From bfca21f176816124cb03fb7612bad989f9876a37 Mon Sep 17 00:00:00 2001 From: Anson Date: Wed, 22 May 2019 06:00:49 -0700 Subject: [PATCH 01/10] Organized Imports --- functions.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/functions.py b/functions.py index 99fa467..6506a19 100644 --- a/functions.py +++ b/functions.py @@ -1,8 +1,8 @@ -import urllib.request import json -from datetime import datetime -import time import re +import time +import urllib.request +from datetime import datetime def getTickers(text: str): From 3113c17efb7502aaa3a132fdf1683f93416484ce Mon Sep 17 00:00:00 2001 From: Anson Date: Mon, 27 May 2019 09:18:24 -0700 Subject: [PATCH 02/10] updated standard reply for tickers in text --- bot.py | 23 ++++-------------- functions.py | 68 ++++++++++++++++------------------------------------ 2 files changed, 26 insertions(+), 65 deletions(-) diff --git a/bot.py b/bot.py index 7ab7238..7e2f389 100644 --- a/bot.py +++ b/bot.py @@ -38,30 +38,17 @@ def tickerDetect(bot, update): """ message = update.message.text chat_id = update.message.chat_id - - # Let user know bot is working - bot.send_chat_action(chat_id=chat_id, action=telegram.ChatAction.TYPING) - tickers = getTickers(message) - data = tickerData(tickers) if tickers else {} + # Let user know bot is working + if tickers: + bot.send_chat_action(chat_id=chat_id, action=telegram.ChatAction.TYPING) + replies = tickerDataReply(tickers) - for ticker in data: + for symbol, reply in replies.items(): - # Keep track of which tickers had a return from tickerData() - if ticker.lower() in tickers: - tickers.remove(ticker.lower()) - - reply = tickerDataReply(data[ticker]) update.message.reply_text(text=reply, parse_mode=telegram.ParseMode.MARKDOWN) - # For any tickers that didnt have data, return that they don't exist. - for ticker in tickers: - update.message.reply_text( - ticker.upper() - + " does not exist, you should search for a real stock like $PSEC" - ) - def news(bot, update): """ diff --git a/functions.py b/functions.py index 6506a19..ca18068 100644 --- a/functions.py +++ b/functions.py @@ -4,6 +4,8 @@ import time import urllib.request from datetime import datetime +import cred + def getTickers(text: str): """ @@ -15,60 +17,32 @@ def getTickers(text: str): return list(set(re.findall(TICKER_REGEX, text))) -def tickerData(tickers: list): +def tickerDataReply(tickers: list): """ - Takes a list of tickers and returns a dictionary of information on ticker. - example: - input list: ["aapl","tsla"] - returns: - { - "AAPL": {"name": "Apple Inc.", "price": 200.72, "change": -0.01074}, - "TSLA": {"name": "Tesla Inc.", "price": 241.98, "change": -0.01168}, - } + Takes a list of tickers and returns a list of strings with information about the ticker. """ - - stockData = {} - IEXURL = ( - "https://api.iextrading.com/1.0/stock/market/batch?symbols=" - + ",".join(tickers) - + "&types=quote" - ) - with urllib.request.urlopen(IEXURL) as url: - IEXData = json.loads(url.read().decode()) - + tickerReplies = {} for ticker in tickers: - ticker = ticker.upper() + IEXURL = ( + f"https://cloud.iexapis.com/stable/stock/{ticker}/quote?token={cred.secret}" + ) + with urllib.request.urlopen(IEXURL) as url: + IEXData = json.loads(url.read().decode()) - # Makes sure ticker exists before populating a dictionary - if ticker in IEXData: - stockData[ticker] = {} - stockData[ticker]["name"] = IEXData[ticker]["quote"]["companyName"] - stockData[ticker]["price"] = IEXData[ticker]["quote"]["latestPrice"] - stockData[ticker]["change"] = IEXData[ticker]["quote"]["changePercent"] + reply = f"The current stock price of {IEXData['companyName']} is $**{IEXData['latestPrice']}**" - return stockData + # Determine wording of change text + change = round(IEXData["changePercent"] * 100, 2) + if change > 0: + reply += f", the stock is currently **up {change}%**" + elif change < 0: + reply += f", the stock is currently **down {change}%**" + else: + reply += ", the stock hasn't shown any movement today." + tickerReplies[ticker] = reply -def tickerDataReply(ticker: dict): - """ - Takes a dictionary, likely produced from tickerData(), and returns a markdown string with information on the ticker. - example: - input dict: {"AAPL": {"name": "Apple Inc.", "price": 200.72, "change": -0.01074}} - returns: - The current stock price of Apple Inc. is $**200.72**, the stock is currently **down -1.07**% - """ - reply = f"The current stock price of {ticker['name']} is $**{ticker['price']}**" - - # Determine wording of change text - change = round(ticker["change"] * 100, 2) - if change > 0: - changeText = f", the stock is currently **up {change}%**" - elif change < 0: - changeText = f", the stock is currently **down {change}%**" - else: - changeText = ", the stock hasn't shown any movement today." - - return reply + changeText + return tickerReplies def tickerNews(tickers: list): From 6b679963dd1d51a22e377407fc87a7a29606d9c4 Mon Sep 17 00:00:00 2001 From: Anson Date: Mon, 27 May 2019 09:19:42 -0700 Subject: [PATCH 03/10] removed news since no longer supported by IEX --- bot.py | 25 ------------------ functions.py | 74 ---------------------------------------------------- 2 files changed, 99 deletions(-) diff --git a/bot.py b/bot.py index 7e2f389..9764764 100644 --- a/bot.py +++ b/bot.py @@ -50,31 +50,6 @@ def tickerDetect(bot, update): update.message.reply_text(text=reply, parse_mode=telegram.ParseMode.MARKDOWN) -def news(bot, update): - """ - /news - Returns a small snippet of general information, and any news articles that are found. - """ - message = update.message.text - chat_id = update.message.chat_id - - # Let user know bot is working - bot.send_chat_action(chat_id=chat_id, action=telegram.ChatAction.TYPING) - - tickers = getTickers(message) - - news = tickerNews(tickers) if tickers else {} - - for ticker in news: - - reply = tickerNewsReply(news[ticker]) - update.message.reply_text(text=reply, parse_mode=telegram.ParseMode.MARKDOWN) - - # Keep track of which tickers had a return from tickerData() - if ticker.lower() in tickers: - tickers.remove(ticker.lower()) - - def dividend(bot, update): """ This Functions is incomplete. diff --git a/functions.py b/functions.py index ca18068..f92de19 100644 --- a/functions.py +++ b/functions.py @@ -45,80 +45,6 @@ def tickerDataReply(tickers: list): return tickerReplies -def tickerNews(tickers: list): - """ - Takes a list of ticker and returns a dictionary of dictionarys - with a list of tuples under news that contains (title, url) - example: - input list: ["aapl"] - returns: - { - "AAPL": { - "name": "Apple Inc.", - "price": 200.72, - "change": -0.01074, - "news": [ - ( - "April Dividend Income Report - Beating Records, Holding Steady", - "https://api.iextrading.com/1.0/stock/aapl/article/8556681822768653", - ), - ( - "Is Vanguard's VIG Better Than Its WisdomTree Counterpart?", - "https://api.iextrading.com/1.0/stock/aapl/article/7238581261167527", - ), - ], - } - } - """ - - data = tickerData(tickers) - - for ticker in tickers: - ticker = ticker.upper() - IEXNews = f"https://api.iextrading.com/1.0/stock/{ticker}/news/last/5" - with urllib.request.urlopen(IEXNews) as url: - newsData = json.loads(url.read().decode()) - - data[ticker]["news"] = [] - for index, story in enumerate(newsData): - tup = (newsData[index]["headline"], newsData[index]["url"]) - data[ticker]["news"].append(tup) - - return data - - -def tickerNewsReply(ticker: dict): - """ - Takes a dictionary, likely produced from tickerNews(), and returns a markdown string with news information on the ticker. - example: - { - "name": "Apple Inc.", - "price": 200.72, - "change": -0.01074, - "news": [ - ( - "April Dividend Income Report - Beating Records, Holding Steady", - "https://api.iextrading.com/1.0/stock/aapl/article/8556681822768653", - ), - ( - "Is Vanguard's VIG Better Than Its WisdomTree Counterpart?", - "https://api.iextrading.com/1.0/stock/aapl/article/7238581261167527", - ), - ], - } - returns: - The current stock price of Apple Inc. is $**200.72**, the stock is currently **down -1.07%** - [April Dividend Income Report - Beating Records, Holding Steady](https://api.iextrading.com/1.0/stock/aapl/article/8556681822768653) - [Is Vanguard's VIG Better Than Its WisdomTree Counterpart?](https://api.iextrading.com/1.0/stock/aapl/article/7238581261167527) - """ - reply = tickerDataReply(ticker) - if len(ticker["news"]) == 0: - return reply + f"\n\tNo News was found for {ticker['name']}" - for title, url in ticker["news"]: - reply = reply + f"\n\t[{title}]({url})" - return reply - - # Below Functions are incomplete From 14ced9682a51666704e999aa6fc3a6bd83d082d8 Mon Sep 17 00:00:00 2001 From: Anson Date: Tue, 28 May 2019 01:00:40 -0700 Subject: [PATCH 04/10] updated dividend function for new API --- functions.py | 61 ++++++++++++++++------------------------------------ 1 file changed, 19 insertions(+), 42 deletions(-) diff --git a/functions.py b/functions.py index f92de19..d67e816 100644 --- a/functions.py +++ b/functions.py @@ -45,51 +45,28 @@ def tickerDataReply(tickers: list): return tickerReplies -# Below Functions are incomplete +def tickerDividend(tickers: list): + messages = {} + for ticker in tickers: + IEXurl = f"https://cloud.iexapis.com/stable/stock/{ticker}/dividends/next?token={cred.secret}" + with urllib.request.urlopen(IEXurl) as url: + data = json.loads(url.read().decode()) -def tickerInfo(ticker): - infoURL = f"https://api.iextrading.com/1.0/stock/{ticker}/stats" + # Pattern IEX uses for dividend date. + pattern = "%Y-%m-%d" - with urllib.request.urlopen(infoURL) as url: - data = json.loads(url.read().decode()) + # Convert divDate to seconds, and subtract it from current time. + dividendSeconds = datetime.strptime(data["paymentDate"], pattern).timestamp() + difference = dividendSeconds - int(time.time()) - info = {} + # Calculate (d)ays, (h)ours, (m)inutes, and (s)econds + d, h = divmod(difference, 86400) + h, m = divmod(h, 3600) + m, s = divmod(m, 60) - info["companyName"] = data["companyName"] - info["marketCap"] = data["marketcap"] - info["yearHigh"] = data["week52high"] - info["yearLow"] = data["week52low"] - info["divRate"] = data["dividendRate"] - info["divYield"] = data["dividendYield"] - info["divDate"] = data["exDividendDate"] + messages[ + "ticker" + ] = f"{data['description']}\n\nThe dividend is in: {d:.0f} Days {h:.0f} Hours {m:.0f} Minutes {s:.0f} Seconds." - return info - - -def tickerDividend(ticker): - data = tickerInfo(ticker) - if data["divDate"] == 0: - return "{} has no dividend.".format(data["companyName"]) - - dividendInfo = "{} current dividend yield is: {:.3f}%, or ${:.3f} per share.".format( - data["companyName"], data["divRate"], data["divYield"] - ) - - divDate = data["divDate"] - - # Pattern IEX uses for dividend date. - pattern = "%Y-%m-%d %H:%M:%S.%f" - - # Convert divDate to seconds, and subtract it from current time. - divSeconds = datetime.strptime(divDate, pattern).timestamp() - difference = divSeconds - int(time.time()) - - # Calculate (d)ays, (h)ours, (m)inutes, and (s)econds - d, h = divmod(difference, 86400) - h, m = divmod(h, 3600) - m, s = divmod(m, 60) - - countdownMessage = f"\n\nThe dividend is in: {d:.0f} Days {h:.0f} Hours {m:.0f} Minutes {s:.0f} Seconds." - - return dividendInfo + countdownMessage + return messages From bc09be8ea4e6442d6a98ff5e076cdec767f0d4ca Mon Sep 17 00:00:00 2001 From: Anson Date: Tue, 28 May 2019 04:59:16 -0700 Subject: [PATCH 05/10] fixed dividend function for if symbol doesnt exist --- functions.py | 31 ++++++++++++++++++------------- 1 file changed, 18 insertions(+), 13 deletions(-) diff --git a/functions.py b/functions.py index d67e816..7917468 100644 --- a/functions.py +++ b/functions.py @@ -52,21 +52,26 @@ def tickerDividend(tickers: list): IEXurl = f"https://cloud.iexapis.com/stable/stock/{ticker}/dividends/next?token={cred.secret}" with urllib.request.urlopen(IEXurl) as url: data = json.loads(url.read().decode()) + if data: + # Pattern IEX uses for dividend date. + pattern = "%Y-%m-%d" - # Pattern IEX uses for dividend date. - pattern = "%Y-%m-%d" + # Convert divDate to seconds, and subtract it from current time. + dividendSeconds = datetime.strptime( + data["paymentDate"], pattern + ).timestamp() + difference = dividendSeconds - int(time.time()) - # Convert divDate to seconds, and subtract it from current time. - dividendSeconds = datetime.strptime(data["paymentDate"], pattern).timestamp() - difference = dividendSeconds - int(time.time()) + # Calculate (d)ays, (h)ours, (m)inutes, and (s)econds + d, h = divmod(difference, 86400) + h, m = divmod(h, 3600) + m, s = divmod(m, 60) - # Calculate (d)ays, (h)ours, (m)inutes, and (s)econds - d, h = divmod(difference, 86400) - h, m = divmod(h, 3600) - m, s = divmod(m, 60) - - messages[ - "ticker" - ] = f"{data['description']}\n\nThe dividend is in: {d:.0f} Days {h:.0f} Hours {m:.0f} Minutes {s:.0f} Seconds." + messages[ + ticker + ] = f"{data['description']}\n\nThe dividend is in: {d:.0f} Days {h:.0f} Hours {m:.0f} Minutes {s:.0f} Seconds." + else: + messages[ticker] = f"{ticker} either doesn't exist or pays no dividend." return messages + From c49a2c321e11cf7c1288cc9cf00ab54de7bce2f7 Mon Sep 17 00:00:00 2001 From: Anson Date: Tue, 28 May 2019 05:02:25 -0700 Subject: [PATCH 06/10] removed news command handler --- bot.py | 1 - 1 file changed, 1 deletion(-) diff --git a/bot.py b/bot.py index 9764764..1fdc203 100644 --- a/bot.py +++ b/bot.py @@ -82,7 +82,6 @@ def main(): # on different commands - answer in Telegram dp.add_handler(CommandHandler("start", start)) dp.add_handler(CommandHandler("help", help)) - dp.add_handler(CommandHandler("news", news)) dp.add_handler(CommandHandler("dividend", dividend)) # on noncommand i.e message - echo the message on Telegram From a195c87b6edea221d1572f7c95b10274e44bbc78 Mon Sep 17 00:00:00 2001 From: Anson Date: Tue, 28 May 2019 05:06:59 -0700 Subject: [PATCH 07/10] updated bot to work with new functions --- bot.py | 29 ++++++++++++++--------------- 1 file changed, 14 insertions(+), 15 deletions(-) diff --git a/bot.py b/bot.py index 1fdc203..05502b7 100644 --- a/bot.py +++ b/bot.py @@ -40,35 +40,33 @@ def tickerDetect(bot, update): chat_id = update.message.chat_id tickers = getTickers(message) - # Let user know bot is working if tickers: + # Let user know bot is working bot.send_chat_action(chat_id=chat_id, action=telegram.ChatAction.TYPING) - replies = tickerDataReply(tickers) - for symbol, reply in replies.items(): + for symbol, reply in tickerDataReply(tickers).items(): - update.message.reply_text(text=reply, parse_mode=telegram.ParseMode.MARKDOWN) + update.message.reply_text( + text=reply, parse_mode=telegram.ParseMode.MARKDOWN + ) def dividend(bot, update): """ - This Functions is incomplete. + waits for /dividend or /div command and then finds dividend info on that ticker. """ message = update.message.text chat_id = update.message.chat_id + tickers = getTickers(message) - # regex to find tickers in messages, looks for up to 4 word characters following a dollar sign and captures the 4 word characters - tickers = re.findall(TICKER_REGEX, message) - bot.send_chat_action(chat_id=chat_id, action=telegram.ChatAction.TYPING) + if tickers: + bot.send_chat_action(chat_id=chat_id, action=telegram.ChatAction.TYPING) - for ticker in tickers: - message = tickerDividend(ticker) - update.message.reply_text(text=message, parse_mode=telegram.ParseMode.MARKDOWN) + for symbol, reply in tickerDividend(tickers).items(): - -def error(bot, update, error): - """Log Errors caused by Updates.""" - logger.warning('Update "%s" caused error "%s"', update, error) + update.message.reply_text( + text=reply, parse_mode=telegram.ParseMode.MARKDOWN + ) def main(): @@ -83,6 +81,7 @@ def main(): dp.add_handler(CommandHandler("start", start)) dp.add_handler(CommandHandler("help", help)) dp.add_handler(CommandHandler("dividend", dividend)) + dp.add_handler(CommandHandler("div", dividend)) # on noncommand i.e message - echo the message on Telegram dp.add_handler(MessageHandler(Filters.text, tickerDetect)) From 230f6fdeace4f17c8a84f615c21937cb49594273 Mon Sep 17 00:00:00 2001 From: Anson Date: Tue, 28 May 2019 05:27:50 -0700 Subject: [PATCH 08/10] added error handling for if ticker doesnt exist --- functions.py | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/functions.py b/functions.py index 7917468..f84d7fe 100644 --- a/functions.py +++ b/functions.py @@ -26,19 +26,22 @@ def tickerDataReply(tickers: list): IEXURL = ( f"https://cloud.iexapis.com/stable/stock/{ticker}/quote?token={cred.secret}" ) - with urllib.request.urlopen(IEXURL) as url: - IEXData = json.loads(url.read().decode()) + try: + with urllib.request.urlopen(IEXURL) as url: + IEXData = json.loads(url.read().decode()) - reply = f"The current stock price of {IEXData['companyName']} is $**{IEXData['latestPrice']}**" + reply = f"The current stock price of {IEXData['companyName']} is $**{IEXData['latestPrice']}**" - # Determine wording of change text - change = round(IEXData["changePercent"] * 100, 2) - if change > 0: - reply += f", the stock is currently **up {change}%**" - elif change < 0: - reply += f", the stock is currently **down {change}%**" - else: - reply += ", the stock hasn't shown any movement today." + # Determine wording of change text + change = round(IEXData["changePercent"] * 100, 2) + if change > 0: + reply += f", the stock is currently **up {change}%**" + elif change < 0: + reply += f", the stock is currently **down {change}%**" + else: + reply += ", the stock hasn't shown any movement today." + except: + reply = f"The ticker: {ticker} was not found." tickerReplies[ticker] = reply From 4da5d9933fa32b930bb2db3de546e8020180335e Mon Sep 17 00:00:00 2001 From: Anson Date: Tue, 28 May 2019 05:28:28 -0700 Subject: [PATCH 09/10] accidentaly deleted error handle in earlier commit --- bot.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/bot.py b/bot.py index 05502b7..c5122d9 100644 --- a/bot.py +++ b/bot.py @@ -67,6 +67,10 @@ def dividend(bot, update): update.message.reply_text( text=reply, parse_mode=telegram.ParseMode.MARKDOWN ) + +def error(bot, update, error): + """Log Errors caused by Updates.""" + logger.warning('Update "%s" caused error "%s"', update, error) def main(): From da5d071f3412df725959175c58bf87b6eeafb741 Mon Sep 17 00:00:00 2001 From: Anson Date: Tue, 28 May 2019 05:33:16 -0700 Subject: [PATCH 10/10] fixed environ vars since now theres more than 1 --- bot.py | 7 +++---- functions.py | 10 ++++------ 2 files changed, 7 insertions(+), 10 deletions(-) diff --git a/bot.py b/bot.py index c5122d9..0daec43 100644 --- a/bot.py +++ b/bot.py @@ -3,11 +3,10 @@ import logging import os import telegram +from functions import * from telegram.ext import CommandHandler, Filters, MessageHandler, Updater -from functions import * - -TOKEN = os.environ["TELEGRAM"] +TELEGRAM_TOKEN = os.environ["TELEGRAM"] TICKER_REGEX = "[$]([a-zA-Z]{1,4})" # Enable logging @@ -76,7 +75,7 @@ def error(bot, update, error): def main(): """Start the bot.""" # Create the EventHandler and pass it your bot's token. - updater = Updater(TOKEN) + updater = Updater(TELEGRAM_TOKEN) # Get the dispatcher to register handlers dp = updater.dispatcher diff --git a/functions.py b/functions.py index f84d7fe..a714205 100644 --- a/functions.py +++ b/functions.py @@ -1,12 +1,11 @@ import json +import os import re import time import urllib.request from datetime import datetime -import cred - - +IEX_TOKEN = os.environ["IEX"] def getTickers(text: str): """ Takes a blob of text and returns any stock tickers found. @@ -24,7 +23,7 @@ def tickerDataReply(tickers: list): tickerReplies = {} for ticker in tickers: IEXURL = ( - f"https://cloud.iexapis.com/stable/stock/{ticker}/quote?token={cred.secret}" + f"https://cloud.iexapis.com/stable/stock/{ticker}/quote?token={IEX_TOKEN}" ) try: with urllib.request.urlopen(IEXURL) as url: @@ -52,7 +51,7 @@ def tickerDividend(tickers: list): messages = {} for ticker in tickers: - IEXurl = f"https://cloud.iexapis.com/stable/stock/{ticker}/dividends/next?token={cred.secret}" + IEXurl = f"https://cloud.iexapis.com/stable/stock/{ticker}/dividends/next?token={IEX_TOKEN}" with urllib.request.urlopen(IEXurl) as url: data = json.loads(url.read().decode()) if data: @@ -77,4 +76,3 @@ def tickerDividend(tickers: list): messages[ticker] = f"{ticker} either doesn't exist or pays no dividend." return messages -