Nautilus Positions
A Nautilus
Positionis the engine-owned, single-source-of-truth record of exposure for an instrument. It is created from anOrderFilledevent, updated by subsequent fills, and closed when net quantity returns to zero - emittingPositionOpened/PositionChanged/PositionClosedalong the way (seenautilus-events.md). Strategies and Actors do not maintain their own position tables; they queryself.cache.position(...)andself.cache.positions_open(...). The kernel computes realized PnL from fills, accumulates commissions, snapshots closed positions on flip, and reconciles cached state against broker truth on startup. For Cortana MK3 this is the direct replacement for MK2’sposition_statetable plus the hand-rolledposition_tracker. The entire bug class catalogued inexit-path-failure-modes.md- alert-without-action, status-without-truth, EXIT_PENDING leaks, tracker-vs-broker drift, orphan contamination - either disappears or is reduced to a thin operator-decision surface because there is no longer a second store to drift from.
Core claim
Position is the only authoritative record of exposure inside the running
engine. The Cache holds it, the ExecutionEngine mutates it, the Portfolio
queries it, and reconciliation realigns it to broker truth on startup. MK2’s
multi-store design (position_state table + in-memory position_tracker +
broker updatePortfolio callback all racing each other) collapses to one
store with a deterministic mutation path. The remaining MK2 audit-trail value
(STATE_TRANSITION log lines, decision rows) becomes a custom event subscriber
on on_position_event rather than a parallel state machine.
Position object - full field reference
Verbatim from the docs (/concepts/positions/), grouped by purpose:
Identifiers
| Field | Type | Meaning |
|---|---|---|
id | PositionId | Unique position identifier (per instrument under NETTING; per open under HEDGING) |
instrument_id | InstrumentId | Traded instrument |
account_id | AccountId | Owning account |
trader_id | TraderId | Owning trader (kernel-assigned) |
strategy_id | StrategyId | Owning strategy (auto-filled from OrderFactory) |
opening_order_id | ClientOrderId | The order that opened the position |
closing_order_id | ClientOrderId | None | The order that closed it (set on PositionClosed only) |
symbol | Symbol | Convenience accessor |
venue | Venue | Convenience accessor |
State
| Field | Type | Meaning |
|---|---|---|
side | PositionSide | Current direction: LONG / SHORT / FLAT |
entry | OrderSide | Opening direction (the side that opened the current run; updates on flip) |
quantity | Quantity | Absolute size |
signed_qty | float (or Decimal) | Signed magnitude - positive for LONG, negative for SHORT |
peak_qty | Quantity | Maximum absolute exposure ever reached during this position’s life |
is_open / is_closed | bool | Aggregate status |
is_long / is_short | bool | Direction predicates |
Pricing and valuation
| Field | Type | Meaning |
|---|---|---|
avg_px_open | float | Quantity-weighted average entry price |
avg_px_close | float | None | Quantity-weighted average exit price (None until any close fills) |
realized_pnl | Money | Realized profit/loss from closed portions, in settlement currency |
realized_return | float | Return as decimal (0.05 == 5%) |
quote_currency, base_currency, settlement_currency | Currency | Currency taxonomy (settlement is the one PnL is denominated in) |
multiplier | Decimal | Contract multiplier (e.g., 100 for SPY options) |
price_precision, size_precision | int | Tick rounding |
is_inverse | bool | Inverse-contract flag (BTC-perp style); changes PnL formula |
Timestamps
| Field | Type | Meaning |
|---|---|---|
ts_init | int (ns since epoch) | When Nautilus created the Position object |
ts_opened | int (ns) | When the first fill landed |
ts_last | int (ns) | Most recent update (any fill or adjustment) |
ts_closed | int | None | When net qty returned to zero (None while open) |
duration_ns | int | None | ts_closed - ts_opened (None while open) |
Associated history
| Field | Type | Meaning |
|---|---|---|
client_order_ids | list[ClientOrderId] | Every order that touched this position |
venue_order_ids | list[VenueOrderId] | Same, broker-side |
trade_ids | list[TradeId] | Every distinct trade execution applied |
events | list[OrderFilled | PositionAdjusted] | Chronological event log applied to this position |
event_count | int | Convenience count |
last_event, last_trade_id | various | Most recent activity |
The events list is load-bearing - it is the local audit trail for this
position’s evolution and is the input to commission and PnL recomputation if
events are ever replayed. MK2’s multi-source reconstruction (decisions.db +
position_state row history + IBKR execDetails) collapses into this one
list.
Lifecycle states and events
Three lifecycle moments, each emitted as a typed event on the MessageBus
(see nautilus-events.md for full event field matrix):
OrderFilled (first fill)
│
▼
┌─────────────────┐
│ PositionOpened │
└────────┬────────┘
│
additional fills │
(same direction or
partial reductions)
▼
┌─────────────────┐
┌───────►│ PositionChanged │
│ └────────┬────────┘
│ │
│ additional │
└───── fills ─────┘
│
▼
┌─────────────────┐
│ PositionClosed │ (net qty == 0)
└─────────────────┘
Key behaviors:
- Opened. First fill creates a Position. The OMS type (NETTING vs HEDGING; see below) controls whether subsequent same-instrument fills create a new position or aggregate into the existing one.
- Changed. Subsequent fills that do not net-to-zero update
quantity/avg_px_open/avg_px_close/realized_pnl/peak_qty/signed_qtyand emitPositionChanged. - Closed. Net quantity reaches zero.
closing_order_idis recorded,ts_closedandduration_nsare populated, finalrealized_pnlis finalized. - Position flip. A single fill that crosses zero (e.g. SHORT→LONG)
is split by the engine into close-then-open. You see two events
(
PositionClosedthenPositionOpened) for one fill. Auditors must treat them as a pair. - Snapshotting (NETTING only). Per the docs, “when a NETTING position closes and then receives a new fill for the same instrument, the execution engine snapshots the closed position state before resetting it,” preserving final quantities, prices, realized PnL, fills, and commissions for historical accuracy. The Position object is reused; the snapshot lives in the cache as a read-only artifact.
The lifecycle is engine-owned. Strategies cannot manually transition a Position. They can only submit orders that cause fills, which the engine then applies. This is the structural change versus MK2.
Position events - verbatim from nautilus-events.md field matrix
| Field | Opened | Changed | Closed |
|---|---|---|---|
trader_id, strategy_id, instrument_id, position_id, account_id | yes | yes | yes |
opening_order_id | yes | yes | yes |
closing_order_id | - | - | yes |
entry, side, signed_qty, quantity | yes | yes | yes |
peak_qty, avg_px_close, realized_return, realized_pnl, unrealized_pnl | - | yes | yes |
duration_ns | - | - | yes |
ts_opened, ts_closed, ts_event, ts_init | yes | yes | yes |
Position events are derived from OrderFilled events. The chain:
OrderFilled arrives → ExecutionEngine resolves position_id (creating one
under HEDGING, looking up under NETTING) → either creates a new position
(emits PositionOpened), updates existing (PositionChanged), or closes it
(PositionClosed).
Critical absence: there is no PositionMarkChanged or
PositionPriceUpdated event. PositionChanged is fill-driven, not
price-driven. Quote-tick movement that updates unrealized_pnl does not emit
a position event - Cortana’s tick-driven mark telemetry must be a custom
event (see nautilus-events.md § “PositionTelemetry”), not a built-in.
Net vs hedging position model - IBKR specifically
Two OMS (Order Management System) types control how positions aggregate. The strategy declares one and the venue has its own; the four-quadrant matrix is from the Nautilus docs verbatim:
| Strategy OMS | Venue OMS | Behavior |
|---|---|---|
| NETTING | NETTING | Single position per instrument both sides |
| HEDGING | HEDGING | Multiple independent positions, each with its own position_id |
| NETTING | HEDGING | Venue tracks multiple; Nautilus maintains single (aggregates internally) |
| HEDGING | NETTING | Venue tracks single; Nautilus maintains virtual sub-positions |
IBKR specifically
IBKR is a NETTING venue at the broker level. Per-account, IBKR holds one
net position per contract - you cannot simultaneously hold +100 and -50
SPY 0DTE 727C; IBKR will net them to +50. This matches MK2’s actual lived
experience: every reconciliation against updatePortfolio returns one row
per contract, never two.
Cortana MK3 should declare NETTING at the strategy level too. Reasons:
- Symmetry with broker truth means reconciliation is one-to-one. No virtual sub-positions to map.
- SPY 0DTE strategy is single-direction-per-contract by design (we don’t long-and-short the same strike simultaneously). HEDGING semantics buy nothing.
- Position flips are vanishingly rare in 0DTE (a flip would mean closing
the call and opening a new contract with a different
position_id). When they happen, NETTING’s snapshot-on-close-then-reopen is exactly the audit trail we want.
A future intra-day strategy that did hold long SPY calls AND long SPY puts
(straddle) is still NETTING-compatible because the calls and puts are
distinct instrument_ids. NETTING’s “single position per instrument” rule
already supports this without HEDGING.
Long-only / short-only / mixed exposure
Cortana is structurally long-options-only - we buy calls or buy puts; we do
not short-sell options. So at the position level Nautilus only ever sees
LONG positions (side == LONG, signed_qty > 0). The SHORT and FLAT
machinery is unused.
Implications:
is_net_long(instrument_id)is always True for any open Cortana position.is_net_short(...)is always False.is_flat(instrument_id)flips True when the position closes - this is the predicate Cortana’s EOD-flatten logic should check, not a custom flag.- The Portfolio’s “context-aware pricing” (long-only → bid for conservative
liquidation, short-only → ask, mixed → mid; see
nautilus-concepts.md§ Portfolio) reduces to “always use bid” for Cortana valuation, which is appropriate - we exit by hitting bid.
P&L computation - semantics
Realized PnL (standard contracts, including SPY options)
realized_pnl = (exit_price - entry_price) * closed_quantity * multiplier
For SPY 0DTE options: multiplier = 100. A +10% TP at avg 1.75 exit, on 100 contracts:
realized_pnl = (1.75 - 1.59) * 100 * 100 = $1,600 (gross of commission)
Matches MK2’s intuitive math.
Realized PnL (inverse contracts) - not used by Cortana but documented for completeness
LONG: realized_pnl = closed_qty * multiplier * (1/entry_px - 1/exit_px)
SHORT: realized_pnl = closed_qty * multiplier * (1/exit_px - 1/entry_px)
is_inverse is False for SPY options, so Cortana uses the standard formula.
Unrealized PnL
position.unrealized_pnl(price) accepts any reference price (bid/ask/mid/
last/mark) and returns Money in settlement currency. For a FLAT position
returns Money(0, settlement_currency). Cortana valuation should pass the
side-appropriate price (bid for our long-only book) to match true
liquidation value.
Total PnL
position.total_pnl(current_price) = realized_pnl + unrealized_pnl(current_price).
Aggregation - FIFO vs weighted-average
The PnL formula above uses entry_price = avg_px_open (quantity-weighted
average across all entry fills). Closes consume from this weighted average,
not FIFO lots. For a single-strike single-entry-then-single-exit lifecycle
(Cortana’s normal case) FIFO and weighted-average produce identical
results. They diverge only when you average down or scale out - neither is
in the current Cortana playbook. Tax-lot accounting (FIFO/LIFO/specific
identification) is not a kernel concern; if Cortana ever needs it, that’s
a downstream report computed from the events list.
Commission and fee handling
Commissions accumulate per currency and are accessible via
position.commissions() which returns a list[Money] (one entry per
distinct currency seen). They are tracked in OrderFilled.commission and
applied to the position separately from the PnL formula above - i.e., the
formula gives gross PnL; commissions are a separate accumulator.
Cortana net-of-fees PnL becomes:
net = position.realized_pnl - sum(position.commissions())
(within a currency).
Edge case from the docs: “Base Currency Commissions: Applied only to spot
currency pairs where commission currency matches instrument.base_currency.
Commission deducts from opening trades’ quantity; affects signed_qty on
closing fills.” This is FX-specific and does not apply to SPY options.
Funding payments (perpetual futures) are tracked as PositionAdjusted
events with quantity_change=None. Also not a Cortana concern but
preserved in position.adjustments.
Query API - Strategy / Actor
All position reads route through self.cache:
# Lookup by ID
self.cache.position(position_id) -> Position | None
# Filtered listings
self.cache.positions(
venue=None, # filter by Venue
instrument_id=None, # filter by instrument
strategy_id=None, # filter by owning strategy
side=None, # PositionSide.LONG / SHORT / FLAT
) -> list[Position]
self.cache.positions_open(...) -> list[Position] # only is_open
self.cache.positions_closed(...) -> list[Position] # only is_closedPlus the Portfolio convenience surface (per nautilus-strategies.md):
# Per-instrument predicates
self.portfolio.is_net_long(instrument_id) -> bool
self.portfolio.is_net_short(instrument_id) -> bool
self.portfolio.is_flat(instrument_id) -> bool
self.portfolio.is_completely_flat() -> bool
# Per-instrument values
self.portfolio.net_position(instrument_id) -> Decimal
self.portfolio.net_exposure(instrument_id) -> Money
self.portfolio.unrealized_pnl(instrument_id) -> Money
self.portfolio.realized_pnl(instrument_id) -> Money
# Per-venue aggregates
self.portfolio.unrealized_pnls(venue) -> dict[Currency, Money]
self.portfolio.realized_pnls(venue) -> dict[Currency, Money]
self.portfolio.net_exposures(venue) -> dict[Currency, Money]These are pull-style queries against the kernel’s central Portfolio. Per
nautilus-concepts.md, valuation uses a fallback chain (cached mark,
side-appropriate quote, last trade, recent bar close) and explicitly flags
unpriceable positions rather than silently zero-valuing them.
Cache-then-publish guarantee
From nautilus-events.md: “Data updates write Cache then publish on the
bus, so handlers can rely on cache.quote_tick(...) returning the very
tick that triggered them. Live execution events apply asynchronously and
may show a ‘brief delay’ before Cache reflects them - a documented
caveat.”
For positions: when on_order_filled fires, the OrderFilled event
payload itself is authoritative. Re-reading cache.position(...) mid-
handler may return state from a tick or two ago in live mode. Use the
event payload for exact-at-event values; use the Cache for current state.
Reconciliation - broker truth wins on startup
Per nautilus-concepts.md and nautilus-events.md, the
LiveExecutionEngine performs reconciliation on startup:
- Connect to venue.
- Pull broker positions, orders, and fills.
- For every cached position with no broker counterpart → close it (broker-truth).
- For every broker position with no cached counterpart → open it (broker-truth).
- For mismatches → reconcile to broker numbers.
- Synthesize the necessary order/fill/position events to align the cache
with broker reality. Each synthesized event has
event.reconciliation = Trueso audit consumers can distinguish them from fresh broker activity.
Critical implication: the Cache is never authoritative on its own. The broker is. Restart-and-rebuild is the recovery model. Crash-only recovery is a property of the engine, not a feature you have to design.
This is the structural property MK2 lacked. MK2 had a position_state
table that was intended to be a persistence cache for reconciliation but
in practice diverged from position_tracker (in-memory) and from
updatePortfolio (broker callback), with no single arbiter. Three
“sources of truth” race; bugs ensue (see exit-path-failure-modes.md
Class 1, 2, 3).
Numerical precision
Per the docs: “Testing confirms f64 arithmetic maintains accuracy for typical trading scenarios” - standard amounts ≥ 0.01, 9-decimal crypto prices within 1e-6 tolerance, and 100-fill sequential trades without drift. Range validated 0.00001 to 99,999.99999 without overflow.
For SPY options at 25.00 with 100-multiplier this is well within the safe envelope. No precision-loss concern for Cortana sizes.
Cortana MK3 implications - MK2 mapping
The two MK2 components that disappear under MK3:
1. position_state SQLite table
MK2 location: src/cortana/positions/store.py:13 (the PositionState
enum) plus a corresponding row schema persisting state across restarts.
MK3 replacement: none - directly removed. The Nautilus Cache (Redis-
or Postgres-backed) holds open positions. Restart triggers reconcile-with-
broker, which authoritatively rebuilds. The “we have a position state
DB and it’s the source of truth” mental model goes away.
2. position_tracker in-memory
MK2 location: src/cortana/positions/manager.py and friends. Hand-rolled
in-memory dict keyed by contract identity, with a custom transition
helper, custom STATE_TRANSITION log lines, custom orphan-detection
logic, custom watchdog gating.
MK3 replacement: the Nautilus Position object inside Cache. All
three custom subsystems (transition helper, log lines, orphan detection)
become either kernel-provided (transitions, events) or unnecessary
(orphan detection: with single-store + on-startup reconcile, “orphan”
just means “broker has a position the engine doesn’t know about” → log
once, operator decides; no auto-adopt logic to write).
MK2 bug class → MK3 mechanism
This is the load-bearing table. Each of the four MK2 bug classes
catalogued in exit-path-failure-modes.md and position-state-machine.md
maps to a Nautilus mechanism that prevents it by construction:
| MK2 bug class | Concrete MK2 incident | Nautilus mechanism that prevents it |
|---|---|---|
| (a) State divergence between two stores - position_state table says X, position_tracker dict says Y, broker says Z | 2026-04-24: tracker said EXIT_PENDING with growing ghost-PnL while broker said position=0. Four hours of lying UI. | Single store. Position lives only in Cache. There is no second store to drift from. cache.position(id) is the only read path; OrderFilled → ExecutionEngine is the only mutation path. Eliminated by design. |
(b) updatePortfolio reconciliation drift - broker reports position=0 but engine never finalized, kept emitting EXIT_PENDING and ghost unrealized | Same 2026-04-24 incident - required fdcf6ad shipping a callback handler that finalized tracker on position=0. | Engine-owned reconciliation. LiveExecutionEngine consumes broker position reports, resolves them against Cache, and emits the necessary PositionClosed synthetic events with reconciliation=True. No custom callback to wire. The “broker says zero, finalize the position” logic is platform code, tested across thousands of users. |
| (c) EXIT_PENDING leaks - exit-in-progress flag on Position never cleared, alerts kept firing after broker truth was flat | 2026-04-24: alert spam during exit-in-progress; required edge-trigger dedup and _exit_in_progress flag handling in 9a118b1. | State-machine engine-owned. Position has only three observable states: open / closed (intermediate “exit in progress” is a property of the order, not the position). The Position closes when net qty hits zero - full stop. No EXIT_PENDING sentinel, no flag to get stuck. The order-side state machine tracks PENDING_CANCEL etc.; the position-side machine has no such transient. |
| (d) Tracker-vs-broker drift - engine qty != broker qty, with reconciler sometimes acting on PENDING_ENTRY positions | 2026-04-27 187: reconciler acted during entry-fill window, orphaned 100 contracts and contaminated TP qty for a sibling position; required 83ba9eb state gates. | Reconcile-on-startup, broker-as-truth. The MK2 issue was acting during entry - there is no equivalent MK3 path because reconciliation is a startup phase, not a continuous loop racing live fills. Subsequent broker-vs-cache divergence emits a synthetic event with reconciliation=True; consumers (including the audit logger) can distinguish synthesized events from fresh ones. The “PENDING_ENTRY exists and reconciler must skip it” gate becomes unnecessary because there is no parallel reconcile loop. |
Which MK2 bug class is most cleanly killed?
Class (a) - state divergence. It’s killed structurally, not by adding a guard. Classes (b), (c), and (d) are killed by the framework’s reconciler behaving correctly, the position state machine being engine-owned, and reconciliation being a startup phase. Those are mechanism wins. But (a) is the deepest - there is no second store. The bug is unrepresentable because the data model doesn’t permit it. That’s the strongest kind of prevention.
This is also why class (a) was the longest-lived MK2 bug class - it kept
re-appearing as new code paths added new accidental “state” (the
_exit_in_progress flag, the _last_tick_at timestamp, the
tp_status field, the tp_order_id string). Each was a tiny private
state shard that drifted independently. MK3 removes the soil they grew in.
What MK3 gains beyond bug elimination
- Position events are auditable for free. Subscribe one Actor to
on_position_eventand Parquet every event. Replaces MK2’s bespoke STATE_TRANSITION log lines with an indexable, replayable record. peak_qtyis platform-tracked. MK2 didn’t have a clean field for high-water-mark size; MK3 gets it for free.- Position duration is platform-tracked.
duration_nsarrives inPositionClosed; we don’t compute it ourselves. - Position flip semantics are clean. Close-then-open as a pair, not a hand-rolled “did the qty go negative” check.
- Snapshot-on-flip preserves history. A NETTING-mode SPY 0DTE strategy that closes one contract and opens a new strike for the same instrument gets a snapshot of the closed position automatically. Cortana barely uses this today, but it’s a clean property to have for postmortems.
What Cortana still owns at the strategy/actor layer
- Why a position exited. Nautilus records
closing_order_idbut does not encode the reason (TP / SL / time-in-trade / EOD-flat / manual / thesis-invalid). Cortana publishes a customExitReasonevent tied to thePositionClosed(seenautilus-events.mdmapping table). - Tick-driven mark telemetry. Cortana’s “current %-PnL printed every
quote tick” UI element is not a position event. Build a custom
PositionTelemetryevent that subscribes toon_quote_tickand readscache.position(id).unrealized_pnl(price)on the fly. - TP/SL geometry. Bracket order construction, software-fallback
trigger logic, and EOD-flatten lives in
CortanaStrategy. The Position itself just records what happened. - Cooldowns and sizing decisions. Pre-entry, before any Position exists. No platform analog.
Caveats and gotchas
PositionChangedis fill-driven, not price-driven. Quote ticks do not emit position events. Custom telemetry handles between-fill mark changes.- Position flips emit two events (
PositionClosedthenPositionOpened). Audit consumers must treat them as a pair, not as a single transition. - Snapshot-on-flip is NETTING-only. HEDGING gets a fresh
position_idper open; the old position remains incache.positions_closed(). NETTING reuses the position slot but preserves the closed state in a snapshot. reconciliation=Trueevents look like real broker events. Any audit consumer (logger, telegram alerter, dashboard) must check this flag before treating a synthesized event as fresh activity. Otherwise: Telegram pings you for every position that was open across a restart.- Cache update lag in live. “You might see a brief delay between an event and its appearance in the Cache.” Use the event payload for exact-at-event state; use the Cache for “what’s true right now.” Don’t re-read Cache mid-handler if you need the values that triggered the handler.
avg_px_closeis None until the first close fill. Code that formats this for display must handle None - easy to miss in templating.- Inverse contracts have side-aware PnL formulas. Cortana doesn’t use
inverse contracts (
is_inverse=Falsefor SPY options) but if Nautilus is later used for crypto perps, the formula switches. - Quanto contracts are documented as a limitation. “Limitations
include panics if inverse instruments lack
base_currencyand improper handling of quanto contracts.” Not a Cortana concern; document for future work. - Numerical drift over thousands of fills is bounded but not zero. f64 arithmetic - drift testing covers 100-fill sequences. Cortana’s per-position fill count is ≤ 5; well inside the safe envelope.
When this concept applies
- Designing the MK3 position-management surface.
- Mapping MK2’s
position_state+position_trackerto a single Cache store. - Reasoning about reconcile-on-startup as the recovery model.
- Auditing Cortana’s exit-path bug catalog against MK3 prevention.
- Computing realized/unrealized/total PnL inside a Strategy or Actor.
- Subscribing to position lifecycle events from an audit-logger Actor.
When it breaks / does not apply
- Mid-position price annotations (e.g., “this position drew down to
-8% at 10:32:14”) - those are not events Nautilus emits. Build a custom
PositionTelemetryevent or persistunrealized_pnl(quote.bid)fromon_quote_tick. - Reason taxonomies for closure - Nautilus records
closing_order_idbut not “why” (TP / SL / time / manual). Cortana publishes a customExitReasonevent in tandem. - Tax-lot accounting (FIFO/LIFO/specific ID) - kernel uses weighted-
average cost basis. Tax reports are a downstream computation off the
eventslist. - Operator-overrideable position state - MK2 had operator UI actions
that mutated
Position.tp_statusdirectly. MK3 equivalent is to submit an order (cancel TP, place new one) and let the engine emit events. No direct field mutation.
See Also
- Nautilus Events - full PositionEvent field matrix and how positions hook the audit-trail subscriber.
- Nautilus Strategies -
self.portfolio/self.cachequery API and order submission that causes position events. - Nautilus Execution - parallel page; ExecutionEngine
fill resolution,
position_idassignment, OMS reconciliation paths. - Nautilus Cache - parallel page; Cache as the single store, Redis/Postgres backends, query semantics.
- Nautilus Concepts - Portfolio section, lines 389-414; cross-instrument aggregation and price-fallback chain.
- Nautilus Actors - Actor capabilities; position-event subscription from non-order-submitting components.
- Exit-path failure modes - MK2 catalog of bug
classes (Class 1/2/3) that Nautilus’s
Positionmodel prevents. - Position state machine - MK2’s hand-rolled three-state machine (PENDING_ENTRY / OPEN / CLOSED) that Nautilus replaces with engine-owned lifecycle.
- Position lifecycle - MK2’s end-to-end walkthrough; MK3 consolidates entry/exit/reconcile under engine ownership.
- 2026-05-09 Nautilus Spike Plan - Saturday evaluation; this page is reference for Steps 4, 5, and 7.
- Brain RESOLVER - page filing rules.
Timeline
- 2026-05-07 | Cody - Filed during pre-spike concept mastery sweep batch 2.