From 5279217324ab305d8f5a5567d4cd1fd9116712ce Mon Sep 17 00:00:00 2001 From: Mark Veidemanis Date: Tue, 18 Oct 2022 07:22:22 +0100 Subject: [PATCH] Begin implementing trade management --- core/migrations/0009_trade_exchange_id.py | 18 +++++ ...x_trade_direction_trade_status_and_more.py | 38 ++++++++++ core/models.py | 69 ++++++++++++++++++- core/templates/window-content/trade.html | 3 + core/views/accounts.py | 2 +- 5 files changed, 126 insertions(+), 4 deletions(-) create mode 100644 core/migrations/0009_trade_exchange_id.py create mode 100644 core/migrations/0010_account_sandbox_trade_direction_trade_status_and_more.py create mode 100644 core/templates/window-content/trade.html diff --git a/core/migrations/0009_trade_exchange_id.py b/core/migrations/0009_trade_exchange_id.py new file mode 100644 index 0000000..0028b7b --- /dev/null +++ b/core/migrations/0009_trade_exchange_id.py @@ -0,0 +1,18 @@ +# Generated by Django 4.1.2 on 2022-10-17 18:07 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('core', '0008_trade'), + ] + + operations = [ + migrations.AddField( + model_name='trade', + name='exchange_id', + field=models.CharField(blank=True, max_length=255, null=True), + ), + ] diff --git a/core/migrations/0010_account_sandbox_trade_direction_trade_status_and_more.py b/core/migrations/0010_account_sandbox_trade_direction_trade_status_and_more.py new file mode 100644 index 0000000..ab71f9f --- /dev/null +++ b/core/migrations/0010_account_sandbox_trade_direction_trade_status_and_more.py @@ -0,0 +1,38 @@ +# Generated by Django 4.1.2 on 2022-10-17 18:18 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('core', '0009_trade_exchange_id'), + ] + + operations = [ + migrations.AddField( + model_name='account', + name='sandbox', + field=models.BooleanField(default=False), + ), + migrations.AddField( + model_name='trade', + name='direction', + field=models.CharField(blank=True, choices=[('buy', 'Buy'), ('sell', 'Sell')], max_length=255, null=True), + ), + migrations.AddField( + model_name='trade', + name='status', + field=models.CharField(blank=True, max_length=255, null=True), + ), + migrations.AlterField( + model_name='trade', + name='symbol', + field=models.CharField(choices=[('BTCUSD', 'Bitcoin/USD')], max_length=255), + ), + migrations.AlterField( + model_name='trade', + name='type', + field=models.CharField(choices=[('market', 'Market'), ('limit', 'Limit')], max_length=255), + ), + ] diff --git a/core/models.py b/core/models.py index 800d718..197bfa3 100644 --- a/core/models.py +++ b/core/models.py @@ -1,5 +1,6 @@ import logging +import ccxt import stripe from django.conf import settings from django.contrib.auth.models import AbstractUser @@ -67,11 +68,16 @@ class User(AbstractUser): class Account(models.Model): + EXCHANGE_CHOICES = ( + ("binance", "Binance"), + ("alpaca", "Alpaca"), + ) user = models.ForeignKey(User, on_delete=models.CASCADE) name = models.CharField(max_length=255) - exchange = models.CharField(max_length=255) + exchange = models.CharField(choices=EXCHANGE_CHOICES, max_length=255) api_key = models.CharField(max_length=255) api_secret = models.CharField(max_length=255) + sandbox = models.BooleanField(default=False) class Session(models.Model): @@ -90,14 +96,71 @@ class Hook(models.Model): class Trade(models.Model): + SYMBOL_CHOICES = (("BTCUSD", "Bitcoin/USD"),) + TYPE_CHOICES = ( + ("market", "Market"), + ("limit", "Limit"), + ) + DIRECTION_CHOICES = ( + ("buy", "Buy"), + ("sell", "Sell"), + ) account = models.ForeignKey(Account, on_delete=models.CASCADE) hook = models.ForeignKey(Hook, on_delete=models.CASCADE, null=True, blank=True) - symbol = models.CharField(max_length=255) - type = models.CharField(max_length=255) + symbol = models.CharField(choices=SYMBOL_CHOICES, max_length=255) + type = models.CharField(choices=TYPE_CHOICES, max_length=255) amount = models.FloatField() price = models.FloatField() stop_loss = models.FloatField(null=True, blank=True) take_profit = models.FloatField(null=True, blank=True) + exchange_id = models.CharField(max_length=255, null=True, blank=True) + status = models.CharField(max_length=255, null=True, blank=True) + direction = models.CharField( + choices=DIRECTION_CHOICES, max_length=255, null=True, blank=True + ) + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self._original = self + + def save(self, *args, **kwargs): + """ + Override the save function to place the trade. + """ + if self.exchange_id is None: + # the trade is not placed yet + if self.account.exchange == "alpaca": + account = ccxt.alpaca( + {"apiKey": self.account.api_key, "secret": self.account.api_secret} + ) + if self.account.sandbox: + account.set_sandbox_mode(True) + if self.type == "market": + order = account.create_order( + self.symbol, self.type, self.direction, self.amount + ) + elif self.type == "limit": + params = "" + order = account.create_limit_order( + self.symbol, + self.type, + self.direction, + self.amount, + self.price, + params, + ) + self.status = "filled" + else: + # there is a trade open + # get trade + # update trade + pass + + super().save(*args, **kwargs) + + def delete(self, *args, **kwargs): + # close the trade + super().delete(*args, **kwargs) class Callback(models.Model): diff --git a/core/templates/window-content/trade.html b/core/templates/window-content/trade.html new file mode 100644 index 0000000..f44eb9f --- /dev/null +++ b/core/templates/window-content/trade.html @@ -0,0 +1,3 @@ +TRADE DETAILS +{{ items }} +{{ status }} \ No newline at end of file diff --git a/core/views/accounts.py b/core/views/accounts.py index 9266006..013aa12 100644 --- a/core/views/accounts.py +++ b/core/views/accounts.py @@ -98,7 +98,7 @@ class AccountAction(LoginRequiredMixin, APIView): if account_id: try: form = AccountForm( - request.data, instance=account.objects.get(id=account_id) + request.data, instance=Account.objects.get(id=account_id) ) except account.DoesNotExist: message = "Account does not exist"