Move risk model to strategy

master
Mark Veidemanis 1 year ago
parent 9a5ed32be9
commit 1974b19157
Signed by: m
GPG Key ID: 5ACFCEED46C0904F

@ -89,7 +89,6 @@ class AccountForm(RestrictedFormMixin, ModelForm):
"exchange",
"api_key",
"api_secret",
"risk_model",
"initial_balance",
"sandbox",
"enabled",
@ -101,7 +100,6 @@ class AccountForm(RestrictedFormMixin, ModelForm):
"api_secret": "The API secret or password/token for the account.",
"sandbox": "Whether to use the sandbox/demo or not.",
"enabled": "Whether the account is enabled.",
"risk_model": "The risk model to use for this account.",
"initial_balance": "The initial balance of the account.",
}
@ -125,6 +123,7 @@ class StrategyForm(RestrictedFormMixin, ModelForm):
"description",
"account",
"asset_group",
"risk_model",
"trading_times",
"order_type",
"time_in_force",
@ -135,8 +134,6 @@ class StrategyForm(RestrictedFormMixin, ModelForm):
"take_profit_percent",
"stop_loss_percent",
"trailing_stop_loss_percent",
"price_slippage_percent",
"callback_price_deviation_percent",
"trade_size_percent",
)
@ -145,6 +142,7 @@ class StrategyForm(RestrictedFormMixin, ModelForm):
"description": "Description of the strategy. Informational only.",
"account": "The account to use for this strategy.",
"asset_group": "Asset groups determine which pairs can be traded.",
"risk_model": "The risk model to use for this strategy. Highly recommended.",
"trading_times": "When the strategy will place new trades.",
"order_type": "Market: Buy/Sell at the current market price. Limit: Buy/Sell at a specified price. Limits protect you more against market slippage.",
"time_in_force": "The time in force controls how the order is executed.",
@ -155,8 +153,6 @@ class StrategyForm(RestrictedFormMixin, ModelForm):
"take_profit_percent": "The take profit will be set at this percentage above/below the entry price.",
"stop_loss_percent": "The stop loss will be set at this percentage above/below the entry price.",
"trailing_stop_loss_percent": "The trailing stop loss will be set at this percentage above/below the entry price. A trailing stop loss will follow the price as it moves in your favor.",
"price_slippage_percent": "The price slippage is the maximum percent the price can move against you before the trade is cancelled. Limit orders will be set at this percentage against your favour. Market orders will have a price bound set if this is supported.",
"callback_price_deviation_percent": "The callback price deviation is the maximum percent the price can change between receiving the callback and acting on it. This protects against rogue or delayed callbacks. Keep it low.",
"trade_size_percent": "Percentage of the account balance to use for each trade.",
}
@ -297,6 +293,8 @@ class RiskModelForm(RestrictedFormMixin, ModelForm):
"max_risk_percent",
"max_open_trades",
"max_open_trades_per_symbol",
"price_slippage_percent",
"callback_price_deviation_percent",
)
help_texts = {
"name": "Name of the risk model. Informational only.",
@ -305,6 +303,8 @@ class RiskModelForm(RestrictedFormMixin, ModelForm):
"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.",
"price_slippage_percent": "The price slippage is the maximum percent the price can move against you before the trade is cancelled. Limit orders will be set at this percentage against your favour. Market orders will have a price bound set if this is supported.",
"callback_price_deviation_percent": "The callback price deviation is the maximum percent the price can change between receiving the callback and acting on it. This protects against rogue or delayed callbacks. Keep it low.",
}

@ -0,0 +1,81 @@
# Generated by Django 4.1.6 on 2023-02-15 18:11
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('core', '0065_assetgroup_when_bearish_assetgroup_when_bullish_and_more'),
]
operations = [
migrations.RemoveField(
model_name='account',
name='risk_model',
),
migrations.RemoveField(
model_name='strategy',
name='callback_price_deviation_percent',
),
migrations.RemoveField(
model_name='strategy',
name='price_slippage_percent',
),
migrations.AddField(
model_name='riskmodel',
name='callback_price_deviation_percent',
field=models.FloatField(default=0.5),
),
migrations.AddField(
model_name='riskmodel',
name='price_slippage_percent',
field=models.FloatField(default=2.5),
),
migrations.AddField(
model_name='strategy',
name='risk_model',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to='core.riskmodel'),
),
migrations.AlterField(
model_name='assetgroup',
name='when_bearish',
field=models.IntegerField(choices=[(6, 'Always allow'), (7, 'Always deny'), (2, 'Bullish'), (3, 'Bearish')], default=3),
),
migrations.AlterField(
model_name='assetgroup',
name='when_bullish',
field=models.IntegerField(choices=[(6, 'Always allow'), (7, 'Always deny'), (2, 'Bullish'), (3, 'Bearish')], default=2),
),
migrations.AlterField(
model_name='assetgroup',
name='when_no_aggregation',
field=models.IntegerField(choices=[(6, 'Always allow'), (7, 'Always deny'), (2, 'Bullish'), (3, 'Bearish')], default=6),
),
migrations.AlterField(
model_name='assetgroup',
name='when_no_data',
field=models.IntegerField(choices=[(6, 'Always allow'), (7, 'Always deny'), (2, 'Bullish'), (3, 'Bearish')], default=7),
),
migrations.AlterField(
model_name='assetgroup',
name='when_no_match',
field=models.IntegerField(choices=[(6, 'Always allow'), (7, 'Always deny'), (2, 'Bullish'), (3, 'Bearish')], default=6),
),
migrations.AlterField(
model_name='assetgroup',
name='when_not_in_bounds',
field=models.IntegerField(choices=[(6, 'Always allow'), (7, 'Always deny'), (2, 'Bullish'), (3, 'Bearish')], default=6),
),
migrations.AlterField(
model_name='assetrule',
name='original_status',
field=models.IntegerField(choices=[(0, 'No data'), (1, 'No match'), (2, 'Bullish'), (3, 'Bearish'), (4, 'No aggregation'), (5, 'Not in bounds'), (6, 'Always allow'), (7, 'Always deny')], default=0),
),
migrations.AlterField(
model_name='assetrule',
name='status',
field=models.IntegerField(choices=[(0, 'No data'), (1, 'No match'), (2, 'Bullish'), (3, 'Bearish'), (4, 'No aggregation'), (5, 'Not in bounds'), (6, 'Always allow'), (7, 'Always deny')], default=0),
),
]

@ -136,9 +136,9 @@ 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)
risk_model = models.ForeignKey(
"core.RiskModel", on_delete=models.SET_NULL, null=True, blank=True
)
# risk_model = models.ForeignKey(
# "core.RiskModel", on_delete=models.SET_NULL, null=True, blank=True
# )
initial_balance = models.FloatField(default=0)
def __str__(self):
@ -386,14 +386,17 @@ class Strategy(models.Model):
take_profit_percent = models.FloatField(default=1.5)
stop_loss_percent = models.FloatField(default=1.0)
trailing_stop_loss_percent = models.FloatField(default=1.0, null=True, blank=True)
price_slippage_percent = models.FloatField(default=2.5)
callback_price_deviation_percent = models.FloatField(default=0.5)
# price_slippage_percent = models.FloatField(default=2.5)
# callback_price_deviation_percent = models.FloatField(default=0.5)
trade_size_percent = models.FloatField(default=0.5)
trends = models.JSONField(null=True, blank=True)
asset_group = models.ForeignKey(
"core.AssetGroup", on_delete=models.PROTECT, null=True, blank=True
)
risk_model = models.ForeignKey(
"core.RiskModel", on_delete=models.PROTECT, null=True, blank=True
)
class Meta:
verbose_name_plural = "strategies"
@ -424,6 +427,9 @@ class RiskModel(models.Model):
# Maximum number of trades per symbol
max_open_trades_per_symbol = models.IntegerField(default=2)
price_slippage_percent = models.FloatField(default=2.5)
callback_price_deviation_percent = models.FloatField(default=0.5)
def __str__(self):
return self.name

@ -19,6 +19,8 @@
<th>max risk percent</th>
<th>max open trades</th>
<th>max open trades per symbol</th>
<th>max price slippage percent</th>
<th>max callback price deviation percent</th>
<th>actions</th>
</thead>
{% for item in object_list %}
@ -31,6 +33,8 @@
<td>{{ item.max_risk_percent }}</td>
<td>{{ item.max_open_trades }}</td>
<td>{{ item.max_open_trades_per_symbol }}</td>
<td>{{ item.price_slippage_percent }}</td>
<td>{{ item.callback_price_deviation_percent }}</td>
<td>
<div class="buttons">
<button

@ -181,9 +181,12 @@ def get_price_bound(direction, strategy, price, current_price):
"""
# Convert the callback price deviation to a ratio
callback_price_deviation_as_ratio = D(
strategy.callback_price_deviation_percent
) / D(100)
if strategy.risk_model is not None:
callback_price_deviation_as_ratio = D(
strategy.risk_model.callback_price_deviation_percent
) / D(100)
else:
callback_price_deviation_as_ratio = D(0.5) / D(100)
log.debug(f"Callback price deviation as ratio: {callback_price_deviation_as_ratio}")
maximum_price_deviation = D(current_price) * D(callback_price_deviation_as_ratio)
@ -197,7 +200,11 @@ def get_price_bound(direction, strategy, price, current_price):
return None
# Convert the maximum price slippage to a ratio
price_slippage_as_ratio = D(strategy.price_slippage_percent) / D(100)
if strategy.risk_model is not None:
price_slippage_as_ratio = D(strategy.risk_model.price_slippage_percent) / D(100)
else:
# Pretty liberal default
price_slippage_as_ratio = D(2.5) / D(100)
log.debug(f"Maximum price slippage as ratio: {price_slippage_as_ratio}")
# Calculate the price bound by multiplying with the price
@ -453,8 +460,8 @@ def execute_strategy(callback, strategy, func):
)
new_trade.save()
if account.risk_model is not None:
allowed = check_risk(account.risk_model, account, new_trade)
if strategy.risk_model is not None:
allowed = check_risk(strategy.risk_model, account, new_trade)
if not allowed["allowed"]:
new_trade.status = "rejected"
new_trade.information = allowed["reason"]

Loading…
Cancel
Save