Repository: AlgoETS/BullETS (language: python)

Issue:
Add utils for general functions
Move the following function to a new utils class:
- is_market_open()

Add a new function to the utils class:
- A function that gets the date that is x amount of market day(s) away


Discussion / hints:

Fix patch:
diff --git a/.gitignore b/.gitignore
index 84b9323..294b91f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -129,4 +129,4 @@ dmypy.json
 .pyre/
 
 # Pycharm User-specific stuff
-.idea
\ No newline at end of file
+.idea
diff --git a/src/bullets/utils/market_utils.py b/src/bullets/utils/market_utils.py
new file mode 100644
index 0000000..43b9818
--- /dev/null
+++ b/src/bullets/utils/market_utils.py
@@ -0,0 +1,42 @@
+from datetime import datetime, timedelta, date
+from bullets.data_source.data_source_interface import Resolution
+from bullets.utils.holiday_date_util import us_holiday_list
+
+
+def is_market_open(date: datetime, resolution: Resolution = Resolution.DAILY) -> bool:
+    """
+    Returns if the market is open on a given day
+    Args:
+        date: Checks if the market is open on the date given
+        resolution: Will check if market is open at a specific time if the resolution demands it
+    Returns: True if the market is open, else False
+    """
+    if date.weekday() >= 5:
+        return False
+
+    if resolution != Resolution.DAILY:
+        if date.hour < 9 or date.hour > 16:
+            return False
+        elif date.hour == 16 and date.minute > 0:
+            return False
+        elif date.hour == 9 and date.minute < 30:
+            return False
+
+    return date not in us_holiday_list(date.year)
+
+
+def get_date_in_x_market_days_away(lapse: int, start_date: datetime = datetime.today()) -> date:
+    """
+    Gets the date that is x amount of market day(s) away
+    Args:
+        lapse: Number of days wanted
+        start_date: date at which the function starts, default: present day
+    Returns: The date that is the amount of market days away in date format
+    """
+    countdown = lapse
+    today = start_date
+    while countdown > 0:
+        today = today + timedelta(days=+1)
+        if is_market_open(today):
+            countdown -= 1
+    return today


Test patch:
diff --git a/src/test/test_market.py b/src/test/test_market.py
new file mode 100644
index 0000000..b64eefe
--- /dev/null
+++ b/src/test/test_market.py
@@ -0,0 +1,80 @@
+import unittest
+
+from bullets import logger
+from bullets.utils.market_utils import *
+
+
+class TestMarket(unittest.TestCase):
+    def test_invalid_weekday_sub_daily_market_open(self):
+        try:
+            date = datetime(2019, 1, 16, 4, 30, 20)
+            self.assertEqual(False, is_market_open(date, Resolution.MINUTE))
+            logger.info("Invalid weekday sub daily market open test successful")
+        finally:
+            pass
+
+    def test_valid_weekday_daily_market_open(self):
+        try:
+            date = datetime(2022, 2, 1, 1, 30, 20)
+            self.assertEqual(True, is_market_open(date, Resolution.DAILY))
+            logger.info("Valid weekday daily market open test successful")
+        finally:
+            pass
+
+    def test_valid_weekday_sub_daily_market_open(self):
+        try:
+            date = datetime(2022, 2, 2, 13, 30, 20)
+            self.assertEqual(True, is_market_open(date, Resolution.MINUTE))
+            logger.info("Valid weekday sub daily market open test successful")
+        finally:
+            pass
+
+    def test_invalid_weekend_daily_market_open(self):
+        try:
+            date = datetime(2022, 2, 3, 1, 30, 20)
+            self.assertEqual(True, is_market_open(date, Resolution.DAILY))
+            logger.info("Invalid weekend daily market open test successful")
+        finally:
+            pass
+
+    def test_invalid_weekend_subdaily_market_open(self):
+        try:
+            date = datetime(202, 2, 5, 4, 30, 20)
+            self.assertEqual(False, is_market_open(date, Resolution.MINUTE))
+            logger.info("Invalid weekend subdaily market open test successful")
+        finally:
+            pass
+
+    def test_days_away_same_week(self):
+        try:
+            start_date = datetime(2022, 2, 7)
+            end_date = datetime(2022, 2, 11)
+            days_away = 4
+            self.assertEqual(True, get_date_in_x_market_days_away(days_away, start_date) == end_date)
+            logger.info("Return date 4 days away same week test successful")
+        finally:
+            pass
+
+    def test_days_away_over_weekend(self):
+        try:
+            start_date = datetime(2022, 2, 7)
+            end_date = datetime(2022, 2, 14)
+            days_away = 5
+            self.assertEqual(True, get_date_in_x_market_days_away(days_away, start_date) == end_date)
+            logger.info("Return date 5 days away over the weekend test successful")
+        finally:
+            pass
+
+    def test_days_away_over_holiday(self):
+        try:
+            start_date = datetime(2022, 2, 18)
+            end_date = datetime(2022, 2, 22)
+            days_away = 1
+            self.assertEqual(True, get_date_in_x_market_days_away(days_away, start_date) == end_date)
+            logger.info("Return date 2 days away over holiday test successful")
+        finally:
+            pass
+
+
+if __name__ == '__main__':
+    unittest.main()