Skip to content

Commit

Permalink
Add OrderManager to Strategy
Browse files Browse the repository at this point in the history
  • Loading branch information
cjdsellers committed Oct 28, 2023
1 parent 113ef27 commit fdbfdf3
Show file tree
Hide file tree
Showing 18 changed files with 331 additions and 126 deletions.
19 changes: 11 additions & 8 deletions RELEASES.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,12 @@ Released on TBC (UTC).

### Enhancements
- Added `WebSocketClient` connection headers, thanks @ruthvik125 and @twitu
- Added `support_contingent_orders` option for venues (to simulate venues which do not support contingent orders)
- Added `StrategyConfig.manage_contingent_orders` option (to automatically manage **open** contingenct orders)

### Breaking Changes
- Transformed orders will now retain the original `ts_init` timestamp
- Removed unimplemented `batch_more` option for `Strategy.modify_order`
- Dropped support for Python 3.9

### Fixes
Expand Down Expand Up @@ -175,7 +178,7 @@ Released on 31st July 2023 (UTC).
- Fixed dictionary representation of orders for `venue_order_id` (for three order types)
- Fixed `Currency` registration with core global map on creation
- Fixed serialization of `OrderInitialized.exec_algorithm_params` to spec (bytes rather than string)
- Fixed assignment of position IDs for contingency orders (when parent filled)
- Fixed assignment of position IDs for contingent orders (when parent filled)
- Fixed `PENDING_CANCEL` -> `EXPIRED` as valid state transition (real world possibility)
- Fixed fill handling of `reduce_only` orders when partially filled
- Fixed Binance reconciliation which was requesting reports for the same symbol multiple times
Expand Down Expand Up @@ -253,8 +256,8 @@ Released on 19th May 2023 (UTC).
- Fixed handling of emulated order contingencies (not based on status of spawned algorithm orders)
- Fixed sending execution algorithm commands from strategy
- Fixed `OrderEmulator` releasing of already closed orders
- Fixed `MatchingEngine` processing of reduce only for child contingency orders
- Fixed `MatchingEngine` position ID assignment for child contingency orders
- Fixed `MatchingEngine` processing of reduce only for child contingent orders
- Fixed `MatchingEngine` position ID assignment for child contingent orders
- Fixed `Actor` handling of historical data from requests (will now call `on_historical_data` regardless of state), thanks for reporting @miller-moore
- Fixed pyarrow schema dictionary index keys being too narrow (int8 -> int16), thanks for reporting @rterbush

Expand Down Expand Up @@ -301,15 +304,15 @@ Released on 30th April 2023 (UTC).
- Added `TWAPExecAlgorithm` and `TWAPExecAlgorithmConfig` to examples
- Build out `ExecAlgorithm` base class for implementing 'first class' execution algorithms
- Rewired execution for improved flow flexibility between emulated orders, execution algorithms and the `RiskEngine`
- Improved handling for `OrderEmulator` updating of contingency orders from execution algorithms
- Improved handling for `OrderEmulator` updating of contingent orders from execution algorithms
- Defined public API for instruments, can now import directly from `nautilus_trader.model.instruments` (denest namespace)
- Defined public API for orders, can now import directly from `nautilus_trader.model.orders` (denest namespace)
- Defined public API for order book, can now import directly from `nautilus_trader.model.orderbook` (denest namespace)
- Now stripping debug symbols after build (reduced binary sizes)
- Refined build and added additional `debug` Makefile convenience targets

### Fixes
- Fixed processing of contingency orders when in a pending update state
- Fixed processing of contingent orders when in a pending update state
- Fixed calculation of PnL for flipped positions (only book realized PnL against open position)
- Fixed `WebSocketClient` session disconnect, thanks for reporting @miller-moore
- Added missing `BinanceSymbolFilterType.NOTIONAL`
Expand Down Expand Up @@ -618,7 +621,7 @@ Released on 28th November 2022 (UTC).
- Renamed `Instrument.get_cost_currency(...)` to `Instrument.get_settlement_currency(...)` (more accurate terminology)

### Enhancements
- Added emulated contingency orders capability to `OrderEmulator`
- Added emulated contingent orders capability to `OrderEmulator`
- Moved `test_kit` module to main package to support downstream project/package testing

### Fixes
Expand Down Expand Up @@ -650,7 +653,7 @@ Released on 18th November 2022 (UTC).
- Fixed bar aggregation start times for bar specs outside typical intervals (60-SECOND rather than 1-MINUTE etc)
- Fixed backtest engine main loop ordering of time events with identically timestamped data
- Fixed `ModifyOrder` message `str` and `repr` when no quantity
- Fixed OCO contingency orders which were actually implemented as OUO for backtests
- Fixed OCO contingent orders which were actually implemented as OUO for backtests
- Fixed various bugs for Interactive Brokers integration, thanks @limx0 and @rsmb7z
- Fixed pyarrow version parsing, thanks @ghill2
- Fixed returning venue from InstrumentId, thanks @rsmb7z
Expand Down Expand Up @@ -1466,7 +1469,7 @@ Released on 12th September 2021.
- Added order custom user tags
- Added `Actor.register_warning_event` (also applicable to `TradingStrategy`)
- Added `Actor.deregister_warning_event` (also applicable to `TradingStrategy`)
- Added `ContingencyType` enum (for contingency orders in an `OrderList`)
- Added `ContingencyType` enum (for contingent orders in an `OrderList`)
- All order types can now be `reduce_only` (#437)
- Refined backtest configuration options
- Improved efficiency of `UUID4` using the Rust `fastuuid` Python bindings
Expand Down
2 changes: 1 addition & 1 deletion docs/concepts/execution.md
Original file line number Diff line number Diff line change
Expand Up @@ -215,7 +215,7 @@ e.g. `O-20230404-001-000-E1` (for the first spawned order)

```{note}
The "primary" and "secondary" / "spawn" terminology was specifically chosen to avoid conflict
or confusion with the "parent" and "child" contingency orders terminology (an execution algorithm may also deal with contingent orders).
or confusion with the "parent" and "child" contingent orders terminology (an execution algorithm may also deal with contingent orders).
```

### Managing execution algorithm orders
Expand Down
2 changes: 1 addition & 1 deletion docs/concepts/orders.md
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ of the stop price based on the offset from the 'market' (bid, ask or last price
- `TICKS` - The offset is based on a number of ticks
- `PRICE_TIER` - The offset is based on an exchange specific price tier

### Contingency Orders
### Contingent Orders
More advanced relationships can be specified between orders such as assigning child order(s) which will only
trigger when the parent order is activated or filled, or linking orders together which will cancel or reduce in quantity
contingent on each other. More documentation for these options can be found in the [advanced order guide](advanced/advanced_orders.md).
Expand Down
5 changes: 5 additions & 0 deletions nautilus_trader/backtest/engine.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -362,6 +362,7 @@ cdef class BacktestEngine:
bar_execution: bool = True,
reject_stop_orders: bool = True,
support_gtd_orders: bool = True,
support_contingent_orders: bool = True,
use_position_ids: bool = True,
use_random_ids: bool = False,
use_reduce_only: bool = True,
Expand Down Expand Up @@ -404,6 +405,9 @@ cdef class BacktestEngine:
If stop orders are rejected on submission if trigger price is in the market.
support_gtd_orders : bool, default True
If orders with GTD time in force will be supported by the venue.
support_contingent_orders : bool, default True
If contingent orders will be supported/respected by the venue.
If False then its expected the strategy will be managing any contingent orders.
use_position_ids : bool, default True
If venue position IDs will be generated on order fills.
use_random_ids : bool, default False
Expand Down Expand Up @@ -455,6 +459,7 @@ cdef class BacktestEngine:
bar_execution=bar_execution,
reject_stop_orders=reject_stop_orders,
support_gtd_orders=support_gtd_orders,
support_contingent_orders=support_contingent_orders,
use_position_ids=use_position_ids,
use_random_ids=use_random_ids,
use_reduce_only=use_reduce_only,
Expand Down
2 changes: 2 additions & 0 deletions nautilus_trader/backtest/exchange.pxd
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,8 @@ cdef class SimulatedExchange:
"""If stop orders are rejected on submission if in the market.\n\n:returns: `bool`"""
cdef readonly bint support_gtd_orders
"""If orders with GTD time in force will be supported by the venue.\n\n:returns: `bool`"""
cdef readonly bint support_contingent_orders
"""If contingent orders will be supported/respected by the venue.\n\n:returns: `bool`"""
cdef readonly bint use_position_ids
"""If venue position IDs will be generated on order fills.\n\n:returns: `bool`"""
cdef readonly bint use_random_ids
Expand Down
6 changes: 6 additions & 0 deletions nautilus_trader/backtest/exchange.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,9 @@ cdef class SimulatedExchange:
If stop orders are rejected on submission if in the market.
support_gtd_orders : bool, default True
If orders with GTD time in force will be supported by the venue.
support_contingent_orders : bool, default True
If contingent orders will be supported/respected by the venue.
If False then its expected the strategy will be managing any contingent orders.
use_position_ids : bool, default True
If venue position IDs will be generated on order fills.
use_random_ids : bool, default False
Expand Down Expand Up @@ -147,6 +150,7 @@ cdef class SimulatedExchange:
bint bar_execution = True,
bint reject_stop_orders = True,
bint support_gtd_orders = True,
bint support_contingent_orders = True,
bint use_position_ids = True,
bint use_random_ids = False,
bint use_reduce_only = True,
Expand Down Expand Up @@ -187,6 +191,7 @@ cdef class SimulatedExchange:
self.bar_execution = bar_execution
self.reject_stop_orders = reject_stop_orders
self.support_gtd_orders = support_gtd_orders
self.support_contingent_orders = support_contingent_orders
self.use_position_ids = use_position_ids
self.use_random_ids = use_random_ids
self.use_reduce_only = use_reduce_only
Expand Down Expand Up @@ -335,6 +340,7 @@ cdef class SimulatedExchange:
bar_execution=self.bar_execution,
reject_stop_orders=self.reject_stop_orders,
support_gtd_orders=self.support_gtd_orders,
support_contingent_orders=self.support_contingent_orders,
use_position_ids=self.use_position_ids,
use_random_ids=self.use_random_ids,
use_reduce_only=self.use_reduce_only,
Expand Down
1 change: 1 addition & 0 deletions nautilus_trader/backtest/matching_engine.pxd
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ cdef class OrderMatchingEngine:
cdef bint _bar_execution
cdef bint _reject_stop_orders
cdef bint _support_gtd_orders
cdef bint _support_contingent_orders
cdef bint _use_position_ids
cdef bint _use_random_ids
cdef bint _use_reduce_only
Expand Down
20 changes: 14 additions & 6 deletions nautilus_trader/backtest/matching_engine.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,9 @@ cdef class OrderMatchingEngine:
If stop orders are rejected if already in the market on submitting.
support_gtd_orders : bool, default True
If orders with GTD time in force will be supported by the venue.
support_contingent_orders : bool, default True
If contingent orders will be supported/respected by the venue.
If False then its expected the strategy will be managing any contingent orders.
use_position_ids : bool, default True
If venue position IDs will be generated on order fills.
use_random_ids : bool, default False
Expand All @@ -149,6 +152,7 @@ cdef class OrderMatchingEngine:
bint bar_execution = True,
bint reject_stop_orders = True,
bint support_gtd_orders = True,
bint support_contingent_orders = True,
bint use_position_ids = True,
bint use_random_ids = False,
bint use_reduce_only = True,
Expand All @@ -172,6 +176,7 @@ cdef class OrderMatchingEngine:
self._bar_execution = bar_execution
self._reject_stop_orders = reject_stop_orders
self._support_gtd_orders = support_gtd_orders
self._support_contingent_orders = support_contingent_orders
self._use_position_ids = use_position_ids
self._use_random_ids = use_random_ids
self._use_reduce_only = use_reduce_only
Expand Down Expand Up @@ -638,7 +643,7 @@ cdef class OrderMatchingEngine:
self._account_ids[order.trader_id] = account_id

cdef Order parent
if order.parent_order_id is not None:
if self._support_contingent_orders and order.parent_order_id is not None:
parent = self.cache.order(order.parent_order_id)
assert parent is not None and parent.contingency_type == ContingencyType.OTO, "OTO parent not found"
if parent.status_c() == OrderStatus.REJECTED and order.is_open_c():
Expand Down Expand Up @@ -1680,7 +1685,10 @@ cdef class OrderMatchingEngine:
# Remove order from market
self._core.delete_order(order)

# Check contingency orders
if not self._support_contingent_orders:
return

# Check contingent orders
cdef ClientOrderId client_order_id
cdef Order child_order
if order.contingency_type == ContingencyType.OTO:
Expand Down Expand Up @@ -1824,7 +1832,7 @@ cdef class OrderMatchingEngine:
self._core.add_order(order)

cpdef void expire_order(self, Order order):
if order.contingency_type != ContingencyType.NO_CONTINGENCY:
if self._support_contingent_orders and order.contingency_type != ContingencyType.NO_CONTINGENCY:
self._cancel_contingent_orders(order)

self._generate_order_expired(order)
Expand All @@ -1843,7 +1851,7 @@ cdef class OrderMatchingEngine:

self._generate_order_canceled(order)

if order.contingency_type != ContingencyType.NO_CONTINGENCY and cancel_contingencies:
if self._support_contingent_orders and order.contingency_type != ContingencyType.NO_CONTINGENCY and cancel_contingencies:
self._cancel_contingent_orders(order)

cpdef void update_order(
Expand Down Expand Up @@ -1895,7 +1903,7 @@ cdef class OrderMatchingEngine:
raise ValueError(
f"invalid `OrderType` was {order.order_type}") # pragma: no cover (design-time error)

if order.contingency_type != ContingencyType.NO_CONTINGENCY and update_contingencies:
if self._support_contingent_orders and order.contingency_type != ContingencyType.NO_CONTINGENCY and update_contingencies:
self._update_contingent_orders(order)

cpdef void trigger_stop_order(self, Order order):
Expand Down Expand Up @@ -1959,7 +1967,7 @@ cdef class OrderMatchingEngine:
)

cdef void _cancel_contingent_orders(self, Order order):
# Iterate all contingency orders and cancel if active
# Iterate all contingent orders and cancel if active
cdef ClientOrderId client_order_id
cdef Order contingent_order
for client_order_id in order.linked_order_ids:
Expand Down
1 change: 1 addition & 0 deletions nautilus_trader/backtest/node.py
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,7 @@ def _create_engine(
frozen_account=config.frozen_account,
reject_stop_orders=config.reject_stop_orders,
support_gtd_orders=config.support_gtd_orders,
support_contingent_orders=config.support_contingent_orders,
use_position_ids=config.use_position_ids,
use_random_ids=config.use_random_ids,
use_reduce_only=config.use_reduce_only,
Expand Down
1 change: 1 addition & 0 deletions nautilus_trader/config/backtest.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ class BacktestVenueConfig(NautilusConfig, frozen=True):
bar_execution: bool = True
reject_stop_orders: bool = True
support_gtd_orders: bool = True
support_contingent_orders: bool = True
use_position_ids: bool = True
use_random_ids: bool = False
use_reduce_only: bool = True
Expand Down
7 changes: 4 additions & 3 deletions nautilus_trader/config/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -440,8 +440,9 @@ 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_contingent_orders : bool, default False
If OUO and OCO **open** contingent orders should be managed automatically by the strategy.
Any emulated orders which are active local will be managed by the `OrderEmulator` instead.
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 @@ -452,7 +453,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_contingent_orders: bool = False
manage_gtd_expiry: bool = False


Expand Down
14 changes: 2 additions & 12 deletions nautilus_trader/execution/emulator.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,7 @@ cdef class OrderEmulator(Actor):
msgbus=msgbus,
cache=cache,
component_name=type(self).__name__,
active_local=True,
submit_order_handler=self._handle_submit_order,
cancel_order_handler=self._cancel_order,
modify_order_handler=self._update_order,
Expand Down Expand Up @@ -254,18 +255,7 @@ cdef class OrderEmulator(Actor):
self._log.info(f"{RECV}{EVT} {event}.", LogColor.MAGENTA)
self.event_count += 1

if isinstance(event, OrderRejected):
self._manager.handle_order_rejected(event)
elif isinstance(event, OrderCanceled):
self._manager.handle_order_canceled(event)
elif isinstance(event, OrderExpired):
self._manager.handle_order_expired(event)
elif isinstance(event, OrderUpdated):
self._manager.handle_order_updated(event)
elif isinstance(event, OrderFilled):
self._manager.handle_order_filled(event)
elif isinstance(event, PositionEvent):
self._manager.handle_position_event(event)
self._manager.handle_event(event)

if not isinstance(event, OrderEvent):
return
Expand Down
4 changes: 3 additions & 1 deletion nautilus_trader/execution/manager.pxd
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ cdef class OrderManager:
cdef MessageBus _msgbus
cdef Cache _cache

cdef readonly bint active_local
cdef readonly bint debug

cdef dict _submit_order_commands
Expand All @@ -69,14 +70,15 @@ cdef class OrderManager:

# -- EVENT HANDLERS -------------------------------------------------------------------------------

cpdef void handle_position_event(self, PositionEvent event)
cpdef void handle_event(self, Event event)
cpdef void handle_order_rejected(self, OrderRejected rejected)
cpdef void handle_order_canceled(self, OrderCanceled canceled)
cpdef void handle_order_expired(self, OrderExpired expired)
cpdef void handle_order_updated(self, OrderUpdated updated)
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 handle_position_event(self, PositionEvent event)

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

Expand Down
Loading

0 comments on commit fdbfdf3

Please sign in to comment.