Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
.env
97 changes: 57 additions & 40 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,40 +1,57 @@
# API-Stock-Signalling
###### NOTE: this is a project to practice your Python industry skills. Please upload your solutions by opening a new branch to the main. All files that are instantly merged to the main will be deleted.
## Overview
A small consultancy who is currently delivering client work for a major bank wants to monitor the prices for the following stocks: Tesla, Apple, Microsoft, Google, and Nike. Once the stock falls below a certain price, they want to be immediately notified so that they can purchase more stocks.
## Goal
Write a real time Python application that monitors the prices then notifies the user when the price of any of these stocks falls by at least £0.25 GBP. The user should also be notified if today's price is less than the 7-day average of that stock price.
## Brief
To do this, the client has recommended using a popular automation website called IFTTT: https://ifttt.com/. They are willing to use a different provider or solution (use those consultancy skills!) if you believe there is a better option.
#### IFTTT Applet

IFTTT stands for “If This Then That” and it’s an automation platform that allows you to connect different apps and services together. An applet is a connection between two or more apps or devices that enables you to do something that those services couldn’t do on their own. Applets consists of two parts triggers and actions. Triggers tell an applet to start, and actions are the end result of an applet run. To use an applet, you’ll need to create a free IFTTT account and connect your apps and devices to IFTTT so that they can talk to each other.

#### Proposed Workflow
- Write a script that returns the stock prices. This will involve making an API request.
- Set up a IFTTT account and applet. This will be accessible via the mobile app which allows you to trigger the webhook service provided by IFTTT.
- You will need to configure the 'webhooks' service to receive web requests. You can find more details here: https://ifttt.com/maker_webhooks.
- From here you can write an application that utilises the requests package to make POST and GET requests.
- Think of what aspects or components of your proposed solution needs to be tested and what would these tests look like and attempt to implement such tests.

## Main Considerations
- Choose an automation approach. Are you planning on using IFTTT or another workflow?
- What API are you going to use? You can use this as a starting point: https://github.com/public-apis/public-apis
- Remember, this is a proposed workflow. If you believe you have a more efficient approach please reach out to the Academy Team.
#### Requirements Gathering
The start to any project is to make sure you have clear and well-defined requirements for your project. Most projects start with a vague idea of what the stakeholder wants, and as a consultant, we will never have as much knowledge about their problem/business context as they do. Therefore, we need to get as much information out of them as possible, as they will subconsciously assume that we know everything. For this project, Alex Naylor will be the stakeholder.

If you don't know the answer to any question then you should always ask - NEVER ASSUME. This will only risk the accuracy of your work and end up having to do everything all over again if you wrongly assume.

Questions to ask yourself constantly throughout the project are:

- What is the purpose of this project, why does the stakeholder want this and what is the desired outcome of the project?
- Is there any extra info that the stakeholder could tell you to help tailor the project to what they want?

## Assessment
For the assessment, you will have a 15 minute technical interview. This will consist of a strict 5 minute presentation on your technical solution. There is no need to create slides for this but you may want to demo your code. For the second half of the session, you will be asked technical questions related to the project. You will be assessed on:
- Project Complexity
- Brief Completness i.e. have you managed to meet the client brief?
- Coding Standards

Good Luck!
# First things first
1. "AAPL", "MSFT", "GOOGL", "TSLA", "NKE" and 0.25 are default settings for companies and drop level. You can change those at your own discretion.
2. Create .env file locating it in your current working directory
3. Make sure .gitignore has .env
4. Inside .env set your email details:

EMAIL_USER=here_is_your_gmail_account@gmail.com
RECEIVER_EMAIL=here_is_your_gmail_account@gmail.com
EMAIL_PASS=here past your pass (see below)

5. Proceed with adding password inside .env following the procedure below

# Create & use app passwords

To help keep your account secure, use "Sign in with Google" to connect apps to your Google Account

1. Go to your Google Account.
2. Select Security.
3. Under "How you sign in to Google," select 2-Step Verification.
4. At the bottom of the page, select App passwords.
5. Enter a name that helps you remember where you’ll use the app password.
6. Select Generate and follow the instructions on your screen. The app password is the 16-character code that generates on your device.

Paste it in .env file replacing here past your pass with whatever 16-character you got:

EMAIL_PASS=here past your pass

# Stock exchange working hours
Our example is looking at "AAPL", "MSFT", "GOOGL", "TSLA", "NKE", which are listed in US. Since stock exchange is not trading 24/7, our watcher will see no change during closed time.
You could replace the tickers with any other companies listed at Stock Exchanges located in working time zones, just double-check at Yahoo finance the ticker is correct.


# Why smtplib over IFTTT
Following YAGNI principle we believe the requirement of sending notification via email provides exactly what we need. For this task smtplib offers more direct and controllable solution, also it is free.

Considering further versions, IFTTT would be chosen for additional web notification solutions, such as Facebook, Telegram, SMS etc.

# Why yfinance over API
yfinance is a library for fetching market data from Yahoo Finance. It provides a simpler interface for fetching stock data compared to making HTTP requests to an API, parsing the responses, and handling errors.

Given that we are planning to run the script every minute, this would total to a max of 1,440 requests per day (assuming one request per run). This falls well within the documented rate limits for the public API based on IP address (48,000 requests per day)

# Create & use app passwords

To help keep your account secure, use "Sign in with Google" to connect apps to your Google Account

1. Go to your Google Account.
2. Select Security.
3. Under "Signing in to Google," select 2-Step Verification.
4. At the bottom of the page, select App passwords.
5. Enter a name that helps you remember where you’ll use the app password.
6. Select Generate and follow the instructions on your screen. The app password is the 16-character code that generates on your device.

Paste it in .env file replacing here past your pass with whatever 16-character you got:
EMAIL_PASS=here past your pass

"AAPL", "MSFT", "GOOGL", "TSLA", "NKE"
90 changes: 90 additions & 0 deletions main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
"""
This script monitors specified stocks, compares their prices with historical averages,
and sends email notifications when certain conditions are met.
"""

#Import the necessary libraries and modules
import yfinance as yf
import smtplib
import schedule
import time
import os
from forex_python.converter import CurrencyRates
from dotenv import load_dotenv
from statistics import mean

# Load environment variable from .env file
load_dotenv()

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why would you need to do that? You should be more specific with your documentation


# Initialize currency converter
currency_converter = CurrencyRates()

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thats cool, never thought to get a currency converter library. Saves lines of code 👍


# Define the stocks to monitor and the email details
stocks_to_monitor = ["AAPL", "MSFT", "GOOGL", "TSLA", "NKE"]

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you want your code to align with pep8 coding conventions, this variable name should all be in caps. This will help your peers understand that it is a constant and it will not be changed

previous_prices = {ticker: 0 for ticker in stocks_to_monitor}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is that the initial dictionary? Will this be updated throughout the running of the code?


#Set-up your own environment .env (README)
email_user = os.environ.get('EMAIL_USER')
receiver_email = os.environ.get('RECEIVER_EMAIL')

#Error Handling (in case user forgets setring-up .env)
try:

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Excellent you used try-catch blocks to catch errors, a good habit to take in

email_pass = os.environ['EMAIL_PASS']
except KeyError:
print("EMAIL_PASS environment variable not found. Check README")
exit(1)

# Return a yfinance Ticker object for the given symbol
def process_tickers():
for ticker in stocks_to_monitor:
stock = yf.Ticker(ticker) # Directly instantiate yf.Ticker within the loop
check_seven_day_avg(stock, ticker)
check_one_minute_price_change(stock, ticker)

# Handling the price retrieval and conversion to GBP
def get_current_price(stock):
current_price_usd = stock.info['currentPrice']
current_price_gbp = currency_converter.convert('USD', 'GBP', current_price_usd)
return current_price_gbp

# Fetch the last 7 days of price data, calculate the average and prepare message
def check_seven_day_avg(stock, ticker):
hist = stock.history(period="7d")
avg = mean(hist["Close"])

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like how you added a statistics package to calculate the mean rather than writing functions for it

avg_gbp = currency_converter.convert('USD', 'GBP', avg)

current_price_gbp = get_current_price(stock)
if current_price_gbp < avg_gbp:
subject = f'Price Alert for {ticker}'
body = f'The current price of {ticker} is below the 7-day average, at {current_price_gbp} GBP.'
send_notification(subject, body)

def check_one_minute_price_change(stock, ticker):
current_price_gbp = get_current_price(stock)
previous_price_gbp = currency_converter.convert('USD', 'GBP', previous_prices[ticker])
diff = previous_price_gbp - current_price_gbp
if diff >= 0.01:

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why is it 0.01 instead of 0.25? It looks as if you converted to GBP, and the criteria says to notify user when stock price drops by at least 25p

subject = f'Price Drop Alert for {ticker}'

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Very precise variable names

body = f'The price of {ticker} has dropped to {current_price_gbp} GBP.'
send_notification(subject, body)

# Store the price in USD for the next check
previous_prices[ticker] = stock.info['currentPrice'] # Fetching the current price in USD again.

# Send an email notification
def send_notification(subject, body):

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I should try these technologies instead of IFTTT, is it free? Do you need a subscription?

try:
with smtplib.SMTP('smtp.gmail.com', 587) as server:
server.starttls()
server.login(email_user, email_pass)
msg = f'Subject: {subject}\n\n{body}'
server.sendmail(email_user, receiver_email, msg)
except Exception as e:
print(f"Error sending email: {e}")

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good error messages format, something that I have been lazy on


# Schedule the process_tickers function to run every minute
schedule.every(1).minutes.do(process_tickers)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will this only run as long as you run the code on the command line? What if you don't want to have your laptop on for this but keep this code running? What would you do?


while True:
schedule.run_pending()
time.sleep(1)