Finish implementing risk models

This commit is contained in:
Mark Veidemanis 2022-12-13 07:20:49 +00:00
parent 4e24ceac72
commit c81cb62aca
Signed by: m
GPG Key ID: 5ACFCEED46C0904F
8 changed files with 182 additions and 21 deletions

View File

@ -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/<str:type>/",
risk.RiskList.as_view(),
name="risks",
),
path(
"risk/<str:type>/create/",
risk.RiskCreate.as_view(),
name="risk_create",
),
path(
"risk/<str:type>/update/<str:pk>/",
risk.RiskUpdate.as_view(),
name="risk_update",
),
path(
"risk/<str:type>/delete/<str:pk>/",
risk.RiskDelete.as_view(),
name="risk_delete",
),
] + static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)

View File

@ -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.",
}

View File

@ -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'),
),
]

View File

@ -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',
),
]

View File

@ -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

View File

@ -254,7 +254,7 @@
<a class="navbar-item" href="{% url 'tradingtimes' type='page' %}">
Trading Times
</a>
<a class="navbar-item" href="{% url 'risk' type='page' %}">
<a class="navbar-item" href="{% url 'risks' type='page' %}">
Risk Management
</a>
</div>

View File

@ -0,0 +1,65 @@
{% include 'partials/notify.html' %}
<table
class="table is-fullwidth is-hoverable"
hx-target="#{{ context_object_name }}-table"
id="{{ context_object_name }}-table"
hx-swap="outerHTML"
hx-trigger="{{ context_object_name_singular }}Event from:body"
hx-get="{{ list_url }}">
<thead>
<th>id</th>
<th>user</th>
<th>name</th>
<th>description</th>
<th>max loss percent</th>
<th>max risk percent</th>
<th>max open trades</th>
<th>max open trades per symbol</th>
<th>actions</th>
</thead>
{% for item in object_list %}
<tr>
<td>{{ item.id }}</td>
<td>{{ item.user }}</td>
<td>{{ item.name }}</td>
<td>{{ item.description }}</td>
<td>{{ item.max_loss_percent }}</td>
<td>{{ item.max_risk_percent }}</td>
<td>{{ item.max_open_trades }}</td>
<td>{{ item.max_open_trades_per_symbol }}</td>
<td>
<div class="buttons">
<button
hx-headers='{"X-CSRFToken": "{{ csrf_token }}"}'
hx-get="{% url 'risk_update' type=type pk=item.id %}"
hx-trigger="click"
hx-target="#{{ type }}s-here"
hx-swap="innerHTML"
class="button">
<span class="icon-text">
<span class="icon">
<i class="fa-solid fa-pencil"></i>
</span>
</span>
</button>
<button
hx-headers='{"X-CSRFToken": "{{ csrf_token }}"}'
hx-delete="{% url 'risk_delete' type=type pk=item.id %}"
hx-trigger="click"
hx-target="#modals-here"
hx-swap="innerHTML"
hx-confirm="Are you sure you wish to delete {{ item.name }}?"
class="button">
<span class="icon-text">
<span class="icon">
<i class="fa-solid fa-xmark"></i>
</span>
</span>
</button>
</div>
</td>
</tr>
{% endfor %}
</table>

View File

@ -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