From 6cfffd99c515105d033897365407096f60ed9cdb Mon Sep 17 00:00:00 2001 From: Timotheus Pokorra Date: Sun, 24 Jan 2021 21:56:14 +0100 Subject: [PATCH] display graphs for exchange rates --- apps/core/static/css/skeleton-tabs.css | 29 ++++++++ apps/core/static/js/skeleton-tabs.js | 22 ++++++ apps/core/templates/base.html | 2 + apps/monitor/graph.py | 96 ++++++++++++++++++++++++++ apps/monitor/templates/monitor.html | 50 +++++++++++++- apps/monitor/views.py | 15 ++++ requirements.txt | 1 + walletmonitor/urls.py | 1 + 8 files changed, 215 insertions(+), 1 deletion(-) create mode 100644 apps/core/static/css/skeleton-tabs.css create mode 100644 apps/core/static/js/skeleton-tabs.js create mode 100644 apps/monitor/graph.py diff --git a/apps/core/static/css/skeleton-tabs.css b/apps/core/static/css/skeleton-tabs.css new file mode 100644 index 0000000..50819e4 --- /dev/null +++ b/apps/core/static/css/skeleton-tabs.css @@ -0,0 +1,29 @@ +/* https://github.com/nathancahill/skeleton-tabs */ +ul.tab-nav { + list-style: none; + border-bottom: 1px solid #bbb; + padding-left: 5px; +} + +ul.tab-nav li { + display: inline; +} + +ul.tab-nav li a.button { + border-bottom-left-radius: 0; + border-bottom-right-radius: 0; + margin-bottom: -1px; + border-bottom: none; +} + +ul.tab-nav li a.active.button { + border-bottom: 0.175em solid #fff; +} + +.tab-content .tab-pane { + display: none; +} + +.tab-content .tab-pane.active { + display: block; +} diff --git a/apps/core/static/js/skeleton-tabs.js b/apps/core/static/js/skeleton-tabs.js new file mode 100644 index 0000000..27b1360 --- /dev/null +++ b/apps/core/static/js/skeleton-tabs.js @@ -0,0 +1,22 @@ +/* https://github.com/nathancahill/skeleton-tabs */ +(function() { + function main() { + var tabButtons = [].slice.call(document.querySelectorAll('ul.tab-nav li a.button')); + + tabButtons.map(function(button) { + button.addEventListener('click', function() { + document.querySelector('li a.active.button').classList.remove('active'); + button.classList.add('active'); + + document.querySelector('.tab-pane.active').classList.remove('active'); + document.querySelector(button.getAttribute('href')).classList.add('active'); + }) + }) + } + + if (document.readyState !== 'loading') { + main(); + } else { + document.addEventListener('DOMContentLoaded', main); + } +})(); diff --git a/apps/core/templates/base.html b/apps/core/templates/base.html index 7cbc06b..13d42b1 100644 --- a/apps/core/templates/base.html +++ b/apps/core/templates/base.html @@ -8,6 +8,8 @@ + + {% if user.is_authenticated %} diff --git a/apps/monitor/graph.py b/apps/monitor/graph.py new file mode 100644 index 0000000..b923280 --- /dev/null +++ b/apps/monitor/graph.py @@ -0,0 +1,96 @@ +from django.db import connection +import datetime +import matplotlib.pyplot as plt +from io import StringIO +from matplotlib.backends.backend_agg import FigureCanvasAgg +from matplotlib.figure import Figure +import matplotlib.ticker as plticker +from django.http import HttpResponse + +class Graph: + + def graph_days(self, Crypto, Fiat, DayDiff): + + x = [] + y = [] + + with connection.cursor() as cursor: + sql = """SELECT date(datetime_valid), rate FROM + (select date(datetime_valid) as day, max(datetime_valid) as last + from exchangerate WHERE crypto_currency = %s AND fiat_currency = %s + AND datetime_valid >= %s + GROUP BY day + ORDER BY datetime_valid DESC) AS a + JOIN exchangerate AS b ON a.last = b.datetime_valid + AND crypto_currency = %s AND fiat_currency = %s + ORDER BY datetime_valid ASC""" + startDate = datetime.datetime.today().replace(hour=0, minute=0, second=0, microsecond=0) - datetime.timedelta(days=int(DayDiff)) + cursor.execute(sql, [Crypto, Fiat, startDate, Crypto, Fiat]) + rows = cursor.fetchall() + for row in rows: + x.append(datetime.datetime.strptime(row[0], '%Y-%m-%d')) + y.append(row[1]) + + if len(x) == 0: + raise Exception("no data for this time range in the database") + + fig_size = plt.rcParams["figure.figsize"] + fig_size[0] = 5 + fig_size[1] = 4 + plt.rcParams["figure.figsize"] = fig_size + + fig = Figure() + ax = fig.add_subplot(1, 1, 1) + + ax.plot(x, y, color='green', linestyle='dashed', linewidth = 2, + marker='o', markerfacecolor='blue', markersize=2) + + loc = plticker.MultipleLocator(base=(len(x)/4.0)) + ax.xaxis.set_major_locator(loc) + + ax.set_xlabel("time") + ax.set_ylabel(Fiat) + ax.set_title(('%s in %s' % (Crypto, Fiat))) + + response = HttpResponse(content_type = 'image/png') + canvas = FigureCanvasAgg(fig) + canvas.print_png(response) + return response + + def graph_hours(self, Crypto, Fiat, HourDiff): + + x = [] + y = [] + + with connection.cursor() as cursor: + sql = """SELECT datetime_valid, rate FROM exchangerate + WHERE crypto_currency = %s AND fiat_currency = %s + AND datetime_valid >= %s + ORDER BY datetime_valid ASC""" + startDate = datetime.datetime.today() - datetime.timedelta(hours=int(HourDiff)) + cursor.execute(sql, [Crypto, Fiat, startDate]) + rows = cursor.fetchall() + for row in rows: + x.append(row[0]) + y.append(row[1]) + + if len(x) == 0: + raise Exception("no data for this time range in the database") + + fig = Figure() + ax = fig.add_subplot(1, 1, 1) + + ax.plot(x, y, color='green', linestyle='dashed', linewidth = 2, + marker='o', markerfacecolor='blue', markersize=2) + + loc = plticker.MultipleLocator(base=(len(x)/5.0)) + ax.xaxis.set_major_locator(loc) + + ax.set_xlabel("time") + ax.set_ylabel(Fiat) + ax.set_title(('%s in %s' % (Crypto, Fiat))) + + response = HttpResponse(content_type = 'image/png') + canvas = FigureCanvasAgg(fig) + canvas.print_png(response) + return response diff --git a/apps/monitor/templates/monitor.html b/apps/monitor/templates/monitor.html index febac7d..50d5652 100644 --- a/apps/monitor/templates/monitor.html +++ b/apps/monitor/templates/monitor.html @@ -3,6 +3,7 @@ {% block content %}

Monitor

+ Refresh

Hint: these numbers are not very accurate.

{% for c in cryptos %} @@ -29,6 +30,7 @@ {% if c.bought_tax_free %} We can sell without paying taxes: {{ c.bought_tax_free|floatformat:3 }} {{ c.crypto }} for {{ c.value_tax_free|floatformat:2 }} EUR
{% endif %} +
My transactions
{% if c.rates %} @@ -44,9 +46,55 @@ {% endfor %}
+ + +
+
+ 48 hours + +
+
+ 3 days + +
+
+ 1 week + +
+
+ 1 month + +
+
+ 6 months + +
+
+ 1 year + +
+
{% endif %} -
My transactions

Google:
{{ c.crypto }} in EUR
diff --git a/apps/monitor/views.py b/apps/monitor/views.py index 1c3170e..8f3b5f8 100644 --- a/apps/monitor/views.py +++ b/apps/monitor/views.py @@ -3,6 +3,7 @@ from django.contrib.auth.decorators import login_required from apps.transactions.models import Transaction from apps.rates.models import ExchangeRate from apps.monitor.calc import Calc +from apps.monitor.graph import Graph @login_required def monitor(request): @@ -20,3 +21,17 @@ def monitor(request): cryptos.append({"crypto": crypto, "rateEUR": rateEUR, "rateUSD": rateUSD, **out2}) return render(request,"monitor.html",{'cryptos':cryptos, 'total_tax_free': total_tax_free, 'total_investment': total_investment, 'current_value': current_value}) + +@login_required +def graph(request): + if not 'crypto' in request.GET: + return + if not 'fiat' in request.GET: + return + Crypto = request.GET['crypto'] + Fiat = request.GET['fiat'] + + if 'number_of_days' in request.GET: + return Graph().graph_days(Crypto, Fiat, request.GET['number_of_days']) + if 'number_of_hours' in request.GET: + return Graph().graph_hours(Crypto, Fiat, request.GET['number_of_hours']) diff --git a/requirements.txt b/requirements.txt index 7ff85a5..677d463 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,4 @@ django django-registration +matplotlib diff --git a/walletmonitor/urls.py b/walletmonitor/urls.py index 8c490df..d125c18 100644 --- a/walletmonitor/urls.py +++ b/walletmonitor/urls.py @@ -27,6 +27,7 @@ urlpatterns = [ path('', core_views.home), path('home/', core_views.home), path('monitor/', monitor_views.monitor), + path('graph/', monitor_views.graph), path('transactions/add', tr_views.add), path('transactions/show', tr_views.show), path('transactions/edit/', tr_views.edit),