Skip to content
Merged
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
9 changes: 9 additions & 0 deletions .github/ISSUE_TEMPLATE/bug_report.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,12 @@
---
name: Bug report
about: Create a report to help us improve
title: ''
labels: bug
assignees: ''

---

## Problem

Describe what is broken.
Expand Down
31 changes: 31 additions & 0 deletions .github/ISSUE_TEMPLATE/feature_request.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
---
name: Feature request
about: Suggest an idea for this project
title: ''
labels: ''
assignees: ''

---

## Goal

Describe what should be achieved.

## Why

Explain why this task is useful for the project.

## Scope

-
-
-

## Acceptance criteria

- [ ]
- [ ]
- [ ]

> [!NOTE]
> Priority:
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ jobs:

steps:
- name: Check out repository
uses: actions/checkout@v4
uses: actions/checkout@v7

- name: Set up Python
uses: actions/setup-python@v5
Expand Down
11 changes: 8 additions & 3 deletions .github/workflows/commit-message.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,15 @@ jobs:

steps:
- name: Check commit message
uses: actions/checkout@v4
uses: actions/checkout@v7
with:
fetch-depth: 0
- name: Fetch base branch
run: git fetch origin "${{ github.base_ref }}"
- name: Validate commit message
shell: bash
run: |
pattern='^((feat|fix|docs|style|refactor|test|chore|ci|build|perf)\(#[0-9]+\): .{1,50}|chore\(deps\): .{1,72})$'
pattern='^((feat|fix|docs|style|refactor|test|chore|ci|build|perf)\(#[0-9]+\): .{1,72}|chore\(deps\): .{1,72})$'
invalid=0

while IFS= read -r commit_message; do
Expand All @@ -44,9 +44,14 @@ jobs:
echo ""
echo "Expected format:"
echo " feat(#1): add feature"
echo " chore(#5): add a file to .gitignore"
echo " chore(#5): add file to .gitignore"
echo " ci(#3): validate commit messages"
echo " chore(deps): update dependencies"
echo ""
echo "Allowed types:"
echo " feat, fix, docs, style, refactor, test, chore, ci, build, perf"
echo ""
echo "Special dependency format:"
echo " chore(deps): message"
exit 1
fi
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
25 changes: 12 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,13 @@ ARGUS is a Python-based market analytics project evolving from a small FX conver

ARGUS is currently focused on building a clean local foundation:

- currency conversion using live exchange-rate data
- currency conversion using live ExchangeRate API data
- historical market-data retrieval using yfinance
- calculator and conversion logic
- input validation and error handling
- Tkinter GUI prototype
- legacy CLI/debug interface
- first pandas/matplotlib-based analytics prototype
- pandas/matplotlib-based analytics prototype
- tests and documentation

> [!IMPORTANT]
Expand Down Expand Up @@ -66,19 +67,16 @@ Each roadmap phase is treated as a separate development sprint. The roadmap desc
## Current Features

- Calculator
- Currency conversion using live exchange rates
- Currency conversion using live ExchangeRate API data
- Historical market-data retrieval using yfinance
- Input validation and error handling
- Tkinter GUI prototype
- Legacy CLI/debug interface
- Basic pandas-based trend metrics
- Matplotlib-based trend visualization
- Mock time-series data for early analytics development
- Basic client, service and analytics pipeline
- Basic test suite

> [!CAUTION]
> Historical market data support is still limited.
> The current live exchange-rate client is useful for simple conversion, but future analytics work will require additional data sources such as Frankfurter or yfinance.

---

## Project Structure
Expand Down Expand Up @@ -115,6 +113,7 @@ README.md

- requests
- python-dotenv
- yfinance
- pandas
- NumPy
- matplotlib
Expand All @@ -124,6 +123,7 @@ README.md
### Current data source

- ExchangeRate API for live currency conversion
- yfinance for historical market-data retrieval and analytics

---

Expand All @@ -136,7 +136,6 @@ Planned or likely future technologies include:
### Data sources

- Frankfurter API for historical FX data
- yfinance for broader market data
- possible additional market-data APIs later

### Data processing
Expand Down Expand Up @@ -193,7 +192,7 @@ Before running ARGUS locally, make sure you have:
- Python 3.11 or newer
- Git
- pip
- an ExchangeRate API key for live currency conversion
- an ExchangeRate API key for live currency conversion. Historical analytics currently use yfinance and do not require an additional API key.

Recommended for development:

Expand Down Expand Up @@ -342,9 +341,9 @@ The project now has a runnable local Python application, a Tkinter GUI prototype

Current focus:

- start Sprint 2 — Market Analytics & Data Source Expansion
- improve historical exchange-rate data support
- continue Sprint 2 — Market Analytics & Data Source Expansion
- extend historical market-data support beyond the first yfinance client
- add stronger market metrics
- expand pandas-based analytics workflows
- improve dashboard usefulness without adding unnecessary chart noise
- document metric definitions, assumptions and data-source behavior
- document metric definitions, assumptions and data-source behavior
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.
Loading
Loading