Implement more detailed link group withdrawal simulations

This commit is contained in:
Mark Veidemanis 2023-05-05 14:39:02 +01:00
parent 35607898f0
commit 0be1b98072
Signed by: m
GPG Key ID: 5ACFCEED46C0904F
7 changed files with 157 additions and 47 deletions

View File

@ -272,4 +272,9 @@ urlpatterns = [
linkgroups.LinkGroupSimulation.as_view(), linkgroups.LinkGroupSimulation.as_view(),
name="linkgroup_simulate", name="linkgroup_simulate",
), ),
path(
"links/<str:type>/withdraw/<str:pk>/",
linkgroups.LinkGroupWithdraw.as_view(),
name="linkgroup_withdraw",
),
] + static(settings.STATIC_URL, document_root=settings.STATIC_ROOT) ] + static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)

View File

@ -37,11 +37,12 @@ class AgoraClient(LocalPlatformClient, BaseClient):
return rtrn return rtrn
# TODO: write test before re-enabling adding total_trades # 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. Withdraw excess funds to our XMR wallets.
""" """
print("CALLING WITHDRAW FUNDS") print("CALLING WITHDRAW FUNDS")
# checks = self.check_all()
totals_all = await self.money.get_total() totals_all = await self.money.get_total()
if totals_all is False: if totals_all is False:
return False return False

View File

@ -43,20 +43,27 @@ class Money(object):
) )
self.es = client 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. Run all the balance checks that output into ES in another thread.
""" """
# TODO: pass link group instead if not all([nordigen, agora]):
if not all([user, nordigen, agora]): raise Exception
if not any([user, link_group]):
raise Exception raise Exception
# I hate circular dependencies # I hate circular dependencies
self.nordigen = nordigen self.nordigen = nordigen
self.agora = agora self.agora = agora
aggregators = Aggregator.objects.filter(user=user, enabled=True) cast = {}
platforms = Platform.objects.filter(user=user, enabled=True) 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) total = await self.get_total(aggregators, platforms, trades=True)

View File

@ -5,21 +5,67 @@ from django.core.management.base import BaseCommand
from core.clients.aggregators.nordigen import NordigenClient from core.clients.aggregators.nordigen import NordigenClient
from core.clients.platforms.agora import AgoraClient 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 from core.util import logs
log = logs.get_logger("scheduling") log = logs.get_logger("scheduling")
INTERVAL_AGGREGATOR = 10 INTERVAL_AGGREGATOR = 10
INTERVAL_WITHDRAWAL = 7200
INTERVALS_PLATFORM = [x[0] for x in INTERVAL_CHOICES] 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(): async def aggregator_job():
aggregators = Aggregator.objects.filter(enabled=True) aggregators = Aggregator.objects.filter(enabled=True)
for aggregator in aggregators: for aggregator in aggregators:
open_trade_currencies = aggregator.trades_currencies open_trade_currencies = aggregator.trades_currencies
log.debug(f"Currencies of open trades: {open_trade_currencies}")
if aggregator.service == "nordigen": if aggregator.service == "nordigen":
instance = None instance = None
if aggregator.fetch_accounts is True: if aggregator.fetch_accounts is True:
@ -74,6 +120,10 @@ class Command(BaseCommand):
log.debug(f"Scheduling {INTERVAL_AGGREGATOR} second aggregator job") log.debug(f"Scheduling {INTERVAL_AGGREGATOR} second aggregator job")
scheduler.add_job(aggregator_job, "interval", seconds=INTERVAL_AGGREGATOR) 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: for interval in INTERVALS_PLATFORM:
if interval == 0: if interval == 0:
continue continue

View File

@ -1,43 +1,24 @@
<h1 class="title">Simulation for $1000</h1> <h1 class="title">Simulation with live data</h1>
{{ object }} <p>Total profit: {{ total_profit }}</p>
<p>
Assuming equal throughput for platforms and requisitions.
<strong>Note that this is just a simulation, equal throughput is highly unlikely.</strong>
</p>
<div class="columns"> <div class="columns">
<div class="column"> <div class="column">
<div class="content"> <div class="content">
<ul> <ul>
{% for key, list in simulation.items %} {% for wallet, list in pay_list.items %}
<li> <li>{{ wallet.name }} to <code>{{ wallet.address }}</code>: </li>
{{ key.0 }}: ${{ key.1 }} <ul>
<ul> {% for amount, item in list %}
{% for item in list %} <li>{{ amount }} for {{ item }}</li>
<li>${{ item.amount }} to {{ item.name }} at <code>{{ item.address }}</code></li> {% endfor %}
{% endfor %} </ul>
</ul>
</li>
{% endfor %} {% endfor %}
</ul> </ul>
</div> </div>
<h1 class="title is-4">Total for wallets</h1> <h1 class="title is-4">Total for wallets</h1>
<div class="box"> <div class="box">
{% for wallet, pay_list in pay_list.items %} {% for wallet, amount in collapsed.items %}
{{ wallet }}: ${{ pay_list.amount }} {{ wallet }}: ${{ amount }}
<progress class="progress" value="{{ pay_list.amount }}" max="1000"></progress> <progress class="progress" value="{{ amount }}" max="{{ total_profit }}"></progress>
{% endfor %}
</div>
</div>
<div class="column">
<div class="box">
{% for key, list in simulation.items %}
<strong>{{ key.0 }}: ${{ key.1 }}</strong>
<progress class="progress" value="{{ key.1 }}" max="1000"></progress>
{% for item in list %}
<em>{{ item.name }}: ${{ item.amount }}</em><progress class="progress" value="{{ item.amount }}" max="{{ item.max }}"></progress>
{% endfor %}
{% endfor %} {% endfor %}
</div> </div>
</div> </div>

View File

@ -89,4 +89,47 @@
</div> </div>
</div> </div>
{% include 'partials/linkgroup-info-sim.html' %} <h1 class="title">Simulation for $1000</h1>
<p>
Assuming equal throughput for platforms and requisitions.
<strong>Note that this is just a simulation, equal throughput is highly unlikely.</strong>
</p>
<div class="columns">
<div class="column">
<div class="content">
<ul>
{% for key, list in simulation.items %}
<li>
{{ key.0 }}: ${{ key.1 }}
<ul>
{% for item in list %}
<li>${{ item.amount }} to {{ item.name }} at <code>{{ item.address }}</code></li>
{% endfor %}
</ul>
</li>
{% endfor %}
</ul>
</div>
<h1 class="title is-4">Total for wallets</h1>
<div class="box">
{% for wallet, pay_list in pay_list.items %}
{{ wallet }}: ${{ pay_list.amount }}
<progress class="progress" value="{{ pay_list.amount }}" max="1000"></progress>
{% endfor %}
</div>
</div>
<div class="column">
<div class="box">
{% for key, list in simulation.items %}
<strong>{{ key.0 }}: ${{ key.1 }}</strong>
<progress class="progress" value="{{ key.1 }}" max="1000"></progress>
{% for item in list %}
<em>{{ item.name }}: ${{ item.amount }}</em><progress class="progress" value="{{ item.amount }}" max="{{ item.max }}"></progress>
{% endfor %}
{% endfor %}
</div>
</div>
</div>

View File

@ -1,6 +1,8 @@
from django.contrib.auth.mixins import LoginRequiredMixin from django.contrib.auth.mixins import LoginRequiredMixin
from django.http import HttpResponse from django.http import HttpResponse
from django.shortcuts import render
from django.urls import reverse from django.urls import reverse
from django.views import View
from mixins.views import ( from mixins.views import (
ObjectCreate, ObjectCreate,
ObjectDelete, ObjectDelete,
@ -15,6 +17,7 @@ from core.clients.aggregators.nordigen import NordigenClient
from core.clients.platforms.agora import AgoraClient from core.clients.platforms.agora import AgoraClient
from core.forms import LinkGroupForm from core.forms import LinkGroupForm
from core.lib.money import Money from core.lib.money import Money
from core.management.commands.scheduling import withdrawal_job
from core.models import ( from core.models import (
Aggregator, Aggregator,
LinkGroup, LinkGroup,
@ -46,7 +49,9 @@ class LinkGroupInfo(LoginRequiredMixin, OTPRequiredMixin, ObjectRead):
self.extra_buttons = [ self.extra_buttons = [
{ {
"url": "#", "url": reverse(
"linkgroup_withdraw", kwargs={"pk": self.object.id, "type": "modal"}
),
"action": "withdraw", "action": "withdraw",
"method": "get", "method": "get",
"label": "Withdraw profit", "label": "Withdraw profit",
@ -205,19 +210,17 @@ class LinkGroupSimulation(LoginRequiredMixin, OTPRequiredMixin, ObjectRead):
return HttpResponse(status=status.HTTP_404_NOT_FOUND) return HttpResponse(status=status.HTTP_404_NOT_FOUND)
money = Money() money = Money()
checks = synchronize_async_helper( checks = synchronize_async_helper(
money.check_all( money.check_all(link_group=pk, nordigen=NordigenClient, agora=AgoraClient)
user=self.request.user, nordigen=NordigenClient, agora=AgoraClient
)
) )
print("CHECKS", checks) print("CHECKS", checks)
aggregators = Aggregator.objects.filter( aggregators = Aggregator.objects.filter(
user=self.request.user, user=self.request.user,
link_group=self.object, link_group=linkgroup,
) )
platforms = Platform.objects.filter( platforms = Platform.objects.filter(
user=self.request.user, user=self.request.user,
link_group=self.object, link_group=linkgroup,
) )
requisitions = Requisition.objects.filter( requisitions = Requisition.objects.filter(
user=self.request.user, user=self.request.user,
@ -234,4 +237,24 @@ class LinkGroupSimulation(LoginRequiredMixin, OTPRequiredMixin, ObjectRead):
print("PAY LIST", pay_list) print("PAY LIST", pay_list)
collapsed = money.collapse_pay_list(pay_list) collapsed = money.collapse_pay_list(pay_list)
print("COLLAPSED", collapsed) 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)