Skip to content
Closed
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
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,5 @@ __pycache__
.ruff_cache

# Virtual environment
.env
.env
.venv
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ dependencies = [
"pandas",
"numpy",
"matplotlib",
"yfinance",
]

[project.optional-dependencies]
Expand Down
9 changes: 5 additions & 4 deletions src/argus/analytics/charts/trend_chart.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import matplotlib.pyplot as plt
import pandas as pd
from argus.services.timeseries_service import prepare_trend_analysis


def create_trendchart(curr: str, dates: pd.DataFrame):
def create_trendchart(curr_symbol: str, start: str, end: str, interval: str):
"""
Create a trend chart for exchange-rate analysis.

Expand Down Expand Up @@ -31,8 +30,10 @@ def create_trendchart(curr: str, dates: pd.DataFrame):
Minimum and maximum exchange-rate values are marked with scatter
points and annotations.
"""
df = pd.DataFrame()
df, min_max_rates = prepare_trend_analysis(curr, dates)
result = prepare_trend_analysis(curr_symbol, start, end, interval)
if result is None:
return None
df, min_max_rates = result
min_date = min_max_rates["min_date"][0]
min_rate = min_max_rates["min_rate"][0]
max_date = min_max_rates["max_date"][0]
Expand Down
43 changes: 43 additions & 0 deletions src/argus/clients/yfinance_client.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import yfinance as yf
import logging


def get_timeseries(curr_symbol, start, end, interval):
"""
Fetch historical exchange-rate time series data from Yahoo Finance.

Args:
curr_symbol (str): Currency symbol used by Yahoo Finance, for example
"EURUSD=X".
start (str): Start date of the requested time range in YYYY-MM-DD format.
end (str): End date of the requested time range in YYYY-MM-DD format.
interval (str): Data interval supported by Yahoo Finance, for example
"1d", "1h", or "15m".

Returns:
pandas.DataFrame | None: A DataFrame containing the columns ``date`` and
``rate`` if data was successfully fetched. Returns ``None`` if the
request fails, returns no data, or an exception occurs.
"""
try:
yf_logger = logging.getLogger("yfinance")
yf_logger.disabled = True
data = yf.download(
tickers=curr_symbol,
start=start,
end=end,
interval=interval,
multi_level_index=False,
progress=False,
)
yf_logger.disabled = False
if data is None:
return None
if data.empty:
return None
data = data.reset_index()
data = data[["Date", "Close"]]
data = data.rename(columns={"Date": "date", "Close": "rate"})
return data
except Exception:
return None
32 changes: 8 additions & 24 deletions src/argus/gui/app.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import tkinter as tk
import pandas as pd
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
from argus.analytics.charts.trend_chart import create_trendchart
from argus.services.calculator_service import calc, check_op
Expand Down Expand Up @@ -77,27 +76,10 @@ def show_trend() -> None:
global trend_canvas
global trend_chart_widget

mock_dates = {
"date": [
"2026-06-01",
"2026-06-02",
"2026-06-03",
"2026-06-04",
"2026-06-05",
"2026-06-06",
"2026-06-07",
"2026-06-08",
"2026-06-09",
"2026-06-10",
"2026-06-11",
"2026-06-12",
"2026-06-13",
"2026-06-14",
"2026-06-15",
]
}
mock_dates = pd.DataFrame(mock_dates)
mock_curr = "USD"
curr_symbol = "EURUSD=X"
start = "2024-01-01"
end = "2025-01-01"
interval = "1d"

calc_frame.pack_forget()
conv_frame.pack_forget()
Expand All @@ -108,14 +90,16 @@ def show_trend() -> None:
content.pack(side="top", fill=tk.BOTH, expand=True)

if trend_canvas is None:
fig = create_trendchart(mock_curr, mock_dates)
fig = create_trendchart(curr_symbol, start, end, interval)
if fig is None:
return None
fig.set_size_inches(7, 4)

trend_canvas = FigureCanvasTkAgg(fig, master=content)
trend_chart_widget = trend_canvas.get_tk_widget()

if trend_chart_widget is None:
return
return None
trend_canvas.draw()
trend_chart_widget.pack(fill=tk.BOTH, expand=True)

Expand Down
39 changes: 24 additions & 15 deletions src/argus/services/timeseries_service.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import pandas as pd
from argus.clients.mock_client import get_mock_timeseries
from argus.clients.yfinance_client import get_timeseries
from argus.analytics.metrics.trend_metrics import (
add_rolling_average,
add_daily_percentage_change,
Expand All @@ -8,24 +8,33 @@


def prepare_trend_analysis(
mock_curr: str, df: pd.DataFrame
) -> tuple[pd.DataFrame, dict]:
curr_symbol: str, start: str, end: str, intervall: str
) -> tuple[pd.DataFrame, dict] | None:
"""
Prepares the data for trend analysis by adding conversion rates, daily percentage change, and rolling average.
Prepare time-series data for trend analysis.

Arg1: mock_curr: str - the currency code for which the trend analysis is to
be performed
Arg2: df: pd.DataFrame - the DataFrame containing the dates for which the
conversion rates are to be added
Fetches historical exchange-rate data for the given currency symbol and
enriches it with daily percentage changes and a rolling average. It also
calculates the minimum and maximum exchange rates for the resulting time
series.

Return: tuple[pd.DataFrame, dict] - a tuple containing the updated DataFrame with conversion rates,
daily percentage change, and rolling average, and a dictionary with the minimum and maximum rates
Args:
curr_symbol (str): Currency symbol used by Yahoo Finance, for example
"EURUSD=X".
start (str): Start date of the requested time range in YYYY-MM-DD format.
end (str): End date of the requested time range in YYYY-MM-DD format.
intervall (str): Data interval supported by Yahoo Finance, for example
"1d", "1h", or "15m".

Returns:
tuple[pd.DataFrame, dict] | None: A tuple containing the prepared
DataFrame and a dictionary with minimum and maximum rates. Returns
``None`` if no time-series data could be fetched.
"""
df["rate"] = 0.0
# For each date one API call to get the rate
for i in range(len(df)):
date = str(df.loc[i, "date"])
df.loc[i, "rate"] = get_mock_timeseries(mock_curr, date)

df = get_timeseries(curr_symbol, start, end, intervall)
if df is None:
return None
df = add_daily_percentage_change(df)
df = add_rolling_average(df)
min_max_rates = get_min_max_rates(df)
Expand Down
Empty file.
Empty file.
72 changes: 72 additions & 0 deletions src/legacy/analytics/charts/trend_chart.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import matplotlib.pyplot as plt
import pandas as pd
from legacy.services.timeseries_service import prepare_trend_analysis


def create_trendchart(curr: str, dates: pd.DataFrame):
"""
Create a trend chart for exchange-rate analysis.

Builds a Matplotlib figure showing the exchange rate, its rolling
average, and the daily percentage change for a selected currency.
The minimum and maximum exchange-rate values are highlighted in the
chart.

Args:
curr (str): Currency code or currency pair identifier used for
the trend analysis.
dates (pd.DataFrame): DataFrame containing the date information
used to prepare the time-series analysis.

Returns:
matplotlib.figure.Figure: Matplotlib figure containing the trend
chart.

Notes:
The chart uses two y-axes:

- The left y-axis displays the exchange rate and rolling average.
- The right y-axis displays the daily percentage change.

Minimum and maximum exchange-rate values are marked with scatter
points and annotations.
"""
df = pd.DataFrame()
df, min_max_rates = prepare_trend_analysis(curr, dates)
min_date = min_max_rates["min_date"][0]
min_rate = min_max_rates["min_rate"][0]
max_date = min_max_rates["max_date"][0]
max_rate = min_max_rates["max_rate"][0]

# Rate and Rolling Average needs seperat x-Achse von Daily Percentage Chnage erhalten
fig, ax1 = plt.subplots(figsize=(5, 3.5), dpi=100)

# Subplot 1
ax1.plot(df["date"], df["rate"], color="black", label="Exchange Rate")
ax1.plot(df["date"], df["roll_avg"], color="blue", label="Rolling Average")

# Scatter and Annote Min/Max Rate
ax1.scatter(min_date, min_rate, color="red")
ax1.scatter(max_date, max_rate, color="green")
ax1.annotate("Min", (min_date, min_rate))
ax1.annotate("Max", (max_date, max_rate))

# Rotate date values for better visibillity
ax1.tick_params(axis="x", rotation=45)

# Subplot 2
ax2 = ax1.twinx()
bar_colors = ["green" if x >= 0 else "red" for x in df["daily_pct_change"]]
ax2.bar(
df["date"],
df["daily_pct_change"],
color=bar_colors,
alpha=0.4,
label="Daily Change",
)
ax2.legend(loc="upper left")
ax2.set_ylabel("Percentage Scale")

# Adjust the layout
fig.tight_layout()
return fig
Empty file.
75 changes: 75 additions & 0 deletions src/legacy/analytics/metrics/trend_metrics.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import pandas as pd


def add_daily_percentage_change(df: pd.DataFrame) -> pd.DataFrame:
"""
Add the daily percentage change of the exchange rate.

Calculates the percentage change between each rate value and the
previous rate value. The result is added as a new column named
``daily_pct_change``.

Args:
df (pd.DataFrame): DataFrame containing at least a ``rate`` column.

Returns:
pd.DataFrame: A copy of the input DataFrame with an added
``daily_pct_change`` column.

Notes:
The first row will contain ``NaN`` because there is no previous
rate value to compare against.
"""
result = df.copy()
result["daily_pct_change"] = result["rate"].pct_change() * 100
return result


def add_rolling_average(df: pd.DataFrame) -> pd.DataFrame:
"""
Add a rolling average of the exchange rate.

Calculates a rolling mean over the ``rate`` column using a fixed
window size of 3 rows. The result is added as a new column named
``roll_avg``.

Args:
df (pd.DataFrame): DataFrame containing at least a ``rate`` column.

Returns:
pd.DataFrame: A copy of the input DataFrame with an added
``roll_avg`` column.
"""
result = df.copy()
result["roll_avg"] = result["rate"].rolling(window=3, min_periods=1).mean()
return result


def get_min_max_rates(df: pd.DataFrame) -> dict:
"""
Get the minimum and maximum exchange-rate values.

Finds the rows with the lowest and highest values in the ``rate``
column and returns their dates and rates in a dictionary.

Args:
df (pd.DataFrame): DataFrame containing at least ``date`` and
``rate`` columns.

Returns:
dict: Dictionary containing the minimum and maximum rate data with
the following keys:

- ``min_date``: Date of the lowest exchange rate.
- ``min_rate``: Lowest exchange-rate value.
- ``max_date``: Date of the highest exchange rate.
- ``max_rate``: Highest exchange-rate value.
"""
min_max = {"min_date": [], "min_rate": [], "max_date": [], "max_rate": []}
min_id = df["rate"].idxmin()
max_id = df["rate"].idxmax()
min_max["min_date"].append(df.loc[min_id, "date"])
min_max["min_rate"].append(df.loc[min_id, "rate"])
min_max["max_date"].append(df.loc[max_id, "date"])
min_max["max_rate"].append(df.loc[max_id, "rate"])
return min_max
Empty file added src/legacy/clients/__init__.py
Empty file.
File renamed without changes.
Loading
Loading