Wednesday, November 30, 2022

Python - Project - Stock Trading News App (Day 36)

This is a 100 Days challenge to learn a new language (Python). 100 Days of Code - The Complete Python Pro Bootcamp 

I will post some notes to motivate myself to finish this challenge.


Goal



If the target stock price is up or down 5% between yesterday, then get the top three relating news and send those result to your phone number.




Web API List
    Twilio

Ex: 
import requests
import os
from twilio.rest import Client
from dotenv import load_dotenv

# Load Environmental Variables from .env
load_dotenv()

# Stock Price Web API
ALPHA_VANTAGE_URL = "https://www.alphavantage.co/query"
ALPHA_VANTAGE_API_KEY = os.environ.get("ALPHA_VANTAGE_API_KEY")

# News Web API
NEWS_API_URL = "https://newsapi.org/v2/everything"
NEWS_API_KEY = os.environ.get("NEWS_API_KEY")

# SMS Web API
TWILIO_ACCOUNT_SID = os.environ.get("TWILIO_ACCOUNT_SID")
TWILIO_AUTH_TOKEN = os.environ.get("TWILIO_AUTH_TOKEN")

# Company to monitor
COMPANY = "AXSM"

def get_stock_price_history_data_by_company(company):
    # Prepare parameters for API
    parameters = {
        "function": "TIME_SERIES_DAILY_ADJUSTED",
        "symbol": company,
        "apikey": ALPHA_VANTAGE_API_KEY
    }

    # Sends a GET request.
    response = requests.get(ALPHA_VANTAGE_URL, params=parameters)
    # Raises HTTPError, if one occurred.
    response.raise_for_status()
    # Returns the json-encoded content of a response, if any.
    res = response.json()

    # Get Daily Time Series Dictionary
    time_series_daily = res["Time Series (Daily)"]
    # Get all keys
    time_series_daily_key = list(time_series_daily.keys())
    # Get close price of yesterday
    yesterday_close_price = float(
        time_series_daily[time_series_daily_key[0]]["4. close"])
    # Get close price of the day before yesterday
    the_day_before_yesterday_close_price = float(
        time_series_daily[time_series_daily_key[1]]["4. close"])
    # Calculate
    return {
"yesterday": yesterday_close_price,
"the_day_before_yesterday": the_day_before_yesterday_close_price
}


def get_first_three_news_by_company(company):
    # Prepare parameters for API
    parameters = {
        "q": company,
        "apiKey": NEWS_API_KEY
    }

    # Sends a GET request.
    response = requests.get(NEWS_API_URL, params=parameters)
    # Raises HTTPError, if one occurred.
    response.raise_for_status()
    # Returns the json-encoded content of a response, if any.
    res = response.json()

    # Get the top three news
    return res["articles"][:3]


def send_sms(text: str):
    client = Client(TWILIO_ACCOUNT_SID, TWILIO_AUTH_TOKEN)

    message = client.messages.create(
        body=text,
        from_='twilio test number',
        to='your_phone_number'
    )

    # If there is a sid, SMS was sent successfully
    print(message.sid)

  
stock_price_historical_data = get_stock_price_history_data_by_company(COMPANY)
chagne_rate = round(
(stock_price_historical_data["yesterday"] -
                  stock_price_historical_data["the_day_before_yesterday"]
)/stock_price_historical_data["yesterday"] * 100, 2)

if abs(chagne_rate) >= 5:
    first_three_news = get_first_three_news_by_company(COMPANY)
    text = f"{COMPANY}: {'🔺' if chagne_rate > 0 else '🔻'} {chagne_rate}%\n\n"

    # Loop through news
    for news in first_three_news:
        text += f"Headine: {news['title']}\nBrief: {news['description']}\n\n"

    send_sms(text)


Monday, November 28, 2022

Python - API Keys, Authentication, and Environmental Variables (Day 35)

This is a 100 Days challenge to learn a new language (Python). 100 Days of Code - The Complete Python Pro Bootcamp 

I will post some notes to motivate myself to finish this challenge.


What is API Keys



Previously, we are using some free web APIs and they are open to everyone without authentication.

But data/content is valuable in real world. Therefore there are plenty of data/content provider to provide web APIs to let subscribed users to use it. (Such as getting the weather historical data )

In order to know who is calling their web APIs, they need to authenticate users.

They will generate a unique API key to each end user, and that unique API key must be passed when making web API calls.


Weather web API




Ex: Get current weather of your city
import requests

OPEN_WEATHER_ENDPOINT = 'https://api.openweathermap.org/data/2.5/weather'
OPEN_WEATHER_API_KEY = 'your open weather api key'

# Prepare parameters for API
parameters = {
    "lat": 49.282730,
    "lon": -123.120735,
    "appid": OPEN_WEATHER_API_KEY,
}
# Sends a GET request.
response = requests.get(
    OPEN_WEATHER_ENDPOINT, params=parameters)
# Raises HTTPError, if one occurred.
response.raise_for_status()
# Returns the json-encoded content of a response, if any.
res = response.json()

print(f"Current Wearther: {res['weather'][0]['main']}")


Ex: Check if it will rain in your city in the next 12 hours (weather id > 700 means it will rain)
import requests

OPEN_WEATHER_ENDPOINT = 'https://api.openweathermap.org/data/2.5/forecast'
OPEN_WEATHER_API_KEY = 'your open weather api key'

# Prepare parameters for API
parameters = {
    "lat": 49.282730,
    "lon": -123.120735,
    # Next 4 * 3 hours
    "cnt": 4,
    "units": "metric",
    "appid": OPEN_WEATHER_API_KEY,
}
# Sends a GET request.
response = requests.get(
    OPEN_WEATHER_ENDPOINT, params=parameters)
# Raises HTTPError, if one occurred.
response.raise_for_status()
# Returns the json-encoded content of a response, if any.
res = response.json()

# Filter data if its weather id is larger than 700
raining_in_12_hours = [item for item in res["list"]
                       if item["weather"][0]["id"] > 700]

if len(raining_in_12_hours) > 0:
    print("It is going to rain. Remember to bring an Umbrella!")



Sending SMS



Ex: 
import requests
from twilio.rest import Client

OPEN_WEATHER_ENDPOINT = 'https://api.openweathermap.org/data/2.5/forecast'
OPEN_WEATHER_API_KEY = 'your open weather api key'
TWILIO_ACCOUNT_SID = 'your twilio account sid'
TWILIO_AUTH_TOKEN = 'your twilio auth token'

# Prepare parameters for API
parameters = {
    "lat": 49.282730,
    "lon": -123.120735,
    # Next 4 * 3 hours
    "cnt": 4,
    "units": "metric",
    "appid": OPEN_WEATHER_API_KEY,
}
# Sends a GET request.
response = requests.get(
    OPEN_WEATHER_ENDPOINT, params=parameters)
# Raises HTTPError, if one occurred.
response.raise_for_status()
# Returns the json-encoded content of a response, if any.
res = response.json()
# Filter data if its weather id is larger than 700
raining_in_12_hours = [item for item in res["list"]
                       if item["weather"][0]["id"] > 700]

if len(raining_in_12_hours) > 0:
    client = Client(TWILIO_ACCOUNT_SID, TWILIO_AUTH_TOKEN)

    message = client.messages.create(
        body='It is going to rain. Remember to bring an Umbrella!',
        from_='twilio test number',
        to='your phone number'
    )

    # If there is a sid, SMS was sent successfully
    print(message.sid)



Environmental Variables



Normally, we don't want to put the web API token or key in our code. Reference

    export YOUR_ENV_NAME=your_env_value

Or Using python-dotenv

Ex: 
import requests
import os
from twilio.rest import Client
from dotenv import load_dotenv

# Load Environmental Variables from .env
load_dotenv()

OPEN_WEATHER_ENDPOINT = 'https://api.openweathermap.org/data/2.5/forecast'
OPEN_WEATHER_API_KEY = os.environ.get("OPEN_WEATHER_API_KEY")
TWILIO_ACCOUNT_SID = os.environ.get("TWILIO_ACCOUNT_SID")
TWILIO_AUTH_TOKEN = os.environ.get("TWILIO_AUTH_TOKEN")

# Prepare parameters for API
parameters = {
    "lat": 49.282730,
    "lon": -123.120735,
    # Next 4 * 3 hours
    "cnt": 4,
    "units": "metric",
    "appid": OPEN_WEATHER_API_KEY,
}
# Sends a GET request.
response = requests.get(
    OPEN_WEATHER_ENDPOINT, params=parameters)
# Raises HTTPError, if one occurred.
response.raise_for_status()
# Returns the json-encoded content of a response, if any.
res = response.json()
# Filter data if its weather id is larger than 700
raining_in_12_hours = [item for item in res["list"]
                       if item["weather"][0]["id"] > 700]

if len(raining_in_12_hours) > 0:
    client = Client(TWILIO_ACCOUNT_SID, TWILIO_AUTH_TOKEN)

    message = client.messages.create(
        body='It is going to rain. Remember to bring an Umbrella!',
        from_='twilio test number',
        to='your phone number'
    )

    # If there is a sid, SMS was sent successfully
    print(message.sid)


Wednesday, November 23, 2022

Python - Project - Quiz App (Day 34)

This is a 100 Days challenge to learn a new language (Python). 100 Days of Code - The Complete Python Pro Bootcamp 

I will post some notes to motivate myself to finish this challenge.


Project - Quiz App



Revise the Quiz Project in Day 17.
    
#1 Call Trivia API to get the question instead of hard-coded in txt file
    
#2 Use Tkinter to improve the User Experience

#3 Review OOP Design



Ex: data.py
import requests

# Prepare parameters for API
parameters = {
    "amount": 10,
    "category": 18, # Science - Computers
    "type": 'boolean',
}
# Sends a GET request.
response = requests.get("https://opentdb.com/api.php", params=parameters)
# Raises HTTPError, if one occurred.
response.raise_for_status()
# Returns the json-encoded content of a response, if any.
res= response.json()
question_data = res["results"]


Ex: question_model.py
class Question:

    def __init__(self, q_text, q_answer):
        self.text = q_text
        self.answer = q_answer


Ex: quiz_brain.py
import html

class QuizBrain:

    def __init__(self, q_list):
        self.question_number = 0
        self.score = 0
        self.question_list = q_list
        self.current_question = None

    def still_has_questions(self):
        return self.question_number < len(self.question_list)

    def next_question(self):
        self.current_question = self.question_list[self.question_number]
        self.question_number += 1
        question_text = html.unescape(self.current_question.text)

        return f"Q.{self.question_number}: {question_text} (True/False): "

    def check_answer(self, user_answer: str) -> bool:
        correct_answer = self.current_question.answer
       
        if user_answer.lower() == correct_answer.lower():
            self.score += 1
            return True

        return False


Ex: ui.py
import tkinter as tk
from quiz_brain import QuizBrain

THEME_COLOR = "#375362"


class QuizInterface:
    def __init__(self, quiz_brain: QuizBrain):
        self.quiz_brain = quiz_brain

        # Init Tkinter
        self.root = tk.Tk()
        self.root.title("Quizzler")
        self.root.config(padx=20, pady=20, background=THEME_COLOR)

        # Score Counter
        self.score_label = tk.Label(
            text="Score: 0",
            foreground='white',
            background=THEME_COLOR)
        self.score_label.grid(row=0, column=1)

        # Init canvas
        self.canvas = tk.Canvas(
            width=300, height=250, background='white', highlightthickness=0
        )
        # Question Text
        self.question_text = self.canvas.create_text(
            150, 125, width=280, text="",
            fill="black", font=('Arial', 15, "italic")
        )
        self.canvas.grid(row=1, column=0, columnspan=2, pady=50)

        # True Btn
        true_image = tk.PhotoImage(file="images/true.png")
        self.true_btn = tk.Button(
            image=true_image, command=self.true_btn_clicked)
        self.true_btn.config(
            background=THEME_COLOR,
            activebackground=THEME_COLOR)
        self.true_btn.grid(row=2, column=0)

        # False Btn
        false_image = tk.PhotoImage(file="images/false.png")
        self.false_btn = tk.Button(
            image=false_image, command=self.false_btn_clicked)
        self.false_btn.config(
            background=THEME_COLOR,
            activebackground=THEME_COLOR)
        self.false_btn.grid(row=2, column=1)

        # Get the next question
        self.get_next_question()

        # Start the Event Loop
        self.root.mainloop()

    def get_next_question(self):
        # Config white bg for canvas
        self.canvas.config(background='white')

        # No more questions
        if not self.quiz_brain.still_has_questions():
            # Show game over info
            self.canvas.itemconfig(self.question_text, text="No more questions")
            # Disable buttons
            self.true_btn.config(state='disabled')
            self.false_btn.config(state='disabled')
            return

        # Not finish all questions yet
        quiz_text = self.quiz_brain.next_question()
        self.canvas.itemconfig(self.question_text, text=quiz_text)

    def true_btn_clicked(self):
        self.show_feedback(self.quiz_brain.check_answer("true"))

    def false_btn_clicked(self):
        self.show_feedback(self.quiz_brain.check_answer("false"))

    def show_feedback(self, is_correct: bool):
        # Config bg of canvas based on the correction of answer
        if is_correct:
            self.canvas.config(background='green')
        else:
            self.canvas.config(background='red')

        # Update Score Label
        self.score_label.config(text=f"Score: {self.quiz_brain.score}")

        # 1 second delay show the next question
        self.root.after(1000, self.get_next_question)


Ex: main.py
from question_model import Question
from data import question_data
from quiz_brain import QuizBrain
from ui import QuizInterface

question_bank = []
for question in question_data:
    question_text = question["question"]
    question_answer = question["correct_answer"]
    new_question = Question(question_text, question_answer)
    question_bank.append(new_question)

quiz = QuizBrain(question_bank)
quiz_ui = QuizInterface(quiz)


Sunday, November 6, 2022

Python - API endpoint and parameters (Day 33)

This is a 100 Days challenge to learn a new language (Python). 100 Days of Code - The Complete Python Pro Bootcamp 

I will post some notes to motivate myself to finish this challenge.


Application Programming Interfaces



An Application Programming Interface (API) is a set of commands, functions, protocols, and objects that programmers can use to create software or interact with an external system.


HTTP Status Code



    1XX: Information
    2XX: Successful Response
    3XX: Redirection Message
    4XX: Client Error
    5XX: Server Error

Reference: Link


requests module



requests is an elegant and simple HTTP library for Python.


API Endpoint



It is an URL to tell our program that where to get data.

Ex: Get ISS Location through Web API
import requests

# Sends a GET request.
response = requests.get("http://api.open-notify.org/iss-now.json")

# Raises HTTPError, if one occurred.
response.raise_for_status()

# Returns the json-encoded content of a response, if any.
data = response.json()

print(data)


Result:
{
'iss_position': {'longitude': '37.1042', 'latitude': '-44.8634'},
'message': 'success', 'timestamp': 1667726746
}



Challenge - Kanye Quote App



Use Tkinter to build a GUI app, and integrate it with kanye.rest Web API to get the quote once the emoji is clicked.




Ex: 
from tkinter import *
import requests


def get_quote():
    """Get quote from kanye.rest web api"""

    # Sends a GET request.
    response = requests.get("https://api.kanye.rest/")

    # Raises HTTPError, if one occurred.
    response.raise_for_status()

    # Returns the json-encoded content of a response, if any.
    data = response.json()

    # Update canvas text
    canvas.itemconfig(quote_text, text=data["quote"])


# Init
root = Tk()
root.title("Kanye Says...")
root.config(padx=50, pady=50)

# Init canvas
canvas = Canvas(width=300, height=414)
background_img = PhotoImage(file="background.png")
canvas.create_image(150, 207, image=background_img)
quote_text = canvas.create_text(
    150,
    207,
    text="Kanye Quote Goes HERE",
    width=250,
    font=("Arial", 30, "bold"),
    fill="white",
)
canvas.grid(row=0, column=0)

# Button
kanye_img = PhotoImage(file="kanye.png")
kanye_button = Button(image=kanye_img, highlightthickness=0,
command=get_quote)
kanye_button.grid(row=1, column=0)

# Start the Event Loop
root.mainloop()



API Parameters



Able to give input to the Web API and get the data back based on the input.

Ex: Get sunrise and sutset info by the current location
import requests

MY_LAT = 49.282730
MY_LNG = -123.120735

# Prepare parameters for API
parameters = {"lat": MY_LAT, "lng": MY_LNG, "formatted": 0}

# Sends a GET request.
response = requests.get("https://api.sunrise-sunset.org/json",
params=parameters)

# Raises HTTPError, if one occurred.
response.raise_for_status()

# Returns the json-encoded content of a response, if any.
data = response.json()

print(data)


Result:
{
'results': {
'sunrise': '2022-11-06T15:07:22+00:00',
'sunset': '2022-11-07T00:44:52+00:00',
'solar_noon': '2022-11-06T19:56:07+00:00',
'day_length': 34650,
'civil_twilight_begin': '2022-11-06T14:35:13+00:00',
'civil_twilight_end': '2022-11-07T01:17:01+00:00',
'nautical_twilight_begin': '2022-11-06T13:57:13+00:00',
'nautical_twilight_end': '2022-11-07T01:55:01+00:00',
'astronomical_twilight_begin': '2022-11-06T13:20:05+00:00',
'astronomical_twilight_end': '2022-11-07T02:32:09+00:00'
}, 'status': 'OK'
}



Project - ISS Overhead Notifier



#1 Get ISS location by Web API
http://open-notify.org/Open-Notify-API/ISS-Location-Now/

#2 Get your city Location
https://www.latlong.net/

#3 Determine it is night by Web API
https://sunrise-sunset.org/api

#4 If ISS is overhead and it is night time, send an eamil to yourself to notify you to look up

#5 Run #4 every 60 seconds

Ex:
import requests
from datetime import datetime
import smtplib
import time

GMAIL_SMTP_SERVER_ADDRESS = "smtp.gmail.com"
GMAIL_SMTP_TLS_PORT = 587
GOOGLE_APP_PWD = "your google app pwd"
MY_EMAIL_ADDRESS = "your email address"
MY_LAT = 49.282730
MY_LONG = -123.120735


def send_email(receiver, message):
    """Send an custom email to the receiver"""

    # Create a connection to SMTP Provider
    with smtplib.SMTP(
        host=GMAIL_SMTP_SERVER_ADDRESS, port=GMAIL_SMTP_TLS_PORT
    ) as connection:

        # Make this connection secure
        connection.starttls()

        # Login with your Google App Password
        connection.login(user=MY_EMAIL_ADDRESS, password=GOOGLE_APP_PWD)

        # Send an email
        connection.sendmail(from_addr=MY_EMAIL_ADDRESS,
to_addrs=receiver, msg=message)


def get_iss_location():
    """Get ISS current location from web api"""

    # Sends a GET request.
    response = requests.get(url="http://api.open-notify.org/iss-now.json")
    # Raises HTTPError, if one occurred.
    response.raise_for_status()
    # Returns the json-encoded content of a response, if any.
    return response.json()


def is_iss_overhead():
    """Determine if ISS is overhead or not"""

    data = get_iss_location()
    iss_latitude = float(data["iss_position"]["latitude"])
    iss_longitude = float(data["iss_position"]["longitude"])

    # Your position is within +5 or -5 degrees of the ISS position.
    if (
        iss_latitude - 5 <= MY_LAT <= iss_latitude + 5
        and iss_longitude - 5 <= MY_LONG <= iss_longitude + 5
    ):
        return True

    return False


def get_sunrise_and_sunset_info(lat, lng):
    """Get the sunrise and sunset info from web api"""

    # Prepare parameters for API
    parameters = {
        "lat": lat,
        "lng": lng,
        "formatted": 0,
    }
    # Sends a GET request.
    response = requests.get("https://api.sunrise-sunset.org/json",
params=parameters)
    # Raises HTTPError, if one occurred.
    response.raise_for_status()
    # Returns the json-encoded content of a response, if any.
    return response.json()


def is_night_time():
    """Determine if it is night time from the current location"""

    data = get_sunrise_and_sunset_info(MY_LAT, MY_LONG)
    sunrise = int(data["results"]["sunrise"].split("T")[1].split(":")[0])
    sunset = int(data["results"]["sunset"].split("T")[1].split(":")[0])

    time_now = datetime.now()

    # If it is dard, then send an email to myself (Look Up)
    if time_now.hour < sunrise or time_now.hour > sunset:
        return True

    return False


while True:
    # Delay 60 seconds
    time.sleep(60)

    # Send an email if iss is overhead and it is night time
    if is_iss_overhead() and is_night_time():
        send_email(
            MY_EMAIL_ADDRESS,
            "Subject: Look Up\n\nISS is overhead!\n",
        )