import json from django.contrib.auth.mixins import LoginRequiredMixin from django.db import IntegrityError from django.http import HttpResponse from mixins.views import AbortSave, ObjectCreate, ObjectDelete, ObjectList, ObjectUpdate from rest_framework import status from rest_framework.parsers import JSONParser from rest_framework.views import APIView from core.forms import AssetGroupForm, AssetRuleForm from core.models import AssetGroup, AssetRule from core.trading import assetfilter from core.util import logs log = logs.get_logger(__name__) # Asset Groups class AssetGroupList(LoginRequiredMixin, ObjectList): list_template = "partials/assetgroup-list.html" model = AssetGroup page_title = "List of asset groups for aggregations. Linked to strategies." page_subtitle = "Asset groups are maps between currencies and their sentiment." list_url_name = "assetgroups" list_url_args = ["type"] submit_url_name = "assetgroup_create" class AssetGroupCreate(LoginRequiredMixin, ObjectCreate): model = AssetGroup form_class = AssetGroupForm submit_url_name = "assetgroup_create" class AssetGroupUpdate(LoginRequiredMixin, ObjectUpdate): model = AssetGroup form_class = AssetGroupForm submit_url_name = "assetgroup_update" class AssetGroupDelete(LoginRequiredMixin, ObjectDelete): model = AssetGroup # Asset Rules class AssetRulesPermissionMixin: # Check the user has permission to view the asset group # We have a user check on the AssetRestriction, but we need to check the # AssetGroup as well def set_extra_args(self, user): self.extra_permission_args = { "group__user": user, "group__pk": self.kwargs["group"], } class AssetRuleList(LoginRequiredMixin, AssetRulesPermissionMixin, ObjectList): list_template = "partials/assetrule-list.html" model = AssetRule page_title = "List of asset rules" list_url_name = "assetrules" list_url_args = ["type", "group"] submit_url_name = "assetrule_create" submit_url_args = ["type", "group"] class AssetRuleCreate(LoginRequiredMixin, AssetRulesPermissionMixin, ObjectCreate): model = AssetRule form_class = AssetRuleForm submit_url_name = "assetrule_create" submit_url_args = ["type", "group"] def form_valid(self, form): """If the form is invalid, render the invalid form.""" try: return super().form_valid(form) except IntegrityError as e: if "UNIQUE constraint failed" in str(e): form.add_error("asset", "Asset rule already exists") return self.form_invalid(form) else: raise e def pre_save_mutate(self, user, obj): try: assetgroup = AssetGroup.objects.get(pk=self.kwargs["group"], user=user) obj.group = assetgroup except AssetGroup.DoesNotExist: log.error(f"Asset Group {self.kwargs['group']} does not exist") raise AbortSave("asset group does not exist or you don't have access") class AssetRuleUpdate(LoginRequiredMixin, AssetRulesPermissionMixin, ObjectUpdate): model = AssetRule form_class = AssetRuleForm submit_url_name = "assetrule_update" submit_url_args = ["type", "pk", "group"] class AssetRuleDelete(LoginRequiredMixin, AssetRulesPermissionMixin, ObjectDelete): model = AssetRule class AssetGroupAPI(APIView): parser_classes = [JSONParser] def post(self, request, webhook_id): # log.debug(f"AssetAPI POST {webhook_id}: {request.data}") print(json.dumps(request.data, indent=2)) try: group = AssetGroup.objects.get(webhook_id=webhook_id) except AssetGroup.DoesNotExist: log.error(f"Asset group {webhook_id} does not exist") return HttpResponse(status=status.HTTP_404_NOT_FOUND) # 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" not in request.data: log.error(f"Asset API {webhook_id} has no meta") return HttpResponse(status=status.HTTP_400_BAD_REQUEST) if "is_match" not in request.data["meta"]: log.error(f"Asset API {webhook_id} has no is_match") return HttpResponse(status=status.HTTP_400_BAD_REQUEST) is_match = request.data["meta"]["is_match"] if "topic" not in request.data: log.error(f"Asset API {webhook_id} has no topic") return HttpResponse(status=status.HTTP_400_BAD_REQUEST) topic = request.data["topic"] new_assets = [] assets_split = topic.split(",") if not assets_split: log.error(f"Asset API {webhook_id} topic {topic} is not an asset") return HttpResponse(status=status.HTTP_400_BAD_REQUEST) for asset in assets_split: if asset: new_assets.append(asset.strip()) else: log.error(f"Asset API {webhook_id} asset {asset} is not an asset") return HttpResponse(status=status.HTTP_400_BAD_REQUEST) aggregation = "none" value = None # Check if we have lower/upper bounds if "aggs" in request.data["meta"]: aggs = request.data["meta"]["aggs"] # TODO: support more aggregations if len(aggs) != 1: log.error( f"Asset API {webhook_id} has an invalid number of aggregations" ) return HttpResponse(status=status.HTTP_400_BAD_REQUEST) first_agg_key = list(aggs.keys())[0] if "value" in aggs[first_agg_key]: aggregation = first_agg_key value = aggs[first_agg_key]["value"] for asset in new_assets: asset_rule, _ = AssetRule.objects.get_or_create( group=group, user=group.user, asset=asset ) if is_match: # Make our own is_match based on the aggregations is_match = assetfilter.check_asset_aggregation( value, asset_rule.trigger_above, asset_rule.trigger_below ) else: # No matches is_match = None asset_rule.aggregation = aggregation if value: asset_rule.value = value if is_match is not None: our_status = is_match else: our_status = 1 asset_rule.original_status = our_status asset_rule.status = assetfilter.update_status_from_mappings( our_status, group ) asset_rule.save() return HttpResponse(status=status.HTTP_200_OK)