VOLTRADE User Guide
← Trading Terminal

VOLTRADE

User Guide & Reference

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.

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. The terminal can be hidden entirely with the ▼ HIDE button.

The right panel contains five tabs: Participants, Unfilled, History, Positions, and Book. The Book tab mirrors the depth-of-book for the currently selected instrument and stays visible while you use other parts of the UI.

Top Bar — Equity & P&L Widgets

The right side of the top bar shows three 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.
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 admin can change market state from the Admin Console (/admin).

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.

Mouse & Trackpad Controls

ActionEffect
HoverCrosshair with price tooltip and cursor price on Y axis.
Click & drag left/rightPan through history (X axis). Automatically locks to a fixed number of visible points so the view stays anchored while you drag.
Click & drag up/downPan the price range (Y axis). Drag up to show higher prices, drag down to show lower prices.
Double-clickReset to live view (latest data, default zoom, Y axis centred on mid-price).
Vertical scroll / pinch (trackpad)Zoom X axis — scroll up / pinch out = zoom in (fewer points visible); scroll down / pinch in = zoom out (more points).
Horizontal swipe (trackpad)Pan X axis — swipe right = back in time, swipe left = forward toward live.
Shift + scrollZoom Y axis — tighten or expand the visible price range.

Chart Controls Reference

A small overlay appears in the top-right corner of the chart whenever you are not in the default live view. It shows the number of visible points and a reminder of the controls. Double-click anywhere on the chart to snap back to live (this also resets any Y-axis pan).

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.

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. The tape remains unaffected by the toggle. Press the button again (which now reads ▸ DEPTH) to restore the strip.

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.

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

Quick Fill Buttons

The two large buttons above the order form — BUY @ ASK and SELL @ BID — submit an immediate limit order at the current best ask or best bid respectively, using the quantity from the QTY field. This is the fastest way to take liquidity. The buttons are disabled when the market is not OPEN or there is no quote.

Manual Order Entry

FieldDescription
SideBUY or SELL. The SEND button turns green for buys and red for sells.
TypeLIMIT — rests at your specified price if not immediately matchable. MARKET — fills against the best available price immediately; no price field needed.
QtyNumber of lots (minimum 1). Also used by the quick-fill buttons.
PriceRequired for LIMIT orders. Leave blank for MARKET orders.

After hitting SEND, the acknowledgement appears in the Python terminal output (bottom panel) as ORDER … → NEW or ORDER … → FILLED etc. Any fills also print as FILL … lines in green.

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
● / ○Green dot = currently connected WebSocket. Empty circle = registered but disconnected.
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).

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.

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. 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.

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.

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.
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).
▼ HIDE / ▲ SHOWCollapse or restore the entire terminal row to reclaim vertical space.
⎋ OPEN TERMINALOpens the dedicated Monaco terminal (see below) in a new tab, pre-populated with your participant ID.

Dedicated Monaco Terminal (/pyterm)

A full-screen Python environment built on Monaco Editor (the same engine as VS Code). Open it via the ⎋ OPEN TERMINAL link in the inline terminal header, or navigate directly to /pyterm?id=YOUR_ID. It connects to the exchange under the same participant ID so your orders, positions, and PnL are shared with the main terminal.

FeatureDetail
Syntax highlightingFull Python syntax colouring with the VOLTRADE dark theme.
AutocompleteType exchange. and press Ctrl+Space to see all available methods with inline documentation.
Ctrl+EnterRun the script (same as clicking ▶ RUN).
Ctrl+.Stop the running script (same as clicking ■ STOP).
Resizable panesDrag the vertical handle between the editor and output to change the split.
EXAMPLESSame five starter snippets as the inline terminal.
Top-level await is supported in both terminals. 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.

Pre-installed Packages — import directly

These ship inside the Pyodide runtime and can be imported with no installation step:

PackageVersionUse
numpy1.26.xFast numerical arrays, linear algebra, FFT, statistics. Essential for vectorised price analysis.
micropipbundledThe in-browser package installer — see below.
import numpy as np

prices = [exchange.price(s)["last"] for s in exchange.instruments() if exchange.price(s)["last"]]
arr = np.array(prices)
print(f"mean={arr.mean():.2f}  std={arr.std():.2f}  min={arr.min():.2f}  max={arr.max():.2f}")

Installing Additional Packages with micropip

Pure-Python packages on PyPI can be installed at the top of your script with await micropip.install(). Run this once — Pyodide caches the install for the duration of the browser session.

import micropip
await micropip.install("pandas")     # install first (only needed once per session)
import pandas as pd

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

Other popular packages you can install the same way:

PackageNotes
pandasDataFrames, time-series, groupby, rolling windows.
scipyStatistics, optimisation, interpolation, Black-Scholes helpers.
sympySymbolic math — useful for options Greeks derivations.
networkxGraph algorithms — correlations as a network.
statsmodelsOLS, ARIMA, cointegration tests.
Install once per session. Calling await micropip.install() every time you press RUN is harmless but slower — Pyodide will skip the download if the package is already cached. Place the install call at the top of your script guarded by a flag if you run the script in a loop.

Not Available

The following do not work inside the browser sandbox:
  • Raw TCP / WebSocket from Pythonsocket, websockets, aiohttp, requests, httpx. All network I/O must go through the exchange object, which uses the page's existing WebSocket connection.
  • Persistent file I/Oopen() works but writes to an in-memory virtual filesystem that is wiped when you close the tab. Nothing is saved to disk.
  • Threadsthreading.Thread is not supported in Pyodide. Use asyncio instead.
  • Packages with compiled C extensions — any package that requires a native binary (e.g., scikit-learn, matplotlib) may fail to install unless Pyodide ships a pre-compiled wheel for it. Check the Pyodide package list first.
  • Multiprocessingmultiprocessing 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.

Order Methods (async)

MethodDescription
await exchange.buy(sym, qty, price=None)Place a buy order. Omit price for MARKET, include it for LIMIT. Returns an ack dict.
await exchange.sell(sym, qty, price=None)Place a sell order. Same signature.
await exchange.cancel(sym, order_id)Cancel an open order. Returns {"accepted": True/False, …}.

Ack Dict Structure

{
  "accepted":     True,            # False if rejected
  "order_id":     12345,           # use this to cancel later
  "order_status": "NEW",           # or PARTIAL / FILLED / REJECTED
  "trades":       [...],           # list of fills if immediately matched
  "reason":       None             # error string if rejected
}

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}%")

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 ~20% annualised (low-vol) to ~50% (high-vol names like UMBR, GLDR, ORBT). 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 8 underlyings: EXMINI, TECHX, ENRGX, ACME, DRFT, QNTM, HRZN, JNTR. 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. The spread between expiries narrows as T grows — F3 always trades richer than F2 which trades richer than F.

ETFs

Three sector basket ETFs track equally-weighted portfolios of four underlying stocks each. Named with a -ETF suffix:

SymbolNameComponents (25% each)
TECH-ETFTechnology Sector ETFDRFT, QNTM, FNTM, NXUS
ENRG-ETFEnergy & Resources ETFCDLR, UMBR, GLDR, YNDR
INDU-ETFIndustrials ETFACME, JNTR, MRVL, SLPH

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.

Options

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

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).

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