voltrade /Trader guide Your exchanges ↗

Overview

Voltrade is a simulated exchange with real price-time priority order matching. It runs a universe of stocks, indexes, futures and options with live-updating fair values, automated market-making bots, and a full depth-of-book. Multiple human participants can connect simultaneously and compete on a live leaderboard.

You can trade manually through the UI or write Python scripts in the built-in dev environment to automate strategies — the same WebSocket API the bots use is exposed to you.

Realistic Market Dynamics. Some exchanges are created with a more true-to-life price engine. On those, instruments move together like a real market: sectors rotate as a group, gold and other safe havens rise when stocks fall, an index (e.g. TECHX) tracks the stocks it contains, and the volatility index (VOLAX) climbs when the market gets turbulent. Volatility comes in calm and stormy regimes, big surprise moves happen, and some names trend while others chop. This makes classic strategies — pairs/relative-value, index arbitrage, volatility trading, momentum and mean-reversion — genuinely tradeable. Whether an exchange uses it is chosen by its host at creation.

Your Account

One account works everywhere on Voltrade. Your saved code and your identity follow you into every exchange you join, on any device you sign in from.

RuleDetail
Display nameUp to 28 characters and unique across the platform — two traders with the same name would make a leaderboard ambiguous. You can change it later by clicking your name in the top bar; the new name appears everywhere except the final standings of competitions that have already closed, which stay frozen as they were.
Email verificationOnly needed to host an exchange. You can join and trade the moment you sign up; the reminder to confirm your email appears in the New exchange dialog, not before.
HostingYou can host one exchange at a time. Hosting runs a whole bot fleet and its market loops, so the limit keeps one account from using up the server. Deleting your exchange frees the slot straight away.
Invite linksOpening an invite while logged out shows the exchange on the login page, with Create account and join or Log in and join. Either one puts you straight on its trading screen.
JoiningNo limit. Being a participant in someone else's exchange does not count against your hosting slot.
Saved codeTwo independent slots — the dev panel on the trading screen and the full Python terminal — each saved with its own SAVE button. Nothing you save runs while you are offline; your code only executes in your own browser, while you are watching it.

Trying It as a Guest

When the platform has a featured public exchange, the homepage shows a Join as guest button (at the top of the page and in the header), and the login page has Trade as a guest. While that exchange is running, the homepage's markets section shows its live prices; clicking any tile joins as a guest too. One click puts you on the live trading screen under a generated name like Guest-7KQ4, with the same starting cash as everyone else. The order ticket, chart, book, positions and leaderboard all work; guests appear on leaderboards with a GUEST tag.

As a guestDetail
PythonLocked. The Python panel, and the full terminal at /pyterm, need a free account.
Other exchangesGuests can only trade the featured exchange. Hosting, joining others, renaming yourself and sending feedback all need an account.
Order rateCapped (20 orders per 10 seconds by default). Plenty for trading by hand; an account lifts it.
Coming backThe session lives in this browser. Return within a week and you pick up where you left off. A guest untouched for 7 days is deleted, along with its positions.

Keeping your trading

The top bar's SIGN UP FREE button (it shows your profit once you have some, e.g. SAVE $1,240.00 · SIGN UP) opens the signup form. Choose an email, a name and a password, and the guest becomes your account: same positions, cash, orders and history, now under your chosen name. You land back on the trading screen with Python unlocked. Names starting with “Guest” are reserved, so pick something else.

Logging in instead of signing up switches this browser to your existing account; the guest's positions are not merged into it.

The Dashboard

The dashboard is a quote board. Each exchange is a tile showing a short ticker made from its name, its market state, its price and a sparkline of recent movement. The price is the exchange's EX-Mini broad index, and the change is measured from where it stood when the market first opened. A tile reads Asleep when nobody has had the exchange open for a while. It is still there and still open, and prices come back the moment someone opens it.

When an exchange is featured, it gets the large panel at the top with a bigger chart and a Trade button. A ticker tape of its main indexes and futures scrolls across the very top of the page. Hover over the tape to pause it.

TabWhat it shows
Your exchangesExchanges you host or have joined, with your role (Host, Trader or Watching) and links to the leaderboard, the admin console (hosts only) and the trading screen. The last tile creates a new exchange.
Browse publicPublic exchanges anyone can see. Join appears on open ones; the rest need an invite link from their host.

Light and dark

The Dark / Light switch in the top bar changes the dashboard's colours. Dark is the default. Your choice is remembered in this browser and applies to the dashboard's forms and pop-ups too.

Sending Feedback

There is a Feedback button in the top bar of your dashboard, which opens a short form. Use it for anything: a bug, an idea, or something in the interface that did not make sense. It goes directly to the person who built Voltrade, and because you are signed in, they can reply to you by email.

Pick a category (General, Bug report, Feature idea, or Question) and write up to 2,000 characters. A counter shows how much room you have left. Press Send, or Ctrl+Enter; Esc closes the form without sending.

What gets sent with your message. Along with the text, the form includes your account name and email, the page you were on, the exchange you had open and its state, and your browser version. That context is what turns “the chart froze” into something that can actually be fixed. Nothing else is collected — your positions, orders and saved code are not included.

Interface Layout

TOP BAR — status · clock · identity · Total Equity · Total P&L · Open Positions
Instruments
left column
Chart + Book/Tape
centre — resizable
Market Panel
right column
⠿
Dev Environment · Python (Pyodide)
⠿

The three panels are separated by drag handles (the thin 4px borders). Drag left/right handles to resize columns; drag the horizontal handle to resize the Python terminal, and the handle between the code and its output to change how the terminal is split. The terminal folds down to a single bar with Close ▼ (or the ` key); closed, the bar still shows the last output line and a Run button. On screens under 800 px tall it starts closed. Your panel sizes, and whether Python is open, are remembered next time.

The right panel has nine tabs. The four you glance at constantly, Traders, Orders, History and Positions, sit in a row. The five occasional ones, Bio, Book, News, Calendar and Settings, are under More; while one of them is open the More button takes its name, so the row always shows where you are. Bio shows detail on the currently selected instrument — description, sector, annualized vol, and related ETFs/futures/options. Book mirrors the depth-of-book for the currently selected instrument. News and Calendar show fired and upcoming headlines respectively; Settings has session info and a disconnect/back-to-dashboard link.

The Dark / Light switch in the top bar changes the terminal's colours. It is the same setting as the dashboard's and the Python editor's, remembered in this browser.

On a Phone

Below roughly 820 pixels wide — every phone in portrait, and most in landscape — the terminal stops trying to show four panels at once and gives each one the whole screen. A tab bar along the bottom switches between them:

TabWhat it shows
MarketsThe instrument list and its type filters. Tapping an instrument selects it and jumps you to Trade, so you rarely come back here twice in a row.
TradeThe chart, the depth and tape stacked beneath it, and the order ticket. The ticket deliberately stays here rather than becoming a tab of its own — you want the price in front of you while you set a limit.
BookThe market panel: Participants, Unfilled, History, Positions, Bio, Book, News, Calendar and Settings. The tab strip scrolls sideways.
PythonThe dev environment, editor above and output below.

The drag handles are gone — there is no width to divide — and so are the clock and the leaderboard link. Log out from the dashboard; the trading screen has no Log out button. Your equity, P&L and open-position counters move onto their own row under the top bar and stay visible whichever tab you are on.

The Python terminal

The Python editor shows one column at a time, with tabs for Files, Code, Output and Reference. Running a script switches you to Output; if output arrives while you are elsewhere, a dot appears on the Output tab. Notebook cells show their results inline, so a notebook stays on Code.

The chart still works the way it does on desktop. Drag to pan, pinch to zoom, and long-press to bring up the crosshair. Double-tap resets the view to fit.

Everything else is unchanged — same account, same exchange, same positions. You can start an order on a laptop and finish it on a phone.

Top Bar — Equity & P&L Widgets

The right side of the top bar shows four live financial widgets for your own account, updated on every market snapshot.

WidgetFormulaDescription
Total EquityCash + mark value of all open positionsYour total account value. Moves with every price tick. This is the primary measure of your performance.
Total P&LRealised P&L + unrealised P&LNet profit/loss since session start. Shown in green when positive, red when negative. Includes both closed positions (realised) and open positions marked to last trade price.
Open Positions(Equity − Cash) − Unrealised P&LThe net dollar amount committed to your current open positions — i.e., cost basis of all holdings. Zero when you have no open positions.
CashUncommitted cash balanceThe portion of your account not tied up in open positions — what's available to spend on new orders.
Total Equity = Cash + Open Positions + Unrealised P&L. If you hold no open positions, equity equals your cash balance. As you trade, cash decreases and position value (mark-to-market) replaces it in the equity figure.

Market States

StateMeaning
PRE-OPENDefault on startup. Participants can connect but orders are rejected. Use this to let everyone join before trading begins.
OPENOrders are accepted and matched. Bots are active. Price history accumulates.
HALTEDTrading suspended. Existing orders remain on the book but no new matching occurs. Orders submitted during a halt are rejected.
CLOSEDSession over. All order submission is rejected.

Only the host can change market state, from that exchange's scoped Admin Console.

Leaving & Coming Back

You can leave an exchange open as long as you like — days, weeks. There is no need to close it when you finish for the day, and nothing is lost if you simply shut your laptop.

When everyone has been disconnected for a few minutes, the exchange goes to sleep in the background. It stays open: it keeps its place in Browse Public, its members, and its session timer. Opening it again brings it straight back.

What you'll find when you return

A session timer still runs to its end. If your exchange has a countdown set, it expires on schedule whether anyone is watching or not — a competition that ends at 3am closes at 3am, and its final leaderboard is frozen then.

One small thing to know: if you look at the leaderboard of an exchange nobody has open, positions are valued at the instrument's fair value rather than at live bid/offer. The numbers can differ very slightly from what you'd see with the market up.

Standings

Every exchange has a ranks page, linked from the dashboard and the terminal. The top three are on a podium, and everyone else is in a table below them. Your own row has a You tag. Only people appear; bots are never ranked.

PartWhat it shows
Rank byP&L, Return % (P&L divided by your starting cash) or Equity. Your browser remembers the choice.
ArrowsPlaces gained (▲) or lost (▼) in the last 5 minutes.
SparklineYour P&L over the last 10 minutes, sampled every 15 seconds while the market is open.
ClockThe time left in the session, if the host has set a timer.

When the session closes, the standings freeze as the final results, without arrows or sparklines.

Projector mode

Present shows the standings full screen in large type, for a classroom screen. It adds the session clock, the biggest climber and the last headline. When the host opens it, it can also show the address for joining. It updates by itself. Press Esc to leave.

Chart Navigation

The price chart shows the last-trade price history for the selected instrument since the session began. Click any instrument in the left column to switch. The chart only records actual traded prices — it does not fall back to a fair value line when there are no trades. An instrument with no fills yet will show a flat or empty chart until the first trade prints.

Line vs Candlesticks

The chart header has a LINE / CANDLES toggle. LINE (the default) draws a continuous last-price line with an area fill. CANDLES aggregates the price history into OHLC candlesticks — green when the candle closed at or above its open, red when it closed below — with wicks marking each candle's high and low.

In candle mode a timeframe selector appears with 30s, 1m (default), and 3m options, controlling how much time each candle covers. A live legend in the top-left corner of the chart always shows the latest bar's O / H / L / C (or price, in line mode) and time; hovering any bar updates the legend to that bar. Switching type or timeframe re-fits the view to the data.

Mouse & Trackpad Controls

The chart is powered by TradingView Lightweight Charts™, so it uses the familiar interaction model of a professional charting terminal:

ActionEffect
HoverMagnet crosshair that snaps to the nearest bar, with price and time pills on the axes and the top-left legend updating to the hovered bar.
Drag chart body left/rightPan through history (scroll the time axis).
Drag the price axis (right edge)Scale the price (Y) axis — drag to stretch or compress the visible price range.
Drag the time axis (bottom edge)Scale the time (X) axis — drag to stretch or compress how much history is shown.
Scroll wheel / pinch (trackpad)Zoom in and out around the cursor.
Double-clickReset the view to fit all available data.

Chart Controls Reference

The price scale on the right snaps to round-number increments and formats prices to each instrument's tick size. The dashed line and coloured tag on the right axis mark the current price; the top-left legend shows the latest (or hovered) bar's values.

The chart header always shows the last trade price, % change from the first data point, and the current bid / ask. On the admin terminal, fair value is also shown.

Charting by TradingView Lightweight Charts™ (Apache 2.0).

Time Axis

A time axis runs along the bottom edge of the chart. Labels show wall-clock time in HH:MM (for intervals ≥ 1 minute) or HH:MM:SS (for shorter intervals). The axis adapts its interval automatically based on the number of visible data points — zooming in shows finer resolution, zooming out coarser.

Faint vertical grid lines align with each time label to make it easy to correlate price movements with specific times.

Order Book & Tape

Central Depth Strip (below the chart)

Shows the top 10 price levels on each side. Bids (green) are displayed with the best bid at the top, nearest the spread. Asks (red) are displayed with the worst ask at the top and best ask nearest the spread. The spread row shows the current bid-ask spread.

The horizontal bars behind each row are proportional to quantity — a wide bar means a large resting order at that level relative to the deepest visible level.

The depth strip can be toggled on or off using the ▾ DEPTH button in the chart header. When hidden, the chart expands to fill the space — this also hides the tape, since they share the same collapsible panel. Press the button again (which now reads ▴ DEPTH) to restore both.

Tape (last 50 trades)

A real-time print of every fill across all participants. Green prices indicate a buyer-aggressed trade (aggressor was buying); red prices indicate a seller-aggressed trade. Each row shows the aggressor's participant ID in a small badge on the right — the participant who sent the crossing order.

To watch a participant's flow, click their row in the Participants tab to open the inspector popover, then use “Highlight this trader in the tape”. Their tape rows are highlighted in amber and the participant row gains a green left border. Toggle it off from the same button. This lets you track specific participants — for example, bots that appear to be trading ahead of announcements.

Book Tab (right panel)

The Book tab in the right panel shows the same depth-of-book in a persistent, full-height view. This is useful when you want to watch the order book continuously without the depth strip taking space below the chart, or when you need the book and the participants/positions tabs open side-by-side in a workflow. The Book tab updates in real time alongside the central strip.

Note: Both the depth strip and the Book tab always show data for the currently selected instrument. Your orders on other instruments are still active and visible in the Unfilled tab.

Placing Orders

Orders are placed from the order ticket below the depth strip. Rather than a single SEND button, the ticket has dedicated BUY and SELL buttons — each shows the exact price it will execute at, so you always see what you're about to do before you click.

ControlDescription
LIMIT / MARKETSegmented toggle at the top. In MARKET mode the price field is hidden and the buttons read BUY MKT / SELL MKT — they fill against the best available price immediately.
QtyNumber of lots (minimum 1). Use the − / + steppers or type a value.
PriceLIMIT only. Leave it blank to hit the touch — BUY fills at the best ask, SELL at the best bid (the fastest way to take liquidity). Type a value, or use the bid / mid / ask chips, to place a specific limit price.
est. notionalLive estimate of the order's value (quantity × price) shown just above the buttons.
BUY / SELLThe two action buttons. Each shows its live execution price and is disabled when the market is not OPEN or there is no quote to hit.

After clicking BUY or SELL, the acknowledgement appears in the Python terminal output (bottom panel) as BUY … → NEW or SELL … → FILLED etc. Any fills also print as FILL … lines in green. If an order is refused (not enough cash, a position limit, the market not open, and so on), or a cancel, flatten or the connection fails, a short notice also pops up at the bottom right of the screen (above the tab bar on a phone). You see it even with the Python panel hidden or locked. Click a notice to dismiss it.

Order Types & Matching

The exchange uses price-time priority: among all resting orders, the best price is matched first; ties are broken by order arrival time (FIFO). There is no hidden order type.

TypeBehaviour
LIMIT BUYMatches against any resting ask ≤ your price. Unfilled remainder rests on the bid side.
LIMIT SELLMatches against any resting bid ≥ your price. Unfilled remainder rests on the ask side.
MARKET BUYSweeps the ask side until filled or book exhausted. Any unfilled quantity is dropped (no resting).
MARKET SELLSweeps the bid side. Unfilled dropped.

A LIMIT order that is fully matched on arrival is acknowledged with status FILLED. A partial match gives PARTIAL and the remainder rests. An unmatched order is NEW and visible in the Unfilled tab.

MARKET orders are never resting — they sweep the book and any unfilled quantity is silently dropped. A MARKET order that only partially fills will not appear in the Unfilled tab and cannot be cancelled (there is nothing left to cancel). Check the Order History tab to see the fills.

Participants Tab

Shows all connected participants sorted by PnL — humans first, then bots. Your own row is highlighted in amber. Each row shows:

FieldMeaning
● / ○ / BOTFor human traders: green dot = currently connected WebSocket, empty circle = registered but disconnected. Bots always show a BOT tag instead, regardless of connection state.
PnLRealised + unrealised profit/loss since session start, marked to last trade price.
fills · vol · eqTotal fill count, total volume traded, and current total equity (cash + mark value of open positions).

Click any participant row to open an inspector popover showing their total PnL, equity, realised/unrealised split, fill count, volume, open positions, and most recent trades. Positions are shown for bots and for yourself; other human traders' positions stay private (you still see their public PnL, volume, and tape activity). The popover includes a “Highlight this trader in the tape” button — turn it on to amber-highlight their prints in the tape and give their participant row a green left border, so you can follow a specific bot or participant you suspect is trading on information. Close the popover by clicking the ✕ or the backdrop.

Unfilled Orders Tab

Shows all your currently resting LIMIT orders (status NEW or PARTIAL). MARKET orders never appear here — any unfilled quantity from a MARKET order is dropped immediately rather than resting on the book. For each resting order:

The badge next to the tab name shows the count of open orders.

Bulk Cancel Actions

An actions bar appears at the top of the Unfilled tab whenever you have open orders. It provides two bulk-cancel shortcuts:

ControlEffect
✕ CANCEL ALL ORDERSSends cancel requests for every open order across all instruments simultaneously. Useful for quickly exiting all resting quotes at end of session or on a risk event.
Instrument dropdown + ✕ CANCELSelect a specific instrument from the dropdown (populated with only the symbols where you have open orders) and press ✕ CANCEL to cancel all resting orders on that instrument only. Use this to pull quotes on one leg without disturbing other positions.
Cancels are best-effort. If an order fills in the instant between you pressing cancel and the server processing the request, the cancel will be rejected (the order was already FILLED). The button will re-enable and the order will disappear from the Unfilled list once the fill is acknowledged.

Order History Tab

A full log of all orders you have submitted this session, most recent first. Each entry shows side, type, price, quantity, status, and individual fill records.

StatusMeaning
NEWResting on the book, no fills yet.
PARTIALSome quantity filled, remainder still resting.
FILLEDFully matched.
CANCELLEDCancelled by you before full fill.

A ✕ RESET HISTORY button clears this client-side list (it doesn't affect your actual orders or the server's record — just what's displayed here).

Positions Tab

Your current net exposure across all instruments. Only instruments with a non-zero position or realised PnL appear.

FieldMeaning
+N / -NNet long (+) or short (-) position in lots.
avgVolume-weighted average cost of the position.
markLast trade price used to mark the position (falls back to fair value if no trades yet).
unrlzdUnrealised PnL = (mark − avg cost) × quantity.
rlzdRealised PnL from closed portions of the position.
totalunrlzd + rlzd.

Click any position row to switch the chart to that instrument.

Flatten Position

Each position row has a flatten button beneath it, labeled with your exact position (e.g. ⬡ FLATTEN LONG +N or ⬡ FLATTEN SHORT N). Pressing it submits an aggressive MARKET order in the opposite direction equal to your entire net position, reducing it to zero in a single action.

PositionAction taken
+N longSubmits a MARKET SELL for N lots — sweeps the bid side immediately.
−N shortSubmits a MARKET BUY for N lots — sweeps the ask side immediately.
Market orders have no price limit. In a thin book the fill price may be significantly worse than the current mid. The FLATTEN button is disabled when the market is not OPEN.

Book Tab

A dedicated right-panel tab that shows the full depth-of-book for the currently selected instrument in a persistent, full-height view — identical data to the central depth strip but always visible regardless of whether the depth strip is toggled on or off.

Switch to this tab when you want the order book always in view while you navigate other tabs, or when you prefer to hide the central depth strip (using the ▾ DEPTH toggle) to give the chart more vertical space.

News Tab

A feed of every headline that has already fired this session, most recent first, color-coded by status: UNCONFIRMED rumour, CONFIRMED/VALIDATED, or DENIED. The tab badge pulses when a new item arrives while you're on another tab.

Click any headline to open a detail panel with the full story, the source and what its credibility tier implies for how the move plays out (see Trading Around News), the complete list of affected instruments, and — for a still-unconfirmed rumour — a live countdown to when it resolves to CONFIRMED or DENIED.

Calendar Tab

Events an admin has pre-scheduled but that haven't fired yet, each showing a vague preview headline, its source, and a live countdown. The badge shows how many events are pending.

Click any upcoming event to open a detail panel with the source's credibility tier, the exact fire time, and a live-updating countdown. The real headline, its market impact, and how it resolves stay hidden until the event fires — that's deliberate, not a bug: publishing them early would hand every trader the same insider-style edge the informed/insider bots exist to exploit unevenly. When the countdown hits zero, switch to the News tab to see what actually happened.

Python Terminal (Dev Environment)

An in-browser Python 3 runtime (Pyodide) connected to the exchange via a pre-built exchange object. You can write and run arbitrary Python to automate orders, scan for opportunities, or run analysis.

Examples in this guide

Python examples here have Copy and Run in editor buttons. Run in editor opens the Python editor in a new tab with the code in a new notebook cell, on the exchange whose trading screen you opened last. If you haven't opened one in this browser yet, open a trading screen first; the code waits ten minutes for the next editor you open.

Press / anywhere on this page to search it. The Trader / Host switch at the top moves between this guide and the host handbook.

Inline Terminal (bottom panel)

A compact split editor/output pane built into the main trading terminal. Useful for quick one-shot scripts and inspections.

ControlAction
▶ RUNExecute the code currently in the editor. Disabled until Pyodide is loaded.
■ STOPInterrupt a running script. Works in any loop — a background line tracer checks the stop flag automatically every 500 Python lines, so no exchange call is required. The script halts within milliseconds. A [Stopped by user] message confirms the halt. Does not affect orders already submitted.
SAVESaves the editor's contents to your account, so it's still there next time you open the terminal — on any device you log in from. Saving is manual: nothing is stored until you press it, and the indicator beside the button reads unsaved when you have changes that aren't stored yet. This panel and the dedicated terminal below save separately, so each keeps its own script.
CLEARWipes the output pane. Does not stop running code — use ■ STOP for that.
EXAMPLESCycles through built-in starter snippets (strategy loop, snapshot, limit buy, market-maker, options scan).
Close ▼ / Open ▲Fold the terminal down to one bar (last output line and Run) to give the chart and order ticket the room, or open it again. The ` key does the same from anywhere outside a text box.
⎋ OPEN TERMINALOpens the dedicated Monaco terminal (see below) in a new tab, pre-populated with your participant ID.

The Python editor (/pyterm)

A full-screen Python environment built on Monaco (the editor inside VS Code). Open it from the trading screen's Python bar (Open editor). It trades as the same participant as the terminal you came from, so orders, positions and P&L are shared. It has two modes over one set of files, switched with Script / Notebook in the top bar.

Your files

The left column lists My files: scripts (py) and notebooks (nb) saved to your account, so they are there on any device you sign in from. + Script and + Notebook start a new one; hover a file for Rename and Delete, or double-click its name to rename it. Open files appear as tabs above the editor, and the tabs you had open come back next visit. A dot on a tab means unsaved changes; saving is manual (Ctrl+S or Save). You can keep up to 50 files. Examples, below your files, open a starter as a new file: a mean-reversion loop, a market maker, spread and tape watchers, an options scan, and a research notebook.

If you saved code in the old single-script terminal, it was moved into main.py the first time you opened the new editor.

Script mode

FeatureDetail
Run / Ctrl+EnterRuns the whole script from the top, with a fresh set of variables each time. The top bar shows Running · 12s while it goes.
Stop / Ctrl+.Stops the script at its next step, even inside while True. Orders already sent stay sent.
Code in another tabCode runs inside the browser tab that started it. If it is running in a different tab (say you opened the editor twice), the top bar says main.py running in another tab and Stop stops it there. The trading screen's Python bar does the same for code running in the editor.
Back to tradingWhile code is running, this switches to your trading tab (opening one if needed) and leaves the editor tab, and your script, running. Closing or reloading the tab while code runs asks first, because that stops it.
Outputprint() output with timestamps. Errors are red and point at the line that raised them; fills are green; system messages are blue.
ProblemsChecked as you type. It catches the two mistakes that silently cost you: calling exchange.buy, sell, cancel or history without await (the order is never sent), and time.sleep() (it freezes the page, Stop included). Each has a one-click fix: Add await / Use asyncio.sleep, also offered from the lightbulb in the editor. Problems never stop a script from running.
Script ordersEvery order and cancel your code sent this session, with its result (accepted, filled, rejected and why).
API referenceThe right column lists every exchange method, with a filter box. Click one to insert it at the cursor, await included where needed. Hover any exchange. call in your code for the same docs, and type exchange. for completions.
Status barConnection, market state, save state, problem count, cursor position and Python version.

Notebook mode

A notebook is a list of cells that share one Python session: a variable set in one cell is there in the next, so you can fetch history once and then look at it ten ways without fetching again.

FeatureDetail
Shift+EnterRuns the cell and moves to the next (adding one at the end). Ctrl+Enter runs it and stays. The number beside a cell, [3], is the order cells ran in; [*] means running.
Last line is shownIf a cell ends with an expression, its value is displayed, as in Jupyter. Lists of dicts, dicts of dicts and pandas DataFrames show as tables; anything else as text.
plot(…)Draws a line chart in the cell: plot(await exchange.history("ACME", n=200), fair=True). Takes a history() list, a list of numbers, or a dict of name → list for several lines; fair=True adds the instrument's fair value as a dashed line.
display(…)Shows a value the same way the last line does, from anywhere in a cell.
Run all / Restart PythonRun all goes top to bottom and stops at the first error. Restart Python clears every variable so you can start clean. Clear outputs keeps the code.
Text cells+ Text adds a note. Write # for a heading, **bold**, `code` and - lists; click a note to edit it.
OutlineThe left column lists the notebook's headings and cells; click one to jump to it.
VariablesThe right column's Variables tab lists everything your cells defined, with type and value, plus live prices for the symbols your notebook mentions.

Notebooks save only the code and notes, not the outputs: run them again to see fresh numbers.

Saved code never runs on its own. Every line of Python here executes in your browser, so a saved script does nothing while you're logged out or the tab is closed — it's stored text, not a running bot. To trade, open the terminal and press ▶ RUN.
Top-level await is supported everywhere. Order methods are async — use await exchange.buy(…) directly in your script without wrapping in an async def.

Output from print() appears in the output pane in real time — even inside while True loops. Red lines are errors (with full tracebacks). Green lines are fill notifications. Blue lines are system messages.

Available Python Packages

The terminal runs Pyodide 0.27.7 — a full CPython 3.12 compiled to WebAssembly. You have access to the entire Python standard library plus several pre-installed scientific packages. Additional packages can be installed at runtime using micropip.

Standard Library — always available

All built-in modules ship with the runtime. Commonly useful ones for trading:

ModuleUse
asyncioAsync loops and sleep — await asyncio.sleep(t) works natively and honours the STOP button. Use it to pace strategy loops and yield time to the event loop.
mathFloor/ceil/log/exp/sqrt, trig, math.inf, math.isnan.
statisticsMean, median, stdev, variance, linear_regression.
randomRandom numbers, shuffling, sampling — useful for simulation.
collectionsdeque, defaultdict, Counter, namedtuple.
itertoolsChain, combinations, groupby, islice, accumulate.
functoolsreduce, lru_cache, partial.
datetimeDate/time arithmetic and formatting.
jsonParse / dump JSON (snapshots are already parsed for you by the exchange object).
reRegular expressions — useful for filtering instrument symbols.
heapqPriority queues.
bisectSorted-list insertion and search.
decimalArbitrary-precision arithmetic for price calculations.
typingType hints.
dataclassesDataclass decorator.
enumEnum types.
copycopy.deepcopy for duplicating snapshot dicts.
pprintPretty-print nested dicts — handy for inspecting snapshots.
sys, osBasic system access. sys.stdout/stderr are redirected to the output pane; os.environ is available but the filesystem is in-memory only.

Bundled Packages — just import them

Scripts and notebooks share the same Python. Before each run the editor scans your code for import statements and loads any package Pyodide ships with, so there is no install step. The first import of a package downloads it (about 1–6 s); after that it is instant until you close the tab.

PackageUse
numpyArrays, linear algebra, vectorised returns and statistics.
pandasDataFrames, time series, groupby, rolling windows.
scipyStatistics (scipy.stats), optimisation, interpolation; norm.cdf for Black-Scholes.
statsmodelsOLS, ARIMA, cointegration and stationarity tests.
sklearn (scikit-learn)Regression, clustering, classifiers, PCA.
xgboost, lightgbmGradient-boosted trees.
cvxpyConvex optimisation, e.g. portfolio weights.
sympySymbolic maths, e.g. deriving Greeks.
networkxGraphs, e.g. correlations as a network.
polars, pyarrowFast columnar DataFrames.
matplotlib, bokeh, altairImport and compute fine, but their figures don't show in the editor. Use plot() for charts.
dateutil, pytz, regex, joblib, patsy, PIL, lxml, bs4Utilities that come along with the above.

The full list is on the Pyodide 0.27.7 package page; anything there imports the same way.

import pandas as pd

snap = exchange.snapshot()
rows = [{"sym": s, "kind": i["kind"], "fair": i.get("fair_value"), "last": i.get("last_price")}
        for s, i in snap["instruments"].items()]
df = pd.DataFrame(rows).set_index("sym")
print(df.head(10).to_string())

Other Packages from PyPI — micropip

A package Pyodide doesn't ship can be installed from PyPI if it is pure Python and its dependencies are available. Install it at the top of the script, or in the first notebook cell; it stays installed until you close the tab.

import micropip
await micropip.install("seaborn")
import seaborn as sns

Checked and working this way: seaborn, plotly, tabulate. Packages that need compiled code Pyodide doesn't have fail to install: yfinance, arch, ta, pandas_ta, TA-Lib, torch, numba, QuantLib.

Not Available

The following do not work inside the browser sandbox:
  • Network access from Python — socket, websockets, aiohttp, requests, httpx. requests imports but can't fetch market data from outside sites. All exchange I/O goes through the exchange object.
  • Persistent file I/O — open() works but writes to an in-memory virtual filesystem that is wiped when you close the tab. Nothing is saved to disk.
  • Threads — threading.Thread is not supported in Pyodide. Use asyncio instead.
  • Multiprocessing — multiprocessing is not available.

Exchange API Reference

The exchange object is pre-created and available immediately after PYTHON READY appears.

Read-only Methods

MethodReturns
exchange.snapshot()Full market snapshot dict: state, instruments, participants, my_id.
exchange.state()"OPEN", "PRE_OPEN", "HALTED", or "CLOSED".
exchange.instruments()List of symbol strings.
exchange.price(sym)Dict with keys last, fair, bid, ask. Any may be None if no trades/quotes yet.
exchange.participants()List of participant dicts with participant_id, display_name, total_pnl, equity, is_bot, etc.
exchange.me()Your own participant dict, or None.
exchange.positions()Dict mapping symbol → {quantity, avg_cost, realized_pnl} for your open positions.
exchange.my_idProperty (no parens) — your own participant ID string.

Order Methods (async)

MethodDescription
await exchange.buy(sym, qty, price=None, order_type=None)Place a buy order. Defaults to LIMIT if price is given, else MARKET — pass order_type explicitly to override that default. Returns an ack dict.
await exchange.sell(sym, qty, price=None, order_type=None)Place a sell order. Same signature.
await exchange.cancel(sym, order_id)Cancel an open order. Returns {"accepted": True/False, …}.
exchange.log(message)Print a highlighted info-colored message to the terminal output, distinct from plain print().

Ack Dict Structure

Every order call returns this dict once the exchange has handled the order.

FieldMeaning
acceptedFalse if the order was rejected.
order_idUse it to cancel later.
order_status"NEW", "PARTIAL", "FILLED" or "REJECTED".
tradesList of fills if it matched straight away.
reasonWhy it was rejected, otherwise None.
# Bid one tick under the best ask
p = exchange.price("ACME")
bid = round(p["ask"] - 0.05, 2)
ack = await exchange.buy("ACME", 10, price=bid)
print(ack["order_status"], ack["order_id"])

Run in editor opens the Python editor in a new tab with this code in a notebook cell, on the exchange you last traded.

Exchange API v2 — Extended Methods

These methods go beyond basic snapshot access. They give you price history, live news, options Greeks, order book depth, and the time-remaining widget — everything you need for quantitative strategies.

This API is identical whether you're in the inline terminal or the dedicated /pyterm page — both share the same exchange implementation.

News & Events

MethodReturns
exchange.news()List of current news items, newest first. Each dict includes id, headline, body, source_label, status ("UNCONFIRMED" / "CONFIRMED" / "DENIED"), affected (list of symbols), created_at, resolved.
exchange.since(event_type=None, after_seq=0)List of exchange events since after_seq. Event types: "news", "scenario", "timer" — omit event_type (or pass None) for all types. Each event has seq, type, payload, ts (Unix timestamp — note the field is ts, not timestamp). Use this for efficient polling — save the last seq and pass it next call.

Flow & Tape Analysis

MethodReturns
exchange.tape(n=100)Most recent n fills across all instruments, newest first. Each entry: {"instrument", "price", "quantity", "aggressor", "buyer_id", "seller_id", "timestamp", "trade_id"} — note the field is instrument/quantity/aggressor, not sym/qty/side.
exchange.flow_by_participant(n=500)Dict mapping participant ID → {"buy_qty", "sell_qty", "trade_count", "instruments"} aggregated over their last n tape entries — not a list of individual trades.
exchange.suspect_flow(n=500, min_trades=3)List of participants with concentrated directional flow, sorted most-one-sided first. Each entry has {"participant_id", "imbalance", "dominant_side", "trade_count", "top_instruments"} — imbalance is 0 (balanced) to 1 (fully one-sided); min_trades filters out low-volume noise.

Price History & Market Data

MethodReturns
await exchange.history(sym, n=100)Async — needs await. Last n price points for sym as a list of {"time": float, "price": float} dicts, oldest first. time is a Unix timestamp. Returns an empty list if no trade history yet.
exchange.book(sym)Full order book for sym as {"bids": [[price, qty], …], "asks": [[price, qty], …]}. Bids in descending order, asks in ascending order — not capped to 10 levels (use depth() below if you want that).
exchange.depth(sym, levels=10)Same shape as book(), sliced to the top levels per side.
exchange.fair_value(sym)Server-computed fair value (float) for sym. This is the "theoretical" mid that market-maker bots quote around. Returns None if not available.

Options Analytics

MethodReturns
exchange.greeks(sym)Dict with {"delta", "gamma", "theta", "vega", "iv", "theo", "S", "K", "T_days"} if sym is an option (S/K are the underlying price and strike, T_days is days to expiry); empty dict {} otherwise. Greeks computed via Black-Scholes in real time by solving for IV from the option's live fair value. See the Greeks reference for field definitions.

Session Timer

MethodReturns
exchange.timer()Dict {"end": float, "remaining": float, "auto_close": bool}, or None if no timer is set — check for None before indexing into it. remaining is seconds until auto-close.
# Poll for new news events efficiently
last_seq = 0
while True:
    events = exchange.since("news", after_seq=last_seq)
    for ev in events:
        last_seq = max(last_seq, ev["seq"])
        n = ev["payload"]
        exchange.log(f"NEWS [{n['status']}]: {n['headline']}")
    await asyncio.sleep(2)

Code Examples

Inspect the Market

state = exchange.snapshot()["state"]
print("market state:", state)
print("instruments:", len(exchange.instruments()))
print("my positions:", exchange.positions())

Place a Limit Order

p = exchange.price("ACME")
print("ACME bid/ask:", p["bid"], "/", p["ask"])

# buy 10 lots just below the best ask
if p["ask"]:
    ack = await exchange.buy("ACME", 10, price=round(p["ask"] - 0.05, 2))
    print("order_id:", ack["order_id"], "status:", ack["order_status"])

Market Order (Immediate Fill)

# sell 5 lots at market — no price needed
ack = await exchange.sell("ORBT", 5)
print(ack)

Cancel an Open Order

ack = await exchange.buy("DRFT", 100, price=210.00)
order_id = ack["order_id"]
print("resting order_id:", order_id)

# ... some time later ...
result = await exchange.cancel("DRFT", order_id)
print("cancel accepted:", result["accepted"])

Simple Market-Maker

# Quote ±5 cents around fair value on DRFT
sym = "DRFT"
p = exchange.price(sym)
if p["fair"]:
    fair = p["fair"]
    bid_ack = await exchange.buy(sym,  5, price=round(fair - 0.05, 2))
    ask_ack = await exchange.sell(sym, 5, price=round(fair + 0.05, 2))
    print("bid:", bid_ack["order_id"], "  ask:", ask_ack["order_id"])

Scan for Mispriced Options

snap = exchange.snapshot()
options = {s: i for s, i in snap["instruments"].items() if i["kind"] == "OPTION"}
print(f"found {len(options)} options")
for sym, info in list(options.items())[:8]:
    last = info.get("last") or info.get("last_price")
    fair = info.get("fair") or info.get("fair_value")
    if last and fair:
        edge_pct = (last - fair) / fair * 100
        print(f"  {sym:>22s}  last={last:>7.2f}  fair={fair:>7.2f}  edge={edge_pct:+.1f}%")

News-Driven Event Strategy

# React to incoming news: go long affected stocks on UNCONFIRMED news
# (betting on confirmation), then close on resolution
seen_news = set()
while True:
    for item in exchange.news():
        nid = item["id"]
        if nid in seen_news:
            continue
        if item["status"] == "UNCONFIRMED" and item["affected"]:
            sym = item["affected"][0]
            p = exchange.price(sym)
            if p["ask"]:
                ack = await exchange.buy(sym, 5, price=p["ask"])
                exchange.log(f"Positioned on news {nid}: {ack['order_status']}")
        elif item["status"] in ("CONFIRMED", "DENIED"):
            seen_news.add(nid)  # stop tracking resolved items
    await asyncio.sleep(3)

Greeks Scanner

# Find options with high gamma (largest convexity per dollar)
syms = [s for s in exchange.instruments() if "-C" in s or "-P" in s]
rows = []
for sym in syms:
    g = exchange.greeks(sym)
    if g:
        rows.append((g["gamma"], sym, g["delta"], g["iv"]))
rows.sort(reverse=True)
for gamma, sym, delta, iv in rows[:10]:
    print(f"{sym:>20s}  Δ={delta:+.3f}  Γ={gamma:.5f}  IV={iv:.1%}")

Price History Momentum

# 5-tick vs 20-tick momentum signal on DRFT
hist = await exchange.history("DRFT", n=25)
if len(hist) >= 25:
    prices = [h["price"] for h in hist]
    fast_ret = (prices[-1] - prices[-5]) / prices[-5]   # 5-tick return
    slow_ret = (prices[-1] - prices[-20]) / prices[-20]  # 20-tick return
    exchange.log(f"fast={fast_ret:+.3%}  slow={slow_ret:+.3%}")
    p = exchange.price("DRFT")
    if fast_ret > 0.001 and slow_ret > 0.001 and p["ask"]:
        ack = await exchange.buy("DRFT", 8, price=p["ask"])
        exchange.log(f"Momentum long: {ack['order_status']}")
    elif fast_ret < -0.001 and slow_ret < -0.001 and p["bid"]:
        ack = await exchange.sell("DRFT", 8, price=p["bid"])
        exchange.log(f"Momentum short: {ack['order_status']}")

Order Book Imbalance Signal

# Use bid/ask queue imbalance as a short-term direction signal
sym = "ACME"
book = exchange.book(sym)
if book["bids"] and book["asks"]:
    bid_qty = sum(q for _, q in book["bids"][:3])  # top 3 bid levels
    ask_qty = sum(q for _, q in book["asks"][:3])  # top 3 ask levels
    total = bid_qty + ask_qty
    imbalance = (bid_qty - ask_qty) / total if total else 0
    exchange.log(f"Book imbalance: {imbalance:+.2f}  (bids={bid_qty}  asks={ask_qty})")
    p = exchange.price(sym)
    if imbalance > 0.40 and p["ask"]:        # strong buy pressure
        ack = await exchange.buy(sym, 5, price=p["ask"])
        exchange.log(f"Imbalance buy: {ack['order_status']}")
    elif imbalance < -0.40 and p["bid"]:     # strong sell pressure
        ack = await exchange.sell(sym, 5, price=p["bid"])
        exchange.log(f"Imbalance sell: {ack['order_status']}")

ETF vs Components Arb

# TECH-ETF tracks DRFT, QNTM, FNTM, NXUS at 25% each
components = ["DRFT", "QNTM", "FNTM", "NXUS"]
weights = [0.25, 0.25, 0.25, 0.25]
etf_sym = "TECH-ETF"
nav = sum(w * (exchange.price(s)["last"] or exchange.price(s)["fair"] or 0)
          for s, w in zip(components, weights))
etf_p = exchange.price(etf_sym)
etf_last = etf_p["last"] or etf_p["fair"]
if nav and etf_last:
    premium = (etf_last - nav) / nav
    exchange.log(f"ETF premium to NAV: {premium:+.3%}")
    # If ETF trades too rich: sell ETF, buy components
    if premium > 0.003 and etf_p["bid"]:
        ack = await exchange.sell(etf_sym, 10, price=etf_p["bid"])
        exchange.log(f"Sold ETF: {ack['order_status']}")

Tape Flow Analysis

# Who has been trading and in what direction?
flow = exchange.flow_by_participant(n=300)
for pid, f in flow.items():
    if f["trade_count"] == 0:
        continue
    total = f["buy_qty"] + f["sell_qty"]
    exchange.log(f"{pid:>12s}  buy_qty={f['buy_qty']}  sell_qty={f['sell_qty']}  "
                 f"trades={f['trade_count']}  instruments={list(f['instruments'].keys())}")

Suspect Flow Scanner

# Find participants whose trades consistently precede price moves
suspects = exchange.suspect_flow(n=500, min_trades=3)   # already sorted, most one-sided first
for s in suspects[:5]:
    exchange.log(f"{s['participant_id']:>12s}  imbalance={s['imbalance']:.0%}  "
                 f"side={s['dominant_side']}  trades={s['trade_count']}  top={s['top_instruments']}")

Session Timer Wind-Down

# Flatten all positions when 30 seconds remain
while True:
    t = exchange.timer()
    remaining = t["remaining"] if t else None
    if remaining is not None and remaining < 30:
        exchange.log(f"⚡ {remaining:.0f}s left — flattening all positions")
        for sym, pos in exchange.positions().items():
            qty = pos["quantity"]
            if qty > 0:
                await exchange.sell(sym, qty)
            elif qty < 0:
                await exchange.buy(sym, -qty)
        break
    await asyncio.sleep(5)

Options & Greeks

An option is the right (but not the obligation) to buy (call) or sell (put) an underlying at a fixed strike price before expiry. Options let you express leveraged directional views, hedge existing positions, and trade volatility itself.

Delta (Δ)

Delta is the rate of change of option price with respect to the underlying. A call with Δ = 0.6 gains approximately $0.60 when the underlying rises $1.

SituationDelta rangeIntuition
Deep in-the-money call→ 1.0Behaves almost like owning the stock
At-the-money call≈ 0.550% chance of expiring ITM
Deep out-of-the-money call→ 0Very unlikely to expire ITM
Any put-1.0 to 0Puts have negative delta — profit when underlying falls

The delta of a portfolio tells you your effective exposure to the underlying. A delta of +500 means you're as exposed as owning 500 units of the stock. Delta hedging means keeping your portfolio delta near zero by trading the underlying.

Gamma (Γ)

Gamma is the rate of change of delta — how fast delta shifts as the underlying moves. High gamma means delta changes quickly; your option becomes more (or less) sensitive very rapidly.

ATM options near expiry have the highest gamma. A gamma of 0.04 means delta increases by 0.04 for every $1 move in the underlying. This convexity is valuable — it means your upside accelerates faster than your downside.

Theta (Θ)

Theta is time decay — the amount an option loses in value per day as expiry approaches, all else equal. Options are wasting assets: a call worth $5 today might be worth $4.80 tomorrow just from time passing.

Theta is negative for option buyers (you lose value daily) and positive for option sellers (you collect it). Near expiry, theta accelerates dramatically — ATM options decay fastest in the final days.

Vega (ν)

Vega measures sensitivity to implied volatility — how much the option price changes per 1% move in IV. A vega of 0.30 means the option gains $0.30 if IV rises 1 percentage point.

Long options are long vega — you profit when volatility rises (e.g., before earnings). Short options are short vega — you profit from volatility declining (IV crush). News events drive sharp vega moves: IV spikes when news is rumoured, then collapses after resolution (the "IV crush").

The exchange.greeks(sym) Method

Returns a dict for any option symbol. Returns {} for non-options.

KeyTypeDefinition
deltafloatdOption/dUnderlying. Range: [−1, 0] puts, [0, 1] calls.
gammafloatdDelta/dUnderlying. Always positive. Highest at ATM.
thetafloatOption price change per calendar day. Negative for long options.
vegafloatPrice change per 1% increase in implied vol.
ivfloatImplied volatility as a decimal (e.g., 0.30 = 30%).
theofloatBlack-Scholes theoretical fair value.
SfloatUnderlying's current price, as used in the calculation.
KfloatStrike price.
T_daysfloatDays remaining to expiry.
# Print a Greek dashboard for all DRFT options
for sym in exchange.instruments():
    if not sym.startswith("DRFT-"):
        continue
    g = exchange.greeks(sym)
    if not g:
        continue
    p = exchange.price(sym)
    last = p["last"] or p["fair"] or 0
    edge = last - g["theo"]
    print(f"{sym:>15s}  Δ={g['delta']:+.3f}  Γ={g['gamma']:.5f}  Θ={g['theta']:+.3f}  ν={g['vega']:.3f}  IV={g['iv']:.1%}  edge={edge:+.2f}")

Common Options Strategies

Long Call — Leveraged Upside

Buy a call to profit when you expect a large upward move. Maximum loss is the premium paid; upside is unlimited. Best used when you have a directional view but want limited downside.

# Buy the DRFT 230-strike call (OTM call, costs less than ATM)
p = exchange.price("DRFT-230-C")
if p["ask"]:
    ack = await exchange.buy("DRFT-230-C", 5, price=p["ask"])
    exchange.log(f"Long call entry: {ack['order_status']}")

Protective Put — Downside Hedge

Own stock + buy a put = capped downside. The put acts like insurance: if the stock falls below the strike, the put gains value, offsetting your loss on the stock position.

# Already long DRFT. Buy the 210-put to hedge
hedge_ack = await exchange.buy("DRFT-210-P", 5, price=exchange.price("DRFT-210-P")["ask"])
exchange.log(f"Hedge in place: {hedge_ack['order_status']}")

Straddle — Pure Volatility Play

Buy an ATM call and ATM put with the same strike and expiry. Profits if the underlying moves sharply in either direction; loses if it stays flat. Use before expected news when you don't know the direction.

# ATM straddle on QNTM (ATM ≈ 330)
call_ack = await exchange.buy("QNTM-330-C", 3, price=exchange.price("QNTM-330-C")["ask"])
put_ack  = await exchange.buy("QNTM-330-P", 3, price=exchange.price("QNTM-330-P")["ask"])
exchange.log(f"Straddle: call={call_ack['order_status']} put={put_ack['order_status']}")

Market Microstructure

Understanding how the market actually works at a mechanical level makes you a better trader — and helps you build smarter strategies.

Bid-Ask Spread & Depth

The bid-ask spread is the gap between the highest buyer's price (bid) and the lowest seller's price (ask). The spread is the market maker's profit margin — they quote both sides and capture the spread on each fill. In liquid instruments (e.g., ACME) spreads are tight (a few cents). In thin instruments (e.g., deep OTM options) spreads can be several percent.

Depth refers to the quantity available at each price level. A deep book has large quantities resting at many price levels — big orders fill without moving the price much. A thin book has small quantities; a large market order can sweep multiple levels and move the price significantly.

ConceptWhat it means for you
Tight spread (1–5 bps)Cheap to trade — cross the spread and you've barely paid. Good for fast in/out.
Wide spread (50+ bps)Expensive to cross. Consider using limit orders inside the spread instead.
Deep book (large qty at bid/ask)Your order won't move the price much. Safe to use market orders.
Thin book (small qty)Large orders will "walk the book" and fill at progressively worse prices.

You can read the live depth with exchange.book(sym) — check how much quantity sits at each level before sizing a market order.

How Bots Behave

63 automated bots operate simultaneously per exchange. Understanding their roles helps you predict when and why prices move.

Bot typeWhat it doesOpportunity for you
Market Makers (MM)Quote symmetric bids and asks around fair value. Provide liquidity. Inventory skew: buy more when short, sell more when long.Take liquidity when fair value diverges from last price. The MM will eventually adjust fair value and the price will follow.
Momentum (MOM)Buy on upticks, sell on downticks. Amplify trends. After confirmed news, they pile in — accelerating the move.Front-run or ride momentum. After a scenario shock, momentum bots drive the continuation — you can join early.
Mean Reversion (MR)Fade large deviations from fair value. Do not fade confirmed news moves.On confirmed news, MR bots stand aside — the trend is cleaner. On rumours, MR bots fade — the price may drift back.
Options Arb (OPT)Buy cheap / sell rich options relative to Black-Scholes fair value. Keep option prices near theoretical.If you can spot a mispriced option before the arb bots do, you can exploit it briefly.
ETF Arb (ETF)Keep ETF price near NAV. If ETF trades at a premium: sell ETF, buy components. If discount: buy ETF, sell components.ETF premiums/discounts rarely persist more than a few ticks. Trade the basket vs ETF spread before the arb bots close it.
An empty exchange runs quieter. When nobody is connected, all but 10 bots stand down; they come back as soon as someone opens the exchange. Prices keep moving while you are away — just more slowly — so a market you return to after an hour will have drifted, not frozen. Give it a few seconds after you connect for the full fleet to spin back up.

During news events, market makers widen their spreads (they become uncertain about fair value). This creates a window where the spread is wider and the book is thinner — skilled traders can provide liquidity at prices that would normally be impossible to fill.

Trading Around News

News events follow a two-phase model: an initial shock (partial move on announcement) followed by resolution drift (remaining move on confirmation, or full reversal on denial).

Source labelCredibilityTypical upfront moveStrategy
Government Statement / Corporate FilingVery high (≥ 95%)90%+ of the full shockAct fast — most of the move happens immediately. Buy/sell as the announcement lands.
ReutersHigh (85–95%)~80% upfrontPosition with the move. Small remaining drift on confirmation is tradeable.
Analyst NoteMedium (60–80%)~60% upfrontConfirmation provides another 20–40% continuation. Denial reverses most of the move.
Industry Whisper / Anonymous TipLow (20–50%)20–35% upfrontHigh-risk play. If confirmed: large additional move. If denied: full reversal. Watch the source carefully.

The resolution window: After a news event fires, there is a delay before the outcome (Confirmed / Denied) is determined. During this window, the price hovers near the partially-moved level. This uncertainty window is where the most interesting trading happens — you must judge whether the headline is credible.

Post-resolution drift: Even after resolution, the full shock may not be immediately priced in. There is a drift period where fair value gradually moves toward the final level. Momentum strategies can exploit this drift by riding the continuation.

Stocks

26 simulated equities (ACME, BRYN, CDLR … ZPHR). Each follows a correlated GBM — a shared market factor causes most stocks to move together with their own idiosyncratic noise layered on top. Volatility ranges from ~40% annualised (low-vol, e.g. HRZN) to 100% (high-vol names like UMBR, GLDR, QNTM). All stocks have a 0.01 tick size.

Indexes

5 synthetic indexes: EXMINI (~4500), TECHX (~18750), FINX (~1380), ENRGX (~780), and VOLAX (~16, the volatility index). Indexes use the same GBM but with lower idiosyncratic vol and coarser tick sizes. VOLAX has very high vol-of-vol.

Futures

Futures are listed on 15 underlyings: EXMINI, TECHX, ENRGX, ACME, DRFT, QNTM, HRZN, JNTR, and the 7 commodities (GLDX, SLVX, OILX, NATG, COPX, WHTX, PLTX). Three expiries exist per underlying:

SymbolExpiryExample
{UNDERLYING}-FFront month — 30 daysDRFT-F
{UNDERLYING}-F2Second month — 60 daysDRFT-F2
{UNDERLYING}-F3Third month — 90 daysDRFT-F3

Fair value is continuously updated as spot × er × T where r = 4% and T is the expiry in years. Longer-dated futures trade at a slight carry premium over the front month, reflecting the cost-of-carry term structure. The calendar spread (e.g. F3 − F2) can be traded by legging into both.

No expiry events occur. Futures are perpetually priced with fixed T. There is no settlement or roll. F3 always trades richer than F2 which trades richer than F — and because the premium is exponential in T, the F3−F2 gap is actually slightly wider than the F2−F gap, not narrower.

ETFs

Seven sector basket ETFs track weighted portfolios of underlying stocks or commodities. Named with a -ETF suffix:

SymbolNameComponents
TECH-ETFTechnology Sector ETFDRFT, QNTM, FNTM, NXUS (25% each)
ENRG-ETFEnergy & Resources ETFCDLR, UMBR, GLDR, YNDR (25% each)
INDU-ETFIndustrials ETFACME, JNTR, MRVL, SLPH (25% each)
HLTH-ETFHealthcare & Biotech ETFHRZN 40%, BRYN 25%, SLPH 25%, KLPS 10%
DFNS-ETFDefense & Aerospace ETFXNTH 40%, JNTR 30%, IGNT 30%
CONS-ETFConsumer & Retail ETFWLLW 30%, ELYS 25%, RVNG 25%, YNDR 20%
CMDT-ETFIndustrial Commodities ETFOILX 40%, NATG 25%, COPX 20%, WHTX 15%

Each ETF's fair value tracks the weighted average of its components' fair values in real time. ETFs can trade at a small premium or discount to NAV (Net Asset Value) — bots will arb the gap, but you can trade that spread too. An ETF position gives broad sector exposure without managing individual stock legs.

ETF prices move less than individual components — diversification reduces volatility. A position in TECH-ETF will have roughly 75% of the vol of owning a single tech stock.

Broad-market ETFs: SNP10 & SNP15

SNP10 and SNP15 track the 10 and 15 highest-priced stocks in the universe (a stand-in for the "biggest" names), cap-weighted rather than equal-weighted — each component's weight is proportional to its own fair value, so pricier names dominate the index more, just like a real market-cap-weighted index (e.g. the S&P 500).

SymbolComponentsWeighting
SNP10The 10 highest-priced stocksCap-weighted (proportional to price)
SNP15The 15 highest-priced stocks — a strict superset of SNP10's 10Cap-weighted (proportional to price)

Because SNP10 is nested inside SNP15 and both are weighted off the same underlying prices, a stock's weight ranking is always consistent between the two: whichever SNP10 name carries more weight also carries more weight in SNP15 (just diluted, since SNP15 splits weight across 5 more names).

Options

Options are listed on DRFT, QNTM, EXMINI, and TECHX — 10 per underlying (5 strikes × call/put), named {UNDERLYING}-{STRIKE}-C or -P, all with 30-day expiry (40 contracts total).

Strikes span ATM ± 2 steps, where the step size is calibrated to the underlying price (e.g. $10 for mid-price stocks, $50 for indexes). Fair values are continuously updated via Black-Scholes (r = 4%, T = 30/365 years). Tick size is 0.05; market-maker spread is wider than equities (reflecting real-world option spreads).

A separate 0DTE family also exists — same-day-expiry options on DRFT, QNTM, EXMINI, TECHX, and HRZN, named {UNDERLYING}W-{STRIKE}-C/-P (50 more contracts). These carry a steeper skew and smile than the 30-day family, and their implied vol rises further as expiry approaches within the session — a real theta/gamma-acceleration effect.

Options incorporate an equity skew: lower-strike puts are priced with higher implied volatility than ATM, and higher-strike calls are cheaper. This reflects the asymmetric demand for downside protection seen in real markets.

Strike positionVol adjustment
Deep OTM put (K ≪ S)Higher vol — most expensive relative to ATM
ATMBase vol
Deep OTM call (K ≫ S)Lower vol — cheapest relative to ATM
No expiry events occur. Options are perpetually priced as 30-day instruments. There is no settlement.

Voltrade simulated exchange · All data is fictional · No real money involved