Skip to content

Commit

Permalink
Make OrderManager more generic
Browse files Browse the repository at this point in the history
  • Loading branch information
cjdsellers committed Oct 28, 2023
1 parent c9997c0 commit c7350ea
Show file tree
Hide file tree
Showing 8 changed files with 93 additions and 55 deletions.
3 changes: 3 additions & 0 deletions nautilus_trader/config/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -440,6 +440,8 @@ class StrategyConfig(NautilusConfig, kw_only=True, frozen=True):
external_order_claims : list[str], optional
The external order claim instrument IDs.
External orders for matching instrument IDs will be associated with (claimed by) the strategy.
manage_contingencies : bool, default False
If OUO and OCO **open** contingency orders should be managed automatically by the strategy.
manage_gtd_expiry : bool, default False
If all order GTD time in force expirations should be managed by the strategy.
If True then will ensure open orders have their GTD timers re-activated on start.
Expand All @@ -450,6 +452,7 @@ class StrategyConfig(NautilusConfig, kw_only=True, frozen=True):
order_id_tag: str | None = None
oms_type: str | None = None
external_order_claims: list[str] | None = None
manage_contingencies: bool = False
manage_gtd_expiry: bool = False


Expand Down
1 change: 1 addition & 0 deletions nautilus_trader/execution/emulator.pxd
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ cdef class OrderEmulator(Actor):

cpdef void _check_monitoring(self, StrategyId strategy_id, PositionId position_id)
cpdef void _cancel_order(self, Order order)
cpdef void _update_order(self, Order order, Quantity new_quantity)

# -------------------------------------------------------------------------------------------------

Expand Down
54 changes: 53 additions & 1 deletion nautilus_trader/execution/emulator.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ from nautilus_trader.model.orders.market cimport MarketOrder
from nautilus_trader.msgbus.bus cimport MessageBus


cdef tuple SUPPORTED_TRIGGERS = (TriggerType.DEFAULT, TriggerType.BID_ASK, TriggerType.LAST_TRADE)
cdef set SUPPORTED_TRIGGERS = {TriggerType.DEFAULT, TriggerType.BID_ASK, TriggerType.LAST_TRADE}


cdef class OrderEmulator(Actor):
Expand Down Expand Up @@ -127,6 +127,7 @@ cdef class OrderEmulator(Actor):
component_name=type(self).__name__,
submit_order_handler=self._handle_submit_order,
cancel_order_handler=self._cancel_order,
modify_order_handler=self._update_order,
debug=config.debug,
)

Expand Down Expand Up @@ -598,6 +599,57 @@ cdef class OrderEmulator(Actor):
if matching_core is not None:
matching_core.delete_order(order)

self.cache.update_order_pending_cancel_local(order)

# Generate event
cdef uint64_t ts_now = self._clock.timestamp_ns()
cdef OrderCanceled event = OrderCanceled(
trader_id=order.trader_id,
strategy_id=order.strategy_id,
instrument_id=order.instrument_id,
client_order_id=order.client_order_id,
venue_order_id=order.venue_order_id, # Probably None
account_id=order.account_id, # Probably None
event_id=UUID4(),
ts_event=ts_now,
ts_init=ts_now,
)
self._manager.send_exec_event(event)

cpdef void _update_order(self, Order order, Quantity new_quantity):
if order is None:
self._log.error(
f"Cannot update order: order for {repr(order.client_order_id)} not found.",
)
return

if self.debug:
self._log.info(
f"Updating order {order.client_order_id} quantity to {new_quantity}.",
LogColor.MAGENTA,
)

# Generate event
cdef uint64_t ts_now = self._clock.timestamp_ns()
cdef OrderUpdated event = OrderUpdated(
trader_id=order.trader_id,
strategy_id=order.strategy_id,
instrument_id=order.instrument_id,
client_order_id=order.client_order_id,
venue_order_id=None, # Not yet assigned by any venue
account_id=order.account_id, # Probably None
quantity=new_quantity,
price=None,
trigger_price=None,
event_id=UUID4(),
ts_event=ts_now,
ts_init=ts_now,
)
order.apply(event)
self.cache.update_order(order)

self._manager.send_risk_event(event)

# -------------------------------------------------------------------------------------------------

cpdef void _trigger_stop_order(self, Order order):
Expand Down
3 changes: 2 additions & 1 deletion nautilus_trader/execution/manager.pxd
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ cdef class OrderManager:
cdef dict _submit_order_commands
cdef object _submit_order_handler
cdef object _cancel_order_handler
cdef object _modify_order_handler

cpdef dict get_submit_order_commands(self)
cpdef void cache_submit_order_command(self, SubmitOrder command)
Expand All @@ -63,6 +64,7 @@ cdef class OrderManager:
# -- COMMAND HANDLERS -----------------------------------------------------------------------------

cpdef void cancel_order(self, Order order)
cpdef void modify_order_quantity(self, Order order, Quantity new_quantity)
cpdef void create_new_submit_order(self, Order order, PositionId position_id=*, ClientId client_id=*)

# -- EVENT HANDLERS -------------------------------------------------------------------------------
Expand All @@ -75,7 +77,6 @@ cdef class OrderManager:
cpdef void handle_order_filled(self, OrderFilled filled)
cpdef void handle_contingencies(self, Order order)
cpdef void handle_contingencies_update(self, Order order)
cpdef void update_order_quantity(self, Order order, Quantity new_quantity)

# -- EGRESS ---------------------------------------------------------------------------------------

Expand Down
79 changes: 27 additions & 52 deletions nautilus_trader/execution/manager.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,8 @@ cdef class OrderManager:
The handler to call when submitting orders.
cancel_order_handler : Callable[[Order], None], optional
The handler to call when canceling orders.
modify_order_handler : Callable[[Order], None], optional
The handler to call when modifying orders.
debug : bool, default False
If debug mode is active (will provide extra debug logging).
Expand All @@ -95,6 +97,7 @@ cdef class OrderManager:
str component_name not None,
submit_order_handler: Optional[Callable[[SubmitOrder], None]] = None,
cancel_order_handler: Optional[Callable[[Order], None]] = None,
modify_order_handler: Optional[Callable[[Order, Quantity], None]] = None,
bint debug = False,
):
Condition.valid_string(component_name, "component_name")
Expand All @@ -107,8 +110,9 @@ cdef class OrderManager:
self._cache = cache

self.debug = debug
self._submit_order_handler: Callable[[SubmitOrder], None] = submit_order_handler
self._cancel_order_handler: Callable[[Order], None] = cancel_order_handler
self._submit_order_handler = submit_order_handler
self._cancel_order_handler = cancel_order_handler
self._modify_order_handler = modify_order_handler

self._submit_order_commands: dict[ClientOrderId, SubmitOrder] = {}

Expand Down Expand Up @@ -181,8 +185,6 @@ cdef class OrderManager:
self._log.warning("Cannot cancel order: already closed.")
return

self._cache.update_order_pending_cancel_local(order)

if self.debug:
self._log.info(f"Cancelling order {order}.", LogColor.MAGENTA)

Expand All @@ -191,20 +193,21 @@ cdef class OrderManager:
if self._cancel_order_handler is not None:
self._cancel_order_handler(order)

# Generate event
cdef uint64_t ts_now = self._clock.timestamp_ns()
cdef OrderCanceled event = OrderCanceled(
trader_id=order.trader_id,
strategy_id=order.strategy_id,
instrument_id=order.instrument_id,
client_order_id=order.client_order_id,
venue_order_id=order.venue_order_id, # Probably None
account_id=order.account_id, # Probably None
event_id=UUID4(),
ts_event=ts_now,
ts_init=ts_now,
)
self.send_exec_event(event)
cpdef void modify_order_quantity(self, Order order, Quantity new_quantity):
"""
Modify the given `order` with the manager.
Parameters
----------
order : Order
The order to modify.
"""
Condition.not_none(order, "order")
Condition.not_none(new_quantity, "new_quantity")

if self._modify_order_handler is not None:
self._modify_order_handler(order, new_quantity)

cpdef void create_new_submit_order(
self,
Expand Down Expand Up @@ -366,9 +369,9 @@ cdef class OrderManager:
child_order.position_id = position_id

if parent_filled_qty._mem.raw != child_order.leaves_qty._mem.raw:
self.update_order_quantity(child_order, parent_filled_qty)
self.modify_order_quantity(child_order, parent_filled_qty)

if child_order.status_c() not in (OrderStatus.INITIALIZED, OrderStatus.EMULATED) or self._submit_order_handler is None:
if not child_order.is_active_local_c() or self._submit_order_handler is None:
return # Order does not need to be released

if not child_order.client_order_id in self._submit_order_commands:
Expand Down Expand Up @@ -435,7 +438,7 @@ cdef class OrderManager:
if order.is_closed_c() and filled_qty._mem.raw == 0 and (order.exec_spawn_id is None or not is_spawn_active):
self.cancel_order(contingent_order)
elif filled_qty._mem.raw > 0 and filled_qty._mem.raw != contingent_order.quantity._mem.raw:
self.update_order_quantity(contingent_order, filled_qty)
self.modify_order_quantity(contingent_order, filled_qty)
elif order.contingency_type == ContingencyType.OCO:
if self.debug:
self._log.info(f"Processing OCO contingent order {client_order_id}.", LogColor.MAGENTA)
Expand All @@ -449,7 +452,7 @@ cdef class OrderManager:
elif order.is_closed_c() and (order.exec_spawn_id is None or not is_spawn_active):
self.cancel_order(contingent_order)
elif leaves_qty._mem.raw != contingent_order.leaves_qty._mem.raw:
self.update_order_quantity(contingent_order, leaves_qty)
self.modify_order_quantity(contingent_order, leaves_qty)

cpdef void handle_contingencies_update(self, Order order):
Condition.not_none(order, "order")
Expand Down Expand Up @@ -482,38 +485,10 @@ cdef class OrderManager:

if order.contingency_type == ContingencyType.OTO:
if quantity._mem.raw != contingent_order.quantity._mem.raw:
self.update_order_quantity(contingent_order, quantity)
self.modify_order_quantity(contingent_order, quantity)
elif order.contingency_type == ContingencyType.OUO:
if quantity._mem.raw != contingent_order.quantity._mem.raw:
self.update_order_quantity(contingent_order, quantity)

cpdef void update_order_quantity(self, Order order, Quantity new_quantity):
if self.debug:
self._log.info(
f"Update contingency order {order.client_order_id} quantity to {new_quantity}.",
LogColor.MAGENTA,
)

# Generate event
cdef uint64_t ts_now = self._clock.timestamp_ns()
cdef OrderUpdated event = OrderUpdated(
trader_id=order.trader_id,
strategy_id=order.strategy_id,
instrument_id=order.instrument_id,
client_order_id=order.client_order_id,
venue_order_id=None, # Not yet assigned by any venue
account_id=order.account_id, # Probably None
quantity=new_quantity,
price=None,
trigger_price=None,
event_id=UUID4(),
ts_event=ts_now,
ts_init=ts_now,
)
order.apply(event)
self._cache.update_order(order)

self.send_risk_event(event)
self.modify_order_quantity(contingent_order, quantity)

# -- EGRESS ---------------------------------------------------------------------------------------

Expand Down
4 changes: 3 additions & 1 deletion nautilus_trader/trading/strategy.pxd
Original file line number Diff line number Diff line change
Expand Up @@ -78,8 +78,10 @@ cdef class Strategy(Actor):
"""The order management system for the strategy.\n\n:returns: `OmsType`"""
cdef readonly list external_order_claims
"""The external order claims instrument IDs for the strategy.\n\n:returns: `list[InstrumentId]`"""
cdef readonly bint manage_contingencies
"""If contingency orders should be managed automatically by the strategy.\n\n:returns: `bool`"""
cdef readonly bint manage_gtd_expiry
"""If all order GTD time in force expirations should be managed by the strategy.\n\n:returns: `bool`"""
"""If all order GTD time in force expirations should be managed automatically by the strategy.\n\n:returns: `bool`"""

# -- REGISTRATION ---------------------------------------------------------------------------------

Expand Down
1 change: 1 addition & 0 deletions nautilus_trader/trading/strategy.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,7 @@ cdef class Strategy(Actor):
self.config = config
self.oms_type = oms_type_from_str(str(config.oms_type).upper()) if config.oms_type else OmsType.UNSPECIFIED
self.external_order_claims = self._parse_external_order_claims(config.external_order_claims)
self.manage_contingencies = config.manage_contingencies
self.manage_gtd_expiry = config.manage_gtd_expiry

# Public components
Expand Down
3 changes: 3 additions & 0 deletions tests/unit_tests/trading/test_strategy.py
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,7 @@ def test_strategy_to_importable_config_with_no_specific_config(self):
"order_id_tag": None,
"strategy_id": None,
"external_order_claims": None,
"manage_contingencies": False,
"manage_gtd_expiry": False,
}

Expand All @@ -210,6 +211,7 @@ def test_strategy_to_importable_config(self):
order_id_tag="001",
strategy_id="ALPHA-01",
external_order_claims=["ETHUSDT-PERP.DYDX"],
manage_contingencies=True,
manage_gtd_expiry=True,
)

Expand All @@ -227,6 +229,7 @@ def test_strategy_to_importable_config(self):
"order_id_tag": "001",
"strategy_id": "ALPHA-01",
"external_order_claims": ["ETHUSDT-PERP.DYDX"],
"manage_contingencies": True,
"manage_gtd_expiry": True,
}

Expand Down

0 comments on commit c7350ea

Please sign in to comment.