Run on a testnet or Binance Demo¶
FastMM ships configs for eight practice environments: Binance Spot Demo Mode, Binance USDⓈ-M futures Demo Trading, the Binance Spot testnet, the Bybit v5 testnet (spot and linear perpetuals), OKX demo trading, the Coinbase Exchange sandbox and the Deribit testnet. Coinbase Advanced Trade, the API of individual Coinbase accounts, has no practice environment (Venue connectors). Two more run xmm across two of them. The two Binance Demo environments share one set of keys; each other environment has its own. None of them accepts live-exchange keys.
Before a longer session, read Kill switch and shutdown.
1. Build¶
The binaries are in build/release/bin/.
2. Keys in the environment¶
Keys are read from the environment through ${VAR} references in [venues.<name>] (Configuration). Copy .env.example to .env (it is ignored by git), fill in the variables and load it:
| Environment | Config | Key variables |
|---|---|---|
| Binance Demo Mode | configs/binance-demo.toml |
FASTMM_BINANCE_API_KEY, FASTMM_BINANCE_API_SECRET |
| Binance USDⓈ-M Demo Trading | configs/binance-usdm-demo.toml |
FASTMM_BINANCE_API_KEY, FASTMM_BINANCE_API_SECRET |
| Binance testnet | configs/binance-testnet.toml |
FASTMM_BINANCE_API_KEY, FASTMM_BINANCE_API_SECRET |
| Bybit testnet | configs/bybit-testnet.toml |
FASTMM_BYBIT_API_KEY, FASTMM_BYBIT_API_SECRET |
| Bybit testnet, linear perpetuals | configs/bybit-linear-testnet.toml |
FASTMM_BYBIT_API_KEY, FASTMM_BYBIT_API_SECRET |
| OKX demo trading | configs/okx-demo.toml |
FASTMM_OKX_API_KEY, FASTMM_OKX_API_SECRET, FASTMM_OKX_API_PASSPHRASE |
| Coinbase Exchange sandbox | configs/coinbase-sandbox.toml |
FASTMM_COINBASE_API_KEY, FASTMM_COINBASE_API_SECRET, FASTMM_COINBASE_API_PASSPHRASE |
| Deribit testnet | configs/deribit-testnet.toml |
FASTMM_DERIBIT_CLIENT_ID, FASTMM_DERIBIT_CLIENT_SECRET |
xmm: Spot Demo quotes, USDⓈ-M Demo hedges |
configs/xmm-binance-demo.toml |
FASTMM_BINANCE_API_KEY, FASTMM_BINANCE_API_SECRET |
xmm: USDⓈ-M Demo quotes, Bybit testnet hedges |
configs/xmm-demo.toml |
the Binance and the Bybit variables |
The Binance configs share variable names, so one set of Binance keys is loaded at a time.
The two xmm configs trade on two venues in one session: they quote on the first and hedge every fill on the second (Quote on one venue, hedge on another).
3. The environments¶
The shipped configs raise stale_ms for quiet feeds (Venue connectors).
Binance Spot Demo Mode¶
- Keys: Binance Demo Trading. Switch to Demo Trading on binance.com and create a key under API Key Management.
- Endpoints (from
configs/binance-demo.toml): streamswss://demo-stream.binance.com/stream, WebSocket APIwss://demo-ws-api.binance.com/ws-api/v3, RESThttps://demo-api.binance.com. - Market data follows the real market; quotes away from the touch rarely fill.
- Fees: the config books 10 bps maker and taker (
[venues.binance.fees]), what the Demo account charged. - Commission asset: buys are charged in the base asset (BTC) and sells in the quote asset (USDT) (Journals, replay and PnL).
- Config:
stale_ms = 10000;[engine] min_requote_ticks = 50andmin_requote_interval_ms = 1000keep the order rate below Binance's limits. - Ed25519 key (unsigned orders after
session.logon, and SBE market data): openssl genpkey -algorithm ed25519 -out ed25519-private.pemandopenssl pkey -in ed25519-private.pem -pubout -out ed25519-public.pem; keep the private key out of the repository (chmod 600).- In Demo Trading, API Key Management: create an API key of type "Self-generated" (Ed25519), paste the contents of
ed25519-public.pem, enable Spot trading (and Futures for USDⓈ-M). Binance shows the API key. - Export
FASTMM_BINANCE_API_KEY=<that key>and either keep the PEM file or export it (export FASTMM_BINANCE_ED25519_PEM="$(cat ed25519-private.pem)"). - In
[venues.binance]:key_type = "ed25519",private_key_file = "/path/ed25519-private.pem"orprivate_key_env = "FASTMM_BINANCE_ED25519_PEM", removeapi_secret, and optionallymd_format = "sbe"(deriveswss://demo-stream-sbe.binance.com/stream). fastmm-live --config configs/binance-demo.toml --dry-runfirst: the SBE market-data channel goes Live and the book syncs. In a live run the order channel goes Live only after thesession.logonreply;session.logon failedin the log means the key, its permissions or the IP whitelist are wrong.
Binance USDⓈ-M futures Demo Trading¶
- Keys: the Binance Demo Trading HMAC keys, the same as for Spot Demo Mode. The USDⓈ-M futures wallet needs a USDT balance, and the account must be in one-way position mode (the connector refuses hedge mode).
- Endpoints (from
configs/binance-usdm-demo.toml): streamswss://demo-fstream.binance.com(/public,/market,/private), WebSocket APIwss://testnet.binancefuture.com/ws-fapi/v1, RESThttps://demo-fapi.binance.com. - Contract: the BTCUSDT perpetual, tick 0.10, lot 0.0001, minimum notional 50 USDT. The config quotes 0.001 BTC about 1 bps from mid with
max_position = "0.003"andmax_loss = "20". - Fees: 2 bps maker and 4 bps taker, charged in USDT (
GET /fapi/v1/commissionRateon the Demo account). - Leverage and margin mode are account settings: the log shows them at startup; the connector does not change them. Funding payments are booked from the income history (Venues).
- After a session, flatten any remaining position with a reduce-only order.
Binance Spot testnet¶
- Keys: generate them on https://testnet.binance.vision (HMAC keys; Ed25519 keys work with
key_type = "ed25519"andprivate_key_file). - Endpoints (from
configs/binance-testnet.toml): streamswss://stream.testnet.binance.vision/stream, WebSocket APIwss://ws-api.testnet.binance.vision/ws-api/v3, RESThttps://testnet.binance.vision. - Market data is the testnet's own book: thin and often silent for several seconds (
stale_ms = 10000). Fills are rare and not representative.
Gate USDT perpetual futures¶
configs/gate-usdt.toml points at production (wss://fx-ws.gateio.ws/v4/ws/usdt, https://fx-api.gateio.ws); Gate's futures TestNet is wss://ws-testnet.gate.com/v4/ws/futures/usdt and https://fx-api-testnet.gateio.ws, with keys from the "Futures TestNet APIKeys" tab (production keys are refused there). FASTMM_GATE_API_KEY / FASTMM_GATE_API_SECRET; the account must be in single position mode and hold USDT in its futures account (Venue connectors).
Hyperliquid perpetuals¶
- Keys: the account's address (
api_key) and a signing key (api_secret): an agent wallet the account approved (app.hyperliquid.xyz, More > API: Generate, then Authorize API Wallet) or the account's own key. An agent can place, modify and cancel orders but cannot move funds; the connector checks it against the account'sextraAgentsat start-up. - Endpoints (from
configs/hyperliquid.toml):wss://api.hyperliquid.xyz/ws(market data, the account streams and the order posts on three connections), RESThttps://api.hyperliquid.xyz. The testnet iswss://api.hyperliquid-testnet.xyz/wsandhttps://api.hyperliquid-testnet.xyzwithtestnet = true; its USDC comes from the testnet faucet, and a mainnet agent is not valid there. - Account: in a unified account the spot USDC is the collateral of every dex (main and HIP-3); otherwise USDC has to be moved to the perp balance. Builder-deployed dexes (xyz's US equities) are named with their prefix,
xyz:NVDA. - Request budget: each new order and modify spends one request of the address budget, 10000 plus one per USDC traded (growth-mode volume counts 10 %). The account itself (not an agent) can reserve more with
reserveRequestWeightat 0.0005 USDC a request, paid from the perp balance; the connector counts the reserved requests and stops new orders and modifies ataddress_reserve. - Dead man's switch:
scheduleCancelis refused until the address has traded enough; the connector logs the reason once and runs without it. - After a session, flatten with
fastmm-ctl flatten(reduce-only) and checkopenOrdersandclearinghouseStateof each dex.
Lighter perpetuals¶
- Account: a Lighter account belongs to an Ethereum address. Connect a wallet at app.lighter.xyz and deposit USDC (from Ethereum or a supported L2 through the app's bridge); the first deposit creates the account and its index. On the testnet (
testnet = true) the faucet funds it. - API key:
tools/lighter_api_key.pymakes a key pair and registers it in a slot (4 to 254) with a ChangePubKey that the wallet's key signs. It runs on the trading host afterpip install lighter-sdk eth-accountand reads the wallet key from the environment, never from the command line. Without--sendit only prints what it would register. Keep the printed private key asFASTMM_LIGHTER_API_KEY. - Endpoints (from
configs/lighter.toml):wss://mainnet.zklighter.elliot.ai/streamcarries the market data, the account channels and the transactions on three connections, and REST ishttps://mainnet.zklighter.elliot.ai. The testnet hosts aretestnet.zklighter.elliot.ai. - Tier: a Standard account trades free, but its orders and cancels wait 300 ms and it may send 60 transactions a minute. Premium (
/api/v1/changeAccountTier, once a day per address, for every sub-account at once) has no wait for makers and cancels and allows 4000 transactions a minute, but each order spends volume quota; the connector stops atquota_reserve. - After a session, flatten with
fastmm-ctl flatten(reduce-only) and checkaccountActiveOrdersand the positions ofaccount.
Bybit v5 spot testnet¶
- Keys: create a system-generated (HMAC) API key on the Bybit testnet site (testnet.bybit.com, API Management) with spot trading permission.
- Endpoints (from
configs/bybit-testnet.toml): publicwss://stream-testnet.bybit.com/v5/public/spot, tradewss://stream-testnet.bybit.com/v5/trade, privatewss://stream-testnet.bybit.com/v5/private(derived fromws_urlwhenws_private_urlis not set), RESThttps://api-testnet.bybit.com. - Config:
supports_replace = false: quotes are replaced with cancel and new, because Bybit does not document whether amendqtyincludes the filled quantity.recv_window_ms = 5000,stale_ms = 10000. - Linear perpetuals:
configs/bybit-linear-testnet.tomlsetscategory = "linear"and the public streamwss://stream-testnet.bybit.com/v5/public/linear. The key needs contract trading permission and USDT in the unified account. The symbol must be in one-way position mode: in hedge modefastmm-liveexits 3 at start-up. Not run against the testnet yet (Venue connectors).
OKX demo trading¶
- Keys: on okx.com switch to Demo trading and create a demo API key (Personal center, Demo trading API) with trade permission. OKX asks for a passphrase when the key is made: it is the third credential,
api_passphrase. Live-trading keys are refused by the demo hosts (50101). - Account (swaps,
configs/okx-demo.toml): net position mode, not the spot account mode, USDT in the trading account. Long/short mode or the spot mode makefastmm-liveexit 3 at start-up. - Endpoints (from
configs/okx-demo.toml): publicwss://wspap.okx.com:8443/ws/v5/public, private and orderswss://wspap.okx.com:8443/ws/v5/private(derived), RESThttps://www.okx.comwithx-simulated-trading: 1(testnet = true). Production iswss://ws.okx.com:8443withtestnet = false; the connector refuses a demo host withtestnet = falseand the reverse. - Units: quantities are contracts. One BTC-USDT-SWAP contract is 0.01 BTC; the config quotes 0.1 contracts (0.001 BTC). On demo the tick is 0.01 and
instIdCodediffers from production; both come fromGET /api/v5/public/instrumentswith the demo header. - Config:
supports_replace = falseuntil amend has been seen on the demo;dead_mans_switch_s = 60armscancel-all-after(whether demo trading honours it is not documented). - Spot:
configs/okx-spot-demo.tomlquotes BTC-USDT at the minimum size (0.00002 BTC) for an account registered on my.okx.com (region = "eea": RESThttps://eea.okx.com, WebSocketwss://wseeapap.okx.com:8443). Any account mode trades spot, the spot mode included; keys only work on the domain of the site they were made on. Quantities are BTC. - Status: spot ran on the demo (orders, fills, a kill -9 and restart, a clean stop; 2026-09-30). For swaps the public stream was run against the demo and production hosts; orders and the private channels are covered by a scripted fake exchange only (Venue connectors).
Coinbase Exchange sandbox¶
- Keys: sign up at https://public.sandbox.exchange.coinbase.com (an account of its own; a coinbase.com login does not work there), then create an API key with the View and Trade permissions. The sandbox shows the secret once and asks for a passphrase: key, secret and passphrase are the three credentials. Add fake funds to the profile in the same interface.
- Endpoints (from
configs/coinbase-sandbox.toml): feedwss://ws-feed-public.sandbox.exchange.coinbase.com(market data, and the user channel on a second connection), RESThttps://api-public.sandbox.exchange.coinbase.com(testnet = true). The connector refuses a sandbox host withtestnet = falseand a production host withtestnet = true. - The sandbox lists a subset of the production products; its books are thin and do not follow production prices, so the config quotes 50 bps from the mid.
- Status: the public feed ran against the sandbox and production; orders and the user channel are covered by a scripted fake exchange only (Venue connectors).
Deribit testnet¶
- Keys: create an account on https://test.deribit.com and an API key under Account, API. The key's client id and client secret go into
FASTMM_DERIBIT_CLIENT_IDandFASTMM_DERIBIT_CLIENT_SECRET(api_keyandapi_secretin the config). - Endpoints (from
configs/deribit-testnet.toml): WebSocketwss://test.deribit.com/ws/api/v2for market data and, on a second connection, authentication and orders; RESThttps://test.deribit.com/api/v2for reference data and the kill-switchprivate/cancel_all_by_instrument. - Instruments expire. The shipped config quotes
BTC-25DEC26-*options withoptions_mm; after that date reference data fails to load (exit 4). Replace the[[instruments]]with live names frompublic/get_instruments?currency=BTC&kind=option. - Config:
stale_ms = 15000anddead_ms = 35000, above the heartbeat interval (heartbeat_interval_s = 10, the minimum).matching_engine_rateandmatching_engine_burstrate-limit order requests locally; set them to your account tier. Option prices are in BTC, so the[risk]notional limits are BTC amounts. - Status: the private payloads follow Deribit's published schemas and a scripted fake exchange, not recorded traffic (Venue connectors).
4. Dry run¶
--dry-run needs no keys, opens market data only and sends no order. The strategy quotes as it would with keys, and its orders stay on paper: each is acknowledged at once and none fills. Every 5 s the log shows what would rest, one line per instrument:
[binance] dry run BTCUSDT: bid 83985.83 x 0.0003 ask 84238.18 x 0.0003 would send new=2 cancel=0 replace=6
A dry run starts flat and runs as engine <name>-dryrun (fastmm-top --name <name>-dryrun); it writes no store, kill ledger or strategy state, so the next keyed session starts from the last keyed one. The summary at the end counts them: dry run: would have sent 2 orders, 2 cancels, 6 replaces (none was sent); the cancels include the two the stop pulls. The status line per venue comes every 10 s on the console ([logging] status_interval_s); a working dry run shows md=live, all books synced (books=1/1), malformed=0 and dropped=0.
5. Short keyed run¶
Start with the shipped [risk] limits (a few hundred USDT of exposure) and a fixed duration, with the journal and log in one directory per session:
mkdir -p runs/demo-1
./build/release/bin/fastmm-live --config configs/binance-demo.toml --duration 10m \
--journal runs/demo-1/session.fmj --log runs/demo-1/engine.log
Watch it from a second terminal (the name is [engine] name):
A Demo session one second after connecting:
[binance] md=live user=live order=live books=1/1 md_msgs=4 resyncs=0 malformed=0 dropped=0 orders=2 cancels=0 order_events=4 rest=2/0err reconnects=0 clock_offset_ms=175 rate_refused(weight/10s/1m/1d/paused)=0/0/0/0/0
clock_offset_ms is the venue clock minus the local clock; signed requests fail as it nears recv_window_ms (Troubleshooting).
6. Stop and check¶
Press Ctrl-C or let --duration elapse, then read the last log lines (Reading the last lines) and confirm that the venue shows no open orders (Go-live checklist). Reconcile the journal against the account with Check PnL.
7. Opt-in live connector tests¶
The live.* test cases in tests/venues/live_binance_test.cpp, live_binance_usdm_test.cpp, live_bybit_test.cpp and live_deribit_test.cpp run each connector against its testnet or Demo environment: book sync, then a far post-only order placed and cancelled. live_coinbase_test.cpp runs Coinbase Advanced Trade against production read only (accounts, open orders, fills, the user channel, the start-up sweep) with COINBASE_API_KEY_NAME and COINBASE_API_PRIVATE_KEY; it places nothing. They carry the ctest label live, which the test presets exclude; a plain ctest runs them, but they skip unless FASTMM_LIVE_TESTS=1 is set, and the order steps need the key variables above. FASTMM_BINANCE_ENV=demo points the Binance test at Demo Mode instead of the testnet.
cmake --build --preset release -j --target fastmm_venues_tests
FASTMM_LIVE_TESTS=1 FASTMM_BINANCE_ENV=demo ctest --test-dir build/release -L live --output-on-failure
# one venue only
FASTMM_LIVE_TESTS=1 ctest --test-dir build/release -L live -R 'venues\.live\.bybit' --output-on-failure