After scratching my head even harder I finally understand how to use classes and Django. The first brick was get_queryset
, as @KenWhitesell already wrote. Here’s my *synchronous` solution:
broker.model.py
:
from django.db import models
from market.models import Market
class Broker(models.Model):
choices_mode = [("live", "live"), ("paper", "paper")]
choices_state = [("enabled", "enabled"), ("disabled", "disabled")]
name = models.CharField(max_length=120, blank=False, null=False)
mode = models.CharField(max_length=5, choices=choices_mode, default="paper", blank=False, null=False)
state = models.CharField(max_length=8, choices=choices_state, default="disabled", blank=False, null=False)
market_data_api_key = models.CharField(max_length=4096, blank=True, null=True)
trading_api_key = models.CharField(max_length=4096, blank=True, null=True)
markets = models.ManyToManyField(Market, blank=True, related_name="broker")
class Meta:
ordering = ["name"]
def __str__(self):
return self.name
account.model.py
:
from django.db import models
from broker.models import Broker
class Account(models.Model):
broker = models.OneToOneField(Broker, related_name="account", on_delete=models.CASCADE)
datetime = models.DateTimeField(auto_now=True)
# data from API
firstname = models.CharField(max_length=120, blank=False, null=False)
lastname = models.CharField(max_length=120, blank=False, null=False)
# live, paper
mode = models.CharField(max_length=5, blank=False, null=False)
# TODO: Make all field types decimal: https://docs.djangoproject.com/en/4.1/ref/models/fields/#decimalfield
balance = models.BigIntegerField(blank=False, null=False)
cash_to_invest = models.BigIntegerField(blank=False, null=False)
cash_to_withdraw = models.BigIntegerField(blank=False, null=False)
amount_bought_intraday = models.BigIntegerField(blank=False, null=False)
amount_sold_intraday = models.BigIntegerField(blank=False, null=False)
amount_open_orders = models.BigIntegerField(blank=False, null=False)
amount_open_withdrawals = models.BigIntegerField(blank=False, null=False)
amount_estimate_taxes = models.BigIntegerField(blank=False, null=False)
def __str__(self):
return f"{self.broker}, {self.broker.mode}, {self.lastname}, {self.firstname}, {self.datetime}"
account.views.py
:
from datetime import datetime
from django.contrib.auth.mixins import LoginRequiredMixin
from django.core.exceptions import ImproperlyConfigured
from django.db.models import QuerySet
from django.urls import reverse_lazy
from django.views.generic import ListView
from .models import Account
from broker.models import Broker
from power_tool.requests import get_request
class AccountListView(LoginRequiredMixin, ListView):
model = Account
template_name = "account/list.html"
login_url = reverse_lazy("base:login")
class Meta:
ordering = ["broker"]
def _create_account(self, broker):
request = get_request(broker.mode, broker.trading_api_key)
if request.status_code == 200:
results = request.json()["results"]
Account.objects.create(
broker=broker,
firstname=results["firstname"] if results["firstname"] is not None else "not defined",
lastname=results["lastname"] if results["lastname"] is not None else "not defined",
mode=broker.mode,
balance=results["balance"],
cash_to_invest=results["cash_to_invest"],
cash_to_withdraw=results["cash_to_withdraw"],
amount_bought_intraday=results["amount_bought_intraday"],
amount_sold_intraday=results["amount_sold_intraday"],
amount_open_orders=results["amount_open_orders"],
amount_open_withdrawals=results["amount_open_withdrawals"],
amount_estimate_taxes=results["amount_estimate_taxes"],
)
def _update_account(self, broker):
request = get_request(broker.mode, broker.trading_api_key)
if request.status_code == 200:
results = request.json()["results"]
broker.account.firstname = results["firstname"] if results["firstname"] is not None else "not defined"
broker.account.lastname = results["lastname"] if results["lastname"] is not None else "not defined"
broker.account.mode = broker.account.broker.mode
broker.account.balance = results["balance"]
broker.account.cash_to_invest = results["cash_to_invest"]
broker.account.cash_to_withdraw = results["cash_to_withdraw"]
broker.account.amount_bought_intraday = results["amount_bought_intraday"]
broker.account.amount_sold_intraday = results["amount_sold_intraday"]
broker.account.amount_open_orders = results["amount_open_orders"]
broker.account.amount_open_withdrawals = results["amount_open_withdrawals"]
broker.account.amount_estimate_taxes = results["amount_estimate_taxes"]
broker.account.save()
def get_queryset(self):
"""
Return the list of items for this view.
The return value must be an iterable and may be an instance of
`QuerySet` in which case `QuerySet` specific behavior will be enabled.
"""
# Update or create accounts
broker_list = Broker.objects.filter(state__exact="enabled")
for broker in broker_list:
# Update existing accounts
if hasattr(broker, "account"):
# TODO: Use logging
# print(
# f"account.datetime: {account.datetime}",
# f"datetime.now: {datetime.now().astimezone()}",
# f"datetime difference: {datetime.now().astimezone().timestamp() - account.datetime.timestamp()} s",
# sep="\n",
# )
# Check if >=60 seconds have been elapsed before updating an account
if datetime.now().astimezone().timestamp() - broker.account.datetime.timestamp() >= 60:
self._update_account(broker)
# Create new accounts
else:
self._create_account(broker)
# Delete accounts if broker is disabled
Account.objects.filter(broker__state__exact="disabled").delete()
# Get currently configured accounts
if self.queryset is not None:
queryset = self.queryset
if isinstance(queryset, QuerySet):
queryset = queryset.all()
elif self.model is not None:
queryset = self.model._default_manager.all()
else:
raise ImproperlyConfigured(
"%(cls)s is missing a QuerySet. Define "
"%(cls)s.model, %(cls)s.queryset, or override "
"%(cls)s.get_queryset()." % {"cls": self.__class__.__name__}
)
ordering = self.get_ordering()
if ordering:
if isinstance(ordering, str):
ordering = (ordering,)
queryset = queryset.order_by(*ordering)
return queryset
power_tool.requests.py
: (helper functions)
import requests
def _get_domain(mode):
return (
"https://paper-trading.lemon.markets/v1/account"
if mode == "paper"
else "https://trading.lemon.markets/v1/account"
)
def get_request(mode, trading_api_key):
domain = _get_domain(mode)
return requests.get(
domain,
headers={"Authorization": f"Bearer {trading_api_key}"},
)
I will check if I understand how async works in Python…