From 0be1b98072e214f82d946ec814eb519fae6bfe92 Mon Sep 17 00:00:00 2001 From: Mark Veidemanis Date: Fri, 5 May 2023 14:39:02 +0100 Subject: [PATCH] Implement more detailed link group withdrawal simulations --- app/urls.py | 5 ++ core/clients/platforms/agora.py | 3 +- core/lib/money.py | 17 ++++-- core/management/commands/scheduling.py | 54 ++++++++++++++++++- .../partials/linkgroup-info-sim.html | 43 +++++---------- core/templates/partials/linkgroup-info.html | 45 +++++++++++++++- core/views/linkgroups.py | 37 ++++++++++--- 7 files changed, 157 insertions(+), 47 deletions(-) diff --git a/app/urls.py b/app/urls.py index 964fe5b..7894808 100644 --- a/app/urls.py +++ b/app/urls.py @@ -272,4 +272,9 @@ urlpatterns = [ linkgroups.LinkGroupSimulation.as_view(), name="linkgroup_simulate", ), + path( + "links//withdraw//", + linkgroups.LinkGroupWithdraw.as_view(), + name="linkgroup_withdraw", + ), ] + static(settings.STATIC_URL, document_root=settings.STATIC_ROOT) diff --git a/core/clients/platforms/agora.py b/core/clients/platforms/agora.py index a6439fe..7e6d09f 100644 --- a/core/clients/platforms/agora.py +++ b/core/clients/platforms/agora.py @@ -37,11 +37,12 @@ class AgoraClient(LocalPlatformClient, BaseClient): return rtrn # TODO: write test before re-enabling adding total_trades - async def withdraw_funds(self): + async def withdraw_funds(self, checks): """ Withdraw excess funds to our XMR wallets. """ print("CALLING WITHDRAW FUNDS") + # checks = self.check_all() totals_all = await self.money.get_total() if totals_all is False: return False diff --git a/core/lib/money.py b/core/lib/money.py index 7e08bf2..2e0a60f 100644 --- a/core/lib/money.py +++ b/core/lib/money.py @@ -43,20 +43,27 @@ class Money(object): ) self.es = client - async def check_all(self, user=None, nordigen=None, agora=None): + async def check_all(self, user=None, link_group=None, nordigen=None, agora=None): """ Run all the balance checks that output into ES in another thread. """ - # TODO: pass link group instead - if not all([user, nordigen, agora]): + if not all([nordigen, agora]): + raise Exception + if not any([user, link_group]): raise Exception # I hate circular dependencies self.nordigen = nordigen self.agora = agora - aggregators = Aggregator.objects.filter(user=user, enabled=True) - platforms = Platform.objects.filter(user=user, enabled=True) + cast = {} + if user is not None: + cast["user"] = user + if link_group is not None: + cast["link_group"] = link_group + + aggregators = Aggregator.objects.filter(enabled=True, **cast) + platforms = Platform.objects.filter(enabled=True, **cast) total = await self.get_total(aggregators, platforms, trades=True) diff --git a/core/management/commands/scheduling.py b/core/management/commands/scheduling.py index 36f0a40..3708ebd 100644 --- a/core/management/commands/scheduling.py +++ b/core/management/commands/scheduling.py @@ -5,21 +5,67 @@ from django.core.management.base import BaseCommand from core.clients.aggregators.nordigen import NordigenClient from core.clients.platforms.agora import AgoraClient -from core.models import INTERVAL_CHOICES, Aggregator, Platform +from core.lib.money import Money +from core.lib.notify import sendmsg +from core.models import INTERVAL_CHOICES, Aggregator, LinkGroup, Platform, Requisition from core.util import logs log = logs.get_logger("scheduling") INTERVAL_AGGREGATOR = 10 +INTERVAL_WITHDRAWAL = 7200 INTERVALS_PLATFORM = [x[0] for x in INTERVAL_CHOICES] +async def withdrawal_job(group=None): + money = Money() + if group is not None: + groups = [group] + else: + groups = LinkGroup.objects.filter(enabled=True) + for group in groups: + checks = await money.check_all( + user=group.user, nordigen=NordigenClient, agora=AgoraClient + ) + aggregators = Aggregator.objects.filter( + user=group.user, + link_group=group, + ) + platforms = Platform.objects.filter( + user=group.user, + link_group=group, + ) + requisitions = Requisition.objects.filter( + user=group.user, + aggregator__in=aggregators, + ) + pay_list = money.get_pay_list( + group, + requisitions, + platforms, + group.user, + checks["total_profit"], + ) + collapsed = money.collapse_pay_list(pay_list) + if any(collapsed.values()): + message = "" + print("COLLAPSED", collapsed) + for wallet, amount in collapsed.items(): + print("ITER", wallet, amount) + message += f"{wallet}: {amount}\n" + print("MESSAGE", message) + await sendmsg( + group.user, + message, + title="Your withdrawal is ready!", + ) + + async def aggregator_job(): aggregators = Aggregator.objects.filter(enabled=True) for aggregator in aggregators: open_trade_currencies = aggregator.trades_currencies - log.debug(f"Currencies of open trades: {open_trade_currencies}") if aggregator.service == "nordigen": instance = None if aggregator.fetch_accounts is True: @@ -74,6 +120,10 @@ class Command(BaseCommand): log.debug(f"Scheduling {INTERVAL_AGGREGATOR} second aggregator job") scheduler.add_job(aggregator_job, "interval", seconds=INTERVAL_AGGREGATOR) + + log.debug(f"Scheduling {INTERVAL_WITHDRAWAL} second withdrawal job") + scheduler.add_job(withdrawal_job, "interval", seconds=INTERVAL_WITHDRAWAL) + for interval in INTERVALS_PLATFORM: if interval == 0: continue diff --git a/core/templates/partials/linkgroup-info-sim.html b/core/templates/partials/linkgroup-info-sim.html index 2f5824c..39b19a4 100644 --- a/core/templates/partials/linkgroup-info-sim.html +++ b/core/templates/partials/linkgroup-info-sim.html @@ -1,43 +1,24 @@ -

Simulation for $1000

-{{ object }} -

- Assuming equal throughput for platforms and requisitions. - Note that this is just a simulation, equal throughput is highly unlikely. -

+

Simulation with live data

+

Total profit: {{ total_profit }}

    - {% for key, list in simulation.items %} -
  • - {{ key.0 }}: ${{ key.1 }} -
      - {% for item in list %} -
    • ${{ item.amount }} to {{ item.name }} at {{ item.address }}
    • - {% endfor %} -
    -
  • + {% for wallet, list in pay_list.items %} +
  • {{ wallet.name }} to {{ wallet.address }}:
  • +
      + {% for amount, item in list %} +
    • {{ amount }} for {{ item }}
    • + {% endfor %} +
    {% endfor %}

Total for wallets

- {% for wallet, pay_list in pay_list.items %} - {{ wallet }}: ${{ pay_list.amount }} - - {% endfor %} -
-
- -
-
- {% for key, list in simulation.items %} - {{ key.0 }}: ${{ key.1 }} - - {% for item in list %} - {{ item.name }}: ${{ item.amount }} - {% endfor %} - + {% for wallet, amount in collapsed.items %} + {{ wallet }}: ${{ amount }} + {% endfor %}
diff --git a/core/templates/partials/linkgroup-info.html b/core/templates/partials/linkgroup-info.html index 4d8335c..e39ce99 100644 --- a/core/templates/partials/linkgroup-info.html +++ b/core/templates/partials/linkgroup-info.html @@ -89,4 +89,47 @@
-{% include 'partials/linkgroup-info-sim.html' %} \ No newline at end of file +

Simulation for $1000

+

+ Assuming equal throughput for platforms and requisitions. + Note that this is just a simulation, equal throughput is highly unlikely. +

+
+
+
+
    + {% for key, list in simulation.items %} +
  • + {{ key.0 }}: ${{ key.1 }} +
      + {% for item in list %} +
    • ${{ item.amount }} to {{ item.name }} at {{ item.address }}
    • + {% endfor %} +
    +
  • + {% endfor %} +
+
+

Total for wallets

+
+ {% for wallet, pay_list in pay_list.items %} + {{ wallet }}: ${{ pay_list.amount }} + + {% endfor %} +
+
+ +
+
+ {% for key, list in simulation.items %} + {{ key.0 }}: ${{ key.1 }} + + {% for item in list %} + {{ item.name }}: ${{ item.amount }} + {% endfor %} + + {% endfor %} +
+
+ +
\ No newline at end of file diff --git a/core/views/linkgroups.py b/core/views/linkgroups.py index 4ed0c61..b053b20 100644 --- a/core/views/linkgroups.py +++ b/core/views/linkgroups.py @@ -1,6 +1,8 @@ from django.contrib.auth.mixins import LoginRequiredMixin from django.http import HttpResponse +from django.shortcuts import render from django.urls import reverse +from django.views import View from mixins.views import ( ObjectCreate, ObjectDelete, @@ -15,6 +17,7 @@ from core.clients.aggregators.nordigen import NordigenClient from core.clients.platforms.agora import AgoraClient from core.forms import LinkGroupForm from core.lib.money import Money +from core.management.commands.scheduling import withdrawal_job from core.models import ( Aggregator, LinkGroup, @@ -46,7 +49,9 @@ class LinkGroupInfo(LoginRequiredMixin, OTPRequiredMixin, ObjectRead): self.extra_buttons = [ { - "url": "#", + "url": reverse( + "linkgroup_withdraw", kwargs={"pk": self.object.id, "type": "modal"} + ), "action": "withdraw", "method": "get", "label": "Withdraw profit", @@ -205,19 +210,17 @@ class LinkGroupSimulation(LoginRequiredMixin, OTPRequiredMixin, ObjectRead): return HttpResponse(status=status.HTTP_404_NOT_FOUND) money = Money() checks = synchronize_async_helper( - money.check_all( - user=self.request.user, nordigen=NordigenClient, agora=AgoraClient - ) + money.check_all(link_group=pk, nordigen=NordigenClient, agora=AgoraClient) ) print("CHECKS", checks) aggregators = Aggregator.objects.filter( user=self.request.user, - link_group=self.object, + link_group=linkgroup, ) platforms = Platform.objects.filter( user=self.request.user, - link_group=self.object, + link_group=linkgroup, ) requisitions = Requisition.objects.filter( user=self.request.user, @@ -234,4 +237,24 @@ class LinkGroupSimulation(LoginRequiredMixin, OTPRequiredMixin, ObjectRead): print("PAY LIST", pay_list) collapsed = money.collapse_pay_list(pay_list) print("COLLAPSED", collapsed) - return collapsed + self.extra_context = {"collapsed": collapsed} + self.extra_context["pay_list"] = pay_list + self.extra_context["total_profit"] = checks["total_profit"] + return pay_list + + +class LinkGroupWithdraw(LoginRequiredMixin, OTPRequiredMixin, View): + template_name = "mixins/partials/notify.html" + + def get(self, request, **kwargs): + pk = self.kwargs.get("pk") + linkgroup = LinkGroup.objects.filter( + user=self.request.user, + id=pk, + ).first() + if not linkgroup: + return HttpResponse(status=status.HTTP_404_NOT_FOUND) + run = synchronize_async_helper(withdrawal_job(linkgroup)) + print("RUN", run) + context = {"class": "success", "message": "Withdrawal run"} + return render(request, self.template_name, context)