diff --git a/core/forms.py b/core/forms.py index 851efc3..04e4b08 100644 --- a/core/forms.py +++ b/core/forms.py @@ -89,10 +89,10 @@ class AccountForm(RestrictedFormMixin, ModelForm): "exchange", "api_key", "api_secret", - "sandbox", - "enabled", "risk_model", "initial_balance", + "sandbox", + "enabled", ) help_texts = { "name": "Name of the account. Informational only.", @@ -119,6 +119,7 @@ class StrategyForm(RestrictedFormMixin, ModelForm): "name", "description", "account", + "asset_group", "trading_times", "order_type", "time_in_force", @@ -138,6 +139,7 @@ class StrategyForm(RestrictedFormMixin, ModelForm): "name": "Name of the strategy. Informational only.", "description": "Description of the strategy. Informational only.", "account": "The account to use for this strategy.", + "asset_group": "Asset groups determine which pairs can be traded.", "trading_times": "When the strategy will place new trades.", "order_type": "Market: Buy/Sell at the current market price. Limit: Buy/Sell at a specified price. Limits protect you more against market slippage.", "time_in_force": "The time in force controls how the order is executed.", @@ -323,6 +325,7 @@ class AssetRestrictionForm(RestrictedFormMixin, ModelForm): "name", "description", "pairs", + "pairs_parsed", ) help_texts = { "name": "Name of the asset restriction group. Informational only.", @@ -330,6 +333,8 @@ class AssetRestrictionForm(RestrictedFormMixin, ModelForm): "pairs": "Comma-separated list of pairs to restrict when a webhook is received. This does nothing on its own.", } + pairs_parsed = forms.BooleanField(widget=forms.HiddenInput) + def clean(self): cleaned_data = super(AssetRestrictionForm, self).clean() if "pairs" in cleaned_data and cleaned_data["pairs"]: diff --git a/core/migrations/0055_strategy_asset_group.py b/core/migrations/0055_strategy_asset_group.py new file mode 100644 index 0000000..991fe1a --- /dev/null +++ b/core/migrations/0055_strategy_asset_group.py @@ -0,0 +1,19 @@ +# Generated by Django 4.1.6 on 2023-02-10 22:57 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('core', '0054_assetrestriction_webhook_id_alter_assetgroup_allowed'), + ] + + operations = [ + migrations.AddField( + model_name='strategy', + name='asset_group', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to='core.assetgroup'), + ), + ] diff --git a/core/migrations/0056_alter_assetrestriction_pairs_parsed.py b/core/migrations/0056_alter_assetrestriction_pairs_parsed.py new file mode 100644 index 0000000..eab2c72 --- /dev/null +++ b/core/migrations/0056_alter_assetrestriction_pairs_parsed.py @@ -0,0 +1,18 @@ +# Generated by Django 4.1.6 on 2023-02-10 23:09 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('core', '0055_strategy_asset_group'), + ] + + operations = [ + migrations.AlterField( + model_name='assetrestriction', + name='pairs_parsed', + field=models.JSONField(blank=True, default=dict, null=True), + ), + ] diff --git a/core/models.py b/core/models.py index b95af3c..414602c 100644 --- a/core/models.py +++ b/core/models.py @@ -369,6 +369,10 @@ class Strategy(models.Model): trade_size_percent = models.FloatField(default=0.5) trends = models.JSONField(null=True, blank=True) + asset_group = models.ForeignKey( + "core.AssetGroup", on_delete=models.PROTECT, null=True, blank=True + ) + class Meta: verbose_name_plural = "strategies" @@ -414,7 +418,7 @@ class AssetGroup(models.Model): allowed = models.JSONField(null=True, blank=True, default=dict) def __str__(self): - return self.name + return f"{self.name} ({self.restrictions})" @property def matches(self): @@ -438,7 +442,7 @@ class AssetRestriction(models.Model): name = models.CharField(max_length=255) description = models.TextField(null=True, blank=True) pairs = models.CharField(max_length=4096, null=True, blank=True) - pairs_parsed = models.JSONField(null=True, blank=True) + pairs_parsed = models.JSONField(null=True, blank=True, default=dict) webhook_id = models.UUIDField(default=uuid.uuid4, editable=False, unique=True) diff --git a/core/views/assets.py b/core/views/assets.py index 9ce76fd..0a21f47 100644 --- a/core/views/assets.py +++ b/core/views/assets.py @@ -8,6 +8,7 @@ from rest_framework.views import APIView from core.forms import AssetGroupForm, AssetRestrictionForm from core.models import AssetGroup, AssetRestriction from core.util import logs +import json log = logs.get_logger(__name__) @@ -109,12 +110,35 @@ class AssetRestrictionAPI(APIView): parser_classes = [JSONParser] def post(self, request, webhook_id): - log.debug(f"AssetAPI POST {webhook_id}: {request.data}") + # log.debug(f"AssetAPI POST {webhook_id}: {request.data}") + print(json.dumps(request.data, indent=2)) try: - webhook = AssetRestriction.objects.get(webhook_id=webhook_id) + restriction = AssetRestriction.objects.get(webhook_id=webhook_id) except AssetRestriction.DoesNotExist: - log.error(f"AssetRestriction {webhook_id} does not exist") + log.error(f"Asset restriction {webhook_id} does not exist") return HttpResponse(status=status.HTTP_404_NOT_FOUND) - return HttpResponse(status=status.HTTP_200_OK) + if restriction.group is not None: + group = restriction.group + else: + log.error(f"Asset restriction {restriction} has no group") + return HttpResponse(status=status.HTTP_404_NOT_FOUND) + + # if group.strategy_set.exists() is not None: + # strategies = group.strategy_set.all() + # else: + # log.error(f"Asset group {group} has no strategy") + # return HttpResponse(status=status.HTTP_404_NOT_FOUND) + # log.debug(f"Asset API {webhook_id} matched to strategies {strategies}") + + if "meta" in request.data: + if "is_match" in request.data["meta"]: + is_match = request.data["meta"]["is_match"] + if isinstance(restriction.pairs_parsed, list): + for pair in restriction.pairs_parsed: + group.allowed[pair] = is_match + group.save() + + return HttpResponse(status=status.HTTP_200_OK) + return HttpResponse(status=status.HTTP_400_BAD_REQUEST)