vike
Desktop app

The options chain

The Options window is a mirrored calls–strike–puts ladder over Deribit's public book, kept live between polls and painted with the trader's own orders and positions; the Greeks window prices those positions off the same chains.

Two tool windows carry options in Vike Trader: Options, the chain grid, and Greeks, the position ledger priced off it (WinKind::Options and WinKind::Greeks in crates/vike-app-core/src/workspace/state.rs). vike-options is the venue-free pure core, with its own concept page; crates/bridges/deribit/src/chain.rs fetches the venue's book; crates/vike-chart/src/options_chain.rs owns only the paint; and two pure modules in vike-app-core, options_books.rs and options_greeks.rs, fold the trader's own state onto the chain — pure so CI's tests execute them, which nothing in the GUI shell's main.rs is.

The grid

crates/vike-chart/src/options_chain.rs paints the mirrored CALLS | Strike · IV | PUTS ladder: nine fields a side, the ATM row marked by an accent rule, an expiry strip above. The field set is vike_options::columns::CHAIN_FIELDS, ordered centre-to-outer on the puts side and reversed for calls so the table mirrors around the spine: Volume, Distance, Rel dist, Bid, Ask, Spread, Theor, Bid %, Ask %. Every value comes from columns::cell_value and every string from columns::fmt. Theor is a Black–Scholes price from the quote's own implied vol at a risk-free rate of zero. A missing input renders as a dash — the column model treats a zero spot or a zero mark as not-available, never as zero.

Three pills drive the view. The underlying selector offers whichever of BTC, ETH and SOL fetched, in that order; switching clears an expiry selection the new bundle lacks (crates/vike-app-core/src/tools.rs's resolve_options_selection — the file every fetch-side symbol below lives in), so a switch never shows an empty grid. The expiry strip labels each contract 02 Jul, or 0DTE for the one settling today (vike_options::make_expiry counts calendar days). The ±N strikes pill cycles STRIKE_WINDOW_PRESETS — 6, 12, 20, 30 — and strike_window_range keeps the window balanced: the same count each side of ATM, capped to the shorter side, so a near expiry short of far-OTM strikes never renders lopsided.

Where the numbers come from

The chain is Deribit's public REST get_book_summary_by_currency — no API key, one call per currency's whole book, behind a 5 s cache (crates/bridges/deribit/src/chain.rs's CACHE_TTL_MS). BTC and ETH have coin-settled books whose premiums are scaled to USD by the underlying price; SOL lives on the shared USDC book, already in dollars, and passes through unscaled (is_usd_quoted). Each refresh takes the nearest 8 expiries per underlying and fetches every chain ±30 strikes wide, the widest preset, so the ±N pill trims what is already held and never re-fetches (fetch_underlying). The poll repeats every 12 s (OPT_POLL_SECS: 12 s × 3 underlyings, 5 s-cached, ≈ 15 REST calls a minute); the Refresh pill wakes it at once, and a burst of clicks coalesces into one refetch. An underlying that fails a pass keeps its last-good bundle; the status reads options error: … only when nothing fetched. VIKE_RECORD_CHAINS=1, off by default, records every polled chain as a kind=chain snapshot (options_provider).

Between polls, two keyless WebSocket feeds from crates/bridges/deribit/src/options_feed.rs keep the grid live. markprice.options.{btc_usd,eth_usd,sol_usdc} pushes mark and IV for every listed option, and apply_markprice_to_chains re-derives the greeks from each fresh IV. ticker.{instrument}.100ms streams top-of-book for a fixed focus set — the front expiry's ATM ±10 strikes of BTC and ETH, subscribed once on the first successful poll (front_expiry_focus_instruments). The REST poll backstops chain structure, open interest, volume, and the bid/ask of every strike outside that focus set.

Trading from the chain

A Bid or Ask cell is clickable only when its quote carries both a venue instrument_name and that price (quote_order): an ask click buys, a bid click sells, and the click opens a confirm ticket while submitting nothing (OptionChainActions::order). The ticket is prefilled with instrument, side and price, defaults the quantity to 0.1, and lets both be edited before Confirm Buy / Confirm Sell or Cancel (OptOrderTicket). Only a confirmed ticket reaches plan_options in crates/vike-app-core/src/order_dispatch.rs, which builds a deribit limit order and admits it through admit(SubmitSource::Options, …) — the gate every window's order passes, so the local cap and the venue-side checks apply as they do to the trade panel.

The trader's own state is painted back onto the ladder. build_books in crates/vike-app-core/src/options_books.rs folds every live (non-terminal) Deribit order and every Deribit position in the core snapshot into an InstrumentBook keyed by instrument name; other venues are ignored, and a strike with neither gets no entry. The position badge is the net signed size across the instrument's legs (dom_math::signed_position_size) — a green +2 or a red -1. Working orders show as a ○N marker whose click cancels the first resting order, repeated to peel them off one at a time, routed to OrderIntent::Cancel as the DOM ladder routes its own.

The chain you look at and the book you trade on are different hosts

Every public read in the Deribit bridge — the chain and both feeds — is hardcoded to mainnet www.deribit.com; every authed socket, the order transport included, is hardcoded to testnet. A ticket confirmed off the real book lands on a different book on a different host, and crates/bridges/deribit/src/exec.rs's header is explicit that demo credentials route the TESTNET order and user-data endpoints, and it names no mainnet execution path at all.

Cancel is a silent success for an order this process did not submit

crates/bridges/deribit/src/client.rs's cancel_order resolves the venue id through an in-memory map owned by one client, and a miss returns Ok(()). A ○N marker for an order placed before a restart or discovered by reconcile reports a cancel that did nothing.

The Greeks window

Greeks is read-only (crates/vike-app-core/src/tool_views/greeks.rs). It takes the snapshot's Deribit positions and prices them off the chains the Options window already fetched, through position_greeks in crates/vike-app-core/src/options_greeks.rs, at r = 0 to match the chain's own enrichment: the underlying's spot and the option's IV are looked up in the loaded chain, vike_options::black_scholes_greeks prices one contract, and the result is scaled by the signed quantity — a long put carries −Δ, a short flips every sign. The table is Instrument, Qty, Avg Px, Delta, Gamma, Vega, Theta, with a NET row over the priced positions; Δ is per 1.0 of underlying, Γ per 1.0², Θ per calendar day, ν per one vol-point.

That NET row is a naive per-unit sum, meaningful within one underlying only — a 1-delta BTC option and a 1-delta ETH option are wildly different dollar exposures. The line beneath it, NET Δ · USD, is the dimensionally correct aggregate (PortfolioNetGreeks): each option leg's delta scaled by its underlying spot, per underlying and as a total. Perp and future legs join it as delta only, folded as the venue-reported coin_delta × spot — a Deribit perp's qty is USD notional, not coin, so the fold never re-derives exposure from it — and contribute nothing to Γ, ν or Θ.

An unpriced position is listed, never dropped

A position whose expiry is not among the eight fetched, whose strike falls outside the ±30 fetched window, or whose chain has no spot or IV yet shows — in every greek, stays in the table, and is named beneath the totals in net_usd.unpriced — as is a perp or future with no venue coin delta or no loaded spot. The net is flagged incomplete; nothing is folded as a guess.

On this page