Nautilus Tutorial - Options Data and Greeks (Bybit), for Cortana
The tutorial builds two Rust v2
LiveNodeexamples that stream live BTC options data from Bybit: (1) a per-instrument Greeks tester that discovers nearest-expiry calls, callssubscribe_option_greekson each, and logs delta/gamma/vega/theta/rho/IV viaon_option_greeks; and (2) an option-chain aggregator that constructs anOptionSeriesId, subscribes viasubscribe_option_chainwith aStrikeRange::AtmRelative {3, 3}filter, and consumesOptionChainSlicesnapshots throughon_option_chain. For Cortana, the split is sharp: Strategy-side patterns transfer cleanly (instrument discovery, chain iteration, Greeks-field reading, strike selection by delta-percentile, on_ handler shapes); the adapter-sidesubscribe_option_greekscall does NOT transfer because IBKR has no native Greeks adapter* (pernautilus-ib.mdandnautilus-greeks.md). Cortana substitutes UW custom-data Greeks (live primary) and the localGreeksCalculatorBlack-Scholes path (backtest + UW fallback). The tutorial is therefore a shape reference - same handler names, same event types, same field semantics - wired to a different upstream source.
What the tutorial actually delivers
Two Rust examples in crates/adapters/bybit/examples/:
| File | Build target | What it does |
|---|---|---|
node_greeks.rs | bybit-greeks-tester | Per-instrument Greeks streaming for nearest-expiry BTC calls |
node_option_chain.rs | bybit-option-chain | Aggregated chain snapshots at ±3 strikes around ATM, every 5s |
Both run as data-only LiveNodes - no execution client, no orders.
This is critical: the tutorial is a “Greeks-on-the-bus” demo, not a
trading strategy. It tests venue-side wiring, the data engine’s
chain-aggregation plumbing, and the strategy-side handler shapes. It
does not demonstrate sizing, entry, exits, hedging, or
portfolio-level Greeks aggregation. Those live in the sibling
delta-neutral options tutorial - see nautilus-tutorials.md.
The tutorial’s notable omissions (verbatim from the WebFetch summary): IV-based limit-order pricing, portfolio delta aggregation, rehedge triggers, session position hydration, strike-selection by delta-percentile. These are explicitly addressed in the delta-neutral-strategy tutorial, not this one. The framing question in the spike-prep brief asked for these - they exist in the next tutorial, and this page flags them as deferred.
Cortana transfer/no-transfer split
| Pattern | Transfer? | Why |
|---|---|---|
subscribe_option_greeks(instrument_id) | No | Bybit-only adapter capability. IBKR has no Greeks adapter. Cortana uses UW custom data + local BS. |
subscribe_option_chain(series_id, strike_range, snapshot_interval_ms) | Partial | The framework’s OptionChainManager plumbing works for any venue with options instruments. The Greeks field on each OptionStrikeData will be None from IBKR’s wire data unless we hydrate it from UW custom data or the local calculator. |
OptionSeriesId::new(venue, underlying, quote, expiry) | Yes | Generic identifier. Same shape for IBKR-routed SPY.SMART chain. |
StrikeRange::{Fixed, AtmRelative, AtmPercent} | Yes | Pure config primitive. No venue dependency. |
on_option_greeks(&self, greeks: &OptionGreeks) handler | Translate | Same handler signature, but events arrive via UW DataClient publishing OptionGreeks-shaped custom data, not subscribe_option_greeks. |
on_option_chain(&self, slice: &OptionChainSlice) handler | Yes | The framework publishes the slice regardless of where Greeks came from. |
| Cache-borrow-then-drop pattern before subscriptions | Yes | Universal Rust borrow rule - applies in any actor. Python doesn’t have the borrow checker but the pattern (read instruments, then act) is still right. |
Instrument discovery via cache.instruments(&venue, Some(&filter)) | Yes | Same call against IBKR-loaded SPY 0DTE chain. |
OptionChainSlice field iteration (get_call/get_put/strikes) | Yes | Generic data type, same API regardless of venue. |
with_delay_post_stop_secs(5) shutdown delay | Yes | Generic LiveNode pattern. |
cargo run --example bybit-greeks-tester build invocation | No | Cortana runs Python; the strategy code lives in a Strategy subclass loaded by a TradingNode. |
Walk-through, step by step
Step 1 - LiveNode + DataClient configuration
Tutorial code (Bybit):
let bybit_config = BybitDataClientConfig {
api_key: None, // loaded from env BYBIT_API_KEY
api_secret: None,
product_types: vec![BybitProductType::Option],
..Default::default()
};
let mut node = LiveNode::builder(trader_id, environment)?
.with_name("BYBIT-OPTIONS-001".to_string())
.add_data_client(None, Box::new(client_factory),
Box::new(bybit_config))?
.with_delay_post_stop_secs(5)
.build()?;Cortana-specific deviation. Cortana wires an
InteractiveBrokersDataClientConfig (per nautilus-ib.md) with
build_options_chain=True, min_expiry_days=0, max_expiry_days=1 to
materialize the SPY 0DTE chain. Plus a UW LiveDataClient (per
nautilus-custom-data.md + nautilus-ib.md UW-as-data-only adapter
sketch) that publishes UW Greeks as custom data onto the same bus.
The with_delay_post_stop_secs(5) shutdown shim is identical and
worth carrying - graceful Greeks-flush on close is good hygiene.
Step 2 - Instrument discovery (DataActor on_start)
Tutorial code (transfer verbatim - modulo Python translation):
fn on_start(&mut self) -> anyhow::Result<()> {
let venue = Venue::new("BYBIT");
let underlying_filter = Ustr::from("BTC");
let mut options: Vec<(InstrumentId, f64, u64)> = {
let cache = self.cache();
let instruments = cache.instruments(&venue, Some(&underlying_filter));
instruments
.iter()
.filter_map(|inst| {
if inst.option_kind() == Some(OptionKind::Call) {
let expiry = inst.expiration_ns()?.as_u64();
let strike = inst.strike_price()?.as_f64();
Some((inst.id(), strike, expiry))
} else {
None
}
})
.collect()
}; // cache borrow dropped here - important
let now_ns = self.timestamp_ns().as_u64();
options.retain(|(_, _, exp)| *exp > now_ns);
// identify nearest expiry, etc.
}Transfer verbatim for Cortana logic. The pattern is:
- Get a snapshot of instruments via the cache.
- Filter by
(venue, underlying). - Read kind / strike / expiry off each instrument.
- Drop the cache borrow before any subscription / subsequent call.
- Apply business filters (non-expired, target expiry).
Translate to Cortana (Python sketch):
class CortanaStrategy(Strategy):
def on_start(self) -> None:
venue = Venue("SMART") # IBKR SMART routing
instruments = self.cache.instruments(venue=venue, underlying="SPY")
now_ns = self.clock.timestamp_ns()
zero_dte = [
inst for inst in instruments
if inst.option_kind in (OptionKind.CALL, OptionKind.PUT)
and inst.expiration_ns > now_ns
and (inst.expiration_ns - now_ns) < ONE_DAY_NS
]
# No explicit borrow drop - Python's RC handles it.
# But still: read everything you need from the cache up front,
# then do subscription work, then handler work.Cortana-specific deviation. Python doesn’t have the
Rc<RefCell<>> borrow contract, so the “cache borrow dropped here”
comment is not load-bearing - but the discipline (read once, then
act) is still right because the cache will be mutated by the
DataEngine concurrently with strategy callbacks (per
nautilus-architecture.md single-threaded dispatch - but if you yield
mid-iteration, mutations land between calls).
Step 3 - Per-instrument Greeks subscription (Bybit-specific dead-end for Cortana)
Tutorial code:
let client_id = self.client_id;
for (instrument_id, _, _) in &options {
self.subscribe_option_greeks(*instrument_id, Some(client_id), None);
self.subscribed_instruments.push(*instrument_id);
}Cortana-specific deviation - DO NOT TRANSFER. The
subscribe_option_greeks adapter call is only implemented for
Deribit, Bybit, OKX (per nautilus-greeks.md “Computation source”
table and nautilus-ib.md Q2). IBKR’s adapter does not route the
IB tick-13 model-Greeks frames through this method. If we call this
against the IBKR client_id, we get a no-op or an error.
Cortana substitute - UW custom-data subscription per
nautilus-custom-data.md:
class CortanaStrategy(Strategy):
def on_start(self) -> None:
# ... instrument discovery as above ...
for inst in zero_dte:
self.subscribe_data(
data_type=DataType(UWOptionGreeks,
metadata={"instrument_id": str(inst.id)}),
client_id=ClientId("UW"),
)
# Plus: warm the local calculator with the underlying
# quote subscription so it can fall back if UW WS drops.
self.subscribe_quote_ticks(inst.id)
# Underlying SPY quote for BS calculator + ATM tracking
self.subscribe_quote_ticks(InstrumentId.from_str("SPY.ARCA"))The handler shape on_option_greeks(&mut self, greeks: &OptionGreeks)
translates to on_data(self, data: Data) with a type-check
(Python pattern from nautilus-custom-data.md):
def on_data(self, data: Data) -> None:
if isinstance(data, UWOptionGreeks):
self._handle_greeks(data)The Greeks field set is nearly identical. UW publishes delta,
gamma, vega, theta, mark IV - same fields the tutorial reads. We can
either define UWOptionGreeks as a @customdataclass mirroring
OptionGreeks (recommended - minimal cognitive overhead, can swap
later) or actually emit OptionGreeks via the UW DataClient (more
ambitious - bypasses the custom-data layer but requires the UW
adapter to publish the framework’s native type, which is a deeper
hook).
Recommended path for spike: UWOptionGreeks as a
@customdataclass. Wraps cleanly, replays through the catalog, and
the strategy reads the same delta/gamma/vega/theta fields the
tutorial reads.
Step 4 - Greeks handler (transfer verbatim - Python translation)
Tutorial code:
fn on_option_greeks(&mut self, greeks: &OptionGreeks) -> anyhow::Result<()> {
log::info!(
"GREEKS | {} | delta={:.4} gamma={:.6} vega={:.4} \
theta={:.4} rho={:.6} | mark_iv={}",
greeks.instrument_id,
greeks.delta,
greeks.gamma,
greeks.vega,
greeks.theta,
greeks.rho,
greeks.mark_iv.map_or("-".to_string(), |v| format!("{v:.2}"))
);
Ok(())
}Transfer verbatim. The field-access pattern is universal:
greeks.delta is an f64, greeks.mark_iv is an Option<f64>. In
Python, greeks.mark_iv is float | None. The Deref<Target = OptionGreekValues> Rust trick (mentioned in nautilus-greeks.md
Computation source) means Greeks fields are accessed directly off the
OptionGreeks event without nesting - Python sees the same flat
attribute access.
OptionGreeks field set (verbatim from tutorial):
| Field | Type | Description | Cortana usage |
|---|---|---|---|
instrument_id | InstrumentId | Option contract | Cache key for Position lookup |
delta | f64 | dV/dS | Strike-selection target (BULL = 0.30, BEAR = -0.30) |
gamma | f64 | dDelta/dS | Power-hour acceleration regime input |
vega | f64 | per 1% IV move (×0.01) | IV-shock sizing veto |
theta | f64 | per calendar day (×1/365.25) | Power-hour decay regime input |
rho | f64 | per unit r | Ignored (0DTE r-sensitivity ~ 0) |
mark_iv | Option<f64> | Mark IV | Smile / regime input |
bid_iv / ask_iv | Option<f64> | Side-aware IV | Liquidity gate (wide IV spread = stale) |
underlying_price | Option<f64> | Forward at expiry | ATM tracker input |
open_interest | Option<f64> | OI | Liquidity gate |
Critical: rho is in OptionGreeks (venue path) but not in
GreeksData (local-calc path) - see nautilus-greeks.md Computation
source. For Cortana 0DTE, rho is irrelevant; this asymmetry is
harmless.
Step 5 - OptionSeriesId (transfer verbatim)
Tutorial code:
let series_id = OptionSeriesId::new(
Venue::new("BYBIT"),
Ustr::from("BTC"),
Ustr::from("USDT"),
UnixNanos::from(expiry),
);Transfer verbatim (Python translation):
series_id = OptionSeriesId(
venue=Venue("SMART"), # or specific options exchange
underlying="SPY",
quote_currency="USD",
expiration_utc=expiry_ns,
)The four-tuple (venue, underlying, quote, expiry) is generic. Since
SPY equity options are physically settled in shares (per
nautilus-options.md Premium / mark / settlement), quote_currency
is USD, not the underlying. SPX is cash-settled - same USD quote.
Step 6 - subscribe_option_chain (transfer with caveat)
Tutorial code:
let strike_range = StrikeRange::AtmRelative {
strikes_above: 3,
strikes_below: 3,
};
let snapshot_interval_ms = Some(5_000); // every 5 seconds
self.subscribe_option_chain(
series_id,
strike_range,
snapshot_interval_ms,
Some(client_id),
None, // params
);Transfer with caveat. The framework’s OptionChainManager
(per nautilus-options.md Option chain architecture) handles the
plumbing identically across venues. The caveat: the Greeks
field on each OptionStrikeData returned by the framework is
populated from the venue’s wire stream. For IBKR, no Greeks arrive
on that path - so slice.get_call(strike).greeks will be None for
every strike, every snapshot, every time.
Cortana’s options:
- Hydrate the chain Greeks ourselves. Subscribe to UW Greeks
per instrument (Step 3 substitute), maintain a local
Dict[InstrumentId, UWOptionGreeks], and join on every chain slice. Lose the framework’s “single slice with everything” atomicity - but gain working Greeks. - Run the local
GreeksCalculatorper chain snapshot. On eachon_option_chaincallback, walk the slice, callcalculator.instrument_greeks(strike_id)for each strike, build our own enriched slice. Adds compute cost per snapshot but stays inside Nautilus primitives. - Custom
OptionChainManager-equivalent. Don’t subscribe to the framework’s chain; build a Cortana actor that aggregates UW Greeks + IBKR quotes and publishes a customCortanaChainSliceevent. Most work; cleanest separation. Defer to MK3 M2+.
Recommendation for spike: Option (2). The framework already
discovers + caches the chain instruments via build_options_chain=True
on the IBKR config. Hydrating Greeks via the local calculator on each
slice is one loop per snapshot - tractable, and Cortana’s BSM
fallback is the right shape regardless.
Snapshot interval choice. The tutorial uses 5000ms - appropriate
for crypto options. Cortana’s “early and right” mandate
(project_early_and_right_mandate) wants fresher Greeks at entry
time. snapshot_interval_ms=None (raw mode) on the chain plus per-tick
processing in the strategy is closer to what we want, at the cost of
higher CPU. Spike-day test: measure CPU at 1000ms vs 500ms vs
None.
ATM-relative strike count. Tutorial uses (3, 3) = 7 strikes. SPY
0DTE has ~80-100 active strikes; Cortana’s strike-selection rule
(buy delta-0.30 call) needs strikes far enough OTM that delta = 0.30
typically lands 4-8 strikes from ATM depending on IV. Recommended
default: AtmRelative(8, 8) - covers the delta-0.30 zone in either
direction with margin.
Step 7 - Chain handler (transfer verbatim)
Tutorial code:
fn on_option_chain(&mut self, slice: &OptionChainSlice) -> anyhow::Result<()> {
log::info!(
"OPTION_CHAIN | {} | atm={} | calls={} puts={} | strikes={}",
slice.series_id,
slice.atm_strike.map_or("-".to_string(), |p| format!("{p}")),
slice.call_count(),
slice.put_count(),
slice.strike_count(),
);
for strike in slice.strikes() {
if let Some(call) = slice.get_call(&strike) {
let delta = call.greeks.as_ref().map(|g| g.delta);
// process call
}
if let Some(put) = slice.get_put(&strike) {
let delta = put.greeks.as_ref().map(|g| g.delta);
// process put
}
}
Ok(())
}Transfer verbatim for the iteration shape. Python:
def on_option_chain(self, slice: OptionChainSlice) -> None:
for strike in slice.strikes():
call = slice.get_call(strike)
if call is not None:
delta = call.greeks.delta if call.greeks else None
# for Cortana: hydrate from UW or local calc if None
# ... same for putsOptionChainSlice API recap (Cortana cheat sheet - verbatim from tutorial):
| Method | Returns | Cortana use |
|---|---|---|
series_id | OptionSeriesId | Trace logging, multi-series routing |
atm_strike | Option<Price> | ATM tracker output, regime input |
call_count() / put_count() | usize | Liquidity sanity check |
strike_count() | usize | Active strike set size |
strikes() | Vec<Price> (sorted) | Strike-selection scan target |
get_call(k) / get_put(k) | Option<&OptionStrikeData> | Per-strike read |
Each OptionStrikeData carries quote: QuoteTick (bid/ask) and
optional greeks: Option<OptionGreeks>. The quote is always present;
greeks may be None (waiting for first Greeks event, or in
Cortana’s case, IBKR-without-UW-Greeks situation).
Cortana subsections - patterns the next tutorial covers
The framing brief asked this page to cover strike-selection by
delta-percentile, IV-based limit-order pricing, portfolio delta
tracking, rehedge triggers, and session position hydration. The
Bybit options-data tutorial does NOT cover these. They live in
the sibling delta-neutral options strategy tutorial (also Bybit,
Rust v2). This page describes them at the conceptual level here -
authoritative coverage will live in the next tutorial brain page when
filed (nautilus-tutorial-delta-neutral-options.md).
Strike selection by delta-percentile
Where it lives: delta-neutral options tutorial. The pattern from
nautilus-options.md § “Strike selection by delta-percentile”:
def on_option_chain(self, slice: OptionChainSlice) -> None:
target_delta = 0.30
best_strike = None
best_diff = float("inf")
for strike in slice.strikes():
call = slice.get_call(strike)
if call is None or call.greeks is None:
continue
diff = abs(call.greeks.delta - target_delta)
if diff < best_diff:
best_diff = diff
best_strike = strikeTransfer for Cortana - but the call.greeks source is UW custom
data + local-calc fallback (see Step 3 substitute), not Bybit
subscribe_option_greeks. The scan logic is venue-agnostic.
MK4+ flag. Cortana might want a richer target - e.g., “closest to delta-0.30 with OI > 500 and spread < 5%“. Layer those filters in the same loop:
if call.quote.bid_size < min_size:
continue
spread_pct = (call.quote.ask_price - call.quote.bid_price) / call.quote.bid_price
if spread_pct > max_spread_pct:
continue
if call.greeks.open_interest is None or call.greeks.open_interest < min_oi:
continueIV-based limit-order pricing
Where it lives: delta-neutral options tutorial. Concept: instead
of a fixed-tick LIMIT relative to mid (e.g., mid + 0.02), price the
limit at a target IV level computed via Black-Scholes inverse -
“buy the call with IV equivalent to mark_iv + X bps”. This produces
prices that auto-adjust as IV moves and avoids chasing on a fast tape.
Cortana V3 research flag. project_confluence_options.md already
flags signal-latency reduction work; IV-based entry pricing is a
candidate for the next research thread because it solves the
“placed limit at 5.65 in 200ms, fill never
came, signal aged out” failure mode. Use Nautilus’s
imply_vol_and_greeks Rust/PyO3 function (per nautilus-greeks.md
Black-Scholes functions) to invert IV → price at a chosen IV target.
Verdict for V1/V2: out of scope. V1 is MARKET orders for entry (thesis: speed > slippage). V2 may revisit when scoring stabilizes enough to absorb a few seconds of limit-fill latency.
Portfolio delta tracking
Where it lives: delta-neutral options tutorial. The
GreeksCalculator.portfolio_greeks(...) call from
nautilus-greeks.md § “Portfolio aggregation”:
pg = self.calculator.portfolio_greeks(
underlyings=["SPY"],
strategy_id=self.config.strategy_id,
)
# pg: PortfolioGreeks with pnl, price, delta, gamma, vega, thetaFilter by strategy_id for multi-strategy nodes - relevant once
Cortana starts running multiple variants in parallel
(MK3 M3+ multi-strategy phase).
For Cortana V1 single-leg: portfolio delta = position delta ×
signed_qty × multiplier - trivial. The full portfolio_greeks call is
heavier than V1 needs but the right shape for V2+ when defined-risk
spreads enter the picture (per nautilus-options.md Multi-leg
primitives).
Rehedge triggers (Greeks-driven + timer-driven)
Where it lives: delta-neutral options tutorial. Two patterns:
- Greeks-driven: subscribe to per-instrument Greeks, on each
update check
abs(portfolio_delta) > threshold, hedge if so. The handler ison_option_greeks- fires every Greeks update. - Timer-driven:
self.clock.set_timer(...)(pernautilus-actors.md/nautilus-strategies.md) firing every N seconds, run portfolio_greeks, hedge on threshold cross.
Cortana V1 is directional, not delta-neutral - there is no “rehedge to flat” target. The pattern still applies inverted: a “rehedge” for Cortana would be position-management - TP1 trim, SL re-wider, exit-on-momentum-fade. The trigger structure (event-driven vs timer-driven) carries over even if the action (“hedge to flat” vs “adjust exit”) differs.
MK3 M2+ implication. A PositionManagerActor that subscribes to
Greeks updates and re-evaluates exits on every change is the natural
evolution of MK2’s PM. The Greeks-driven trigger pattern from this
tutorial is the right shape for it.
Session position hydration / startup state recovery
Where it lives: delta-neutral options tutorial AND
nautilus-execution.md § Reconcile-on-startup. The pattern: on
LiveNode start, the framework’s LiveExecutionEngine automatically
queries the venue for open positions, open orders, account state,
and synthesizes events to bring the cache + portfolio in sync.
For Cortana (per nautilus-ib.md Reconnection behavior): IBKR’s
adapter does this automatically. No strategy-side hydration code
needed. When the strategy on_start() fires, the cache already
reflects open positions; self.cache.positions(...) returns the
real broker state.
This closes GH #46 by construction. MK2’s PM exit invariant
(project_pm_ibkr_exit_invariant.md) - “did the broker actually
close the position?” - becomes a non-question because the engine
re-reads broker truth on every (re)connect.
Spike-day test (already in 2026-05-09-nautilus-spike.md):
verify that after stopping the engine mid-position, restarting,
on_start sees the open position via cache.positions(). If yes,
this is the standalone win worth taking even if MK3 says NO-GO.
Cortana MK3 implications (M1-M2 milestone work)
M1 (Strategy plumbing):
- Build the
UWOptionGreeks@customdataclassmirroringOptionGreeksfield set (delta, gamma, vega, theta, mark_iv, underlying_price, open_interest, plus Cortana-specific extras likegexandflow_scoreif we want one event for everything). - Wire the UW DataClient to publish these via
_handle_data(...). - Wire a
CortanaStrategyon_datathat type-checks and routes to a private_handle_greeksmethod matching the tutorial’son_option_greeksshape. - Subscribe to
subscribe_option_chainwithStrikeRange.AtmRelative(8, 8)against the IBKR-loaded SPY 0DTE series. Usesnapshot_interval_ms=None(raw mode) for entry-time freshness. - In
on_option_chain, hydrate Greeks per-strike from either UW (cached from_handle_greeks) orGreeksCalculator.instrument_greeks(fallback). Carry the same field-iteration shape from the tutorial. - Implement strike-selection by scanning the slice for
abs(delta - 0.30)minimum (BULL CALL) orabs(delta - (-0.30))(BEAR PUT). Reject strikes failing liquidity gates (OI, spread, bid_size).
M2 (PM + risk):
- Layer a
PositionManagerActorthat subscribes toUWOptionGreeksevents for the open position and re-evaluates exit conditions on each update (Greeks-driven rehedge analog). RiskEnginesubclass callsportfolio_greeks(strategy_id=...)pre-trade for delta-veto (pernautilus-greeks.md“Veto entry if portfolio delta > 0.5 SPY-equivalent”).- Defer IV-based limit pricing - V3 research thread.
UW-side adapter glue needed (the missing piece):
The tutorial’s subscribe_option_greeks is a one-liner because
Bybit’s adapter does the work. Cortana must build the
equivalent UW glue. Per nautilus-custom-data.md and
nautilus-ib.md UW-as-data-only sketch:
- A
UWLiveDataClient(subclassLiveDataClient) opening the UW WebSocket. - On each UW frame: build a
UWOptionGreeksinstance withts_event = uw_payload.ts_ms * 1_000_000,ts_init = self._clock.timestamp_ns(), fields parsed from the payload. self._handle_data(greeks)to route through DataEngine → Cache → MessageBus.- Subscription routing:
subscribe_data(DataType(UWOptionGreeks, metadata={"underlying": "SPY"}))from the strategy fans out to all SPY 0DTE Greeks updates without per-instrument subscriptions.
This adapter glue is the load-bearing M1 deliverable. Without it, the tutorial’s whole on_option_greeks pattern doesn’t work for Cortana - not because the framework lacks it, but because the specific UW-IBKR-substituted-for-Bybit wiring is Cortana’s to build.
Open spike-day questions (specific to this tutorial’s patterns)
- Does
subscribe_option_chainwork over the IBKR adapter at all? Pernautilus-options.mdAdapter Support table, only Deribit / Bybit / OKX list ✓ forsubscribe_option_chain. Test on Saturday: callsubscribe_option_chainagainst the IBKR client, confirm whether (a) it returnsOptionChainSliceevents with hydrated quotes (GreeksNone), (b) it errors out, or (c) it silently does nothing. If (b) or (c), Cortana must build a customCortanaChainAggregatoractor - defer to M2. - What’s the latency of UW → strategy
on_datacallback? UW publishes ~5-10 events/sec for SPY chain; round-trip through the DataClient → DataEngine → MessageBus → Strategy is the path. If median latency > 50ms, the “early and right” mandate is at risk for entry decisions. - Does
cache.instruments(venue, underlying)return the IBKR 0DTE chain afterbuild_options_chain=Truematerialization? The tutorial assumes the chain is in the cache byon_start. Confirm IBKR chain materialization is complete before the strategy’son_startfires (or that the strategy can safely defer subscription toon_dataafter instruments arrive). StrikeRange.AtmRelativecooldown / hysteresis behavior (already a question innautilus-options.md) - does it churn during 9:30 ET open volatility?
See Also
- Nautilus Greeks -
OptionGreeksvsGreeksData, localGreeksCalculator, dual-path decision (UW primary live, BSM primary backtest) - Nautilus Options -
OptionContract,OptionChainSlice,StrikeRangevariants, ATM tracker - Nautilus Strategies - Strategy lifecycle,
on_data/on_option_chain/on_option_greekshandlers - Nautilus Actors - Actor vs Strategy decision,
subscribe_datapatterns - Nautilus Custom Data -
@customdataclass, UW custom DataClient sketch, the missing UW-side adapter glue - Nautilus IBKR - IBKR adapter limits, no native Greeks streaming, Greeks gap (carryover #8) decision
- Nautilus Tutorials - index page; flags the delta-neutral tutorial as the next one to read for the patterns this tutorial omitted
- Nautilus Instruments -
OptionContractmetadata, multiplier, expiry, strike fields - 2026-05-09 Nautilus Spike Plan:
~/conductor/workspaces/cortanaroi-mk2/belo-horizonte/plans/2026-05-09-nautilus-spike.md - MK3 Roadmap:
~/conductor/workspaces/cortanaroi-mk2/belo-horizonte/plans/2026-05-09-mk3-roadmap.md - Source: https://nautilustrader.io/docs/latest/tutorials/options_data_bybit/
- Companion (next to file): delta-neutral options strategy tutorial - covers strike-selection by delta-percentile, IV-based limit pricing, portfolio delta tracking, rehedge triggers, session position hydration
Timeline
2026-05-07 | Cody - Filed during pre-spike concept mastery sweep batch 5 (tutorials).