@@ -25,14 +25,18 @@ attempts to operate without a managing `Trader` instance.
2525
2626"""
2727
28+ import pandas as pd
29+
2830from nautilus_trader.trading.config import ImportableStrategyConfig
2931from nautilus_trader.trading.config import StrategyConfig
32+ from nautilus_trader.trading.messages import MarketExitStrategy
3033
3134from libc.stdint cimport uint64_t
3235
3336from nautilus_trader.cache.base cimport CacheFacade
3437from nautilus_trader.cache.cache cimport Cache
3538from nautilus_trader.common.actor cimport Actor
39+ from nautilus_trader.common.component cimport CMD
3640from nautilus_trader.common.component cimport EVT
3741from nautilus_trader.common.component cimport RECV
3842from nautilus_trader.common.component cimport Clock
@@ -163,6 +167,7 @@ cdef class Strategy(Actor):
163167 self .external_order_claims = self ._parse_external_order_claims(config.external_order_claims)
164168 self .manage_contingent_orders = config.manage_contingent_orders
165169 self .manage_gtd_expiry = config.manage_gtd_expiry
170+ self ._is_exiting = False
166171
167172 # Public components
168173 self .clock = self ._clock
@@ -243,6 +248,29 @@ cdef class Strategy(Actor):
243248 " occur here, such as resetting indicators and other state"
244249 )
245250
251+ cpdef void on_market_exit(self ):
252+ """
253+ Actions to be performed when a market exit has been initiated.
254+
255+ Warnings
256+ --------
257+ Override this method in a subclass to implement custom market exit logic.
258+
259+ """
260+ # Optionally override in subclass
261+
262+ cpdef void after_market_exit(self ):
263+ """
264+ Actions to be performed after a market exit has been completed.
265+
266+ Warnings
267+ --------
268+ Override this method in a subclass to implement custom logic after
269+ market exit.
270+
271+ """
272+ # Optionally override in subclass
273+
246274# -- REGISTRATION ---------------------------------------------------------------------------------
247275
248276 cpdef void register(
@@ -1664,7 +1692,77 @@ cdef class Strategy(Actor):
16641692 self ._log.info(f" Expiring GTD order {order.client_order_id}" , LogColor.BLUE)
16651693 self .cancel_order(order)
16661694
1667- # -- HANDLERS -------------------------------------------------------------------------------------
1695+ cpdef void market_exit(self ):
1696+ """
1697+ Initiate an iterative market exit for the strategy.
1698+
1699+ Will cancel all open orders and close all open positions, and wait for
1700+ all in-flight orders to resolve and positions to close before stopping
1701+ the strategy.
1702+ """
1703+ if self ._is_exiting:
1704+ return
1705+
1706+ self ._is_exiting = True
1707+
1708+ self ._log.info(" Initiating market exit..." , LogColor.BLUE)
1709+ self .on_market_exit()
1710+
1711+ # Get all instruments the strategy has open orders or positions for
1712+ cdef list open_orders = self .cache.orders_open(None , None , self .id)
1713+ cdef list open_positions = self .cache.positions_open(None , None , self .id)
1714+
1715+ cdef set instruments = set ()
1716+ cdef Order order
1717+ for order in open_orders:
1718+ instruments.add(order.instrument_id)
1719+
1720+ cdef Position position
1721+ for position in open_positions:
1722+ instruments.add(position.instrument_id)
1723+
1724+ cdef InstrumentId instrument_id
1725+ for instrument_id in instruments:
1726+ self .cancel_all_orders(instrument_id)
1727+ self .close_all_positions(instrument_id)
1728+
1729+ # Start iterative check
1730+ self ._log.warning(f" Setting market exit timer for {self.id}" )
1731+ self ._clock.set_timer(
1732+ f" MARKET-EXIT-CHECK:{self.id}" ,
1733+ pd.Timedelta(milliseconds = self .config.inflight_check_interval_ms),
1734+ None ,
1735+ None ,
1736+ self ._check_market_exit,
1737+ True ,
1738+ False ,
1739+ )
1740+
1741+ cpdef void _check_market_exit(self , TimeEvent event):
1742+ if self .state != ComponentState.RUNNING:
1743+ return
1744+
1745+ self ._log.warning(f" Timer triggered: {event.name}" )
1746+ cdef list open_orders = self .cache.orders_open(None , None , self .id)
1747+ cdef list inflight_orders = self .cache.orders_inflight(None , None , self .id)
1748+
1749+ if open_orders or inflight_orders:
1750+ return
1751+
1752+ cdef list open_positions = self .cache.positions_open(None , None , self .id)
1753+ if open_positions:
1754+ # If there are open positions but no orders, we should re-send close orders
1755+ for position in open_positions:
1756+ self .close_position(position)
1757+
1758+ return
1759+
1760+ # All clear
1761+ if f" MARKET-EXIT-CHECK:{self.id}" in self ._clock.timer_names:
1762+ self ._clock.cancel_timer(name = f" MARKET-EXIT-CHECK:{self.id}" )
1763+
1764+ self .after_market_exit()
1765+ self .stop()
16681766
16691767 cpdef void handle_event(self , Event event):
16701768 """
0 commit comments