Nautilus Orders
Nautilus exposes nine concrete order types (MARKET, LIMIT, STOP_MARKET, STOP_LIMIT, MARKET_IF_TOUCHED, LIMIT_IF_TOUCHED, MARKET_TO_LIMIT, TRAILING_STOP_MARKET, TRAILING_STOP_LIMIT) plus a first-class
bracket(...)primitive that wires entry + take-profit + stop-loss as a contingent group. Every conditional order type can be locally emulated by theOrderEmulator: when the strategy passes anemulation_trigger(BID_ASK,LAST_PRICE,MARK_PRICE, etc.), Nautilus subscribes to the trigger feed itself, holds the order inEMULATEDstate, and only transmits a basic MARKET or LIMIT to the venue at trigger time. Withoutemulation_trigger, the order is forwarded to the venue as-is and the venue is responsible for trigger logic. Emulation is opt-in, not automatic - the user picks. This is the load-bearing answer to spike question #6: emulated brackets give Cortana genuine software-fallback defense-in-depth (Nautilus owns the trigger), while non-emulated brackets on IBKR are venue-native bracket translation (single trigger). Forfeedback_dual_tp_defense_in_depth.mdthe choice is therefore deliberate: opt intoemulation_trigger=MARK_PRICE(orBID_ASK) on the TP/SL legs and the framework runs the fallback we currently hand-roll.
Cody’s open question - answered
Q: When IBKR supports brackets natively, does Nautilus’s bracket give us software-fallback defense-in-depth, or is it just venue-native bracket translation?
A: Both, depending on whether you set emulation_trigger on the bracket
legs. Default = venue-native (no SW fallback). Opt-in via
emulation_trigger = real software fallback (the OrderEmulator subscribes
to the trigger feed and owns trigger logic locally). For Cortana MK3, set
emulation_trigger on the TP and SL legs.
Verbatim from the docs: “The only requirement to emulate an order is to
pass a TriggerType to the emulation_trigger parameter.” And: “The
platform makes it possible to emulate most order types locally, regardless
of whether the type is supported on a trading venue.” And: “NO_TRIGGER:
disables local emulation completely and order is fully submitted to the
venue.”
So the routing rule is mechanical:
emulation_trigger | What happens |
|---|---|
NO_TRIGGER (or omitted) | Order forwarded to venue. IBKR handles the bracket natively. No SW fallback. Single trigger - if IBKR’s LMT misses, the system has nothing else watching. |
BID_ASK / LAST_PRICE / MARK_PRICE / etc. | OrderEmulator holds the order in EMULATED state, subscribes to the trigger feed, watches it locally. When triggered, it transforms to a basic MARKET (or LIMIT) and submits to IBKR. The trigger logic itself is owned by Nautilus, not the venue. |
This means MK2’s hand-rolled position_manager.on_quote_tick software
fallback (for when the broker LMT fails to fill) maps directly to a Nautilus
OrderEmulator subscription. We get the same defense-in-depth pattern, but
the framework owns the subscription, the cache-then-publish ordering, the
trigger evaluation, and the reduce-only semantics. We delete the hand-rolled
fallback loop.
The only nuance: for brackets specifically, you set emulation_trigger
on the bracket builder, which propagates to the contingent legs. The
parent (entry) is typically not emulated (Cortana enters at MARKET).
The TP child (LIMIT) and SL child (STOP_MARKET) are the ones that benefit
from emulation. This is the right shape - entry is fast-fire-and-forget,
exits are where we want belt-and-suspenders.
Caveat - the docs do not enumerate what happens if emulation_trigger
is omitted and the venue rejects the order type (e.g., a venue that
doesn’t support trailing stops at all). Verify on Saturday’s spike. The
implication for Cortana on IBKR: STOP_MARKET, LIMIT, MARKET, and bracket
groups are all supported natively, so the rejection path is not the
binding constraint - the SW-fallback choice is.
Reference: order type taxonomy
Nine concrete order types, grouped by execution semantics.
Foundational types
MARKET
- Semantics: Execute immediately at best available price. Consumes liquidity (taker).
- Cannot be emulated. The doc lists MARKET and MARKET_TO_LIMIT as the
two types that pass through to the venue regardless of
emulation_trigger. - Cortana use: V1 entry pattern.
order_factory.market(...)for the bracket parent.
LIMIT
- Semantics: Rest in order book at specified
price. Provides liquidity until matched (maker). May not fill at all. - Emulatable: yes. Useful when the venue does not support reserve pricing or when you want trigger logic Nautilus controls.
- Iceberg: Pass
display_qty < quantityto expose only part of the order on the book. “Specifying adisplay_qtyof zero is also equivalent to setting an order as hidden.”display_qty=Nonemeans full display. - Cortana use: V1 take-profit child of brackets, marketable-LMT exits.
Conditional / triggered types
STOP_MARKET
- Semantics: Resting trigger. When the venue’s selected price feed hits
trigger_price, the order becomes a MARKET order and races to fill. - Emulatable: yes. Without emulation, the venue’s matching engine watches the trigger.
- Cortana use: V1 stop-loss child of brackets.
-25%from entry.
STOP_LIMIT
- Semantics: When
trigger_pricehits, the order becomes a LIMIT atprice(not market). Protects against gap slippage at the cost of potential no-fill. - Emulatable: yes.
- Cortana use: not currently. Could replace STOP_MARKET in V2 if slippage on stop-outs becomes a bigger problem than no-fill risk.
MARKET_IF_TOUCHED
- Semantics: Conditional MARKET entry. When
trigger_priceis reached (from either side, depending on side), order fires as MARKET. Distinguished from STOP_MARKET by use case: STOP is exit/protection, MIT is entry/initiation. - Emulatable: yes.
- Cortana use: V2 candidate for “enter on a confirming touch.”
LIMIT_IF_TOUCHED
- Semantics: Conditional LIMIT entry. Trigger fires a LIMIT at
price. - Emulatable: yes.
- Cortana use: V2 candidate.
Hybrid / advanced
MARKET_TO_LIMIT
- Semantics: Submit as MARKET. “If partial fill occurs, remainder resubmits as LIMIT at executed price.” Caps slippage on the partial-fill tail.
- Cannot be emulated (passes through with MARKET).
- Cortana use: candidate for the BULL/BEAR initial entry to bound slippage when the impulse engine fires into thin liquidity.
TRAILING_STOP_MARKET
- Semantics: Stop-loss whose trigger price trails the market by a fixed offset. As price moves favorably, trigger ratchets in. When price reverses by the offset, the trailing stop fires as MARKET.
- Configuration:
trailing_offset(the offset value) +trailing_offset_type(PRICE,BASIS_POINTS,TICKS,PRICE_TIER,DEFAULT). - Emulatable: yes. Especially valuable when the venue’s trailing-stop semantics differ from what the strategy assumes.
- Cortana use: NOT for V1. The mandate is
feedback_no_hwm_trailing_language.md- single-shot +10% fixed TP, no trailing. This order type is here for
completeness, but V1 strategy code should not call
trailing_stop_market. V2+ might use it if the trend-day-detection work matures (project_runner_ladder.md).
- single-shot +10% fixed TP, no trailing. This order type is here for
completeness, but V1 strategy code should not call
TRAILING_STOP_LIMIT
- Semantics: Same as TRAILING_STOP_MARKET but releases as a LIMIT at a
configured
limit_offsetfrom the trigger. - Cortana use: same gating as TRAILING_STOP_MARKET - V2+ only.
Trigger types (for STOP_*, MIT, LIT, trailing stops)
Determine which price feed the trigger evaluates against:
LAST_PRICE- last tradeBID_ASK- best bid/ask (default for emulation)DOUBLE_LAST- two consecutive last trades (slippage-resistant)DOUBLE_BID_ASK- two consecutive bid/ask updatesLAST_OR_BID_ASK- either signal qualifiesMID_POINT- bid/ask midMARK_PRICE- venue markINDEX_PRICE- underlying indexDEFAULT- venue’s default (typicallyLAST_PRICE)
For options, MARK_PRICE is the saner choice - last-trade can be stale
or one-sided in thin contracts (per nautilus-options.md).
Reference: bracket order primitive
Verbatim: “a parent order (entry order) and two child orders: a
take-profit LIMIT order and a stop-loss STOP_MARKET order.” Bracket
behavior is contingent: “When the parent order executes, the system
places the child orders.”
Construction
bracket = self.order_factory.bracket(
instrument_id=self.instrument_id,
order_side=OrderSide.BUY, # parent side
quantity=self.instrument.make_qty(qty),
entry_order_type=OrderType.MARKET, # or LIMIT, etc.
tp_price=self.instrument.make_price(tp), # take-profit limit price
sl_trigger_price=self.instrument.make_price(sl), # stop-loss trigger
# Optional:
emulation_trigger=TriggerType.MARK_PRICE, # opt into local emulation
time_in_force=TimeInForce.DAY,
contingency_type=ContingencyType.OUO, # or OCO
)
self.submit_order_list(bracket)(Exact parameter names verified against OrderFactory.bracket(...) in
the spike - the docs reference but do not enumerate the signature; see
the python_api/common.html#nautilus_trader.common.factories.OrderFactory
page.)
Contingency types
Brackets are an OTO+OCO (or OTO+OUO) construction.
| Contingency | Meaning |
|---|---|
| OTO (One-Triggers-Other) | Child orders activate only when parent fills. Two trigger modes: partial (children release pro-rata to each parent partial fill) or full (children release only after parent fully fills). The default backtest venue uses partial-trigger; pass oto_trigger_mode="FULL" for full-trigger. |
| OCO (One-Cancels-Other) | “Two or more linked live orders where executing one cancels the remainder.” When TP fills, SL is canceled (and vice versa). |
| OUO (One-Updates-Other) | “Two or more linked live orders where executing one reduces the open quantity of the remainder.” Useful for partial-fill bracket exits - TP fills 50% → SL is reduced to 50% rather than canceled. |
For Cortana V1 (single-shot exits, no scaling out), OCO is sufficient.
If V2 introduces scale-out (project_runner_ladder.md), OUO becomes the
correct contingency.
Bracket fields shared via order_list_id and parent_order_id
The doc states OCO/OUO groups are “managed via shared order_list_id and
parent-child relationships tracked through parent_order_id.” This is
how the engine knows which child belongs to which parent for cancel-on-fill
and reduce-on-partial logic.
Margin caveat
Verbatim: “You should be aware of the margin requirements of positions, as bracketing a position will consume more order margin.” For long-only 0DTE option buys this is not binding (premium is fully paid up front), but relevant if MK3 ever introduces short-premium structures.
Reference: time-in-force (TIF)
| TIF | Meaning |
|---|---|
GTC | Good Till Cancel - active until canceled |
GTD | Good Till Date - active until specified expiration; emits OrderExpired at expiry |
DAY | Active until end of trading session |
IOC | Immediate or Cancel - execute immediately, cancel any unfilled remainder |
FOK | Fill or Kill - execute full quantity or cancel entirely (no partial) |
AT_THE_OPEN | Active only at session open |
AT_THE_CLOSE | Active only at session close |
For Cortana V1 single-shot 0DTE patterns, DAY is the primary TIF - the EOD-flatten invariant means we never want orders surviving past close. A runaway GTC after 16:00 ET would be a P0 invariant violation.
Emulation: the load-bearing concept
This is the section the spike most depends on. Quoting the doc:
“The platform makes it possible to emulate most order types locally, regardless of whether the type is supported on a trading venue.”
“The
OrderEmulatorwill subscribe to any needed market data (if not already) to update the matching core.”
“The only requirement to emulate an order is to pass a
TriggerTypeto theemulation_triggerparameter.”
“Emulation lets you use order types even when your trading venue does not natively support them. Nautilus locally mimics the behavior of these order types … while using only MARKET and LIMIT orders for actual execution on the venue.”
The emulation lifecycle
- Strategy submits order with
emulation_trigger != NO_TRIGGER. - Order routes to RiskEngine for pre-trade checks (size, notional, etc.).
- Order arrives at
OrderEmulator; state becomesEMULATED. OrderEmulatorsubscribes to the trigger feed (e.g.,BID_ASKquotes orMARK_PRICEupdates) for that instrument.- The emulator runs a local matching core that watches the feed against the order’s trigger price.
- When triggered, the order is transformed to a basic MARKET or LIMIT;
emulation_triggeris cleared toNONE; state becomesRELEASED. - The transformed order undergoes a second RiskEngine check.
- ExecutionEngine sends the basic order to the venue; normal lifecycle
resumes (
SUBMITTED→ACCEPTED→FILLED).
Two RiskEngine checks (intake + release) is the framework’s belt-and- suspenders. A meta-prob veto rule placed in the RiskEngine fires at both checkpoints - even if conditions changed during the emulation hold.
What’s emulatable
| Type | Emulatable? |
|---|---|
| MARKET | No (passes through) |
| LIMIT | Yes |
| STOP_MARKET | Yes |
| STOP_LIMIT | Yes |
| MARKET_IF_TOUCHED | Yes |
| LIMIT_IF_TOUCHED | Yes |
| MARKET_TO_LIMIT | No (passes through with MARKET) |
| TRAILING_STOP_MARKET | Yes |
| TRAILING_STOP_LIMIT | Yes |
Brackets inherit emulation from their child legs - the LIMIT TP and
STOP_MARKET SL are individually emulatable, and the bracket builder
threads emulation_trigger through to them.
Emulated-order gotcha (cross-reference nautilus-strategies.md)
Verbatim: “the order object transforms when the emulated order is
released.” Implication: the strategy must query emulated orders through
the Cache by client_order_id, not by holding a reference. After
release, the in-memory shape changes (it’s now a MARKET or LIMIT, not a
STOP_LIMIT or trailing stop). Holding stale references leads to attribute
errors or subtly wrong state reads.
# WRONG
order = self.order_factory.stop_market(..., emulation_trigger=...)
self.submit_order(order)
# ...later in on_quote_tick:
print(order.trigger_price) # may not exist anymore!
# RIGHT
self._sl_id = order.client_order_id
self.submit_order(order)
# ...later:
order = self.cache.order(self._sl_id)
if order is not None and not order.is_closed:
...Trigger-feed subscription cost
Per the doc, the OrderEmulator subscribes to “any needed market data
(if not already).” Two implications:
- If the strategy already subscribes to
BID_ASKquotes for an instrument, emulation is free (no incremental subscription). - If you emulate against a feed nothing else uses (e.g.,
INDEX_PRICE), the emulator opens that subscription on demand.
For Cortana, MARK_PRICE emulation on the TP/SL legs of a 0DTE option
position is essentially free - we already subscribe to the option chain
(per nautilus-options.md), and Greeks updates carry mark.
Reference: order parameters
post_only
Verbatim: “Will only ever participate in providing liquidity to the limit order book, and never initiating a trade which takes liquidity as an aggressor.”
If a post_only LIMIT would cross the book at submission, the venue (or
emulator) refuses it. Use to guarantee maker rebates and avoid taker fees.
Not relevant to Cortana V1 (we do not chase rebates on options).
reduce_only
Verbatim: “Will only ever reduce an existing position on an instrument and never open a new position (if already flat).”
Critical for Cortana SL orders. A reduce_only=True STOP_MARKET
cannot accidentally flip the position from long-call to short-call if a
sizing bug or race condition double-submits. The market_exit() helper
(see nautilus-strategies.md) automatically applies reduce_only=True
to its exit orders, which is what makes the EOD-flatten invariant safe.
display_qty
Iceberg control. display_qty=None means full display.
display_qty < quantity exposes only part. display_qty=0 is a hidden
order (where the venue supports it). Not relevant to Cortana V1
(SPY 0DTE option spreads are too thin for icebergs to make sense).
exec_algorithm_id + exec_algorithm_params
Per nautilus-strategies.md, you can hand off execution to a custom
ExecAlgorithm (e.g., TWAP) by passing
exec_algorithm_id=ExecAlgorithmId("TWAP") and a parameter dict. The
order routes to the algorithm’s matching core before reaching the venue.
The orders page itself does not enumerate these - see the parallel
Nautilus Execution page for details.
Not relevant to Cortana V1 (single-shot orders on liquid SPY options). Could become relevant in V2 if we ever want to TWAP a large entry.
emulation_trigger
See the Emulation section above. Values:
| Value | Meaning |
|---|---|
NO_TRIGGER | Disable emulation; submit to venue as-is |
DEFAULT | Same as BID_ASK |
BID_ASK | Trigger on best bid/ask |
LAST_PRICE | Trigger on last trade |
DOUBLE_LAST | Two consecutive last trades |
DOUBLE_BID_ASK | Two consecutive quote updates |
LAST_OR_BID_ASK | Either signal qualifies |
MID_POINT | Bid/ask mid |
MARK_PRICE | Venue mark |
INDEX_PRICE | Underlying index |
contingency_type
OTO / OCO / OUO - see the Bracket section. For a bare order
(non-bracket), this is None.
Reference: order modification
self.modify_order(order, new_quantity=...)
self.modify_order(order, new_price=...)
self.modify_order(order, new_trigger_price=...)Modify request → state transitions to PENDING_UPDATE → venue acks →
OrderUpdated event fires → state returns to its prior open state with
new fields. If the venue rejects, OrderModifyRejected event fires and
state returns to ACCEPTED (or wherever it was) with original fields.
Modifiable fields (the doc references but does not exhaustively enumerate; verify against IBKR adapter on spike):
quantity- commonprice(LIMIT, STOP_LIMIT) - commontrigger_price(STOP_*, MIT, LIT) - commonexpire_time(GTD) - venue-dependent
If the order is already closed (FILLED, CANCELED, REJECTED,
EXPIRED, DENIED), modify is a logged no-op.
For a bracket parent modification, the engine cascades: modifying the parent quantity reduces or increases child qty proportionally (OUO) or refuses (OCO single-shot). Verify behavior on spike.
Reference: order cancellation
self.cancel_order(order) # one order
self.cancel_orders(order_list) # specific list
self.cancel_all_orders() # all open + emulated
# Optional filters: instrument_id=..., side=...Cancel request → state transitions to PENDING_CANCEL → venue acks →
OrderCanceled event fires → terminal state CANCELED. If the venue
rejects (already filled, unknown ID, etc.), OrderCancelRejected fires
and state returns to its prior state.
Bracket cancellation cascades: canceling a parent cancels both children. Canceling one child of an OCO pair is venue-dependent - most venues will let you cancel a single OCO leg, leaving the other live; others reject. Verify on spike.
The market_exit() helper (see nautilus-strategies.md) cancels all
open and in-flight orders before submitting reduce-only market closes.
Verbatim from that doc: “During a market exit, non-reduce-only orders
are automatically denied.” This is what makes EOD-flat safe - no
in-flight on_data can sneak a new entry past the cancel sweep.
Reference: order state machine
Full state set (from the doc verbatim):
| Status | Class | Description |
|---|---|---|
INITIALIZED | Local | Order instantiated within Nautilus |
EMULATED | Local | Held by OrderEmulator for local trigger |
RELEASED | Local | Released from emulator, awaiting submission |
DENIED | Terminal | Refused by Nautilus (RiskEngine, invariant) |
SUBMITTED | In-flight | Sent to venue, awaiting ack |
ACCEPTED | Open | Acked by venue |
REJECTED | Terminal | Refused by venue |
TRIGGERED | Open | STOP price hit on venue (venue-side trigger) |
PENDING_UPDATE | Open | Modify request in-flight |
PENDING_CANCEL | Open | Cancel request in-flight |
PARTIALLY_FILLED | Open | Partial fill received |
FILLED | Terminal | Fully filled |
CANCELED | Terminal | Cancel confirmed |
EXPIRED | Terminal | TIF (GTD/DAY/IOC/FOK) elapsed without fill |
State classes:
- Local (
INITIALIZED,EMULATED,RELEASED) - order has not yet been transmitted to the venue. - In-flight (
SUBMITTED,PENDING_UPDATE,PENDING_CANCEL) - request has been sent but ack not yet received. - Open (
ACCEPTED,TRIGGERED,PARTIALLY_FILLED, plus the in-flight states) - order is live at the venue. - Terminal (
DENIED,REJECTED,CANCELED,EXPIRED,FILLED) - order is finished; no further events possible.
Each state corresponds to an event
The full event-event-handler mapping is in nautilus-events.md. Key
event ↔ state correspondences:
| State entry | Event |
|---|---|
INITIALIZED | OrderInitialized |
DENIED | OrderDenied |
EMULATED | OrderEmulated |
RELEASED | OrderReleased |
SUBMITTED | OrderSubmitted |
ACCEPTED | OrderAccepted |
REJECTED | OrderRejected |
TRIGGERED | OrderTriggered |
PENDING_UPDATE | OrderPendingUpdate |
PENDING_CANCEL | OrderPendingCancel |
PARTIALLY_FILLED / FILLED | OrderFilled |
CANCELED | OrderCanceled |
EXPIRED | OrderExpired |
Plus modify/cancel-rejection events that don’t drive state transitions
but emit telemetry: OrderModifyRejected, OrderCancelRejected,
OrderUpdated.
Cortana MK3 implications
MK2 → Nautilus bracket-exit pattern
MK2 today: market entry (IBKR), then PM places bracket TP (LMT +10%) and
SL (STP -25%) at IBKR after fill confirmation, then position_manager
runs an on_quote_tick software fallback for both legs in case the broker
LMT misses. This is the dual-TP defense-in-depth pattern from
feedback_dual_tp_defense_in_depth.md.
MK3 in Nautilus terms - single line of code at entry decision:
bracket = self.order_factory.bracket(
instrument_id=chosen_call_id,
order_side=OrderSide.BUY,
quantity=self.instrument.make_qty(contracts),
entry_order_type=OrderType.MARKET,
tp_price=self.instrument.make_price(entry * 1.10),
sl_trigger_price=self.instrument.make_price(entry * 0.75),
# The dual-TP defense-in-depth: emulate locally so Nautilus
# owns the trigger logic, not just IBKR.
emulation_trigger=TriggerType.MARK_PRICE,
time_in_force=TimeInForce.DAY,
contingency_type=ContingencyType.OCO,
)
self.submit_order_list(bracket)What this gives us, mapped against the MK2 invariants:
| MK2 invariant / failure mode | MK3 Nautilus answer |
|---|---|
feedback_dual_tp_defense_in_depth - TP must have software fallback when broker LMT fails | emulation_trigger=MARK_PRICE makes Nautilus the fallback. The OrderEmulator subscribes to mark, watches it locally, fires a basic LIMIT (or MARKET) when triggered. Independent of IBKR’s bracket. |
feedback_no_hwm_trailing_language - single-shot +10% fixed TP, no trailing | tp_price=entry*1.10 as a LIMIT child. We do not use TRAILING_STOP_* types. |
feedback_no_kill_with_open_positions - never kill engine while position open | market_exit() helper cancels all open + in-flight, then submits reduce-only closes. The manage_stop=True config makes this automatic on Strategy.stop(). |
project_pm_ibkr_exit_invariant - PM exit intent → SELL at IBKR → position actually closes | Belt-and-suspenders: bracket child placed at IBKR (OCO), AND emulator watches mark locally, AND LiveExecutionEngine.reconciliation continuously aligns cached state with broker truth. Three layers. |
feedback_dual_tp_defense_in_depth - TP must have SW fallback | (See above) |
| Stale-mark-price bug (2026-05-06) | Cache-then-publish ordering for QuoteTick/TradeTick/Bar means the emulator’s read of mark is always the value that triggered the dispatch. No stale-cache window. |
What we delete from MK2
- The
position_manager.on_quote_ticksoftware-fallback loop for TP/SL. The OrderEmulator owns this. - The hand-rolled
oca_groupIBKR tagging. Nautilus’sOCOcontingency is the framework primitive. - The retry-loop on cancel (when broker rejects a cancel, MK2 retries N
times).
OrderCancelRejected→ strategy decides; the framework does not retry blindly. - The reduce-only flag bookkeeping. Pass
reduce_only=Trueon the SL child once; the framework propagates.
What we keep / add
- The +10% / -25% magnitudes. These are strategy decisions, not framework decisions.
- A custom RiskEngine rule that vetoes any bracket whose
(tp_price - entry_price)is not ≈ +10% (defensive sanity check against accidental config drift). - An
AuditLoggerActor (pernautilus-events.md) that subscribes toevents.*and writes Parquet - captures the bracket parent + both children + every state transition + the eventual fills, without Cortana code touching a database.
Open questions for Saturday’s spike
- Bracket emulation propagation: when
emulation_triggeris set onorder_factory.bracket(...), does it apply to (a) the parent only, (b) the children only, (c) all three? Verify in theOrderFactory.bracket(...)source. Docs do not explicitly say. - OCO leg-by-leg cancel on IBKR: when the SL fires and IBKR is the venue, does Nautilus cancel the TP leg automatically (OCO contract honored), or do we need the emulator’s local OCO logic?
- Mid-emulation modify: can we modify the TP price while the
bracket is in
EMULATEDstate (e.g., to bump TP from +10% to +15% if the trend-day detector fires)? The docs say emulated orders “can be modified … and updated” - verify behavior. oto_trigger_modedefault for IBKR: is itPARTIALorFULL? For Cortana 0DTE single-fill entries this rarely matters (the parent either fills fully or rejects), but partial fills on illiquid strikes could leave half a SL active.- Bracket parent modification: can we cancel just the bracket parent without cascading to children? Use case: change-of-mind on entry but still want children in a manual-entry path. Probably not, but verify.
- Reconciliation behavior for emulated orders: on engine restart
mid-trade, does
LiveExecutionEngine.reconcile_state()correctly resume anEMULATED-state order, or are emulated orders dropped on restart? Check~/brain/concepts/nautilus-concepts.mdReconciliation section alongside.
Caveats and gotchas
- Emulation is opt-in. A bracket without
emulation_triggeris IBKR-bracket-only. No SW fallback. Make the choice deliberate. - Object reference invalidates on release. Always query emulated
orders through
self.cache.order(client_order_id), never hold a Python reference pastOrderEmulatedevent. - Two RiskEngine checks for emulated orders (intake + release). A custom Risk rule that vetoes based on stale data could fail twice for the same order. Idempotency matters.
- MARKET and MARKET_TO_LIMIT cannot be emulated. If you pass
emulation_triggeron a MARKET, the framework either ignores it or rejects - verify. - Trailing stops are emulatable but Cortana V1 must not use them.
The mandate is single-shot fixed TP. Keep the
trailing_*order factory methods out ofCortanaStrategy. AT_THE_OPEN/AT_THE_CLOSETIF is venue-dependent. IBKR supports MOC/MOO orders separately from these TIFs; verify the mapping.- Bracket OCO behavior on partial fills is contingency-type-specific. OCO with a partial fill leaves the unfilled remainder of the other leg active at full size - wrong for a Cortana exit. Use OUO for scale-out semantics; OCO is fine for single-shot all-or-nothing.
When this concept applies
- Designing the MK3 entry → bracket pipeline.
- Choosing emulated vs venue-native exits (load-bearing decision).
- Implementing the dual-TP defense-in-depth pattern.
- Enumerating order states for the audit-trail subscriber.
- Authoring custom RiskEngine rules that gate specific order types.
When it breaks / does not apply
- Combo orders / multi-leg spreads.
OptionSpreadis a separate primitive (seenautilus-options.md); the bracket primitive does not cover spread legs. V2+ structural plays will need a different shape. - OTC / RFQ orders. The order taxonomy is exchange-style; request-for-quote workflows are not modeled here.
- Order types unique to a single venue (e.g., IBKR’s
IBOrderTags.algoStrategy="Adaptive"). These flow through IBKR-adapter-specifictagsparameters, not the standard order types. Seenautilus-integrations.md.
See Also
- Nautilus Strategies -
submit_order/OrderFactoryoverview,market_exithelper, RiskEngine integration - Nautilus Events - 17 order-lifecycle events that pair with each state transition
- Nautilus Execution (parallel) - ExecutionEngine, ExecutionClient, exec algorithms (TWAP)
- Nautilus Options - option-instrument context for V1 single-leg flow; chain-driven strike selection feeds into the bracket order
- Nautilus Value Types -
Price.make_price,Quantity.make_qty, the precision contracts that order-factory parameters must honor - Nautilus Concepts - Kernel, MessageBus, Cache, ExecutionEngine, RiskEngine canon
- Nautilus Integrations - IBKR adapter
capabilities,
IBOrderTags, native bracket support - 2026-05-09 Nautilus Spike Plan:
~/conductor/workspaces/cortanaroi-mk2/belo-horizonte/plans/2026-05-09-nautilus-spike.md- Step 4 / Step 5 implementation reference, spike question #6
~/.claude/projects/.../memory/feedback_dual_tp_defense_in_depth.md- TP must have software fallback when broker LMT fails
~/.claude/projects/.../memory/feedback_no_hwm_trailing_language.md- single-shot +10% fixed TP, no trailing
~/.claude/projects/.../memory/project_pm_ibkr_exit_invariant.md- PM exit intent → SELL at IBKR → position actually closes
Timeline
- 2026-05-07 | Cody - Filed during pre-spike concept mastery sweep batch 2.