fisk/core/trading/risk.py

114 lines
3.9 KiB
Python
Raw Normal View History

2023-01-11 19:46:47 +00:00
from decimal import Decimal as D
from core.exchanges import convert
from core.models import Trade
from core.trading import market
2022-12-13 07:20:49 +00:00
def check_max_loss(risk_model, initial_balance, account_balance):
"""
Check that the account balance is within the max loss limit.
"""
max_loss_percent = risk_model.max_loss_percent
2023-01-11 19:46:47 +00:00
# Calculate the inverse of the max loss percent as a ratio
2023-01-11 19:46:47 +00:00
inverse_loss_multiplier = 1 - max_loss_percent / 100
minimum_balance = initial_balance * inverse_loss_multiplier
return account_balance > minimum_balance
2022-12-13 07:20:49 +00:00
def check_max_risk(risk_model, account_balance_usd, account_trades):
2022-12-13 07:20:49 +00:00
"""
Check that all of the trades in the account are within the max risk limit.
"""
2023-01-11 19:46:47 +00:00
max_risk_percent = D(risk_model.max_risk_percent)
# Calculate the max risk of the account in USD
2023-01-11 19:46:47 +00:00
max_risk_usd = account_balance_usd * (max_risk_percent / D(100))
2022-12-13 07:20:49 +00:00
total_risk = 0
2022-12-13 07:20:49 +00:00
for trade in account_trades:
max_tmp = []
2023-01-06 08:52:15 +00:00
# Need to calculate the max risk in base account currency
# Percentages relate to the price movement, without accounting the
# size of the trade
if "stop_loss_usd" in trade:
max_tmp.append(trade["stop_loss_usd"])
if "trailing_stop_loss_usd" in trade:
max_tmp.append(trade["trailing_stop_loss_usd"])
2023-01-11 19:46:47 +00:00
if max_tmp:
max_risk = max(max_tmp)
total_risk += max_risk
2023-01-11 19:46:47 +00:00
allowed = total_risk < max_risk_usd
return allowed
2022-12-13 07:20:49 +00:00
def check_max_open_trades(risk_model, account_trades):
"""
Check that the number of trades in the account is within the max open trades limit.
"""
return len(account_trades) < risk_model.max_open_trades
2023-02-17 22:23:12 +00:00
def check_max_open_trades_per_symbol(risk_model, account_trades, return_symbols=False):
2022-12-13 07:20:49 +00:00
"""
Check we cannot open more trades per symbol than permissible.
"""
symbol_map = {}
for trade in account_trades:
symbol = trade["symbol"]
if symbol not in symbol_map:
symbol_map[symbol] = 0
symbol_map[symbol] += 1
2023-02-17 22:23:12 +00:00
violating_symbols = []
2022-12-13 07:20:49 +00:00
for symbol, count in symbol_map.items():
if count >= risk_model.max_open_trades_per_symbol:
2023-02-17 22:23:12 +00:00
violating_symbols.append(symbol)
if return_symbols:
return violating_symbols
if violating_symbols:
return False
return True
2023-01-01 21:52:51 +00:00
def check_risk(risk_model, account, proposed_trade):
"""
Run the risk checks on the account and the proposed trade.
"""
# Check that the max loss is not exceeded
max_loss_check = check_max_loss(
risk_model, account.initial_balance, account.client.get_balance()
)
if not max_loss_check:
return {"allowed": False, "reason": "Maximum loss exceeded."}
# Check that the account max trades is not exceeded
2023-01-11 19:46:47 +00:00
account_trades = account.client.get_all_open_trades()
if isinstance(proposed_trade, Trade):
proposed_trade = proposed_trade.__dict__
account_trades.append(proposed_trade)
account_trades = convert.convert_trades(account_trades)
account_trades = market.convert_trades_to_usd(account, account_trades)
2023-01-01 21:52:51 +00:00
max_open_trades_check = check_max_open_trades(risk_model, account_trades)
if not max_open_trades_check:
return {"allowed": False, "reason": "Maximum open trades exceeded."}
# Check that the max trades per symbol is not exceeded
max_open_trades_per_symbol_check = check_max_open_trades_per_symbol(
risk_model, account_trades
)
if not max_open_trades_per_symbol_check:
return {"allowed": False, "reason": "Maximum open trades per symbol exceeded."}
# Check that the max risk is not exceeded
2023-01-11 19:46:47 +00:00
account_balance_usd = account.client.get_balance(return_usd=True)
max_risk_check = check_max_risk(risk_model, account_balance_usd, account_trades)
2023-01-01 21:52:51 +00:00
if not max_risk_check:
return {"allowed": False, "reason": "Maximum risk exceeded."}
2023-01-11 19:46:47 +00:00
return {"allowed": True}