diff --git a/app/apps/dca/migrations/0002_alter_dcaentry_amount_paid_and_more.py b/app/apps/dca/migrations/0002_alter_dcaentry_amount_paid_and_more.py new file mode 100644 index 0000000..8f150bf --- /dev/null +++ b/app/apps/dca/migrations/0002_alter_dcaentry_amount_paid_and_more.py @@ -0,0 +1,23 @@ +# Generated by Django 5.1.2 on 2024-11-13 03:54 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('dca', '0001_initial'), + ] + + operations = [ + migrations.AlterField( + model_name='dcaentry', + name='amount_paid', + field=models.DecimalField(decimal_places=30, max_digits=42, verbose_name='Amount Paid'), + ), + migrations.AlterField( + model_name='dcaentry', + name='amount_received', + field=models.DecimalField(decimal_places=30, max_digits=42, verbose_name='Amount Received'), + ), + ] diff --git a/app/apps/dca/models.py b/app/apps/dca/models.py index 04e757b..4bfbfdf 100644 --- a/app/apps/dca/models.py +++ b/app/apps/dca/models.py @@ -1,6 +1,9 @@ +from datetime import timedelta from decimal import Decimal +from statistics import mean, stdev from django.db import models +from django.template.defaultfilters import date from django.utils import timezone from django.utils.translation import gettext_lazy as _ @@ -64,6 +67,72 @@ class DCAStrategy(models.Model): return (self.total_profit_loss() / total_invested) * 100 return Decimal("0") + def investment_frequency_data(self): + def _empty_frequency_data(): + return { + "intervals_line": [], + "labels": [], + } + + entries = self.entries.order_by("date") + + if entries.count() < 2: + return _empty_frequency_data() + + dates = list(entries.values_list("date", flat=True)) + intervals = [(dates[i + 1] - dates[i]).days for i in range(len(dates) - 1)] + + # Create data points for the intervals chart + labels = [] + intervals_line = [] + + for i in range(len(dates) - 1): + labels.append( + f"{date(dates[i], 'SHORT_DATE_FORMAT')} → {date(dates[i + 1], 'SHORT_DATE_FORMAT')}" + ) + intervals_line.append(intervals[i]) + + return { + "intervals_line": intervals_line, + "labels": labels, + } + + def price_comparison_data(self): + entries = self.entries.order_by("date") + + if entries.count() < 1: + return { + "labels": [], + "entry_prices": [], + "current_prices": [], + "amounts_bought": [], + } + + labels = [] + entry_prices = [] + current_prices = [] + amounts_bought = [] + + for entry in entries: + # Entry price calculation + entry_price = entry.amount_paid or 0 + + # Current value calculation using exchange rate + current_price = entry.current_value() or 0 + + labels.append(date(entry.date, "SHORT_DATE_FORMAT")) + # We use floats here because it's easier to transpose to Django's template + entry_prices.append(float(entry_price)) + current_prices.append(float(current_price)) + amounts_bought.append(float(entry.amount_received)) + + return { + "labels": labels, + "entry_prices": entry_prices, + "current_prices": current_prices, + "amounts_bought": amounts_bought, + } + class DCAEntry(models.Model): strategy = models.ForeignKey( diff --git a/app/apps/dca/views.py b/app/apps/dca/views.py index 319a76f..ec868fb 100644 --- a/app/apps/dca/views.py +++ b/app/apps/dca/views.py @@ -152,7 +152,10 @@ def strategy_detail(request, strategy_id): "current_total_value": strategy.current_total_value(), "total_profit_loss": strategy.total_profit_loss(), "total_profit_loss_percentage": strategy.total_profit_loss_percentage(), + "investment_frequency": strategy.investment_frequency_data(), + "price_comparison_data": strategy.price_comparison_data(), } + print(strategy.price_comparison_data()) return render(request, "dca/fragments/strategy/details.html", context) diff --git a/app/templates/dca/fragments/strategy/details.html b/app/templates/dca/fragments/strategy/details.html index f45c216..52a0ade 100644 --- a/app/templates/dca/fragments/strategy/details.html +++ b/app/templates/dca/fragments/strategy/details.html @@ -1,180 +1,103 @@ {% load i18n %} -
| - | {% trans "Date" %} | -{% trans "Amount Paid" %} | -{% trans "Amount Received" %} | -{% trans "Current Value" %} | -{% trans "P/L" %} | -
|---|---|---|---|---|---|
|
-
-
-
-
-
- |
- {{ entry.date|date:"SHORT_DATE_FORMAT" }} | -- {% if entry.profit_loss_percentage > 0 %} - {{ entry.profit_loss_percentage|floatformat:"2g" }}% - {% elif entry.profit_loss_percentage < 0 %} - {{ entry.profit_loss_percentage|floatformat:"2g" }}% - {% endif %} - | -
| + | {% trans "Date" %} | +{% trans "Amount Paid" %} | +{% trans "Amount Received" %} | +{% trans "Current Value" %} | +{% trans "P/L" %} | +
|---|---|---|---|---|---|
|
+
+
+
+
+
+ |
+ {{ entry.date|date:"SHORT_DATE_FORMAT" }} | +
+ |
+
+ |
+
+ |
+ + {% if entry.profit_loss_percentage > 0 %} + {{ entry.profit_loss_percentage|floatformat:"2g" }}% + {% elif entry.profit_loss_percentage < 0 %} + {{ entry.profit_loss_percentage|floatformat:"2g" }}% + {% endif %} + | +
+ {% trans "The straighter the blue line, the more consistent your DCA strategy is." %} +
+ +