Check the max risk relative to the account balance

This commit is contained in:
Mark Veidemanis 2023-01-06 07:20:55 +00:00
parent ae42d9b223
commit 93be9e6ffe
Signed by: m
GPG Key ID: 5ACFCEED46C0904F
4 changed files with 136 additions and 44 deletions

View File

@ -1,7 +1,6 @@
from decimal import Decimal as D from decimal import Decimal as D
from core.lib.elastic import store_msg from core.lib.elastic import store_msg
from core.trading.market import to_currency
def get_balance_hook(user_id, user_name, account_id, account_name, balance): def get_balance_hook(user_id, user_name, account_id, account_name, balance):
@ -89,9 +88,6 @@ def sl_price_to_percent(sl_price, side, current_price, current_units, unrealised
else: else:
profit = False profit = False
print("CHANGE PERCENT: ", change_percent)
print("PROFIT", profit)
if profit: if profit:
change_percent = 0 - abs(change_percent) change_percent = 0 - abs(change_percent)
else: else:
@ -120,6 +116,15 @@ def convert_open_trades(open_trades):
"pl": unrealised_pl, "pl": unrealised_pl,
} }
# Add some extra fields, sometimes we have already looked up the
# prices and don't need to call convert_trades_to_usd
if "take_profit_usd" in trade:
cast["take_profit_usd"] = trade["take_profit_usd"]
if "stop_loss_usd" in trade:
cast["stop_loss_usd"] = trade["stop_loss_usd"]
if "trailing_stop_loss_usd" in trade:
cast["trailing_stop_loss_usd"] = trade["trailing_stop_loss_usd"]
if "takeProfitOrder" in trade: if "takeProfitOrder" in trade:
if trade["takeProfitOrder"]: if trade["takeProfitOrder"]:
take_profit = trade["takeProfitOrder"]["price"] take_profit = trade["takeProfitOrder"]["price"]
@ -156,9 +161,3 @@ def convert_open_trades(open_trades):
trades.append(cast) trades.append(cast)
return trades return trades
def convert_trades_to_usd(account, trades):
"""
Convert a list of trades to USD.
"""

View File

@ -1,6 +1,6 @@
from django.test import TestCase from django.test import TestCase
from core.exchanges.common import convert_open_trades from core.exchanges import common
from core.models import RiskModel, User from core.models import RiskModel, User
from core.trading import risk from core.trading import risk
@ -25,8 +25,11 @@ class RiskModelTestCase(TestCase):
"side": "BUY", "side": "BUY",
# We already calculated the TP percent loss relative to the account size # We already calculated the TP percent loss relative to the account size
"take_profit_percent": 9, "take_profit_percent": 9,
"take_profit_usd": 9,
"stop_loss_percent": 9, "stop_loss_percent": 9,
"stop_loss_usd": 9,
"trailing_stop_loss_percent": 9, "trailing_stop_loss_percent": 9,
"trailing_stop_loss_usd": 9,
} }
def test_check_max_loss(self): def test_check_max_loss(self):
@ -65,7 +68,9 @@ class RiskModelTestCase(TestCase):
Check that we can open a trade within the max risk limit. Check that we can open a trade within the max risk limit.
""" """
account_trades = [self.trade] account_trades = [self.trade]
allowed = risk.check_max_risk(self.risk_model, account_trades) allowed = risk.check_max_risk(
self.risk_model, self.account_initial_balance, account_trades
)
self.assertTrue(allowed) self.assertTrue(allowed)
def test_check_max_risk_multiple_trades(self): def test_check_max_risk_multiple_trades(self):
@ -76,8 +81,12 @@ class RiskModelTestCase(TestCase):
trade = self.trade.copy() trade = self.trade.copy()
trade["stop_loss_percent"] = 1 trade["stop_loss_percent"] = 1
trade["trailing_stop_loss_percent"] = 1 trade["trailing_stop_loss_percent"] = 1
trade["stop_loss_usd"] = 1
trade["trailing_stop_loss_usd"] = 1
account_trades = [trade] * 9 account_trades = [trade] * 9
allowed = risk.check_max_risk(self.risk_model, account_trades) allowed = risk.check_max_risk(
self.risk_model, self.account_initial_balance, account_trades
)
self.assertTrue(allowed) self.assertTrue(allowed)
def test_check_max_risk_fail_exact(self): def test_check_max_risk_fail_exact(self):
@ -87,8 +96,13 @@ class RiskModelTestCase(TestCase):
""" """
trade = self.trade.copy() trade = self.trade.copy()
trade["stop_loss_percent"] = 10 trade["stop_loss_percent"] = 10
trade["trailing_stop_loss_percent"] = 10
trade["stop_loss_usd"] = 10
trade["trailing_stop_loss_usd"] = 10
account_trades = [trade] account_trades = [trade]
allowed = risk.check_max_risk(self.risk_model, account_trades) allowed = risk.check_max_risk(
self.risk_model, self.account_initial_balance, account_trades
)
self.assertFalse(allowed) self.assertFalse(allowed)
def test_check_max_risk_fail_exact_multiple_trades(self): def test_check_max_risk_fail_exact_multiple_trades(self):
@ -99,8 +113,12 @@ class RiskModelTestCase(TestCase):
trade = self.trade.copy() trade = self.trade.copy()
trade["stop_loss_percent"] = 1 trade["stop_loss_percent"] = 1
trade["trailing_stop_loss_percent"] = 1 trade["trailing_stop_loss_percent"] = 1
trade["stop_loss_usd"] = 1
trade["trailing_stop_loss_usd"] = 1
account_trades = [trade] * 10 account_trades = [trade] * 10
allowed = risk.check_max_risk(self.risk_model, account_trades) allowed = risk.check_max_risk(
self.risk_model, self.account_initial_balance, account_trades
)
self.assertFalse(allowed) self.assertFalse(allowed)
def test_check_max_open_trades(self): def test_check_max_open_trades(self):
@ -191,7 +209,8 @@ class RiskModelTestCase(TestCase):
allowed = risk.check_max_open_trades_per_symbol(self.risk_model, account_trades) allowed = risk.check_max_open_trades_per_symbol(self.risk_model, account_trades)
self.assertFalse(allowed) self.assertFalse(allowed)
def check_max_risk_market_data(self): # Market data tests, account size: $1000
def test_check_max_risk_market_data(self):
""" """
Check that we can open a trade within the max risk limit with market data. Check that we can open a trade within the max risk limit with market data.
""" """
@ -206,13 +225,16 @@ class RiskModelTestCase(TestCase):
"stopLossOrder": { "stopLossOrder": {
"price": 0.95, # down by 5%, 5% risk "price": 0.95, # down by 5%, 5% risk
}, },
# Hardcoded prices to avoid calling market API here
"stop_loss_usd": 50, # 5% of $1000
} }
converted = convert_open_trades([trade]) converted = common.convert_open_trades([trade])
self.assertEqual(converted[0]["stop_loss_percent"], 5) self.assertEqual(converted[0]["stop_loss_percent"], 5)
max_risk_check = risk.check_max_risk(self.risk_model, converted) self.assertEqual(converted[0]["stop_loss_usd"], 50)
max_risk_check = risk.check_max_risk(self.risk_model, 1000, converted)
self.assertTrue(max_risk_check) # 5% risk is fine self.assertTrue(max_risk_check) # 5% risk is fine
def check_max_risk_market_data_multiple(self): def test_check_max_risk_market_data_multiple(self):
""" """
Check that we can open a trade within the max risk limit with market data Check that we can open a trade within the max risk limit with market data
and multiple trades. and multiple trades.
@ -228,12 +250,16 @@ class RiskModelTestCase(TestCase):
"stopLossOrder": { "stopLossOrder": {
"price": 0.96, # down by 4%, 4% risk "price": 0.96, # down by 4%, 4% risk
}, },
# Hardcoded prices to avoid calling market API here
"stop_loss_usd": 40, # 4% of $1000
} }
converted = convert_open_trades([trade, trade]) converted = common.convert_open_trades([trade, trade])
max_risk_check = risk.check_max_risk(self.risk_model, converted) self.assertEqual(converted[0]["stop_loss_percent"], 4)
self.assertEqual(converted[0]["stop_loss_usd"], 40)
max_risk_check = risk.check_max_risk(self.risk_model, 1000, converted)
self.assertTrue(max_risk_check) # 8% risk is fine self.assertTrue(max_risk_check) # 8% risk is fine
def check_max_risk_market_data_fail(self): def test_check_max_risk_market_data_fail(self):
""" """
Check that we can not open a trade outside the max risk limit with market data. Check that we can not open a trade outside the max risk limit with market data.
""" """
@ -248,13 +274,16 @@ class RiskModelTestCase(TestCase):
"stopLossOrder": { "stopLossOrder": {
"price": 0.9, # down by 10%, 10% risk "price": 0.9, # down by 10%, 10% risk
}, },
# Hardcoded prices to avoid calling market API here
"stop_loss_usd": 100, # 10% of $1000
} }
converted = convert_open_trades([trade]) converted = common.convert_open_trades([trade])
self.assertEqual(converted[0]["stop_loss_percent"], 10) self.assertEqual(converted[0]["stop_loss_percent"], 10)
max_risk_check = risk.check_max_risk(self.risk_model, converted) self.assertEqual(converted[0]["stop_loss_usd"], 100)
max_risk_check = risk.check_max_risk(self.risk_model, 1000, converted)
self.assertFalse(max_risk_check) # 10% risk is too much self.assertFalse(max_risk_check) # 10% risk is too much
def check_max_risk_market_data_fail_multiple(self): def test_check_max_risk_market_data_fail_multiple(self):
""" """
Check that we can not open a trade outside the max risk limit with market data Check that we can not open a trade outside the max risk limit with market data
and multiple trades. and multiple trades.
@ -270,12 +299,18 @@ class RiskModelTestCase(TestCase):
"stopLossOrder": { "stopLossOrder": {
"price": 0.95, # down by 5%, 5% risk "price": 0.95, # down by 5%, 5% risk
}, },
# Hardcoded prices to avoid calling market API here
"stop_loss_usd": 50, # 5% of $1000
} }
converted = convert_open_trades([trade, trade]) converted = common.convert_open_trades([trade, trade])
max_risk_check = risk.check_max_risk(self.risk_model, converted) self.assertEqual(converted[0]["stop_loss_percent"], 5)
self.assertEqual(converted[0]["stop_loss_usd"], 50)
self.assertEqual(converted[1]["stop_loss_percent"], 5)
self.assertEqual(converted[1]["stop_loss_usd"], 50)
max_risk_check = risk.check_max_risk(self.risk_model, 1000, converted)
self.assertFalse(max_risk_check) # 10% risk is too much self.assertFalse(max_risk_check) # 10% risk is too much
def check_max_risk_market_data_fail_multiple_mixed(self): def test_check_max_risk_market_data_fail_multiple_mixed(self):
""" """
Check that we can not open a trade outside the max risk limit with market data Check that we can not open a trade outside the max risk limit with market data
and multiple trades, mixing SL and TSL. and multiple trades, mixing SL and TSL.
@ -291,16 +326,23 @@ class RiskModelTestCase(TestCase):
"stopLossOrder": { "stopLossOrder": {
"price": 0.95, # down by 5%, 5% risk "price": 0.95, # down by 5%, 5% risk
}, },
# Hardcoded prices to avoid calling market API here
"stop_loss_usd": 50, # 5% of $1000
"trailing_stop_loss_usd": 50,
} }
trade2 = trade.copy() trade2 = trade.copy()
trade2["trailingStopLossOrder"] = {"price": 0.95} trade2["trailingStopLossOrder"] = {"price": 0.95}
del trade2["stopLossOrder"] del trade2["stopLossOrder"]
converted = convert_open_trades([trade, trade2]) converted = common.convert_open_trades([trade, trade2])
max_risk_check = risk.check_max_risk(self.risk_model, converted) self.assertEqual(converted[0]["stop_loss_percent"], 5)
self.assertEqual(converted[0]["stop_loss_usd"], 50)
self.assertEqual(converted[1]["trailing_stop_loss_percent"], 5)
self.assertEqual(converted[1]["trailing_stop_loss_usd"], 50)
max_risk_check = risk.check_max_risk(self.risk_model, 1000, converted)
self.assertFalse(max_risk_check) # 10% risk is too much self.assertFalse(max_risk_check) # 10% risk is too much
def check_max_risk_market_data_fail_multiple_mixed_both(self): def test_check_max_risk_market_data_fail_multiple_mixed_both(self):
""" """
Check that we can not open a trade outside the max risk limit with market data Check that we can not open a trade outside the max risk limit with market data
and multiple trades, mixing SL and TSL, where both are set. and multiple trades, mixing SL and TSL, where both are set.
@ -316,10 +358,17 @@ class RiskModelTestCase(TestCase):
"stopLossOrder": { "stopLossOrder": {
"price": 0.95, # down by 5%, 5% risk "price": 0.95, # down by 5%, 5% risk
}, },
# Hardcoded prices to avoid calling market API here
"stop_loss_usd": 50, # 5% of $1000
"trailing_stop_loss_usd": 49,
} }
trade2 = trade.copy() trade2 = trade.copy()
trade2["trailingStopLossOrder"] = {"price": 0.951} trade2["trailingStopLossOrder"] = {"price": 0.951}
converted = convert_open_trades([trade, trade2]) converted = common.convert_open_trades([trade, trade2])
max_risk_check = risk.check_max_risk(self.risk_model, converted) self.assertEqual(converted[0]["stop_loss_percent"], 5)
self.assertEqual(converted[0]["stop_loss_usd"], 50)
self.assertEqual(float(converted[1]["trailing_stop_loss_percent"]), 4.9)
self.assertEqual(converted[1]["trailing_stop_loss_usd"], 49)
max_risk_check = risk.check_max_risk(self.risk_model, 1000, converted)
self.assertFalse(max_risk_check) # 10% risk is too much self.assertFalse(max_risk_check) # 10% risk is too much

View File

@ -10,6 +10,35 @@ from core.util import logs
log = logs.get_logger(__name__) log = logs.get_logger(__name__)
def side_to_direction(side):
"""
Convert a side to a direction.
"""
if side == "long":
return "buy"
elif side == "short":
return "sell"
else:
return False
def convert_trades_to_usd(account, trades):
"""
Convert a list of trades to USD.
"""
for trade in trades:
amount = trade["amount"]
symbol = trade["symbol"]
side = trade["side"]
direction = side_to_direction(side)
base, quote = get_base_quote(account.exchange, symbol)
print("BASE", base)
print("QUOTE", quote)
print("AMOUNT", amount)
amount_usd = to_currency(direction, account, amount, quote, "USD")
print("AMOUNT USD", amount_usd)
def get_pair(account, base, quote, invert=False): def get_pair(account, base, quote, invert=False):
""" """
Get the pair for the given account and currencies. Get the pair for the given account and currencies.
@ -36,6 +65,21 @@ def get_pair(account, base, quote, invert=False):
return symbol return symbol
def get_base_quote(exchange, symbol):
"""
Get the base and quote currencies from a symbol.
:param exchange: Exchange name
:param symbol: Symbol
:return: Tuple of base and quote currencies
"""
if exchange == "alpaca":
separator = "/"
elif exchange == "oanda":
separator = "_"
base, quote = symbol.split(separator)
return (base, quote)
def to_currency(direction, account, amount, from_currency, to_currency): def to_currency(direction, account, amount, from_currency, to_currency):
""" """
Convert an amount from one currency to another. Convert an amount from one currency to another.

View File

@ -1,6 +1,3 @@
from core.trading.market import to_currency
def check_max_loss(risk_model, initial_balance, account_balance): def check_max_loss(risk_model, initial_balance, account_balance):
""" """
Check that the account balance is within the max loss limit. Check that the account balance is within the max loss limit.
@ -10,23 +7,26 @@ def check_max_loss(risk_model, initial_balance, account_balance):
return account_balance > max_loss return account_balance > max_loss
def check_max_risk(risk_model, account_trades): def check_max_risk(risk_model, account_balance_usd, account_trades):
""" """
Check that all of the trades in the account are within the max risk limit. Check that all of the trades in the account are within the max risk limit.
""" """
max_risk_percent = risk_model.max_risk_percent max_risk_percent = risk_model.max_risk_percent
# Calculate the max risk of the account in USD
max_risk_usd = account_balance_usd * (max_risk_percent / 100)
total_risk = 0 total_risk = 0
for trade in account_trades: for trade in account_trades:
max_tmp = [] max_tmp = []
# Need to calculate the max risk in base account currency # Need to calculate the max risk in base account currency
# Percentages relate to the price movement, without accounting the size of the trade # Percentages relate to the price movement, without accounting the
if "stop_loss_percent" in trade: # size of the trade
max_tmp.append(trade["stop_loss_percent"]) if "stop_loss_usd" in trade:
if "trailing_stop_loss_percent" in trade: max_tmp.append(trade["stop_loss_usd"])
max_tmp.append(trade["trailing_stop_loss_percent"]) if "trailing_stop_loss_usd" in trade:
max_tmp.append(trade["trailing_stop_loss_usd"])
total_risk += max(max_tmp) total_risk += max(max_tmp)
print("Total risk: ", total_risk) allowed = total_risk < max_risk_usd
return total_risk < max_risk_percent return allowed
def check_max_open_trades(risk_model, account_trades): def check_max_open_trades(risk_model, account_trades):