Skip to content

Commit 931dc0f

Browse files
committed
Refine previous commit
1 parent 1b6a416 commit 931dc0f

File tree

4 files changed

+46
-6
lines changed

4 files changed

+46
-6
lines changed

nautilus_trader/trading/config.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,10 @@ class StrategyConfig(NautilusConfig, kw_only=True, frozen=True):
6565
inflight_check_interval_ms : int, default 100
6666
The interval in milliseconds to check for in-flight orders and open positions
6767
during a market exit.
68+
market_exit_max_attempts : int, default 100
69+
The maximum number of attempts to wait for orders and positions to close
70+
during a market exit before forcing a stop. Defaults to 100 attempts
71+
(10 seconds at 100ms intervals).
6872
6973
"""
7074

@@ -80,6 +84,7 @@ class StrategyConfig(NautilusConfig, kw_only=True, frozen=True):
8084
log_commands: bool = True
8185
log_rejected_due_post_only_as_warning: bool = True
8286
inflight_check_interval_ms: int = 100
87+
market_exit_max_attempts: int = 100
8388

8489

8590
class ImportableStrategyConfig(NautilusConfig, frozen=True):

nautilus_trader/trading/strategy.pxd

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ cdef class Strategy(Actor):
6868
cdef bint _log_commands
6969
cdef bint _log_rejected_due_post_only_as_warning
7070
cdef bint _is_exiting
71+
cdef int _market_exit_attempts
7172

7273
cdef readonly OrderFactory order_factory
7374
"""The order factory for the strategy.\n\n:returns: `OrderFactory`"""

nautilus_trader/trading/strategy.pyx

Lines changed: 38 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,6 @@ from libc.stdint cimport uint64_t
3636
from nautilus_trader.cache.base cimport CacheFacade
3737
from nautilus_trader.cache.cache cimport Cache
3838
from nautilus_trader.common.actor cimport Actor
39-
from nautilus_trader.common.component cimport CMD
4039
from nautilus_trader.common.component cimport EVT
4140
from nautilus_trader.common.component cimport RECV
4241
from nautilus_trader.common.component cimport Clock
@@ -168,6 +167,7 @@ cdef class Strategy(Actor):
168167
self.manage_contingent_orders = config.manage_contingent_orders
169168
self.manage_gtd_expiry = config.manage_gtd_expiry
170169
self._is_exiting = False
170+
self._market_exit_attempts = 0
171171

172172
# Public components
173173
self.clock = self._clock
@@ -432,6 +432,10 @@ cdef class Strategy(Actor):
432432
if self._manager:
433433
self._manager.reset()
434434

435+
# Reset market exit state
436+
self._is_exiting = False
437+
self._market_exit_attempts = 0
438+
435439
self.on_reset()
436440

437441
# -- ABSTRACT METHODS -----------------------------------------------------------------------------
@@ -1698,12 +1702,13 @@ cdef class Strategy(Actor):
16981702
16991703
Will cancel all open orders and close all open positions, and wait for
17001704
all in-flight orders to resolve and positions to close before stopping
1701-
the strategy.
1705+
the strategy.
17021706
"""
17031707
if self._is_exiting:
17041708
return
17051709

17061710
self._is_exiting = True
1711+
self._market_exit_attempts = 0
17071712

17081713
self._log.info("Initiating market exit...", LogColor.BLUE)
17091714
self.on_market_exit()
@@ -1727,7 +1732,7 @@ cdef class Strategy(Actor):
17271732
self.close_all_positions(instrument_id)
17281733

17291734
# Start iterative check
1730-
self._log.warning(f"Setting market exit timer for {self.id}")
1735+
self._log.info(f"Setting market exit timer for {self.id}")
17311736
self._clock.set_timer(
17321737
f"MARKET-EXIT-CHECK:{self.id}",
17331738
pd.Timedelta(milliseconds=self.config.inflight_check_interval_ms),
@@ -1742,7 +1747,30 @@ cdef class Strategy(Actor):
17421747
if self.state != ComponentState.RUNNING:
17431748
return
17441749

1745-
self._log.warning(f"Timer triggered: {event.name}")
1750+
self._market_exit_attempts += 1
1751+
self._log.debug(f"Market exit check triggered: {event.name} (attempt {self._market_exit_attempts})")
1752+
1753+
# Check if max attempts reached
1754+
if self._market_exit_attempts >= self.config.market_exit_max_attempts:
1755+
timer_name = f"MARKET-EXIT-CHECK:{self.id}"
1756+
if timer_name in self._clock.timer_names:
1757+
self._clock.cancel_timer(name=timer_name)
1758+
1759+
self._log.warning(
1760+
f"Market exit max attempts ({self.config.market_exit_max_attempts}) reached. "
1761+
f"Forcing stop. Open orders: {len(self.cache.orders_open(None, None, self.id))}, "
1762+
f"inflight orders: {len(self.cache.orders_inflight(None, None, self.id))}, "
1763+
f"open positions: {len(self.cache.positions_open(None, None, self.id))}",
1764+
LogColor.YELLOW
1765+
)
1766+
1767+
# Reset before stopping
1768+
self._is_exiting = False
1769+
self._market_exit_attempts = 0
1770+
self.after_market_exit()
1771+
self.stop()
1772+
return
1773+
17461774
cdef list open_orders = self.cache.orders_open(None, None, self.id)
17471775
cdef list inflight_orders = self.cache.orders_inflight(None, None, self.id)
17481776

@@ -1758,9 +1786,13 @@ cdef class Strategy(Actor):
17581786
return
17591787

17601788
# 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}")
1789+
timer_name = f"MARKET-EXIT-CHECK:{self.id}"
1790+
if timer_name in self._clock.timer_names:
1791+
self._clock.cancel_timer(name=timer_name)
17631792

1793+
# Reset before stopping
1794+
self._is_exiting = False
1795+
self._market_exit_attempts = 0
17641796
self.after_market_exit()
17651797
self.stop()
17661798

tests/unit_tests/trading/test_strategy.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,7 @@ def test_strategy_to_importable_config_with_no_specific_config(self) -> None:
202202
"log_commands": True,
203203
"log_rejected_due_post_only_as_warning": True,
204204
"inflight_check_interval_ms": 100,
205+
"market_exit_max_attempts": 100,
205206
}
206207

207208
def test_strategy_to_importable_config(self) -> None:
@@ -237,6 +238,7 @@ def test_strategy_to_importable_config(self) -> None:
237238
"log_commands": True,
238239
"log_rejected_due_post_only_as_warning": True,
239240
"inflight_check_interval_ms": 100,
241+
"market_exit_max_attempts": 100,
240242
}
241243

242244
def test_strategy_equality(self) -> None:

0 commit comments

Comments
 (0)