After working with Tkinter to generate QR Codes in our last Python Tutorial and sending SMS messages with Twilio and NodeJS, Program Coffee moved ahead in a more in depth and complete Desktop App that not only monitors #API #JSON data, but also sends alerts via SMS (#twilio) for the user with valuable information!
The first step we (fancy way to say "I" :D-}--= ) did was the planning of the app, considering this is a more complex and have more than three steps of execution. For this Program Coffee moved with a small flowchart with the basic logic of the app.
The flowchart explains itself, but the important point is:
1. We have a loop running the requests to the APIs as well as the logic.
2. We also have a GUI based on Tkinter, and we know it doesn't run well in parallel with programs in the same thread :C )
So, based on our simple flowchart we could understand that we need flags for our loop and mainly, we need a separate #python #thread for our logic to run on the background of our Tkinter GUI.
Now that we have an idea of the size/type of beast we are dealing with, let's start with the code:
import threading
from tkinter import *
from tkinter import messagebox
import requests as requests
import time
from twilio.rest import Client
The first step is to import all the tools we will use in this application. 1. Threading will be important to run the logic separate from the GUI (in another thread). 2. Tkinter will make our GUI, we will need also the messagebox (to ensure correct input)
3. Requests, so we can send API requests to all our APIs (3 in total)
4. Time will stop our monitoring program for the interval that we want (otherwise it will send requests every time the loop is performed)
5. Twilio.rest will allow us to connect with our Twilio account and send the SMS messages
The next step is to have all the APIs keys (and Auth Tokens) stored as variables so we can access the APIs responses, for that we need to make free accounts on Twilio, AplhaVantage and NewsAPI.
STOCK_ENDPOINT = "https://www.alphavantage.co/query"
NEWS_ENDPOINT = "https://newsapi.org/v2/everything"
STOCK_API_KEY = "Your_AplhaVantage_API_Key"
NEWS_API_KEY = "Your_NewsAPI_API_Key"
TWILIO_SID = "Your_Twilio_SID"
TWILIO_AUTH_TOKEN = "Your_Twilio_Auth_Token"
Now, we will use a important feature to ensure our thread is operating on its own, and not called again with another data (more info here), and it works well to make time.sleep() more precise.
# Declare a global variable to control the ON and OFF of the program. (Declare False to stop running) --> THE FLAG
running = True
# locks the thread to run a instance until the instance is over (finished or flagged off
lock = threading.Lock()
The next step is to create a function that will get the inputs from the GUI and check them, if the inputs are not correct, then we send a message to the user (messagebox), else it will start/call the monitor function.
-Important: Since this function sends message back directly to the GUI, it needs to run in the same primary thread.
def monitor(): # running in the main thread (same as GUI
global running
ticker = stock_name_entry.get()
target = target_price_entry.get()
if len(ticker) == 0:
messagebox.showinfo(title="Error", message="Input Ticker")
elif len(target) == 0:
messagebox.showinfo(title="Error", message="Input target price")
else:
is_valid = messagebox.askokcancel(title=ticker,
message=f"Monitor the stock: {ticker} \nat the price: {target}?") # gets
# the "OK of the user to start the monitoring
if is_valid:
running = True
check_running(ticker, target)
1. The two inputs that the user will send are "ticker" and "target" that will correspond respectively to the ticker of the stock and the price target of it.
2. We also ensure the FLAG "running" is set to "True" if the user submit the inputs.
3. Finally if user submits the inputs, we call the check_running function with the arguments we received from the GUI. This function will monitor if our flag is on and also call the second thread to run the API calls and logic.
def check_running(ticker, target):
if running:
# update_price(ticker, target)
threading.Thread(target=update_price, args=(ticker, target)).start()
window.after(3000, check_running, ticker, target)
The check_running function will first ensure the "running" FLAG is set to True, if so it will start a new thread with our main function (update_price) as target and "ticker" and "target" as arguments.
The window.after will re-run the check_running function every 3 seconds ensuring the flag is on or off. Now, let's work on the main function "update_price". It will send all the API requests and verify if the data it got as response matches our "target". This will be all performed in the separate thread that we started in the check_running function.
def update_price(ticker, target):
global running
with lock:
stock_params = {
"function": "TIME_SERIES_DAILY_ADJUSTED", # fix this to get the desired time
"symbol": ticker,
# "outputsize":"full",
# "interval": "60min",
"apikey": STOCK_API_KEY,
}
while running:
response = requests.get(STOCK_ENDPOINT, params=stock_params)
data = response.json()["Time Series (Daily)"]
data_list = [value for (key, value) in data.items()]
yesterday_data = data_list[0]
day_before_yesterday_data = data_list[1]
yesterday_closing_price = yesterday_data["4. close"]
day_before_yesterday_closing_price = day_before_yesterday_data["4. close"]
print(yesterday_closing_price)
difference = float(yesterday_closing_price) - float(
day_before_yesterday_closing_price) # wrap everything in abs() to get the positive (absolute) value
print(difference)
diff_percentage = (difference / float(yesterday_closing_price)) * 100
print(diff_percentage)
if diff_percentage > 5 or diff_percentage < -5: # check if the variation is greater than expected
news_params = {
"apiKey": NEWS_API_KEY,
"qInTitle": ticker,
}
news_response = requests.get(NEWS_ENDPOINT, params=news_params)
articles = news_response.json()["articles"]
two_articles = articles[:2]
formatted_articles = [
f"{ticker}: {diff_percentage}% \nHeadline: {article['title']}. \nBrief: {article['description']}" for
article in two_articles]
client = Client(TWILIO_SID, TWILIO_AUTH_TOKEN)
for article in formatted_articles:
client.messages.create(
body=article,
from_="+YourTwilioPhoneNumer",
to="+YourPhoneNumber"
)
#time.sleep(86400) = a day
time.sleep(5) #used for testing
This is the most complex function in our program as it will run all the logic as well as the requests for APIs. In a bigger program (and ideal scenario) it would be ideal to separate the API requests as well as deleting the "prints" (used for debugging and visualization) so the program follows all best practices (this tutorial is already huge, so let's move on with what is the goal -> Making API requests from a Tkinter interface!!! and explain each step). 1. Firstly we access the global variable "running" (our FLAG) 2. Then, using the lock to lock our thread to the current task (very simple explanation)
3. Using instructions from Alphavantage, create a stock_params dictionary with all the parameters that will be sent to the API request. (You can find instructions on their official website or follow this amazing tutorial {I based a lot of this work on this}) 4. Check the FLAG, if (or better: while) it is running, it will first declare a variable to store the response for the request to the Aplhavantage API, declaring variables to store values of the JSON response data that we get. 5. Then we will compare the value we got with a set value of variation (hardcoded), and if it is bigger than the variation set, it will trigger a request to the newsapi.org where we will request two articles (the two first) (hardcoded too) and from the JSON response we will create a message (with the ticker, variation, and description (the title) of the article. 6. Open a Twilio client that will send the message from its pre-set number to your personal number (hardcoded) with the message we just created in the previous step.
7. Finaly, put this loop to sleep for a set time (hardcoded)
Now that we have this entire logic set, we need to create a function to end the loop. One function that will set the FLAG "running" to false.
def end_monitor():
global running
running = False
messagebox.showinfo(title="Monitor Ended", message="The monitor has finished")
The end_monitor is a simple function that just sets "running" to False when it is called. This function will be our End Monitoring button in our program. Now that we have all the logic ready, let's create a simple Graphical Interface that will get the data and also have the buttons to start the monitor and to end the monitor.
if __name__ == '__main__':
window = Tk()
window.title("Stock Price Monitor")
window.config(padx=100, pady=100)
# Labels
stock_label = Label(text="Stock:")
stock_label.grid(row=1, column=0)
target_label = Label(text="Target Price")
target_label.grid(row=1, column=1)
# Entries
stock_name_entry = Entry(width=10)
stock_name_entry.grid(row=2, column=0, columnspan=1)
stock_name_entry.focus()
target_price_entry = Entry(width=10)
target_price_entry.grid(row=2, column=1, columnspan=1)
target_price_entry.focus()
# Buttons
add_button = Button(text="Monitor", width=10,
command=monitor)
add_button.grid(row=4, column=0, columnspan=2)
add_button = Button(text="End Monitor", width=10, command=end_monitor)
add_button.grid(row=6, column=0, columnspan=2)
window.mainloop()
Our GUI will be fairly simple, with two entries with labels, and two buttons under these entries. All items are organized by a grid configuration (just to look a bit nicer).
Note that the buttons have a "command" that will call our functions. Also note that our entries are stored in variables, these variables are the ones that will be used in our functions. Finally, the end result should be a beautiful interface that looks neat and will be running automatic checking your favorite stock daily! In case it drops or surges above your threshold the application will send you a message!
And that is pretty much (very very lots of much) it!
Tasks for the eagers and enthusiasts:
1. Implement all the hardcoded parts as parameters (easy task)
2. Implement the target logic in the "update_price" function (easy task)
3. Turn the FLAG "running" to False when the SMS message is sent (super easy)
Be ready to see this whole functionality and more in a more robust and complete MongoEexpressReactNodeJS application in the near future!!!
I hope you learned a lot and enjoyed the process!
Note: this tutorial started as a small GUI - API request - SMS simple task, but it turned out to become much more complex than that (as any tutorial that is not re-read --copy-- of documentation).
See you soon!
Yuri Falcao
--Program Coffee--
Comments