Check the max risk relative to the account balance
This commit is contained in:
parent
ae42d9b223
commit
93be9e6ffe
|
@ -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.
|
|
||||||
"""
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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):
|
||||||
|
|
Loading…
Reference in New Issue