From c81cb62acaaf7cc7ddc77840e8c312988108c430 Mon Sep 17 00:00:00 2001 From: Mark Veidemanis Date: Tue, 13 Dec 2022 07:20:49 +0000 Subject: [PATCH] Finish implementing risk models --- app/urls.py | 21 ++++++ core/forms.py | 31 +++++++-- .../0048_riskmodel_account_riskmodel.py | 33 ++++++++++ ...049_rename_riskmodel_account_risk_model.py | 18 +++++ core/models.py | 13 +++- core/templates/base.html | 2 +- core/templates/partials/risk-list.html | 65 +++++++++++++++++++ core/views/risk.py | 20 +++--- 8 files changed, 182 insertions(+), 21 deletions(-) create mode 100644 core/migrations/0048_riskmodel_account_riskmodel.py create mode 100644 core/migrations/0049_rename_riskmodel_account_risk_model.py create mode 100644 core/templates/partials/risk-list.html diff --git a/app/urls.py b/app/urls.py index 105f32c..4010844 100644 --- a/app/urls.py +++ b/app/urls.py @@ -30,6 +30,7 @@ from core.views import ( notifications, positions, profit, + risk, signals, strategies, trades, @@ -214,4 +215,24 @@ urlpatterns = [ notifications.NotificationsUpdate.as_view(), name="notifications_update", ), + path( + "risk//", + risk.RiskList.as_view(), + name="risks", + ), + path( + "risk//create/", + risk.RiskCreate.as_view(), + name="risk_create", + ), + path( + "risk//update//", + risk.RiskUpdate.as_view(), + name="risk_update", + ), + path( + "risk//delete//", + risk.RiskDelete.as_view(), + name="risk_delete", + ), ] + static(settings.STATIC_URL, document_root=settings.STATIC_ROOT) diff --git a/core/forms.py b/core/forms.py index 1fbc479..3814f9c 100644 --- a/core/forms.py +++ b/core/forms.py @@ -7,6 +7,7 @@ from .models import ( Account, Hook, NotificationSettings, + RiskModel, Signal, Strategy, Trade, @@ -110,19 +111,14 @@ class SignalForm(RestrictedFormMixin, ModelForm): class AccountForm(RestrictedFormMixin, ModelForm): class Meta: model = Account - fields = ( - "name", - "exchange", - "api_key", - "api_secret", - "sandbox", - ) + fields = ("name", "exchange", "api_key", "api_secret", "sandbox", "risk_model") help_texts = { "name": "Name of the account. Informational only.", "exchange": "The exchange to use for this account.", "api_key": "The API key or username for the account.", "api_secret": "The API secret or password/token for the account.", "sandbox": "Whether to use the sandbox/demo or not.", + "risk_model": "The risk model to use for this account.", } @@ -298,3 +294,24 @@ class NotificationSettingsForm(RestrictedFormMixin, ModelForm): "ntfy_topic": "The topic to send notifications to.", "ntfy_url": "Custom NTFY server. Leave blank to use the default server.", } + + +class RiskModelForm(RestrictedFormMixin, ModelForm): + class Meta: + model = RiskModel + fields = ( + "name", + "description", + "max_loss_percent", + "max_risk_percent", + "max_open_trades", + "max_open_trades_per_symbol", + ) + help_texts = { + "name": "Name of the risk model. Informational only.", + "description": "Description of the risk model. Informational only.", + "max_loss_percent": "The maximum percent of the account balance that can be lost before we cease trading.", + "max_risk_percent": "The maximum percent of the account balance that can be risked on all open trades.", + "max_open_trades": "The maximum number of open trades.", + "max_open_trades_per_symbol": "The maximum number of open trades per symbol.", + } diff --git a/core/migrations/0048_riskmodel_account_riskmodel.py b/core/migrations/0048_riskmodel_account_riskmodel.py new file mode 100644 index 0000000..d6f974a --- /dev/null +++ b/core/migrations/0048_riskmodel_account_riskmodel.py @@ -0,0 +1,33 @@ +# Generated by Django 4.1.4 on 2022-12-21 21:43 + +import django.db.models.deletion +from django.conf import settings +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('core', '0047_notificationsettings'), + ] + + operations = [ + migrations.CreateModel( + name='RiskModel', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(max_length=255)), + ('description', models.TextField(blank=True, null=True)), + ('max_loss_percent', models.FloatField(default=0.05)), + ('max_risk_percent', models.FloatField(default=0.05)), + ('max_open_trades', models.IntegerField(default=10)), + ('max_open_trades_per_symbol', models.IntegerField(default=2)), + ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), + ], + ), + migrations.AddField( + model_name='account', + name='riskmodel', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='core.riskmodel'), + ), + ] diff --git a/core/migrations/0049_rename_riskmodel_account_risk_model.py b/core/migrations/0049_rename_riskmodel_account_risk_model.py new file mode 100644 index 0000000..6223681 --- /dev/null +++ b/core/migrations/0049_rename_riskmodel_account_risk_model.py @@ -0,0 +1,18 @@ +# Generated by Django 4.1.4 on 2022-12-21 21:51 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('core', '0048_riskmodel_account_riskmodel'), + ] + + operations = [ + migrations.RenameField( + model_name='account', + old_name='riskmodel', + new_name='risk_model', + ), + ] diff --git a/core/models.py b/core/models.py index ff154c0..ad887cb 100644 --- a/core/models.py +++ b/core/models.py @@ -112,7 +112,7 @@ class Account(models.Model): supported_symbols = models.JSONField(default=list) instruments = models.JSONField(default=list) currency = models.CharField(max_length=255, null=True, blank=True) - riskmodel = models.ForeignKey( + risk_model = models.ForeignKey( "core.RiskModel", on_delete=models.SET_NULL, null=True, blank=True ) @@ -383,6 +383,13 @@ class RiskModel(models.Model): name = models.CharField(max_length=255) description = models.TextField(null=True, blank=True) # Maximum amount of money to have lost from the initial balance to stop trading - maximum_loss_ratio = models.FloatField(default=0.05) + max_loss_percent = models.FloatField(default=0.05) # Maximum amount of money to risk on all open trades - maximum_risk_ratio = models.FloatField(default=0.05) + max_risk_percent = models.FloatField(default=0.05) + # Maximum number of trades + max_open_trades = models.IntegerField(default=10) + # Maximum number of trades per symbol + max_open_trades_per_symbol = models.IntegerField(default=2) + + def __str__(self): + return self.name diff --git a/core/templates/base.html b/core/templates/base.html index 80fe54e..8762ca3 100644 --- a/core/templates/base.html +++ b/core/templates/base.html @@ -254,7 +254,7 @@ Trading Times - + Risk Management diff --git a/core/templates/partials/risk-list.html b/core/templates/partials/risk-list.html new file mode 100644 index 0000000..09aef07 --- /dev/null +++ b/core/templates/partials/risk-list.html @@ -0,0 +1,65 @@ +{% include 'partials/notify.html' %} + + + + + + + + + + + + + + {% for item in object_list %} + + + + + + + + + + + + {% endfor %} + +
idusernamedescriptionmax loss percentmax risk percentmax open tradesmax open trades per symbolactions
{{ item.id }}{{ item.user }}{{ item.name }}{{ item.description }}{{ item.max_loss_percent }}{{ item.max_risk_percent }}{{ item.max_open_trades }}{{ item.max_open_trades_per_symbol }} +
+ + +
+
diff --git a/core/views/risk.py b/core/views/risk.py index 8e516fb..d9b22da 100644 --- a/core/views/risk.py +++ b/core/views/risk.py @@ -1,7 +1,7 @@ from django.contrib.auth.mixins import LoginRequiredMixin -from core.forms import RiskForm -from core.models import Risk +from core.forms import RiskModelForm +from core.models import RiskModel from core.util import logs from core.views import ObjectCreate, ObjectDelete, ObjectList, ObjectUpdate @@ -9,29 +9,29 @@ log = logs.get_logger(__name__) class RiskList(LoginRequiredMixin, ObjectList): - list_template = "partials/signal-list.html" - model = Risk + list_template = "partials/risk-list.html" + model = RiskModel page_title = "List of risk management strategies. Linked to accounts." - list_url_name = "risk" + list_url_name = "risks" list_url_args = ["type"] submit_url_name = "risk_create" class RiskCreate(LoginRequiredMixin, ObjectCreate): - model = Risk - form_class = RiskForm + model = RiskModel + form_class = RiskModelForm submit_url_name = "risk_create" class RiskUpdate(LoginRequiredMixin, ObjectUpdate): - model = Risk - form_class = RiskForm + model = RiskModel + form_class = RiskModelForm submit_url_name = "risk_update" class RiskDelete(LoginRequiredMixin, ObjectDelete): - model = Risk + model = RiskModel