Skip to content

Commit

Permalink
refactor: ♻️ Using generated logger and unified ForexData fetch()
Browse files Browse the repository at this point in the history
  • Loading branch information
awhipp committed Apr 29, 2024
1 parent 89f0813 commit 5d45e59
Show file tree
Hide file tree
Showing 12 changed files with 71 additions and 87 deletions.
12 changes: 3 additions & 9 deletions foresight/indicator_services/indicator.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,17 @@

import datetime
import json

# Setup logging and log timestamp prepend
import logging
import time

import pandas as pd
from boto3_type_annotations.sqs import Client
from utils.aws import get_client
from utils.database import TimeScaleService

from foresight.utils.logger import generate_logger


logging.basicConfig(
format="%(asctime)s %(levelname)-8s %(message)s",
level=logging.INFO,
datefmt="%Y-%m-%d %H:%M:%S",
)
logger = logging.getLogger(__name__)
logger = generate_logger(name=__name__)


class Indicator:
Expand Down
14 changes: 5 additions & 9 deletions foresight/indicator_services/moving_average_indicator.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,12 @@
# Setup logging and log timestamp prepend
import logging
"""Moving average indicator class"""

import pandas as pd
from indicator import Indicator

from foresight.indicator_services.indicator import Indicator
from foresight.utils.logger import generate_logger

logging.basicConfig(
format="%(asctime)s %(levelname)-8s %(message)s",
level=logging.INFO,
datefmt="%Y-%m-%d %H:%M:%S",
)
logger = logging.getLogger(__name__)

logger = generate_logger(name=__name__)


class MovingAverageIndicator(Indicator):
Expand Down
10 changes: 2 additions & 8 deletions foresight/interface_service/app.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
# app.py

import json
import logging
import os

import dotenv
Expand All @@ -10,20 +9,15 @@
from flask import render_template

from foresight.utils.database import TimeScaleService
from foresight.utils.logger import generate_logger


app = Flask(__name__)

# Load environment variables
dotenv.load_dotenv()

# Setup logging and log timestamp prepend
logging.basicConfig(
format="%(asctime)s %(levelname)-8s %(message)s",
level=logging.INFO,
datefmt="%Y-%m-%d %H:%M:%S",
)
logger = logging.getLogger(__name__)
logger = generate_logger(name=__name__)


def get_latest() -> list[dict]:
Expand Down
2 changes: 1 addition & 1 deletion foresight/stream_service/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,9 @@
import dotenv
import requests

from foresight.stream_service.models.forex_data import ForexData
from foresight.stream_service.models.stream import Stream
from foresight.utils.logger import generate_logger
from foresight.utils.models.forex_data import ForexData


logger = generate_logger(__name__)
Expand Down
2 changes: 1 addition & 1 deletion foresight/stream_service/models/stream.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@

from pydantic import BaseModel

from foresight.stream_service.models.forex_data import ForexData
from foresight.stream_service.models.pricing import Pricing
from foresight.utils.models.forex_data import ForexData


class Stream(BaseModel):
Expand Down
10 changes: 3 additions & 7 deletions foresight/utils/database.py
Original file line number Diff line number Diff line change
@@ -1,22 +1,18 @@
"""Provides a singleton class to interact with the TimescaleDB database."""

import logging
import os

import dotenv
import psycopg2
import psycopg2.extras

from foresight.utils.logger import generate_logger


dotenv.load_dotenv(".env")


logging.basicConfig(
format="%(asctime)s %(levelname)-8s %(message)s",
level=logging.INFO,
datefmt="%Y-%m-%d %H:%M:%S",
)
logger = logging.getLogger(__name__)
logger = generate_logger(name=__name__)


class TimeScaleService:
Expand Down
Empty file.
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,16 @@

from datetime import datetime

import pandas as pd
from pydantic import BaseModel

from foresight.utils.database import TimeScaleService
from foresight.utils.logger import generate_logger


logger = generate_logger(name=__name__)

timeMap: dict = {"S": "second", "M": "minute", "H": "hour", "D": "day"}


class ForexData(BaseModel):
Expand Down Expand Up @@ -71,3 +78,42 @@ def drop_table(table_name: str = "forex_data"):

# Execute SQL queries here
TimeScaleService().execute(query=f"DROP TABLE {table_name}")

@staticmethod
def fetch(instrument: str = "EUR_USD", timescale: str = "M") -> pd.DataFrame:
"""
Fetch all data from the database and return a DataFrame.
Parameters:
instrument (str): The instrument to fetch
timescale (str): The timescale to fetch (S = Second, M = Minute)
Returns:
dict: The data from the database
"""
try:
df = pd.DataFrame(
TimeScaleService().execute(
query=f"""
SELECT
TO_CHAR(
date_trunc(
'{timeMap[timescale.lower()]}', time
),
'YYYY-MM-DD HH24:MI:SS'
) as time,
AVG(ask) as ask, AVG(bid) as bid
FROM forex_data
WHERE instrument = '{instrument}'
AND time >= NOW() - INTERVAL '60 minute'
GROUP BY time
ORDER BY time ASC
""",
),
)

return (
df.groupby(df["time"]).agg({"ask": "mean", "bid": "mean"}).reset_index()
)
except Exception as fetch_exception: # pylint: disable=broad-except
logger.error("Error fetching data: %s", fetch_exception)
58 changes: 8 additions & 50 deletions foresight/window_service/app.py
Original file line number Diff line number Diff line change
@@ -1,62 +1,21 @@
"""Aggregates the data from the database and calculates one-minute averages."""

import logging
import time

import pandas as pd
from boto3_type_annotations.sqs import Client

from foresight.utils.aws import get_client
from foresight.utils.database import TimeScaleService
from foresight.utils.logger import generate_logger
from foresight.utils.models.forex_data import ForexData


logging.basicConfig(
format="%(asctime)s %(levelname)-8s %(message)s",
level=logging.INFO,
datefmt="%Y-%m-%d %H:%M:%S",
)
logger = logging.getLogger(__name__)


timeMap: dict = {"S": "second", "M": "minute", "H": "hour", "D": "day"}


def fetch_data(instrument: str = "EUR_USD", timescale: str = "M") -> pd.DataFrame:
"""
Fetch all data from the database and return a DataFrame.
Parameters:
instrument (str): The instrument to fetch
timescale (str): The timescale to fetch (T = tick, M = minutes)
Returns:
dict: The data from the database
"""
try:
df = pd.DataFrame(
TimeScaleService().execute(
query=f"""
SELECT
TO_CHAR(
date_trunc('{timeMap[timescale.lower()]}', time), 'YYYY-MM-DD HH24:MI:SS'
) as time,
AVG(ask) as ask, AVG(bid) as bid
FROM forex_data
WHERE instrument = '{instrument}'
AND time >= NOW() - INTERVAL '60 minute'
GROUP BY time
ORDER BY time ASC
""",
),
)

return df.groupby(df["time"]).agg({"ask": "mean", "bid": "mean"}).reset_index()
except Exception as fetch_exception: # pylint: disable=broad-except
logger.error("Error fetching data: %s", fetch_exception)
logger = generate_logger(name=__name__)


if __name__ == "__main__":
# Execute SQL queries here
# ! TODO create subscription_feeds class
TimeScaleService().create_table(
query="""CREATE TABLE IF NOT EXISTS subscription_feeds (
queue_url VARCHAR(255) NOT NULL,
Expand All @@ -77,7 +36,7 @@ def fetch_data(instrument: str = "EUR_USD", timescale: str = "M") -> pd.DataFram

# Calculate averages for each subscription
for subscription in subscriptions:
data = fetch_data(
data: pd.DataFrame = ForexData.fetch(
instrument=subscription["instrument"],
timescale=subscription["timescale"],
)
Expand Down Expand Up @@ -105,7 +64,6 @@ def fetch_data(instrument: str = "EUR_USD", timescale: str = "M") -> pd.DataFram
logger.error(f"Error: {sending_exception}")

# Run at the start of the next minutes
# til_next_minute = round(60 - time.time() % 60, 2)
# logger.info(f"Sleeping for {til_next_minute} seconds")
# time.sleep(til_next_minute)
time.sleep(5)
til_next_minute = round(60 - time.time() % 60, 2)
logger.info(f"Sleeping for {til_next_minute} seconds")
time.sleep(til_next_minute)
2 changes: 1 addition & 1 deletion tests/stream_service/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@

import pytest

from foresight.stream_service.models.forex_data import ForexData
from foresight.utils.database import TimeScaleService
from foresight.utils.models.forex_data import ForexData


@pytest.fixture()
Expand Down
Empty file added tests/utils/models/__init__.py
Empty file.
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@

import pytest

from foresight.stream_service.models.forex_data import ForexData
from foresight.utils.database import TimeScaleService
from foresight.utils.models.forex_data import ForexData


def test_valid_forex_data():
Expand Down

1 comment on commit 5d45e59

@github-actions
Copy link

Choose a reason for hiding this comment

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

Code Coverage

Code Coverage
FileStmtsMissCoverMissing
foresight
   __init__.py00100% 
foresight/indicator_services
   __init__.py00100% 
   indicator.py64640%3–5, 7–10, 12, 15, 18, 21–24, 26, 33–38, 44, 46–50, 52, 54, 57, 63, 65, 67–69, 73, 77–78, 82–84, 88, 90, 92, 94, 96, 108, 110–111, 117, 119, 122, 125, 128, 130, 132, 134–136, 138–139, 141, 143, 145
   moving_average_indicator.py18180%3, 5–6, 9, 12, 20–21, 28, 30, 33–34, 36–37, 40, 42, 45–46, 51
foresight/interface_service
   __init__.py00100% 
   app.py31310%3–4, 6–9, 11–12, 15, 18, 20, 23, 25, 33–34, 36, 39–40, 42, 45–46, 48–51, 53–54, 56, 59–61
foresight/stream_service
   __init__.py00100% 
   app.py711381%82, 110, 131, 133–134, 139, 142–148
foresight/stream_service/models
   __init__.py00100% 
   pricing.py40100% 
   stream.py180100% 
foresight/utils
   __init__.py00100% 
   aws.py12120%3, 5, 8, 11, 13–14, 21, 27, 30, 32–33, 40
   database.py511374%51–52, 67–70, 75, 86–87, 89, 93–95
   logger.py90100% 
foresight/utils/models
   __init__.py00100% 
   forex_data.py28582%94–95, 115, 118–119
foresight/window_service
   __init__.py00100% 
   app.py34340%3, 5–6, 8–11, 14, 17, 19, 29, 31–33, 38–39, 44–53, 57–59, 63–64, 67–69
tests
   __init__.py00100% 
tests/stream_service
   __init__.py00100% 
   conftest.py19952%21–22, 30, 33, 37–41
   test_app.py400100% 
tests/stream_service/models
   __init__.py00100% 
tests/utils/models
   __init__.py00100% 
   test_forex_data.py300100% 
tests/window_service
   __init__.py00100% 
   test_app.py00100% 
TOTAL42919953% 

Please sign in to comment.