From c0cd35222b105623c149a45ed0e74449c2e2b400 Mon Sep 17 00:00:00 2001 From: Arjun Narayanan Date: Fri, 13 Oct 2023 10:10:41 +0100 Subject: [PATCH 1/3] Arjun test commit --- stock_signaller.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 stock_signaller.py diff --git a/stock_signaller.py b/stock_signaller.py new file mode 100644 index 0000000..e69de29 From 1fca792899a0dcba8d7eb259d8e911a3cf028394 Mon Sep 17 00:00:00 2001 From: Arjun Narayanan Date: Fri, 13 Oct 2023 12:22:45 +0100 Subject: [PATCH 2/3] Arjun Final Commit --- .idea/.gitignore | 3 + .idea/API-Stock-Signalling.iml | 8 ++ .../inspectionProfiles/profiles_settings.xml | 6 + .idea/misc.xml | 4 + .idea/modules.xml | 8 ++ .idea/vcs.xml | 6 + stock_signaller.py | 116 ++++++++++++++++++ test_stock_signaller.py | 74 +++++++++++ 8 files changed, 225 insertions(+) create mode 100644 .idea/.gitignore create mode 100644 .idea/API-Stock-Signalling.iml create mode 100644 .idea/inspectionProfiles/profiles_settings.xml create mode 100644 .idea/misc.xml create mode 100644 .idea/modules.xml create mode 100644 .idea/vcs.xml create mode 100644 test_stock_signaller.py diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..26d3352 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,3 @@ +# Default ignored files +/shelf/ +/workspace.xml diff --git a/.idea/API-Stock-Signalling.iml b/.idea/API-Stock-Signalling.iml new file mode 100644 index 0000000..d0876a7 --- /dev/null +++ b/.idea/API-Stock-Signalling.iml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/inspectionProfiles/profiles_settings.xml b/.idea/inspectionProfiles/profiles_settings.xml new file mode 100644 index 0000000..105ce2d --- /dev/null +++ b/.idea/inspectionProfiles/profiles_settings.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..a971a2c --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..c64dab5 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..94a25f7 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/stock_signaller.py b/stock_signaller.py index e69de29..248caae 100644 --- a/stock_signaller.py +++ b/stock_signaller.py @@ -0,0 +1,116 @@ +import requests + +# Stock API access parameters +BASE_URL = "https://api.twelvedata.com" +API_KEY = "a234819a7d95480cac20af90c630e0b9" + +# IFTTT named events that trigger email sending +EVENT_WEEKLY_AVERAGE = "price_less_than_weekly_average" +EVENT_PRICE_DROP = "stock_price_25p_drop" + +# IFTTT general webhook URL --> different event names for either stock +# price event +IFTTT_WEBHOOK_URL = ( + "https://maker.ifttt.com/trigger/{event_name}/json/with/key/i9fIToN8DAw1mChzvPMK1" +) + + +def get_exchange_rate(): + """ + Obtains and returns the latest USD to GBP exchange rate from + TwelveData API + """ + endpoint = ( + f"{BASE_URL}/time_series?" + f"symbol=USD/GBP&interval=1min&apikey={API_KEY}" + ) + response = requests.get(endpoint) + data = response.json() + latest_data = data['values'][0] + return float(latest_data['close']) + + +def get_stock_price(company, exchange_rate): + """ + Obtains and returns the current stock price for desired stock + """ + endpoint = ( + f"{BASE_URL}/time_series?" + f"symbol={company}&interval=1min&apikey={API_KEY}" + ) + response = requests.get(endpoint) + data = response.json() + latest_data = data['values'][0] + usd_price = float(latest_data['close']) + return usd_price * exchange_rate + + +def get_previous_stock_price(company, exchange_rate): + """ + Obtains and returns the stock price for desired stock from 1 minute + ago + """ + endpoint = ( + f"{BASE_URL}/time_series?" + f"symbol={company}&interval=1min&apikey={API_KEY}" + ) + + response = requests.get(endpoint) + data = response.json() + previous_data = data['values'][1] + usd_price = float(previous_data['close']) + return usd_price * exchange_rate + + +def get_seven_day_average(company, exchange_rate): + """ + Obtains and returns the stock price of the desired stock, at market + closing time from past seven days and calculates weekly average + """ + endpoint = ( + f"{BASE_URL}/time_series?" + f"symbol={company}&interval=1day&apikey={API_KEY}" + ) + response = requests.get(endpoint) + data = response.json() + last_seven_days = data['values'][:7] + total = 0 + for day in last_seven_days: + total += float(day['close']) * exchange_rate + return total / 7 + + +def send_notification(event_name, stock_symbol): + """ + Sends notification to relevant IFTTT applet + """ + url = IFTTT_WEBHOOK_URL.format(event_name=event_name) + data = {"Stock Name": stock_symbol} + requests.post(url, json=data) + + +def check_and_notify(company): + """ + Implements all requests to TwelveData API and defines stock + price conditionals to be checked + """ + exchange_rate = get_exchange_rate() + current_price = get_stock_price(company, exchange_rate) + previous_price = get_previous_stock_price(company, exchange_rate) + seven_day_average = get_seven_day_average(company, exchange_rate) + + if current_price < seven_day_average: + send_notification(EVENT_WEEKLY_AVERAGE, company) + print(f"Notification sent for {EVENT_WEEKLY_AVERAGE} - {company}") + + if (previous_price - current_price) > 0.25: + send_notification(EVENT_PRICE_DROP, company) + print(f"Notification sent for {EVENT_PRICE_DROP} - {company}") + + +if __name__ == "__main__": + stocks_to_check = ['AAPL', 'TSLA'] + for stock in stocks_to_check: + check_and_notify(stock) + + diff --git a/test_stock_signaller.py b/test_stock_signaller.py new file mode 100644 index 0000000..7fe5e00 --- /dev/null +++ b/test_stock_signaller.py @@ -0,0 +1,74 @@ +import unittest +import stock_signaller + + +class TestStockFunctions(unittest.TestCase): + + def test_get_exchange_rate(self): + """ + Test if exchange rate function returns a float + """ + result = stock_signaller.get_exchange_rate() + self.assertIsInstance(result, float) + + def test_get_stock_price(self): + """ + Test if immedate stock price function returns a float + """ + result = stock_signaller.get_stock_price('AAPL', 1.5) + self.assertIsInstance(result, float) + + def test_get_previous_stock_price(self): + """ + Test if 1 minute prior stock price function returns a float + """ + result = stock_signaller.get_previous_stock_price('AAPL', 1.5) + self.assertIsInstance(result, float) + + def test_seven_day_average_return_type(self): + """ + Test if seven-day stock price average function returns a float + """ + result = stock_signaller.get_seven_day_average('AAPL', 1.5) + self.assertIsInstance(result, float) + + def test_seven_day_average_calculation(self): + """ + Test the calculation of the seven-day average + """ + symbol = 'AAPL' + exchange_rate = stock_signaller.get_exchange_rate() + # personally calculating the expected seven-day average + endpoint = ( + f"{stock_signaller.BASE_URL}/time_series?" + f"symbol={symbol}&interval=1day&apikey={stock_signaller.API_KEY}" + ) + response = stock_signaller.get(endpoint) + data = response.json() + last_seven_days = data['values'][:7] + total = 0 + for day in last_seven_days: + total += float(day['close']) * exchange_rate + expected_average = total / 7 + # compare it with the returned value from the function + result = stock_signaller.get_seven_day_average(symbol, exchange_rate) + self.assertAlmostEqual(result, expected_average, places=2) + + def test_exchange_rate_multiplication(self): + """ + Test the multiplication by the exchange rate + """ + symbol = 'AAPL' + exchange_rate = stock_signaller.get_exchange_rate() + # get a stock price in USD + usd_price = stock_signaller.get_stock_price(symbol, 1) + # manually calculate expected value after exchange + # rate multiplication + expected_value = usd_price * exchange_rate + # compare it with the returned value from the function + result = stock_signaller.get_stock_price(symbol, exchange_rate) + self.assertAlmostEqual(result, expected_value, places=2) + + + + From 9c5bbc31d9990f98042e2e9b693ae91eb14ded4c Mon Sep 17 00:00:00 2001 From: Arjun Narayanan Date: Fri, 13 Oct 2023 12:57:01 +0100 Subject: [PATCH 3/3] Arjun Final Commit v2 --- stock_signaller.py | 3 +++ test_stock_signaller.py | 20 ++++++++++---------- 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/stock_signaller.py b/stock_signaller.py index 248caae..aa806a0 100644 --- a/stock_signaller.py +++ b/stock_signaller.py @@ -113,4 +113,7 @@ def check_and_notify(company): for stock in stocks_to_check: check_and_notify(stock) +# stocks_to_check = ['MSFT', 'GOOGL', 'AAPL', 'NKE', 'TSLA'] + + diff --git a/test_stock_signaller.py b/test_stock_signaller.py index 7fe5e00..07d7a03 100644 --- a/test_stock_signaller.py +++ b/test_stock_signaller.py @@ -6,35 +6,35 @@ class TestStockFunctions(unittest.TestCase): def test_get_exchange_rate(self): """ - Test if exchange rate function returns a float + Tests if exchange rate function returns a float """ result = stock_signaller.get_exchange_rate() self.assertIsInstance(result, float) def test_get_stock_price(self): """ - Test if immedate stock price function returns a float + Tests if immedate stock price function returns a float """ result = stock_signaller.get_stock_price('AAPL', 1.5) self.assertIsInstance(result, float) def test_get_previous_stock_price(self): """ - Test if 1 minute prior stock price function returns a float + Tests if 1 minute prior stock price function returns a float """ result = stock_signaller.get_previous_stock_price('AAPL', 1.5) self.assertIsInstance(result, float) def test_seven_day_average_return_type(self): """ - Test if seven-day stock price average function returns a float + Tests if seven-day stock price average function returns a float """ result = stock_signaller.get_seven_day_average('AAPL', 1.5) self.assertIsInstance(result, float) def test_seven_day_average_calculation(self): """ - Test the calculation of the seven-day average + Tests the calculation of the seven-day average """ symbol = 'AAPL' exchange_rate = stock_signaller.get_exchange_rate() @@ -50,22 +50,22 @@ def test_seven_day_average_calculation(self): for day in last_seven_days: total += float(day['close']) * exchange_rate expected_average = total / 7 - # compare it with the returned value from the function + # compared it with the returned value from the function result = stock_signaller.get_seven_day_average(symbol, exchange_rate) self.assertAlmostEqual(result, expected_average, places=2) def test_exchange_rate_multiplication(self): """ - Test the multiplication by the exchange rate + Tests the multiplication by the exchange rate """ symbol = 'AAPL' exchange_rate = stock_signaller.get_exchange_rate() - # get a stock price in USD + # gets a stock price in USD usd_price = stock_signaller.get_stock_price(symbol, 1) - # manually calculate expected value after exchange + # manually calculates expected value after exchange # rate multiplication expected_value = usd_price * exchange_rate - # compare it with the returned value from the function + # compared it with the returned value from the function result = stock_signaller.get_stock_price(symbol, exchange_rate) self.assertAlmostEqual(result, expected_value, places=2)