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.
The trade panel
Order entry and sizing from a window — the equity hero, the per-venue accounts table, the trade card with its size modes, leverage and TP/SL bracket, and the one dispatch chokepoint every order crosses.
Studio
The in-app IDE for a strategy over recorded history — a Rhai editor, a store-backed slice picker, worker-thread Run, Sweep and Walk-Forward, and a tabbed results pane, in one window of the desktop app.