Remove asset restrictions and make asset groups smarter
This commit is contained in:
parent
287facbab2
commit
dcfb963be6
42
app/urls.py
42
app/urls.py
|
@ -76,7 +76,7 @@ urlpatterns = [
|
||||||
),
|
),
|
||||||
path(
|
path(
|
||||||
f"{settings.ASSET_PATH}/<str:webhook_id>/",
|
f"{settings.ASSET_PATH}/<str:webhook_id>/",
|
||||||
assets.AssetRestrictionAPI.as_view(),
|
assets.AssetGroupAPI.as_view(),
|
||||||
name="asset",
|
name="asset",
|
||||||
),
|
),
|
||||||
path("signals/<str:type>/", signals.SignalList.as_view(), name="signals"),
|
path("signals/<str:type>/", signals.SignalList.as_view(), name="signals"),
|
||||||
|
@ -264,26 +264,26 @@ urlpatterns = [
|
||||||
name="assetgroup_delete",
|
name="assetgroup_delete",
|
||||||
),
|
),
|
||||||
# Asset Restrictions
|
# Asset Restrictions
|
||||||
path(
|
# path(
|
||||||
"restriction/<str:type>/<str:group>/",
|
# "restriction/<str:type>/<str:group>/",
|
||||||
assets.AssetRestrictionList.as_view(),
|
# assets.AssetRestrictionList.as_view(),
|
||||||
name="assetrestrictions",
|
# name="assetrestrictions",
|
||||||
),
|
# ),
|
||||||
path(
|
# path(
|
||||||
"restriction/<str:type>/create/<str:group>/",
|
# "restriction/<str:type>/create/<str:group>/",
|
||||||
assets.AssetRestrictionCreate.as_view(),
|
# assets.AssetRestrictionCreate.as_view(),
|
||||||
name="assetrestriction_create",
|
# name="assetrestriction_create",
|
||||||
),
|
# ),
|
||||||
path(
|
# path(
|
||||||
"restriction/<str:type>/update/<str:group>/<str:pk>/",
|
# "restriction/<str:type>/update/<str:group>/<str:pk>/",
|
||||||
assets.AssetRestrictionUpdate.as_view(),
|
# assets.AssetRestrictionUpdate.as_view(),
|
||||||
name="assetrestriction_update",
|
# name="assetrestriction_update",
|
||||||
),
|
# ),
|
||||||
path(
|
# path(
|
||||||
"restriction/<str:type>/delete/<str:group>/<str:pk>/",
|
# "restriction/<str:type>/delete/<str:group>/<str:pk>/",
|
||||||
assets.AssetRestrictionDelete.as_view(),
|
# assets.AssetRestrictionDelete.as_view(),
|
||||||
name="assetrestriction_delete",
|
# name="assetrestriction_delete",
|
||||||
),
|
# ),
|
||||||
# Asset group filters
|
# Asset group filters
|
||||||
path(
|
path(
|
||||||
"assetfilter/<str:group_id>/flip/<str:symbol>/",
|
"assetfilter/<str:group_id>/flip/<str:symbol>/",
|
||||||
|
|
|
@ -2,10 +2,9 @@ from django.contrib import admin
|
||||||
from django.contrib.auth.admin import UserAdmin
|
from django.contrib.auth.admin import UserAdmin
|
||||||
|
|
||||||
from .forms import CustomUserCreationForm
|
from .forms import CustomUserCreationForm
|
||||||
from .models import (
|
from .models import ( # AssetRestriction,
|
||||||
Account,
|
Account,
|
||||||
AssetGroup,
|
AssetGroup,
|
||||||
AssetRestriction,
|
|
||||||
Callback,
|
Callback,
|
||||||
Hook,
|
Hook,
|
||||||
NotificationSettings,
|
NotificationSettings,
|
||||||
|
@ -94,11 +93,11 @@ class RiskModelAdmin(admin.ModelAdmin):
|
||||||
|
|
||||||
|
|
||||||
class AssetGroupAdmin(admin.ModelAdmin):
|
class AssetGroupAdmin(admin.ModelAdmin):
|
||||||
list_display = ("user", "name", "description")
|
list_display = ("user", "name", "description", "webhook_id")
|
||||||
|
|
||||||
|
|
||||||
class AssetRestrictionAdmin(admin.ModelAdmin):
|
# class AssetRestrictionAdmin(admin.ModelAdmin):
|
||||||
list_display = ("user", "name", "description", "webhook_id", "group")
|
# list_display = ("user", "name", "description", "webhook_id", "group")
|
||||||
|
|
||||||
|
|
||||||
admin.site.register(User, CustomUserAdmin)
|
admin.site.register(User, CustomUserAdmin)
|
||||||
|
@ -115,4 +114,4 @@ admin.site.register(Strategy, StrategyAdmin)
|
||||||
admin.site.register(NotificationSettings, NotificationSettingsAdmin)
|
admin.site.register(NotificationSettings, NotificationSettingsAdmin)
|
||||||
admin.site.register(RiskModel, RiskModelAdmin)
|
admin.site.register(RiskModel, RiskModelAdmin)
|
||||||
admin.site.register(AssetGroup, AssetGroupAdmin)
|
admin.site.register(AssetGroup, AssetGroupAdmin)
|
||||||
admin.site.register(AssetRestriction, AssetRestrictionAdmin)
|
# admin.site.register(AssetRestriction, AssetRestrictionAdmin)
|
||||||
|
|
107
core/forms.py
107
core/forms.py
|
@ -4,10 +4,9 @@ from django.core.exceptions import FieldDoesNotExist
|
||||||
from django.forms import ModelForm
|
from django.forms import ModelForm
|
||||||
from mixins.restrictions import RestrictedFormMixin
|
from mixins.restrictions import RestrictedFormMixin
|
||||||
|
|
||||||
from .models import (
|
from .models import ( # AssetRestriction,
|
||||||
Account,
|
Account,
|
||||||
AssetGroup,
|
AssetGroup,
|
||||||
AssetRestriction,
|
|
||||||
Hook,
|
Hook,
|
||||||
NotificationSettings,
|
NotificationSettings,
|
||||||
RiskModel,
|
RiskModel,
|
||||||
|
@ -309,47 +308,85 @@ class AssetGroupForm(RestrictedFormMixin, ModelForm):
|
||||||
fields = (
|
fields = (
|
||||||
"name",
|
"name",
|
||||||
"description",
|
"description",
|
||||||
|
"aggregation",
|
||||||
|
"trigger_below",
|
||||||
|
"trigger_above",
|
||||||
)
|
)
|
||||||
help_texts = {
|
help_texts = {
|
||||||
"name": "Name of the asset group. Informational only.",
|
"name": "Name of the asset group. Informational only.",
|
||||||
"description": "Description of the asset group. Informational only.",
|
"description": "Description of the asset group. Informational only.",
|
||||||
|
"aggregation": "The aggregation method to use for this asset group.",
|
||||||
|
"trigger_below": "Trigger when the aggregation is below this value.",
|
||||||
|
"trigger_above": "Trigger when the aggregation is above this value.",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class AssetRestrictionForm(RestrictedFormMixin, ModelForm):
|
|
||||||
class Meta:
|
|
||||||
model = AssetRestriction
|
|
||||||
fields = (
|
|
||||||
"name",
|
|
||||||
"description",
|
|
||||||
"pairs",
|
|
||||||
"pairs_parsed",
|
|
||||||
)
|
|
||||||
help_texts = {
|
|
||||||
"name": "Name of the asset restriction group. Informational only.",
|
|
||||||
"description": "Description of the asset restriction group. Informational only.",
|
|
||||||
"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, required=False)
|
|
||||||
|
|
||||||
def clean(self):
|
def clean(self):
|
||||||
cleaned_data = super(AssetRestrictionForm, self).clean()
|
cleaned_data = super(AssetGroupForm, self).clean()
|
||||||
if "pairs" in cleaned_data and cleaned_data["pairs"]:
|
if "aggregation" in cleaned_data:
|
||||||
new_pairs = []
|
if cleaned_data["aggregation"] == "none":
|
||||||
pair_split = cleaned_data["pairs"].split(",")
|
if "trigger_below" in cleaned_data and cleaned_data["trigger_below"]:
|
||||||
if not pair_split:
|
self.add_error(
|
||||||
self.add_error("pairs", "You must specify at least one pair.")
|
"trigger_below",
|
||||||
|
"You cannot specify a trigger below value when aggregation is set to none.",
|
||||||
|
)
|
||||||
return
|
return
|
||||||
for pair in pair_split:
|
if "trigger_above" in cleaned_data and cleaned_data["trigger_above"]:
|
||||||
if pair:
|
self.add_error(
|
||||||
new_pairs.append(pair.strip())
|
"trigger_above",
|
||||||
else:
|
"You cannot specify a trigger above value when aggregation is set to none.",
|
||||||
self.add_error("pairs", f"You cannot have an empty pair: {pair}")
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
cleaned_data["pairs_parsed"] = new_pairs
|
|
||||||
else:
|
else:
|
||||||
cleaned_data["pairs_parsed"] = {}
|
# Check if either trigger_below or trigger_above has been set
|
||||||
|
if not any(
|
||||||
|
[cleaned_data["trigger_below"], cleaned_data["trigger_above"]]
|
||||||
|
):
|
||||||
|
self.add_error(
|
||||||
|
"trigger_below",
|
||||||
|
"You must specify a trigger below and/or trigger above value when aggregation is set to anything other than none.",
|
||||||
|
)
|
||||||
|
self.add_error(
|
||||||
|
"trigger_above",
|
||||||
|
"You must specify a trigger below and/or trigger above value when aggregation is set to anything other than none.",
|
||||||
|
)
|
||||||
|
return
|
||||||
return cleaned_data
|
return cleaned_data
|
||||||
|
|
||||||
|
|
||||||
|
# class AssetRestrictionForm(RestrictedFormMixin, ModelForm):
|
||||||
|
# class Meta:
|
||||||
|
# model = AssetRestriction
|
||||||
|
# fields = (
|
||||||
|
# "name",
|
||||||
|
# "description",
|
||||||
|
# "pairs",
|
||||||
|
# "pairs_parsed",
|
||||||
|
# )
|
||||||
|
# help_texts = {
|
||||||
|
# "name": "Name of the asset restriction group. Informational only.",
|
||||||
|
# "description": "Description of the asset restriction group. Informational only.",
|
||||||
|
# "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, required=False)
|
||||||
|
|
||||||
|
# def clean(self):
|
||||||
|
# cleaned_data = super(AssetRestrictionForm, self).clean()
|
||||||
|
# if "pairs" in cleaned_data and cleaned_data["pairs"]:
|
||||||
|
# new_pairs = []
|
||||||
|
# pair_split = cleaned_data["pairs"].split(",")
|
||||||
|
# if not pair_split:
|
||||||
|
# self.add_error("pairs", "You must specify at least one pair.")
|
||||||
|
# return
|
||||||
|
# for pair in pair_split:
|
||||||
|
# if pair:
|
||||||
|
# new_pairs.append(pair.strip())
|
||||||
|
# else:
|
||||||
|
# self.add_error("pairs", f"You cannot have an empty pair: {pair}")
|
||||||
|
# return
|
||||||
|
|
||||||
|
# cleaned_data["pairs_parsed"] = new_pairs
|
||||||
|
# else:
|
||||||
|
# cleaned_data["pairs_parsed"] = {}
|
||||||
|
|
||||||
|
# return cleaned_data
|
||||||
|
|
|
@ -0,0 +1,23 @@
|
||||||
|
# Generated by Django 4.1.6 on 2023-02-13 10:28
|
||||||
|
|
||||||
|
import uuid
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('core', '0058_remove_assetgroup_account'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='assetgroup',
|
||||||
|
name='webhook_id',
|
||||||
|
field=models.UUIDField(default=uuid.uuid4, editable=False, unique=True),
|
||||||
|
),
|
||||||
|
migrations.DeleteModel(
|
||||||
|
name='AssetRestriction',
|
||||||
|
),
|
||||||
|
]
|
|
@ -0,0 +1,28 @@
|
||||||
|
# Generated by Django 4.1.6 on 2023-02-13 10:52
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('core', '0059_assetgroup_webhook_id_delete_assetrestriction'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='assetgroup',
|
||||||
|
name='aggregation',
|
||||||
|
field=models.CharField(choices=[('none', 'None'), ('avg_sentiment', 'Average sentiment')], default='none', max_length=255),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='assetgroup',
|
||||||
|
name='trigger_above',
|
||||||
|
field=models.FloatField(blank=True, null=True),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='assetgroup',
|
||||||
|
name='trigger_below',
|
||||||
|
field=models.FloatField(blank=True, null=True),
|
||||||
|
),
|
||||||
|
]
|
|
@ -41,6 +41,10 @@ SIGNAL_TYPE_CHOICES = (
|
||||||
("exit", "Exit"),
|
("exit", "Exit"),
|
||||||
("trend", "Trend"),
|
("trend", "Trend"),
|
||||||
)
|
)
|
||||||
|
AGGREGATION_CHOICES = (
|
||||||
|
("none", "None"),
|
||||||
|
("avg_sentiment", "Average sentiment"),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class Plan(models.Model):
|
class Plan(models.Model):
|
||||||
|
@ -414,8 +418,17 @@ class AssetGroup(models.Model):
|
||||||
# Dict like {"RUB": True, "USD": False}
|
# Dict like {"RUB": True, "USD": False}
|
||||||
allowed = models.JSONField(null=True, blank=True, default=dict)
|
allowed = models.JSONField(null=True, blank=True, default=dict)
|
||||||
|
|
||||||
|
webhook_id = models.UUIDField(default=uuid.uuid4, editable=False, unique=True)
|
||||||
|
|
||||||
|
aggregation = models.CharField(
|
||||||
|
choices=AGGREGATION_CHOICES, max_length=255, default="none"
|
||||||
|
)
|
||||||
|
|
||||||
|
trigger_below = models.FloatField(null=True, blank=True)
|
||||||
|
trigger_above = models.FloatField(null=True, blank=True)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return f"{self.name} ({self.restrictions})"
|
return f"{self.name}"
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def matches(self):
|
def matches(self):
|
||||||
|
@ -426,23 +439,16 @@ class AssetGroup(models.Model):
|
||||||
truthy_values = [x for x in self.allowed.values() if x is True]
|
truthy_values = [x for x in self.allowed.values() if x is True]
|
||||||
return f"{len(truthy_values)}/{len(self.allowed)}"
|
return f"{len(truthy_values)}/{len(self.allowed)}"
|
||||||
|
|
||||||
@property
|
|
||||||
def restrictions(self):
|
|
||||||
"""
|
|
||||||
Get the total number of restrictions for this group.
|
|
||||||
"""
|
|
||||||
return self.assetrestriction_set.count()
|
|
||||||
|
|
||||||
|
# class AssetRestriction(models.Model):
|
||||||
|
# user = models.ForeignKey(User, on_delete=models.CASCADE)
|
||||||
|
# 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, default=list)
|
||||||
|
|
||||||
class AssetRestriction(models.Model):
|
# webhook_id = models.UUIDField(default=uuid.uuid4, editable=False, unique=True)
|
||||||
user = models.ForeignKey(User, on_delete=models.CASCADE)
|
|
||||||
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, default=list)
|
|
||||||
|
|
||||||
webhook_id = models.UUIDField(default=uuid.uuid4, editable=False, unique=True)
|
# group = models.ForeignKey(
|
||||||
|
# AssetGroup, on_delete=models.CASCADE, null=True, blank=True
|
||||||
group = models.ForeignKey(
|
# )
|
||||||
AssetGroup, on_delete=models.CASCADE, null=True, blank=True
|
|
||||||
)
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{% load cache %}
|
{% load cache %}
|
||||||
{% load cachalot cache %}
|
{% load cachalot cache %}
|
||||||
{% get_last_invalidation 'core.AssetGroup' 'core.AssetRestriction' as last %}
|
{% get_last_invalidation 'core.AssetGroup' as last %}
|
||||||
{% include 'mixins/partials/notify.html' %}
|
{% include 'mixins/partials/notify.html' %}
|
||||||
{% cache 600 objects_assetgroups request.user.id object_list last %}
|
{% cache 600 objects_assetgroups request.user.id object_list last %}
|
||||||
<table
|
<table
|
||||||
|
@ -16,7 +16,7 @@
|
||||||
<th>name</th>
|
<th>name</th>
|
||||||
<th>description</th>
|
<th>description</th>
|
||||||
<th>status</th>
|
<th>status</th>
|
||||||
<th>restrictions</th>
|
<th>hook</th>
|
||||||
<th>actions</th>
|
<th>actions</th>
|
||||||
</thead>
|
</thead>
|
||||||
{% for item in object_list %}
|
{% for item in object_list %}
|
||||||
|
@ -35,7 +35,15 @@
|
||||||
{{ item.matches }}
|
{{ item.matches }}
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
<td>{{ item.restrictions }}</td>
|
<td>
|
||||||
|
<a
|
||||||
|
class="has-text-grey"
|
||||||
|
onclick="window.prompt('Copy to clipboard: Ctrl+C, Enter', '{{settings.URL}}/{{settings.ASSET_PATH}}/{{ item.webhook_id }}/');">
|
||||||
|
<span class="icon" data-tooltip="Copy to clipboard">
|
||||||
|
<i class="fa-solid fa-copy" aria-hidden="true"></i>
|
||||||
|
</span>
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<div class="buttons">
|
<div class="buttons">
|
||||||
<button
|
<button
|
||||||
|
@ -65,15 +73,6 @@
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
</button>
|
</button>
|
||||||
<a href="{% url 'assetrestrictions' type='page' group=item.id %}"><button
|
|
||||||
class="button">
|
|
||||||
<span class="icon-text">
|
|
||||||
<span class="icon">
|
|
||||||
<i class="fa-solid fa-eye"></i>
|
|
||||||
</span>
|
|
||||||
</span>
|
|
||||||
</button>
|
|
||||||
</a>
|
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
from django.test import TestCase
|
from django.test import TestCase
|
||||||
|
|
||||||
from core.models import AssetGroup, User
|
from core.models import AssetGroup, User
|
||||||
from core.trading.assetfilter import get_allowed
|
from core.trading import assetfilter
|
||||||
|
|
||||||
|
|
||||||
class AssetfilterTestCase(TestCase):
|
class AssetfilterTestCase(TestCase):
|
||||||
|
@ -21,8 +21,75 @@ class AssetfilterTestCase(TestCase):
|
||||||
Test that the asset filter works.
|
Test that the asset filter works.
|
||||||
"""
|
"""
|
||||||
self.group.allowed = {"EUR_USD": True, "EUR_GBP": False}
|
self.group.allowed = {"EUR_USD": True, "EUR_GBP": False}
|
||||||
self.assertTrue(get_allowed(self.group, "EUR_USD", "buy"))
|
self.assertTrue(assetfilter.get_allowed(self.group, "EUR_USD", "buy"))
|
||||||
self.assertFalse(get_allowed(self.group, "EUR_GBP", "sell"))
|
self.assertFalse(assetfilter.get_allowed(self.group, "EUR_GBP", "sell"))
|
||||||
|
|
||||||
# Default true
|
# Default true
|
||||||
self.assertTrue(get_allowed(self.group, "nonexistent", "sell"))
|
self.assertTrue(assetfilter.get_allowed(self.group, "nonexistent", "sell"))
|
||||||
|
|
||||||
|
def test_check_asset_aggregation(self):
|
||||||
|
"""
|
||||||
|
Test that the asset aggregation works.
|
||||||
|
"""
|
||||||
|
# Test within lower bound
|
||||||
|
self.assertTrue(
|
||||||
|
assetfilter.check_asset_aggregation(
|
||||||
|
1.0, trigger_above=None, trigger_below=2.0
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
# Test within upper bound
|
||||||
|
self.assertTrue(
|
||||||
|
assetfilter.check_asset_aggregation(
|
||||||
|
1.0, trigger_above=0.0, trigger_below=None
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
# Test within bounds
|
||||||
|
self.assertTrue(
|
||||||
|
assetfilter.check_asset_aggregation(
|
||||||
|
1.0, trigger_above=0.0, trigger_below=2.0
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
# Test outside bounds
|
||||||
|
self.assertFalse(
|
||||||
|
assetfilter.check_asset_aggregation(
|
||||||
|
1.0, trigger_above=2.0, trigger_below=3.0
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
# Test outside lower bound
|
||||||
|
self.assertFalse(
|
||||||
|
assetfilter.check_asset_aggregation(
|
||||||
|
1.0, trigger_above=None, trigger_below=0.0
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
# Test outside upper bound
|
||||||
|
self.assertFalse(
|
||||||
|
assetfilter.check_asset_aggregation(
|
||||||
|
1.0, trigger_above=2.0, trigger_below=None
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
# Test no bounds, just to be sure
|
||||||
|
self.assertFalse(
|
||||||
|
assetfilter.check_asset_aggregation(
|
||||||
|
1.0, trigger_above=None, trigger_below=None
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
# Test both bounds, but inverted
|
||||||
|
self.assertFalse(
|
||||||
|
assetfilter.check_asset_aggregation(
|
||||||
|
1.0, trigger_above=2.0, trigger_below=0.0
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
# Test within negative and positive bounds
|
||||||
|
self.assertTrue(
|
||||||
|
assetfilter.check_asset_aggregation(
|
||||||
|
-1.0, trigger_above=-2.0, trigger_below=0.0
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
|
@ -12,3 +12,22 @@ def get_allowed(group, symbol, direction):
|
||||||
return True
|
return True
|
||||||
|
|
||||||
return allowed[symbol]
|
return allowed[symbol]
|
||||||
|
|
||||||
|
|
||||||
|
def check_asset_aggregation(value, trigger_above, trigger_below):
|
||||||
|
"""
|
||||||
|
Check if the value is within the bounds of the aggregation
|
||||||
|
"""
|
||||||
|
# If both are defined
|
||||||
|
if trigger_above is not None and trigger_below is not None:
|
||||||
|
if value > trigger_above and value < trigger_below:
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
if trigger_below is not None:
|
||||||
|
if value < trigger_below:
|
||||||
|
# Value is less than lower bound, match
|
||||||
|
return True
|
||||||
|
if trigger_above is not None:
|
||||||
|
if value > trigger_above:
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
|
@ -1,12 +1,10 @@
|
||||||
import json
|
import json
|
||||||
|
|
||||||
from cachalot.api import invalidate
|
|
||||||
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.shortcuts import render
|
||||||
from django.views import View
|
from django.views import View
|
||||||
from mixins.views import (
|
from mixins.views import (
|
||||||
AbortSave,
|
|
||||||
ObjectCreate,
|
ObjectCreate,
|
||||||
ObjectDelete,
|
ObjectDelete,
|
||||||
ObjectList,
|
ObjectList,
|
||||||
|
@ -17,8 +15,9 @@ from rest_framework import status
|
||||||
from rest_framework.parsers import JSONParser
|
from rest_framework.parsers import JSONParser
|
||||||
from rest_framework.views import APIView
|
from rest_framework.views import APIView
|
||||||
|
|
||||||
from core.forms import AssetGroupForm, AssetRestrictionForm
|
from core.forms import AssetGroupForm # , AssetRestrictionForm
|
||||||
from core.models import AssetGroup, AssetRestriction
|
from core.models import AssetGroup # , AssetRestriction
|
||||||
|
from core.trading import assetfilter
|
||||||
from core.util import logs
|
from core.util import logs
|
||||||
|
|
||||||
log = logs.get_logger(__name__)
|
log = logs.get_logger(__name__)
|
||||||
|
@ -59,73 +58,73 @@ class AssetGroupDelete(LoginRequiredMixin, ObjectDelete):
|
||||||
# Asset Restrictions
|
# Asset Restrictions
|
||||||
|
|
||||||
|
|
||||||
class AssetRestrictionsPermissionMixin:
|
# class AssetRestrictionsPermissionMixin:
|
||||||
# Check the user has permission to view the asset group
|
# # Check the user has permission to view the asset group
|
||||||
# We have a user check on the AssetRestriction, but we need to check the
|
# # We have a user check on the AssetRestriction, but we need to check the
|
||||||
# AssetGroup as well
|
# # AssetGroup as well
|
||||||
def set_extra_args(self, user):
|
# def set_extra_args(self, user):
|
||||||
self.extra_permission_args = {
|
# self.extra_permission_args = {
|
||||||
"group__user": user,
|
# "group__user": user,
|
||||||
"group__pk": self.kwargs["group"],
|
# "group__pk": self.kwargs["group"],
|
||||||
}
|
# }
|
||||||
|
|
||||||
|
|
||||||
class AssetRestrictionList(
|
# class AssetRestrictionList(
|
||||||
LoginRequiredMixin, AssetRestrictionsPermissionMixin, ObjectList
|
# LoginRequiredMixin, AssetRestrictionsPermissionMixin, ObjectList
|
||||||
):
|
# ):
|
||||||
list_template = "partials/assetrestriction-list.html"
|
# list_template = "partials/assetrestriction-list.html"
|
||||||
model = AssetRestriction
|
# model = AssetRestriction
|
||||||
page_title = "List of asset restrictions. Linked to asset groups."
|
# page_title = "List of asset restrictions. Linked to asset groups."
|
||||||
page_subtitle = (
|
# page_subtitle = (
|
||||||
"Allows API calls to permit or prohibit trading on defined currency pairs."
|
# "Allows API calls to permit or prohibit trading on defined currency pairs."
|
||||||
)
|
# )
|
||||||
|
|
||||||
list_url_name = "assetrestrictions"
|
# list_url_name = "assetrestrictions"
|
||||||
list_url_args = ["type", "group"]
|
# list_url_args = ["type", "group"]
|
||||||
|
|
||||||
submit_url_name = "assetrestriction_create"
|
# submit_url_name = "assetrestriction_create"
|
||||||
submit_url_args = ["type", "group"]
|
# submit_url_args = ["type", "group"]
|
||||||
|
|
||||||
|
|
||||||
class AssetRestrictionCreate(
|
# class AssetRestrictionCreate(
|
||||||
LoginRequiredMixin, AssetRestrictionsPermissionMixin, ObjectCreate
|
# LoginRequiredMixin, AssetRestrictionsPermissionMixin, ObjectCreate
|
||||||
):
|
# ):
|
||||||
model = AssetRestriction
|
# model = AssetRestriction
|
||||||
form_class = AssetRestrictionForm
|
# form_class = AssetRestrictionForm
|
||||||
|
|
||||||
submit_url_name = "assetrestriction_create"
|
# submit_url_name = "assetrestriction_create"
|
||||||
submit_url_args = ["type", "group"]
|
# submit_url_args = ["type", "group"]
|
||||||
|
|
||||||
def form_invalid(self, form):
|
# def form_invalid(self, form):
|
||||||
"""If the form is invalid, render the invalid form."""
|
# """If the form is invalid, render the invalid form."""
|
||||||
return self.get(self.request, **self.kwargs, form=form)
|
# return self.get(self.request, **self.kwargs, form=form)
|
||||||
|
|
||||||
def pre_save_mutate(self, user, obj):
|
# def pre_save_mutate(self, user, obj):
|
||||||
try:
|
# try:
|
||||||
assetgroup = AssetGroup.objects.get(pk=self.kwargs["group"], user=user)
|
# assetgroup = AssetGroup.objects.get(pk=self.kwargs["group"], user=user)
|
||||||
obj.group = assetgroup
|
# obj.group = assetgroup
|
||||||
except AssetGroup.DoesNotExist:
|
# except AssetGroup.DoesNotExist:
|
||||||
log.error(f"Asset Group {self.kwargs['group']} does not exist")
|
# log.error(f"Asset Group {self.kwargs['group']} does not exist")
|
||||||
raise AbortSave("asset group does not exist or you don't have access")
|
# raise AbortSave("asset group does not exist or you don't have access")
|
||||||
|
|
||||||
|
|
||||||
class AssetRestrictionUpdate(
|
# class AssetRestrictionUpdate(
|
||||||
LoginRequiredMixin, AssetRestrictionsPermissionMixin, ObjectUpdate
|
# LoginRequiredMixin, AssetRestrictionsPermissionMixin, ObjectUpdate
|
||||||
):
|
# ):
|
||||||
model = AssetRestriction
|
# model = AssetRestriction
|
||||||
form_class = AssetRestrictionForm
|
# form_class = AssetRestrictionForm
|
||||||
|
|
||||||
submit_url_name = "assetrestriction_update"
|
# submit_url_name = "assetrestriction_update"
|
||||||
submit_url_args = ["type", "pk", "group"]
|
# submit_url_args = ["type", "pk", "group"]
|
||||||
|
|
||||||
|
|
||||||
class AssetRestrictionDelete(
|
# class AssetRestrictionDelete(
|
||||||
LoginRequiredMixin, AssetRestrictionsPermissionMixin, ObjectDelete
|
# LoginRequiredMixin, AssetRestrictionsPermissionMixin, ObjectDelete
|
||||||
):
|
# ):
|
||||||
model = AssetRestriction
|
# model = AssetRestriction
|
||||||
|
|
||||||
|
|
||||||
class AssetRestrictionAPI(APIView):
|
class AssetGroupAPI(APIView):
|
||||||
parser_classes = [JSONParser]
|
parser_classes = [JSONParser]
|
||||||
|
|
||||||
def post(self, request, webhook_id):
|
def post(self, request, webhook_id):
|
||||||
|
@ -133,16 +132,16 @@ class AssetRestrictionAPI(APIView):
|
||||||
print(json.dumps(request.data, indent=2))
|
print(json.dumps(request.data, indent=2))
|
||||||
|
|
||||||
try:
|
try:
|
||||||
restriction = AssetRestriction.objects.get(webhook_id=webhook_id)
|
group = AssetGroup.objects.get(webhook_id=webhook_id)
|
||||||
except AssetRestriction.DoesNotExist:
|
except AssetGroup.DoesNotExist:
|
||||||
log.error(f"Asset restriction {webhook_id} does not exist")
|
log.error(f"Asset group {webhook_id} does not exist")
|
||||||
return HttpResponse(status=status.HTTP_404_NOT_FOUND)
|
return HttpResponse(status=status.HTTP_404_NOT_FOUND)
|
||||||
|
|
||||||
if restriction.group is not None:
|
# if restriction.group is not None:
|
||||||
group = restriction.group
|
# group = restriction.group
|
||||||
else:
|
# else:
|
||||||
log.error(f"Asset restriction {restriction} has no group")
|
# log.error(f"Asset restriction {restriction} has no group")
|
||||||
return HttpResponse(status=status.HTTP_404_NOT_FOUND)
|
# return HttpResponse(status=status.HTTP_404_NOT_FOUND)
|
||||||
|
|
||||||
# if group.strategy_set.exists() is not None:
|
# if group.strategy_set.exists() is not None:
|
||||||
# strategies = group.strategy_set.all()
|
# strategies = group.strategy_set.all()
|
||||||
|
@ -151,18 +150,52 @@ class AssetRestrictionAPI(APIView):
|
||||||
# return HttpResponse(status=status.HTTP_404_NOT_FOUND)
|
# return HttpResponse(status=status.HTTP_404_NOT_FOUND)
|
||||||
# log.debug(f"Asset API {webhook_id} matched to strategies {strategies}")
|
# log.debug(f"Asset API {webhook_id} matched to strategies {strategies}")
|
||||||
|
|
||||||
if "meta" in request.data:
|
if "meta" not in request.data:
|
||||||
if "is_match" in request.data["meta"]:
|
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"]
|
is_match = request.data["meta"]["is_match"]
|
||||||
if isinstance(restriction.pairs_parsed, list):
|
if "topic" not in request.data:
|
||||||
for pair in restriction.pairs_parsed:
|
log.error(f"Asset API {webhook_id} has no topic")
|
||||||
group.allowed[pair] = is_match
|
return HttpResponse(status=status.HTTP_400_BAD_REQUEST)
|
||||||
|
|
||||||
|
topic = request.data["topic"]
|
||||||
|
print("YES TOPIC", topic)
|
||||||
|
new_pairs = []
|
||||||
|
pair_split = topic.split(",")
|
||||||
|
if not pair_split:
|
||||||
|
log.error(f"Asset API {webhook_id} topic {topic} is not a pair")
|
||||||
|
return HttpResponse(status=status.HTTP_400_BAD_REQUEST)
|
||||||
|
for pair in pair_split:
|
||||||
|
if pair:
|
||||||
|
new_pairs.append(pair.strip())
|
||||||
|
else:
|
||||||
|
log.error(f"Asset API {webhook_id} pair {pair} is not a pair")
|
||||||
|
return HttpResponse(status=status.HTTP_400_BAD_REQUEST)
|
||||||
|
print("YES PAIRS", new_pairs)
|
||||||
|
|
||||||
|
# Check if we have lower/upper bounds
|
||||||
|
if group.aggregation != "none":
|
||||||
|
if "aggs" in request.data["meta"]:
|
||||||
|
aggs = request.data["meta"]["aggs"]
|
||||||
|
if group.aggregation in aggs:
|
||||||
|
if "value" in aggs[group.aggregation]:
|
||||||
|
value = aggs[group.aggregation]["value"]
|
||||||
|
print("YES AVG", value)
|
||||||
|
is_match = assetfilter.check_asset_aggregation(
|
||||||
|
value, group.trigger_above, group.trigger_below
|
||||||
|
)
|
||||||
|
print("YES AVG IS MATCH", is_match)
|
||||||
|
|
||||||
|
for pair in new_pairs:
|
||||||
|
group.allowed[pair] = is_match is True or None
|
||||||
group.save()
|
group.save()
|
||||||
invalidate(restriction)
|
|
||||||
invalidate(group)
|
|
||||||
|
|
||||||
return HttpResponse(status=status.HTTP_200_OK)
|
return HttpResponse(status=status.HTTP_200_OK)
|
||||||
return HttpResponse(status=status.HTTP_400_BAD_REQUEST)
|
|
||||||
|
|
||||||
|
|
||||||
# Asset group allowed field
|
# Asset group allowed field
|
||||||
|
|
Loading…
Reference in New Issue