Skip to content

Commit

Permalink
Fix RiskEngine cumulative notional check
Browse files Browse the repository at this point in the history
  • Loading branch information
cjdsellers committed Dec 1, 2023
1 parent c035c7f commit fbeb07f
Show file tree
Hide file tree
Showing 2 changed files with 32 additions and 12 deletions.
1 change: 1 addition & 0 deletions RELEASES.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ Released on TBD (UTC).
- Fixed conversion of fixed precision integers to floats (should be dividing to avoid rounding errors), thanks for reporting @filipmacek
- Fixed daily timestamp parsing for Interactive Brokers, thanks @benjaminsingleton
- Fixed live reconciliation trade processing for partially filled then canceled orders
- Fixed `RiskEngine` cumulative notional risk check for `CurrencyPair` SELL orders on multi-currency cash accounts

---

Expand Down
43 changes: 31 additions & 12 deletions nautilus_trader/risk/engine.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ from nautilus_trader.model.identifiers cimport ComponentId
from nautilus_trader.model.identifiers cimport InstrumentId
from nautilus_trader.model.instruments.base cimport Instrument
from nautilus_trader.model.instruments.currency_pair cimport CurrencyPair
from nautilus_trader.model.objects cimport Currency
from nautilus_trader.model.objects cimport Money
from nautilus_trader.model.objects cimport Price
from nautilus_trader.model.objects cimport Quantity
Expand Down Expand Up @@ -627,6 +628,7 @@ cdef class RiskEngine(Component):
Money cum_notional_buy = None
Money cum_notional_sell = None
Money order_balance_impact = None
Currency base_currency = None
double xrate
for order in orders:
if order.order_type == OrderType.MARKET or order.order_type == OrderType.MARKET_TO_LIMIT:
Expand Down Expand Up @@ -666,10 +668,11 @@ cdef class RiskEngine(Component):
####################################################################
# CASH account balance risk check
####################################################################
if max_notional and isinstance(instrument, CurrencyPair) and order.side == OrderSide.SELL:
if isinstance(instrument, CurrencyPair) and order.side == OrderSide.SELL:
xrate = 1.0 / last_px.as_f64_c()
notional = Money(order.quantity.as_f64_c() * xrate, instrument.base_currency)
max_notional = Money(max_notional * Decimal(xrate), instrument.base_currency)
if max_notional:
max_notional = Money(max_notional * Decimal(xrate), instrument.base_currency)
else:
notional = instrument.notional_value(order.quantity, last_px, use_quote_for_inverse=True)

Expand Down Expand Up @@ -713,6 +716,9 @@ cdef class RiskEngine(Component):
)
return False # Denied

if base_currency is None:
base_currency = instrument.get_base_currency()

if order.is_buy_c():
if cum_notional_buy is None:
cum_notional_buy = Money(-order_balance_impact, order_balance_impact.currency)
Expand All @@ -725,16 +731,29 @@ cdef class RiskEngine(Component):
)
return False # Denied
elif order.is_sell_c():
if cum_notional_sell is None:
cum_notional_sell = Money(order_balance_impact, order_balance_impact.currency)
else:
cum_notional_sell._mem.raw += order_balance_impact._mem.raw
if free is not None and cum_notional_sell._mem.raw >= free._mem.raw:
self._deny_order(
order=order,
reason=f"CUM_NOTIONAL_EXCEEDS_FREE_BALANCE {free.to_str()} @ {cum_notional_sell.to_str()}",
)
return False # Denied
if account.base_currency is not None:
if cum_notional_sell is None:
cum_notional_sell = Money(order_balance_impact, order_balance_impact.currency)
else:
cum_notional_sell._mem.raw += order_balance_impact._mem.raw
if free is not None and cum_notional_sell._mem.raw >= free._mem.raw:
self._deny_order(
order=order,
reason=f"CUM_NOTIONAL_EXCEEDS_FREE_BALANCE {free.to_str()} @ {cum_notional_sell.to_str()}",
)
return False # Denied
elif base_currency is not None:
free = account.balance_free(base_currency)
if cum_notional_sell is None:
cum_notional_sell = notional
else:
cum_notional_sell._mem.raw += notional._mem.raw
if free is not None and cum_notional_sell._mem.raw >= free._mem.raw:
self._deny_order(
order=order,
reason=f"CUM_NOTIONAL_EXCEEDS_FREE_BALANCE {free.to_str()} @ {cum_notional_sell.to_str()}",
)
return False # Denied

# Finally
return True # Passed
Expand Down

0 comments on commit fbeb07f

Please sign in to comment.