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, notifications,
positions, positions,
profit, profit,
risk,
signals, signals,
strategies, strategies,
trades, trades,
@ -214,4 +215,24 @@ urlpatterns = [
notifications.NotificationsUpdate.as_view(), notifications.NotificationsUpdate.as_view(),
name="notifications_update", 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) ] + static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)

View File

@ -7,6 +7,7 @@ from .models import (
Account, Account,
Hook, Hook,
NotificationSettings, NotificationSettings,
RiskModel,
Signal, Signal,
Strategy, Strategy,
Trade, Trade,
@ -110,19 +111,14 @@ class SignalForm(RestrictedFormMixin, ModelForm):
class AccountForm(RestrictedFormMixin, ModelForm): class AccountForm(RestrictedFormMixin, ModelForm):
class Meta: class Meta:
model = Account model = Account
fields = ( fields = ("name", "exchange", "api_key", "api_secret", "sandbox", "risk_model")
"name",
"exchange",
"api_key",
"api_secret",
"sandbox",
)
help_texts = { help_texts = {
"name": "Name of the account. Informational only.", "name": "Name of the account. Informational only.",
"exchange": "The exchange to use for this account.", "exchange": "The exchange to use for this account.",
"api_key": "The API key or username for the account.", "api_key": "The API key or username for the account.",
"api_secret": "The API secret or password/token for the account.", "api_secret": "The API secret or password/token for the account.",
"sandbox": "Whether to use the sandbox/demo or not.", "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_topic": "The topic to send notifications to.",
"ntfy_url": "Custom NTFY server. Leave blank to use the default server.", "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) supported_symbols = models.JSONField(default=list)
instruments = models.JSONField(default=list) instruments = models.JSONField(default=list)
currency = models.CharField(max_length=255, null=True, blank=True) 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 "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) name = models.CharField(max_length=255)
description = models.TextField(null=True, blank=True) description = models.TextField(null=True, blank=True)
# Maximum amount of money to have lost from the initial balance to stop trading # 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 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' %}"> <a class="navbar-item" href="{% url 'tradingtimes' type='page' %}">
Trading Times Trading Times
</a> </a>
<a class="navbar-item" href="{% url 'risk' type='page' %}"> <a class="navbar-item" href="{% url 'risks' type='page' %}">
Risk Management Risk Management
</a> </a>
</div> </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 django.contrib.auth.mixins import LoginRequiredMixin
from core.forms import RiskForm from core.forms import RiskModelForm
from core.models import Risk from core.models import RiskModel
from core.util import logs from core.util import logs
from core.views import ObjectCreate, ObjectDelete, ObjectList, ObjectUpdate from core.views import ObjectCreate, ObjectDelete, ObjectList, ObjectUpdate
@ -9,29 +9,29 @@ log = logs.get_logger(__name__)
class RiskList(LoginRequiredMixin, ObjectList): class RiskList(LoginRequiredMixin, ObjectList):
list_template = "partials/signal-list.html" list_template = "partials/risk-list.html"
model = Risk model = RiskModel
page_title = "List of risk management strategies. Linked to accounts." page_title = "List of risk management strategies. Linked to accounts."
list_url_name = "risk" list_url_name = "risks"
list_url_args = ["type"] list_url_args = ["type"]
submit_url_name = "risk_create" submit_url_name = "risk_create"
class RiskCreate(LoginRequiredMixin, ObjectCreate): class RiskCreate(LoginRequiredMixin, ObjectCreate):
model = Risk model = RiskModel
form_class = RiskForm form_class = RiskModelForm
submit_url_name = "risk_create" submit_url_name = "risk_create"
class RiskUpdate(LoginRequiredMixin, ObjectUpdate): class RiskUpdate(LoginRequiredMixin, ObjectUpdate):
model = Risk model = RiskModel
form_class = RiskForm form_class = RiskModelForm
submit_url_name = "risk_update" submit_url_name = "risk_update"
class RiskDelete(LoginRequiredMixin, ObjectDelete): class RiskDelete(LoginRequiredMixin, ObjectDelete):
model = Risk model = RiskModel