From 8efa087aeecfb353726af342acc4a386f743f03c Mon Sep 17 00:00:00 2001 From: Herculino Trotta Date: Sun, 5 Jan 2025 10:54:31 -0300 Subject: [PATCH] feat(api): add API endpoints to add DCA entries and strategies --- app/apps/api/serializers/__init__.py | 1 + app/apps/api/serializers/dca.py | 79 ++++++++++++++++++++++++++++ app/apps/api/urls.py | 2 + app/apps/api/views/__init__.py | 1 + app/apps/api/views/dca.py | 41 +++++++++++++++ 5 files changed, 124 insertions(+) create mode 100644 app/apps/api/serializers/dca.py create mode 100644 app/apps/api/views/dca.py diff --git a/app/apps/api/serializers/__init__.py b/app/apps/api/serializers/__init__.py index 11c1f3c..0fd06bc 100644 --- a/app/apps/api/serializers/__init__.py +++ b/app/apps/api/serializers/__init__.py @@ -1,3 +1,4 @@ from .transactions import * from .accounts import * from .currencies import * +from .dca import * diff --git a/app/apps/api/serializers/dca.py b/app/apps/api/serializers/dca.py new file mode 100644 index 0000000..49a0b28 --- /dev/null +++ b/app/apps/api/serializers/dca.py @@ -0,0 +1,79 @@ +from rest_framework import serializers +from apps.dca.models import DCAEntry, DCAStrategy + + +class DCAEntrySerializer(serializers.ModelSerializer): + profit_loss = serializers.DecimalField( + max_digits=42, decimal_places=30, read_only=True + ) + profit_loss_percentage = serializers.DecimalField( + max_digits=42, decimal_places=30, read_only=True + ) + current_value = serializers.DecimalField( + max_digits=42, decimal_places=30, read_only=True + ) + entry_price = serializers.DecimalField( + max_digits=42, decimal_places=30, read_only=True + ) + + class Meta: + model = DCAEntry + fields = [ + "id", + "strategy", + "date", + "amount_paid", + "amount_received", + "notes", + "created_at", + "updated_at", + "profit_loss", + "profit_loss_percentage", + "current_value", + "entry_price", + ] + read_only_fields = ["created_at", "updated_at"] + + +class DCAStrategySerializer(serializers.ModelSerializer): + entries = DCAEntrySerializer(many=True, read_only=True) + total_invested = serializers.DecimalField( + max_digits=42, decimal_places=30, read_only=True + ) + total_received = serializers.DecimalField( + max_digits=42, decimal_places=30, read_only=True + ) + average_entry_price = serializers.DecimalField( + max_digits=42, decimal_places=30, read_only=True + ) + total_entries = serializers.IntegerField(read_only=True) + current_total_value = serializers.DecimalField( + max_digits=42, decimal_places=30, read_only=True + ) + total_profit_loss = serializers.DecimalField( + max_digits=42, decimal_places=30, read_only=True + ) + total_profit_loss_percentage = serializers.DecimalField( + max_digits=42, decimal_places=30, read_only=True + ) + + class Meta: + model = DCAStrategy + fields = [ + "id", + "name", + "target_currency", + "payment_currency", + "notes", + "created_at", + "updated_at", + "entries", + "total_invested", + "total_received", + "average_entry_price", + "total_entries", + "current_total_value", + "total_profit_loss", + "total_profit_loss_percentage", + ] + read_only_fields = ["created_at", "updated_at"] diff --git a/app/apps/api/urls.py b/app/apps/api/urls.py index b2f8028..4bf0457 100644 --- a/app/apps/api/urls.py +++ b/app/apps/api/urls.py @@ -13,6 +13,8 @@ router.register(r"account-groups", views.AccountGroupViewSet) router.register(r"accounts", views.AccountViewSet) router.register(r"currencies", views.CurrencyViewSet) router.register(r"exchange-rates", views.ExchangeRateViewSet) +router.register(r"dca/strategies", views.DCAStrategyViewSet) +router.register(r"dca/entries", views.DCAEntryViewSet) urlpatterns = [ path("", include(router.urls)), diff --git a/app/apps/api/views/__init__.py b/app/apps/api/views/__init__.py index 11c1f3c..0fd06bc 100644 --- a/app/apps/api/views/__init__.py +++ b/app/apps/api/views/__init__.py @@ -1,3 +1,4 @@ from .transactions import * from .accounts import * from .currencies import * +from .dca import * diff --git a/app/apps/api/views/dca.py b/app/apps/api/views/dca.py new file mode 100644 index 0000000..d31ba12 --- /dev/null +++ b/app/apps/api/views/dca.py @@ -0,0 +1,41 @@ +from rest_framework import viewsets +from rest_framework.decorators import action +from rest_framework.response import Response +from apps.dca.models import DCAStrategy, DCAEntry +from apps.api.serializers import DCAStrategySerializer, DCAEntrySerializer + + +class DCAStrategyViewSet(viewsets.ModelViewSet): + queryset = DCAStrategy.objects.all() + serializer_class = DCAStrategySerializer + + @action(detail=True, methods=["get"]) + def investment_frequency(self, request, pk=None): + strategy = self.get_object() + return Response(strategy.investment_frequency_data()) + + @action(detail=True, methods=["get"]) + def price_comparison(self, request, pk=None): + strategy = self.get_object() + return Response(strategy.price_comparison_data()) + + @action(detail=True, methods=["get"]) + def current_price(self, request, pk=None): + strategy = self.get_object() + price_data = strategy.current_price() + if price_data: + price, date = price_data + return Response({"price": price, "date": date}) + return Response({"price": None, "date": None}) + + +class DCAEntryViewSet(viewsets.ModelViewSet): + queryset = DCAEntry.objects.all() + serializer_class = DCAEntrySerializer + + def get_queryset(self): + queryset = DCAEntry.objects.all() + strategy_id = self.request.query_params.get("strategy", None) + if strategy_id is not None: + queryset = queryset.filter(strategy_id=strategy_id) + return queryset