Configuration¶
Every FastMM program reads one TOML file passed with --config <file.toml>. Examples: configs/.
General rules¶
- An unknown key or section is an error that names the key and its line; so is a
[venues.<name>]key the connector itskindnames does not have, and a[strategy.params]key the strategy does not declare. - A key with the wrong type is an error, reported with its line and column.
- Decimal keys (
tick,lot,max_order_qty,max_loss, ...) accept"0.01"or0.01; both are parsed as decimal text into fixed point, not through adouble. fastmm-liveandfastmm-gatewayreplace${NAME}tokens in[venues.<name>]values:ws_url,ws_api_url,rest_url,ca_file,api_key,api_secret,api_passphraseand every connector key. A variable that is not set is an error, except that--dry-runclears an unsetapi_key,api_secretorapi_passphrase.- A literal
api_key,api_secretorapi_passphraselonger than 32 characters is refused withvenues.<name>.<key> looks like an inline secret; use ${ENV_VAR} or --allow-inline-secrets;--allow-inline-secretsaccepts it. - Logs and journals contain the configuration with
api_key,api_secretandapi_passphrasemasked. - Types:
decimalvalues are marked in the meaning;anykeys accept a string or a number.
[engine]¶
| Key | Type | Required | Meaning |
|---|---|---|---|
name |
string | session name, used in logs, journal file names and the status file (default "fastmm") | |
cpu |
integer | CPU core of the engine thread; -1 = not pinned (default -1) | |
net_cpus |
integer array | CPU cores of the network threads, one per venue in order; with spin_mode = "adaptive" a thread whose core it shares with the engine or another network thread blocks when idle instead of spinning (default []) | |
spin_mode |
string | busy (spin forever) | adaptive (spin briefly, then block until work arrives; use on WSL2 and laptops) (default adaptive) | |
net_backend |
string | event loop of fastmm-live and fastmm-sim-exchange: epoll | io_uring (Linux 5.13 or newer; falls back to epoll with a warning) (default epoll) | |
threading |
string | fastmm-live: split (engine and network threads, rings between them) | single (one venue; its network loop, the engine and order sending run on the engine thread, cpu; net_cpus is ignored) (default split) | |
journal |
boolean | record every consumed event to a .fmj journal (default true) | |
journal_dir |
string | directory for journals (default "runs") | |
journal_sync |
string | how far a journal write is pushed: async (msync every 100 ms; survives the process dying) | fdatasync (msync + fdatasync on the same tick; survives power loss) (default async) | |
journal_max_bytes |
integer | roll the journal over to the next part at this size, bytes; each part is a complete journal ("x.fmj", "x.1.fmj", ...); 0 = one file per session (default 0) | |
journal_retention_days |
integer | delete .fmj files in journal_dir last written more than this many days ago, at start; 0 = keep everything (default 0) | |
epoch_file |
string | session epoch file, keeps client order ids unique across restarts (default "runs/session_epoch") | |
kill_file |
string | latched kill switch and cumulative PnL, so [risk] max_loss is a budget across restarts (default " |
|
instance_lock |
boolean | fastmm-live: take an exclusive lock on lock_file before touching the account and refuse to start (exit code 8) while another process holds it; --standby and --takeover take it whatever this says (default false) | |
lock_file |
string | the instance lock's file (default " |
|
handoff_timeout_ms |
integer | fastmm-live --takeover: how long to wait for the running session to release the instance lock once it accepted the handoff, ms (default 75000) | |
ack_timeout_ms |
integer | force-cancel an order whose ack has not arrived within this long, ms; 0 = off (default 0) | |
queue_conservatism |
number | the queue position estimate ctx.queue_ahead, from 0 to 1: at 0 a level's shrink is shared between cancels ahead of and behind our order, at 1 cancels never move it up (default 1.0; a backtest uses [backtest] queue_conservatism when set) | |
flatten_interval_ms |
integer | how often an operator flatten looks at the position left and sends the next reduce-only slice, ms (default 500) | |
flatten_timeout_ms |
integer | how long an operator flatten keeps trying before it gives up and leaves the position, ms; 0 = until it is flat or an operator stops it (default 60000) | |
flatten_slippage_bps |
integer | how far through the touch a flatten prices its orders when the command gives no --max-slippage-bps, basis points (default 25) | |
rng_seed |
integer | seed of the strategy random generator ctx.rng() (default 1) | |
md_ring_bytes |
integer | market-data ring per venue, bytes, a power of two (default 4194304) | |
order_ring_bytes |
integer | order-event ring per venue, bytes, a power of two (default 1048576) | |
journal_ring_bytes |
integer | engine-to-journal ring, bytes, a power of two (default 16777216) | |
max_events_per_step |
integer | events taken from each ring per engine iteration (default 64) | |
feed_budget_per_ring |
integer | fastmm-live: events the engine takes from one input ring before the next ring's turn; 1 interleaves the rings, so a burst of one account's order events does not hold up the market data behind it (default 64) | |
net_spin_dedicated |
boolean | with spin_mode = "adaptive": a network thread pinned to a core of its own (net_cpus, shared with neither the engine nor another network thread) polls without ever blocking, as with "busy"; threads on shared or unpinned cores still block when idle (default false) | |
crossed_grace_ms |
integer | tolerate a crossed book this long before pulling its quotes, ms (default 100) | |
latency_publish_ms |
integer | latency histogram publish interval, ms (default 1000) | |
tsc_recalibrate_s |
integer | fastmm-live: TSC recalibration period, s; 0 = off (default 10) | |
timer_slack_ns |
integer | fastmm-live: timer slack of its threads, ns; how late a timed wait may end (adaptive spin_mode: the engine's idle wait, at most 1 ms, and with threading = "single" a 50 us sleep); 0 = the kernel's, 50000 (default 0) | |
restore_position |
boolean | carry the previous session's positions over from the store and replay the venue's executions since its last recorded fill (venues that can replay executions); default true | |
lock_memory |
boolean | fastmm-live: mlockall() the process, so no page is swapped out or faulted in on the hot path; needs ulimit -l above the process size, a warning otherwise (default false) | |
cpu_dma_latency_us |
integer | fastmm-live: holds /dev/cpu_dma_latency at this many us for the session, so no CPU enters an idle state that takes longer to wake; 0 keeps every CPU polling in C0; needs write access to the device, a warning otherwise; -1 = no request (default -1) | |
rt_priority |
integer | fastmm-live: SCHED_FIFO priority 1..99 of the engine thread (with threading = "single" it also runs the network loop); needs CAP_SYS_NICE or ulimit -r at least as high, a warning otherwise; a busy-spinning FIFO thread starves the kernel's threads on its core unless the core is isolated; 0 = the default scheduler (default 0) | |
net_rt_priority |
integer | fastmm-live: SCHED_FIFO priority 1..99 of the network threads, as rt_priority; ignored with threading = "single"; 0 = the default scheduler (default 0) | |
log_irq_affinity |
boolean | fastmm-live: at start, log each network interface's queue interrupts with the CPUs they may run on and which of them are cpu or net_cpus, and warn about any that may run on the engine's CPU; read only (default false) | |
min_requote_ticks |
integer | keep a resting quote whose price is within this many ticks of the desired price (default 1) | |
min_requote_interval_ms |
integer | change the same quote slot at most this often, ms (default 50) | |
min_qty_bps |
integer | keep a resting quote whose remaining quantity covers this share of the desired quantity, bps (default 8000 = 80 %) | |
post_only |
boolean | send quotes as post-only orders (default true) | |
supports_replace |
boolean | let the quote manager amend orders in place where the venue supports it (default true) | |
reject_backoff_ms |
integer | after a venue reject other than a post-only cross, no new orders on that side for this long, doubling with each further reject, ms; 0 = off (default 1000) | |
reject_backoff_max_ms |
integer | cap of the doubling reject backoff, ms (default 60000) | |
quote_token_reserve |
integer | order tokens of [risk] orders_per_sec a quote that does not reduce the position leaves for the strategy's own orders and for quotes that do; quotes short of a token wait and are placed in turn as tokens return, not refused (default 0) | |
on_kill |
string | fastmm-live after a kill switch the engine trips itself ([risk] max_loss, a full ring, every venue killed): exit (normal shutdown, exit code 6) | stay (keep running with quoting off) (default exit) |
io_uring is blocked by some seccomp profiles, including Docker's default (Network reactor). tsc_recalibrate_s: the engine clock stays continuous and slews each measured offset away over one period (at most 500 ppm); it steps, with a warning, only when it was more than 1 ms off. on_kill: Kill switch and shutdown.
[venues.<name>]¶
One table per venue; <name> is how instruments refer to it.
| Key | Type | Required | Meaning |
|---|---|---|---|
kind |
string | yes | name of a registered connector; the keys it adds to this section are its own (see Connectors below) |
ws_url |
string | market-data WebSocket URL | |
ws_api_url |
string | order-entry WebSocket API URL, where the venue has one | |
rest_url |
string | REST base URL | |
api_key |
string | API key, written as "${VARIABLE}" | |
api_secret |
string | API secret, written as "${VARIABLE}" | |
api_passphrase |
string | API key passphrase where the venue has one (OKX, Coinbase Exchange), written as "${VARIABLE}" | |
testnet |
boolean | the endpoints are a testnet or demo environment (default true) | |
supports_replace |
boolean | the venue can amend an order in place (default false) | |
insecure_tls |
boolean | skip TLS certificate verification; local simulator only (default false) | |
ca_file |
string | extra CA certificate, for example tests/fixtures/tls/cert.pem for the local simulator | |
public_only |
boolean | market data only: no keys, no private sessions, no orders on this venue (default false) | |
pool_of |
string | name of another [venues. |
|
recv_window_ms |
integer | validity window of signed requests, ms (default 3000) | |
source_ip |
string | the venue's connections leave this host from this address, IPv4 or IPv6; it must be one of the host's (default: the kernel chooses) | |
source_interface |
string | the venue's connections leave this host from this network interface's address; exclusive with source_ip (default: the kernel chooses) | |
fill_audit_interval_s |
integer | fastmm-live compares the venue's trade history with the stored fills this often, s; 0 = off (default 0, at least 10) | |
fill_audit_lag_s |
integer | a fill audit reads up to this long ago, s, so the execution replay has booked what it will (default 180) | |
fill_audit_mode |
string | "report" logs and counts what differs; "book" also books the executions the engine missed (default "report") | |
fees |
table | [venues. |
|
treasury |
table | [venues. |
[venues.<name>.fees]¶
| Key | Type | Required | Meaning |
|---|---|---|---|
maker_bps |
number | maker fee, bps; negative = rebate (default 0) | |
taker_bps |
number | taker fee, bps (default 0) |
Account pools¶
An exchange counts orders per account (Binance Spot: 100 new orders per 10 s and 200,000 per day). pool_of puts several accounts behind one venue: the primary carries the instruments and the market data as any venue does, and each member is another account of the same kind that takes orders for the primary's instruments with its own keys, balances and order-count windows.
[venues.binance] # the primary: market data and orders
kind = "binance_spot"
api_key = "${K1}"
api_secret = "${S1}"
[venues.binance_b] # a member: orders only, its own key and balances
kind = "binance_spot"
pool_of = "binance"
api_key = "${K2}"
api_secret = "${S2}"
A member's kind is the primary's; a member is nobody's primary, lists no [[instruments]] of its own and is not public_only; a pool holds at most 8 accounts, the primary included; the connector must run as a pool member (Binance Spot and USDⓈ-M do). The engine routes each new order to one account and the order stays there (Account pools); in a backtest, [venues.<member>] kind = "sim" with [backtest.venues.<member>.balances] gives the member its own simulated account ([backtest.venues.<name>]).
Binance counts request weight per IP, so a pool's accounts share the IP's window (6000 a minute on Spot, 2400 on USDⓈ-M): each request of any account counts against it, and a 429 or 418 answered to one account pauses or stops them all. Order counts stay each account's. Accounts that leave from different addresses (Source address) count apart.
Source address¶
On a host with several addresses, source_ip or source_interface sends every connection of a venue (market data, user stream, order entry, REST, and the blocking requests of start-up and the kill switch) from one of them, so that a venue counting request weight per IP sees each account's address:
[venues.binance_b]
kind = "binance_spot"
pool_of = "binance"
source_ip = "10.0.1.21" # or: source_interface = "eth1"
The socket is bound to the address before it connects; source_interface binds to the interface's first address in the family of the venue's address. Binding chooses the source address only: the route, and with it the network device, follows the host's routing table, so no privilege is needed, and on a host with several devices the routing must send traffic from each address out of its device (source-based routing rules, as clouds set up for secondary interfaces). The operating system must hold the address already: fastmm-live and fastmm-gateway refuse to start when it is not one of the host's (source_ip 10.0.1.21 is not an address of this host) or the interface does not exist; any address in a loopback interface's network counts (127.0.0.2 on Linux). Only the venue's addresses of a family the source has are tried.
The two are exclusive; neither: the kernel chooses, as before. Binance Spot and USDⓈ-M accounts of a pool count request weight per source address: those leaving from one address (or all without one) share a window, cooldown and ban, as above. Every connector except nasdaq_itch binds; that one refuses the keys.
Fill audit¶
fill_audit_interval_s makes fastmm-live compare, every so many seconds, the account's executions as the venue reports them with the fills the store holds for that venue (every session of the engine), one by one by the venue's trade id: quantity, price, commission, side and order.
[venues.binance]
kind = "binance_spot"
fill_audit_interval_s = 600 # 0: off (default)
fill_audit_lag_s = 180 # read up to 3 minutes ago
fill_audit_mode = "report" # or "book"
Each audit reads the window from where the last one ended to fill_audit_lag_s ago, the first from the session's start; a window that could not be read is read again with the next. The lag leaves the execution replay (once a minute) and the store time to book what they will. The venue's side is the same trade history the execution replay reads (myTrades, userTrades), read apart from it on the venue's network thread: one request per subscribed symbol and page, within the bulk share of the request weight and after the orders; nothing it reads reaches the engine. A difference is logged (fill audit: missing execution ..., phantom, differs in qty,fee, booked more than once, then an ERROR summary) and counted in the status file and the metrics (fastmm_venue_fill_audit_*), once per execution.
fill_audit_mode = "book" also books what the engine missed: the execution replay reads again from the first missing execution, the OMS keeps the executions it has not seen, and the next audit reads again from there to confirm. An execution is asked for once.
The audit needs the store ([storage]) and the venue in this process (not behind fastmm-gateway, where the key is ignored with a warning); a connector without it refuses to start. Binance Spot and USDⓈ-M have it. To audit after the fact, against a file the venue exported, use fastmm-pnl audit (Query what you traded).
[venues.<primary>.treasury]¶
Each account of a pool has its own balance, so one can run short of the quote asset (or the margin) while another holds more than it needs. The pool treasury moves one asset between the pool's accounts by internal transfers, in fastmm-live, off by default.
| Key | Type | Required | Meaning |
|---|---|---|---|
enabled |
boolean | move the asset between the pool's accounts (default false) | |
dry_run |
boolean | log and journal the planned transfers without sending them; needs no transfer key (default false) | |
asset |
string | the asset kept spread, for example USDT; required when enabled | |
weights |
table | { |
|
min_free |
table | { |
|
threshold |
number | an account is short below its target times (1 - threshold), 0 to 1 (default 0.2) | |
min_amount |
any | smallest transfer, decimal (default 0) | |
max_amount |
any | largest transfer, decimal; 0 = no cap (default 0) | |
step |
any | transfer amounts round down to a multiple of it, decimal (default 0.01) | |
interval_s |
integer | how often the balances are looked at and a transfer in flight is asked for, s (default 10) | |
min_interval_s |
integer | least time between two transfers sent, s (default 60) | |
max_per_hour |
integer | transfers sent in any hour; 0 = no limit (default 12) | |
cooldown_s |
integer | no transfer for this long after one failed or timed out, s (default 300) | |
timeout_s |
integer | a transfer unresolved this long times out (cooldown_s; still asked for), and one the venue has no record of fails, s (default 60) | |
settle_s |
integer | longest wait after a transfer for both accounts' balances to report it, s (default 30) | |
state_file |
string | the transfers in flight, the id counter and the limiter across restarts (default |
[venues.binance]
kind = "binance_spot"
transfer_api_key_env = "MASTER_TRANSFER_KEY" # the master account's key, internal transfer only
transfer_api_secret_env = "MASTER_TRANSFER_SECRET"
sub_account_email = "${ACCOUNT_A_EMAIL}"
[venues.binance_b]
kind = "binance_spot"
pool_of = "binance"
sub_account_email = "${ACCOUNT_B_EMAIL}"
[venues.binance.treasury]
enabled = true
asset = "USDT"
weights = { binance = 2, binance_b = 1 } # default: equal shares
min_free = { binance_b = "200" }
max_amount = "5000"
The plan, every interval_s: each account's target is the pool's free balance of asset shared by weights. An account is short below max(target × (1 − threshold), min_free) and asks for max(target, min_free) less what it has; an account gives what it holds above max(target, min_free), and no more than the venue lets leave it (BalanceMsg::kWithdrawable: on Binance USDⓈ-M the asset's maxWithdrawAmount, which leaves unrealised profit out; other connectors say nothing and the free balance can leave). The largest need is matched with the largest giver first (ties: the account listed first), each transfer at most max_amount, rounded down to step, none below min_amount. The free balance is the engine's estimate (the venue's report less what the engine's own orders have held since), so a transfer never takes what an order holds; an account whose balance has not been reported stops the plan.
One transfer at a time per pool, and at most one per min_interval_s and max_per_hour. After a transfer is done, both accounts' balances are asked for again and nothing new is planned until both have reported after it (at most settle_s). A failure, or a transfer still unresolved after timeout_s, holds the pool for cooldown_s; a failed transfer is never sent again, the next one is planned from the balances then. dry_run = true logs and journals what would be sent, needs no transfer key, and repeats the same plan only after min_interval_s.
Every transfer has a client id (fm + the primary's venue id + the time in base 36 + a counter) and is written to state_file before it is sent. A process that stops with a transfer unanswered asks the venue for it by that id when it starts again, before it plans anything; a transfer the venue has no record of after timeout_s failed. Each step (planned, sent, done, failed, timed out) is logged and reaches the engine as a ControlCommand::Transfer message (ControlTransferMsg), which the journal records; tools/journal_dump.py prints them. The status file and fastmm-top's Prometheus endpoint carry the counters (fastmm_treasury_*).
The primary's connector carries the transfers out and must have internal transfers (VenueCapabilities::internal_transfer; Binance Spot and USDⓈ-M do, with transfer_api_key_env, see Venues); each account's connector names its account (sub_account_email). The treasury runs on a thread of its own (fm-treasury) and does not run in a dry run of the whole session or in a strategy attached to fastmm-gateway. A backtest runs every enabled treasury on simulated time against the pool's simulated accounts ([backtest.venues.<member>.balances]): it looks at the venue's free amounts every interval_s, a transfer is carried out [backtest] transfer_latency_ms after it is sent (sim::SimTreasuryPort), the engine hears the new balances on each account's link, and nothing is written to state_file. The run stays deterministic; the result counts the transfers (BacktestResult::treasury, "treasury" in summary.json, a line in the summary table) and transfers.csv lists them.
Connectors¶
The connector kind names owns the rest of the section: it declares its keys, validates them and
reports an unknown one with its line. The central schema (include/fastmm/config/schema.hpp)
knows only the generic keys above. A project can register its own connector and keys
(Add a venue). The built-in connectors:
kind |
Aliases | Connector | API keys | Order entry | Replace | Positions |
|---|---|---|---|---|---|---|
binance_spot |
binance, sim |
Binance Spot (testnet, Demo Mode, or the Binance-compatible simulator with kind = "sim") | yes | yes | yes | yes |
binance_usdm |
Binance USDⓈ-M perpetual futures (Demo Trading) | yes | yes | yes | yes | |
bybit |
bybit_spot |
Bybit v5 spot and linear perpetuals (testnet) | yes | yes | yes | yes |
coinbase_advanced |
Coinbase Advanced Trade spot (production, CDP API key) | yes | yes | no | no | |
coinbase_exchange |
Coinbase Exchange spot (sandbox) | yes | yes | no | no | |
deribit |
Deribit options and futures (testnet) | yes | yes | yes | yes | |
gate_usdt |
gate, gate_futures |
Gate APIv4 USDT-settled perpetual futures | yes | yes | yes | yes |
gemini |
Gemini perpetuals and spot (sandbox) | yes | yes | no | yes | |
hyperliquid |
hl |
Hyperliquid perpetuals, main and builder-deployed dexes | yes | yes | yes | yes |
lighter |
zklighter |
Lighter (zkLighter) perpetuals | yes | yes | yes | yes |
nasdaq_itch |
Nasdaq TotalView-ITCH market data, with OUCH order entry to fastmm-sim-itch | no | yes | yes | no | |
okx |
OKX v5 USDT-margined perpetual swaps and spot (demo trading) | yes | yes | yes | yes |
Replace and Order entry are what the connector can do; a session may not, depending on its
configuration (a dry run, order_entry = "none", missing credentials).
binance_spot¶
| Key | Type | Required | Meaning |
|---|---|---|---|
stale_ms |
integer | no traffic for this long marks the feed stale and pulls the venue's quotes, ms (default 2000) | |
dead_ms |
integer | no traffic for this long forces a reconnect, ms; raised to at least 45000 | |
order_api |
string | order entry: ws (default) | rest | |
allow_offline_reference_data |
boolean | start without REST reference data, using the configured tick and lot (default false) | |
cancel_on_order_channel_loss |
boolean | cancel all orders over REST when order entry drops (default true) | |
emit_ack_from_response |
boolean | acknowledge orders from the request response, not the event stream (default true) | |
amend_keep_priority |
boolean | reduce size with order.amend.keepPriority, which keeps the queue position, instead of order.cancelReplace (default true) | |
order_rate_threshold |
number | share of the venue's order-count limits (10 s, 1 d) the connector fills before it refuses an order; the request weight stays at 0.9 (default 0.9) | |
max_order_amends |
integer | keepPriority amendments allowed on one order before falling back to cancelReplace (default 10, the venue's MAX_NUM_ORDER_AMENDS filter) | |
depth_limit |
integer | REST snapshot depth, 5 to 5000 (weight 5 up to 100, 25 up to 500, 50 up to 1000, 250 above); default: 1000 with up to 10 subscribed symbols, else 100 | |
key_type |
string | hmac (default) | ed25519 | |
private_key_file |
string | Ed25519 private key file (PKCS#8 PEM), with key_type = ed25519 | |
private_key_env |
string | environment variable holding the Ed25519 private key PEM (instead of private_key_file) | |
md_format |
string | json (default) | sbe (binary market data; needs an Ed25519 api_key) | |
sbe_ws_url |
string | SBE stream URL; empty = ws_url with stream. -> stream-sbe. | |
user_stream |
string | ws_api (default) | listen_key | none | |
position_from_balance |
boolean | derive positions from account balances | |
fetch_fees |
boolean | fastmm-live: fetch the account's maker and taker rates per symbol at start-up (GET /api/v3/account/commission) and use them instead of the configured fees; start-up fails when the request does (default false) | |
transfer_api_key_env |
string | pool treasury: environment variable holding the master account's API key with internal transfer enabled, used for transfers between the pool's accounts only (default none: no transfers) | |
transfer_api_secret_env |
string | environment variable holding that key's HMAC secret | |
transfer_rest_url |
string | host of the transfer endpoint, /sapi/v1/sub-account/universalTransfer (default https://api.binance.com) | |
sub_account_email |
string | this account's sub-account email in a transfer; empty: the master account (default empty) |
binance_usdm¶
| Key | Type | Required | Meaning |
|---|---|---|---|
stale_ms |
integer | no traffic for this long marks the feed stale and pulls the venue's quotes, ms (default 2000) | |
dead_ms |
integer | no traffic for this long forces a reconnect, ms; raised to at least 45000 for market data and 240000 for the other channels | |
order_api |
string | order entry: ws (default) | rest | |
allow_offline_reference_data |
boolean | start without REST reference data, using the configured tick and lot (default false) | |
dead_mans_switch_ms |
integer | venue-side countdownCancelAll window in ms; the venue cancels every open order of a symbol if the connector goes quiet for this long. 0 disables it (default 60000) | |
post_only_rpi |
boolean | send post-only orders as RPI (matched only against app and web orders, hidden from the API depth) instead of GTX; needs supports_replace = false (default false) | |
cancel_on_order_channel_loss |
boolean | cancel all orders over REST when order entry drops (default true) | |
emit_ack_from_response |
boolean | acknowledge orders from the request response, not the event stream (default true) | |
depth_limit |
integer | REST snapshot depth: 5, 10, 20, 50, 100, 500 or 1000 (weight 2, 5, 10 or 20); default: 1000 with up to 10 subscribed symbols, else 100 | |
key_type |
string | hmac (default) | ed25519 | |
private_key_file |
string | Ed25519 private key file (PKCS#8 PEM), with key_type = ed25519 | |
private_key_env |
string | environment variable holding the Ed25519 private key PEM (instead of private_key_file) | |
order_rate_threshold |
number | share of the venue's order-count limits (10 s, 1 d) the connector fills before it refuses an order; the request weight stays at 0.9 (default 0.9) | |
md_ticker_conns |
integer | extra connections carrying only the bookTicker streams; the first copy of each update is used, which takes the occasional slow push server out of the path (0 to 8, default 0) | |
ws_private_url |
string | private WebSocket URL; empty = derived from ws_url | |
position_from_account_update |
boolean | correct the engine position from ACCOUNT_UPDATE when it differs from the fills (default true) | |
one_way_mode |
boolean | at start, switch an account in hedge mode to one-way mode when it holds no position and no open order (default false: a hedge-mode account refuses to start) | |
leverage |
integer | at start, set this leverage (1 to 125) on every enabled symbol; 0 leaves the account's (default 0) | |
transfer_api_key_env |
string | pool treasury: environment variable holding the master account's API key with internal transfer enabled, used for transfers between the pool's accounts only (default none: no transfers) | |
transfer_api_secret_env |
string | environment variable holding that key's HMAC secret | |
transfer_rest_url |
string | host of the transfer endpoint, /sapi/v1/sub-account/universalTransfer (default https://api.binance.com) | |
sub_account_email |
string | this account's sub-account email in a transfer; empty: the master account (default empty) |
bybit¶
| Key | Type | Required | Meaning |
|---|---|---|---|
category |
string | product: spot (default) | linear (USDT- and USDC-margined perpetuals, one-way position mode only); ws_url must be the matching public stream | |
stale_ms |
integer | no traffic for this long marks the feed stale and pulls the venue's quotes, ms (default 2000) | |
dead_ms |
integer | no traffic for this long forces a reconnect, ms; raised to at least 45000 | |
order_api |
string | order entry: ws (default) | rest | |
allow_offline_reference_data |
boolean | start without REST reference data, using the configured tick and lot (default false) | |
dead_mans_switch_s |
integer | Bybit disconnect-cancel-all window in seconds, 3 to 300; the venue cancels every spot order (every derivatives order with category = linear) once no private connection is left. 0 disables it (default 0: Bybit only grants DCP to institutional accounts) | |
cancel_on_order_channel_loss |
boolean | cancel all orders over REST when order entry drops (default true) | |
emit_ack_from_response |
boolean | acknowledge orders from the request response, not the event stream (default true) | |
depth |
integer | order book subscription depth, 1 to 1000 | |
ws_private_url |
string | private WebSocket URL; empty = derived from ws_url | |
ping_interval_ms |
integer | application ping interval, ms, at least 1000 | |
orders_per_second |
integer | client-side order rate cap, orders/s | |
position_from_wallet |
boolean | spot: derive positions from the wallet | |
position_from_stream |
boolean | linear: correct the engine position from the position topic when it differs from the fills (default true) |
coinbase_advanced¶
Coinbase Advanced Trade spot, production only (testnet = false): api_key is the CDP key name and api_secret its EC private key (Venue connectors).
| Key | Type | Required | Meaning |
|---|---|---|---|
ws_private_url |
string | WebSocket URL of the user channel (order states); empty = wss://advanced-trade-ws-user.coinbase.com | |
stale_ms |
integer | no traffic for this long marks the feed stale and pulls the venue's quotes, ms (default 2000) | |
dead_ms |
integer | no traffic for this long forces a reconnect, ms (default 10000; heartbeats come every second) | |
orders_per_second |
integer | client-side cap on new orders, per second (default 8) | |
cancel_batch |
integer | orders one batch_cancel request names in cancel-all, 1 to 100 (default 50) | |
allow_offline_reference_data |
boolean | start without REST reference data, using the configured tick and lot (default false) | |
cancel_on_order_channel_loss |
boolean | cancel all orders over REST when the user channel drops (default true) | |
emit_ack_from_response |
boolean | acknowledge new orders from the POST orders reply as well as the user channel (default true) |
coinbase_exchange¶
Coinbase Exchange spot; the third credential is the generic api_passphrase, and testnet = true (the default) selects the sandbox hosts (Venue connectors).
| Key | Type | Required | Meaning |
|---|---|---|---|
ws_private_url |
string | WebSocket URL of the user channel (order events); empty = ws_url | |
depth_channel |
string | order book channel: level2_batch (default; batched every 50 ms, no signature) | level2 (signed: needs the keys) | |
stp |
string | self-trade prevention of every order: dc (default) | co | cn | cb | |
stale_ms |
integer | no traffic for this long marks the feed stale and pulls the venue's quotes, ms (default 2000) | |
dead_ms |
integer | no traffic for this long forces a reconnect, ms (default 10000; heartbeats come every second) | |
orders_per_second |
integer | client-side cap on new orders, per second (default 8; the venue allows 15 private REST requests a second) | |
cancel_all_rounds |
integer | kill-switch DELETE /orders per product, repeated until one cancels nothing, at most this often, 1 to 10 (default 3) | |
allow_offline_reference_data |
boolean | start without REST reference data, using the configured tick and lot (default false) | |
cancel_on_order_channel_loss |
boolean | cancel all orders over REST when the user channel drops (default true) | |
emit_ack_from_response |
boolean | acknowledge new orders from the POST /orders reply as well as the user channel (default true) |
deribit¶
| Key | Type | Required | Meaning |
|---|---|---|---|
stale_ms |
integer | no traffic for this long marks the feed stale and pulls the venue's quotes, ms (default 10000) | |
dead_ms |
integer | no traffic for this long forces a reconnect, ms; raised to three heartbeat intervals, 30000 by default | |
allow_offline_reference_data |
boolean | start without REST reference data, using the configured tick and lot (default false) | |
cancel_on_order_channel_loss |
boolean | cancel all orders over REST when order entry drops (default true) | |
emit_ack_from_response |
boolean | acknowledge orders from the request response, not the event stream (default true) | |
ws_private_url |
string | private WebSocket URL; empty = derived from ws_url | |
currencies |
any | currencies for reference data, user channels and reconciliation, "BTC" or ["BTC", "ETH"] (default "BTC") | |
book_interval |
string | book channel interval, 100ms (default) | agg2 | |
ticker_interval |
string | ticker channel interval, 100ms (default) | agg2 | |
trades_interval |
string | trades channel interval, 100ms (default) | agg2 | |
heartbeat_interval_s |
integer | public/set_heartbeat interval, s, at least 10 | |
reject_post_only |
boolean | reject crossing post-only orders instead of repricing them (default true) | |
cancel_on_disconnect |
boolean | cancel-on-disconnect on the order connection (default true) | |
matching_engine_rate |
integer | order requests per second of the account tier (default 5) | |
matching_engine_burst |
integer | order request burst of the account tier (default 20) |
okx¶
USDT-margined swaps, quantities in contracts, and spot pairs, quantities in the base coin. The
third credential is the generic api_passphrase, testnet = true (the default) selects demo
trading, and region the site the account is registered on
(Venue connectors).
| Key | Type | Required | Meaning |
|---|---|---|---|
region |
string | the site the account is registered on, which sets ws_url and rest_url when they are empty: global (default; www.okx.com) | eea or my (my.okx.com: eea.okx.com, wseea / wseeapap) | us or app (app.okx.com: us.okx.com, wsus / wsuspap) | |
ws_private_url |
string | private WebSocket URL (login, orders, positions); empty = ws_url's host + /ws/v5/private | |
td_mode |
string | margin mode of every swap order: cross (default) | isolated; spot orders go with cash | |
depth_channel |
string | order book channel: books (default; 400 levels, 100 ms) | books50-l2-tbt | books-l2-tbt (10 ms; VIP4 and a login) | |
order_api |
string | order entry: ws (default) | rest | |
dead_mans_switch_s |
integer | cancel-all-after countdown, seconds, 10 to 120, refreshed every third of it; the venue cancels every pending order of the account when it runs out. 0 disables it (default 60) | |
stale_ms |
integer | no traffic for this long marks the feed stale and pulls the venue's quotes, ms (default 2000) | |
dead_ms |
integer | no traffic for this long forces a reconnect, ms; raised to at least twice ping_interval_ms plus 5000 | |
ping_interval_ms |
integer | "ping" interval on every connection, ms, 1000 to 25000 (default 20000; OKX closes a connection idle for 30 s) | |
orders_per_second |
integer | client-side cap on new orders and amends, per second (default 25; OKX allows 60 per 2 s per instrument) | |
position_from_stream |
boolean | correct the engine position from the positions channel when it differs from the fills (default true) | |
allow_offline_reference_data |
boolean | start without REST reference data, using the configured tick and lot (default false) | |
cancel_on_order_channel_loss |
boolean | cancel all orders over REST when order entry drops (default true) | |
emit_ack_from_response |
boolean | acknowledge new orders from the request response, not the orders channel (default true) |
gate_usdt¶
USDT-settled perpetual futures on Gate, quantities in contracts, single position mode; one WebSocket URL serves the public channels, the private channels and the order API (Venue connectors).
| Key | Type | Required | Meaning |
|---|---|---|---|
settle |
string | settlement currency of the futures: usdt (default) | usd1 | btc; ws_url and rest_url must match it when set | |
order_api |
string | order entry: ws (default, futures.order_place) | rest | |
book_level |
integer | futures.obu depth: 50 (pushed every 20 ms, default) | 400 (every 100 ms) | |
stale_ms |
integer | no traffic for this long marks the feed stale and pulls the venue's quotes, ms (default 2000) | |
dead_ms |
integer | no traffic for this long forces a reconnect, ms; raised to at least 2 x ping_interval_ms + 5000 | |
ping_interval_ms |
integer | futures.ping interval on every connection, ms, at least 1000 (default 15000) | |
orders_per_second |
integer | client-side cap on new orders and amends per second (default 50; the venue allows 100 per UID, 200 cancels) | |
emit_ack_from_response |
boolean | acknowledge orders from the order_place reply, not the orders channel (default true) | |
position_from_stream |
boolean | correct the engine position from the positions channel when it differs from the fills (default true) | |
cancel_on_order_channel_loss |
boolean | cancel all orders over REST when order entry drops (default true) | |
allow_offline_reference_data |
boolean | start without the contract table, using the configured tick, lot and multiplier (default false) | |
fetch_fees |
boolean | read the account's maker/taker rates from GET /futures/{settle}/fee at start (default false) | |
dead_mans_switch_s |
integer | POST /futures/{settle}/countdown_cancel_all window in seconds, refreshed at a third of it; the venue cancels every order of the account when no refresh arrives. 0 disables it (default 0) |
gemini¶
Perpetuals and spot on the WebSocket API. The URLs default to the sandbox hosts and testnet must match the hosts; api_key is an account-scoped key with a time-based nonce (Venue connectors).
| Key | Type | Required | Meaning |
|---|---|---|---|
cancel_on_disconnect |
boolean | cancelOnDisconnect=true on the order connection: the venue cancels the orders placed on it when it closes (default true) | |
heartbeat |
boolean | the API key has "Requires Heartbeat": POST /v1/heartbeat every 15 s; the venue cancels the key's orders after 30 s without one (default false) | |
stale_ms |
integer | no traffic for this long marks the feed stale and pulls the venue's quotes, ms (default 2000) | |
dead_ms |
integer | no traffic for this long forces a reconnect, ms; raised to at least twice ping_interval_ms plus 5000 | |
ping_interval_ms |
integer | ping interval on both connections, ms, 1000 to 60000 (default 10000; the venue sends no heartbeats) |
|
orders_per_second |
integer | client-side cap on new orders, per second (default 20) | |
allow_offline_reference_data |
boolean | start without REST reference data, using the configured tick and lot (default false) | |
cancel_on_order_channel_loss |
boolean | cancel this key's orders over REST (order/cancel/session) when the order connection drops (default true) |
hyperliquid¶
Perpetuals on the main dex and the builder-deployed (HIP-3) dexes, such as xyz's US equities (xyz:NVDA). api_key is the account's address and api_secret the signing key: an agent wallet the account approved, or the account's own. testnet must be false for mainnet (Venue connectors).
| Key | Type | Required | Meaning |
|---|---|---|---|
dex |
string | builder-deployed perp dex whose coins may be named without their prefix (symbol NVDA for xyz:NVDA with dex = "xyz"); default none | |
vault_address |
string | trade for this vault or sub-account of the account (0x and 40 hex digits); default none | |
order_api |
string | order entry: ws (default, WebSocket post) | rest | |
l2_book |
boolean | subscribe l2Book, 20 levels a side (default true) | |
bbo |
boolean | subscribe bbo, the touch (default true) | |
trades |
boolean | subscribe trades (default true) | |
stale_ms |
integer | no traffic for this long marks the feed stale and pulls the venue's quotes, ms (default 5000) | |
dead_ms |
integer | no traffic for this long forces a reconnect, ms; raised to at least 2 x ping_interval_ms + 5000 (default 60000) | |
ping_interval_ms |
integer | {"method":"ping"} interval on every connection, ms, 1000 to 50000 (default 30000; the venue closes a connection that sent nothing for 60 s) | |
orders_per_second |
integer | client-side cap on new orders and modifies per second (default 20) | |
posts_per_minute |
integer | client-side cap on actions sent per minute, at most 2000 (default 1800; the venue allows 2000 WebSocket messages a minute) | |
address_reserve |
integer | refuse new orders and modifies when no more than this many requests of the address budget (userRateLimit) would remain (default 200) | |
rate_limit_refresh_s |
integer | read the address budget (userRateLimit) this often, s, at least 10 (default 60) | |
emit_ack_from_response |
boolean | acknowledge orders from the action's reply, not the orderUpdates stream (default true) | |
cancel_on_order_channel_loss |
boolean | cancel all orders over REST when order entry drops (default true) | |
allow_offline_reference_data |
boolean | start without metaAndAssetCtxs, using the configured tick and lot (default false) | |
dead_mans_switch_s |
integer | scheduleCancel window in seconds, at least 10, refreshed at a third of it; each refresh spends one request of the address budget and the venue allows 10 triggers a day. 0 disables it (default 0) | |
latency_log_s |
integer | log the post reply round trip and the order open lag (block time - send time) this often, s; 0 off (default 60) |
lighter¶
Lighter (zkLighter) perpetuals. api_key is the account index, api_secret the API key's private key (80 hex digits) and api_key_index the slot it is registered in; testnet = true selects the testnet hosts and chain id 300 (Venue connectors).
| Key | Type | Required | Meaning |
|---|---|---|---|
api_key_index |
integer | the slot the API key is registered in, 4 to 254 (tools/lighter_api_key.py registers one); needed with api_secret | |
order_api |
string | order entry: ws (default, jsonapi/sendtx) | rest | |
order_book |
boolean | subscribe order_book, the whole book (default true) | |
ticker |
boolean | subscribe ticker, the touch (default true) | |
trades |
boolean | subscribe trade (default true) | |
skip_nonce |
boolean | sign every transaction with the SkipNonce attribute, so one the venue refuses does not fail the ones after it (default true) | |
stale_ms |
integer | no traffic for this long marks the feed stale and pulls the venue's quotes, ms (default 5000) | |
dead_ms |
integer | no traffic for this long forces a reconnect, ms; raised to at least 2 x ping_interval_ms + 5000 (default 60000) | |
ping_interval_ms |
integer | {"type":"ping"} interval on every connection, ms, 1000 to 110000 (default 30000; the venue closes a connection that sent nothing for 2 minutes) | |
orders_per_second |
integer | client-side cap on new orders and modifies per second (default 20) | |
tx_per_minute |
integer | client-side cap on transactions sent per minute (default 3600; the venue allows 4000 on a Premium account with no LIT staked, 60 on a Standard one) | |
quota_reserve |
integer | refuse new orders and modifies when no more than this much of the volume quota would remain (default 50) | |
order_expiry_s |
integer | lifetime of a resting order, s, 300 to 2592000 (default 2419200, 28 days) | |
auth_ttl_s |
integer | lifetime of the auth token of the account channel and the authenticated REST calls, s, 600 to 28740; the account channel reconnects with a new one at 90 % of it (default 25200) | |
terminal_wait_ms |
integer | hold an order's cancel or expiry this long for the fills it counts, ms (default 2000) | |
emit_ack_from_response |
boolean | acknowledge orders from the sendtx reply (accepted) rather than their open on account_all_orders (default false) | |
cancel_on_order_channel_loss |
boolean | cancel all orders over REST when order entry drops (default true) | |
allow_offline_reference_data |
boolean | start without orderBookDetails; no market can be traded then (default false) | |
dead_mans_switch_s |
integer | scheduled CancelAll window in seconds, 310 to 1296000, refreshed at a third of it over the trade connection; each refresh is one transaction. 0 disables it (default 0) | |
latency_log_s |
integer | log the sendtx reply round trip and the order open lag (venue time - send time) this often, s; 0 off (default 60) |
nasdaq_itch¶
Line and recovery keys apply to the multicast feed; rx_backend selects how it is received and
order_entry whether OUCH order entry is opened at all.
| Key | Type | Required | Meaning |
|---|---|---|---|
rx_backend |
string | multicast receive, kernel (UDP sockets) | af_xdp (needs CAP_NET_ADMIN, CAP_NET_RAW, CAP_BPF, CAP_IPC_LOCK) | dpdk (a -DFASTMM_WITH_DPDK=ON build, spin_mode = "busy") (default kernel) | |
interface |
string | interface of both lines, name or IPv4 address; af_xdp needs a name (default: routing table) | |
line_a |
string | line A, " |
|
line_b |
string | line B, " |
|
line_a_interface |
string | interface of line A, overrides interface | |
line_b_interface |
string | interface of line B, overrides interface | |
line_a_source |
string | source address of line A: a source-specific join (default any source) | |
line_b_source |
string | source address of line B | |
depth |
integer | price levels per side sent to the engine, 1 to 256 (default 20) | |
queues |
any | af_xdp: RX queues to bind on every line interface, [0, 1] or "0,1" (default: every RX queue the interface has) | |
dpdk_eal_args |
string | dpdk: rte_eal_init arguments, space-separated (e.g. "--no-huge --no-pci --in-memory --vdev=net_af_packet0,iface=eth1") | |
dpdk_port |
string | dpdk: ethdev name, e.g. net_af_packet0 or a PCI address (default: the first port) | |
dpdk_exception_port |
string | dpdk: ethdev name of a net_tap vdev that carries the kernel's traffic on the port (ARP, GLIMPSE, re-requests, IGMP, kernel TCP); default none | |
dpdk_exception_ip |
string | dpdk: "a.b.c.d/len" given to the exception port's interface | |
dpdk_exception_interval_us |
integer | dpdk: how often the exception port is read, microseconds; 0 = every poll (default 20) | |
xdp_mode |
string | af_xdp: auto | zerocopy | native_copy | generic (default auto: the first that works in that order) | |
rcvbuf |
integer | kernel: SO_RCVBUF, bytes; 0 = system default (default 0) | |
batch |
integer | datagrams per recvmmsg (kernel) or RX descriptors per poll (af_xdp), 1 to 1024 (default 32) | |
rerequest |
string | MoldUDP64 re-request server, " |
|
glimpse_url |
string | GLIMPSE 5.0 server, " |
|
glimpse_username |
string | GLIMPSE login, at most 6 characters (default glimps, the simulator's) | |
glimpse_password |
string | GLIMPSE password, at most 10 characters (default glimpse) | |
reorder_packets |
integer | packets held ahead of a gap (default 256) | |
gap_timeout_ns |
integer | a missing sequence awaited this long on every line is a gap, ns; size it from the A/B skew in the status (default 2000000) | |
max_request_attempts |
integer | re-requests of one gap before it is unrecoverable; 0 = no limit (default 4) | |
request_timeout_ns |
integer | re-send an unanswered re-request after this long, ns (default 250000000) | |
recovery_buffer_packets |
integer | datagrams buffered while a GLIMPSE snapshot is taken, allocated at start (default 65536) | |
price_window_ticks |
integer | L3 book price window per side, in 0.0001 steps, a multiple of 64; orders outside go to an overflow store (default 65536) | |
max_orders |
integer | resting orders per instrument the L3 book holds (default 262144) | |
hw_timestamps |
boolean | kernel: enable NIC receive timestamps on the line interfaces (SIOCSHWTSTAMP, CAP_NET_ADMIN) (default false) | |
hw_clock |
string | none | phc_synced: use the NIC timestamp as recv_ts (only when the PHC is synchronised to CLOCK_REALTIME) (default none) | |
order_entry |
string | none (every order is rejected) | sim_ouch (OUCH 5.0 to fastmm-sim-itch) (default none) | |
ouch_url |
string | sim_ouch: OUCH 5.0 server, " |
|
order_transport |
string | sim_ouch: kernel (TCP socket, TCP_NODELAY; the only value) (default kernel) | |
ouch_username |
string | sim_ouch: login, at most 6 characters (default fmouch, the simulator's) | |
ouch_password |
string | sim_ouch: password, at most 10 characters (default ouch) |
[[instruments]]¶
| Key | Type | Required | Meaning |
|---|---|---|---|
venue |
string | yes | name of a [venues. |
symbol |
string | yes | venue symbol, for example BTCUSDT |
base |
string | base asset, for example BTC | |
quote |
string | quote asset, for example USDT | |
asset_class |
string | spot | perpetual (perp) | future (futures) | option | fx | equity (default spot) | |
tick |
any | yes | price increment, decimal |
lot |
any | yes | quantity increment, decimal |
min_qty |
any | smallest order quantity, decimal | |
max_qty |
any | largest order quantity, decimal | |
min_notional |
any | smallest order value, settlement currency (base coin for an inverse contract), decimal | |
contract_multiplier |
any | units of the underlying per contract, decimal; PnL and notional use it (default 1) | |
enabled |
boolean | trade this instrument; false loads it without trading (default true) | |
price_decimals |
integer | price display precision (default 8) | |
expiry |
string | expiry of a derivative, ISO-8601 | |
strike |
any | option strike, decimal | |
option_type |
string | call | put | |
maker_bps |
number | maker fee of this instrument, bps; negative = rebate. Overrides the venue's fees table | |
taker_bps |
number | taker fee of this instrument, bps. Overrides the venue's fees table | |
initial_margin |
any | initial margin of a derivative, fraction of the notional (0.1 at 10x leverage), decimal; the balance check and estimate use it (default: none, an order passes while the venue reports margin available) |
Live connectors replace tick, lot and the size bounds with the venue's reference data when they connect; the configured values are used by backtests and the simulator.
[strategy]¶
| Key | Type | Required | Meaning |
|---|---|---|---|
name |
string | yes | registered strategy name (see --list-strategies) |
max_param_age_ms |
integer | disable quoting before the first parameter update and while none was applied for this long, ms of engine time (default 0: off) | |
state_file |
string | file the strategy's state() is written to every state_interval_s and at the end, and restore() reads at the start (a strategy that keeps state; default: none) | |
state_interval_s |
integer | how often the state is written, seconds of engine time (default 300) | |
state_snapshot_interval_s |
integer | with a journal, how often the strategy's state() and the positions are written to it, seconds of engine time, for a backtest to start from (default 0: never) | |
params |
table | [strategy.params] table: the strategy's parameters |
[strategy.params] holds scalars, validated against the strategy's parameter schema:
- Names, types, defaults and bounds come from the strategy's
FASTMM_PARAMdeclarations;--list-strategiesprints them (--format jsonfor tools). decimalvalues take up to 8 decimals andbpsvalues up to 4; exponent notation is accepted (Fixed point).msandintvalues are whole numbers.- An unknown parameter, a value with too many decimals or an out-of-range value is an error at startup.
--strategyand--paramoverride this section (Command lines).max_param_age_msbounds the age of parameter updates sent while the session runs (Strategy API).
[risk]¶
Every limit is off when it is 0 or omitted. Risk model describes the checks and their order.
| Key | Type | Required | Meaning |
|---|---|---|---|
max_order_qty |
any | largest order quantity, decimal | |
max_order_notional |
any | largest order value, settlement currency (base coin for an inverse contract), decimal | |
max_position |
any | largest absolute position per instrument, counting same-side open orders, decimal | |
max_open_orders |
integer | open orders per instrument | |
price_collar_bps |
integer | refuse limit prices further than this from the mid, bps | |
fat_finger_bps |
integer | refuse limit prices further than this from the last trade, bps | |
stale_md_ms |
integer | refuse orders when the instrument's book is older than this, ms | |
max_loss |
any | trip the kill switch when net PnL falls to -max_loss, settlement currency (the [accounting] reporting_currency when set), decimal; latched across restarts in kill_file | |
max_gross_notional |
any | refuse an order that would take the portfolio's summed |position| at the last marks past this, settlement currency (or reporting_currency), decimal | |
max_net_notional |
any | refuse an order that would take the portfolio's signed position sum further past this, settlement currency (or reporting_currency), decimal | |
orders_per_sec |
integer | token-bucket order rate, orders/s | |
burst |
integer | token-bucket capacity, orders (default orders_per_sec) | |
stp |
boolean | self-trade prevention against our own resting orders (default true) | |
max_feed_lag_ms |
integer | pull a venue's quotes and refuse orders that could rest there without reducing the position while its market data arrives this much later than its baseline, ms; resumes 100 ms after the last message over it (default 0: off) | |
check_balance |
boolean | refuse an order the venue's reported balance, less this session's orders and fills since, cannot cover; off for a venue until it reports balances (default true) | |
underlying |
table | one [risk.underlying.BTC] table per base asset: a net position limit over every instrument and venue that trades it |
[risk.underlying.<BASE>]¶
A limit on the net position in one base asset, in base units, over every instrument of the session whose base it is, on every venue. Long 0.3 BTC on one venue and short 0.3 BTC on another is 0 BTC.
- A contract counts
qty * contract_multiplierbase units, an inverse oneqty * multiplier / markat its current mark. Options do not count. Risk model has the check and when it refuses. - The base asset is matched against each instrument's
basewithout regard to case, once the venues' reference data has loaded; a base asset no instrument has (other than options) exits with code 3. At most 8. max_net = 0tracks the underlying (status, metrics) without a limit, sofastmm-ctl limits underlying.BTC.max_net=...can set one at run time.
| Key | Type | Required | Meaning |
|---|---|---|---|
max_net |
any | yes | refuse an order that would take the net position in this base asset, open orders on the order's side included, further past this; base units (contracts times contract_multiplier, inverse contracts at the mark), decimal; 0: tracked, no limit |
[gateway]¶
Read by fastmm-gateway only: account guards over every attached strategy, checked before an order reaches the connector (Run behind a gateway). The rate and max_open_notional are per venue; max_loss, max_gross_notional and max_net_notional are over the account's positions on every venue, which the gateway books from the fills that pass through it and marks at the mids of its books. max_loss is carried in the gateway's kill file ([engine] kill_file, default <journal_dir>/<name>.kill) and latches like [risk] max_loss. Each strategy keeps its own [risk].
| Key | Type | Required | Meaning |
|---|---|---|---|
orders_per_sec |
integer | fastmm-gateway: new orders and replaces per second per venue, over every attached strategy (default 0: off) | |
burst |
integer | fastmm-gateway: token-bucket capacity of orders_per_sec, orders (default orders_per_sec) | |
max_open_notional |
any | fastmm-gateway: refuse an order that would take the notional working at its venue, over every attached strategy and both sides, past this; settlement currency, decimal | |
max_loss |
any | fastmm-gateway: trip the account's kill switch when the net PnL of every strategy together, carried across restarts in the gateway's kill file, reaches -max_loss; decimal, in [accounting] reporting_currency when set | |
max_gross_notional |
any | fastmm-gateway: refuse an order that would take the sum of the account's |position| at the marks past this, unless it reduces its instrument's position; decimal | |
max_net_notional |
any | fastmm-gateway: refuse an order that would take the account's net position at the marks further past this, unless it reduces its instrument's position; decimal | |
underlying |
table | fastmm-gateway: one [gateway.underlying.BTC] table per base asset: the account's net limit, over every strategy and venue | |
check_balance |
boolean | fastmm-gateway: refuse an order the venue's reported balance, less the orders and fills of every attached strategy since, cannot cover (default true) | |
shared |
table | fastmm-gateway: one [gateway.shared."venue:symbol"] table per instrument that several strategies may trade at once (default: one strategy per instrument) |
[gateway.underlying.<BASE>]¶
[risk.underlying] for the account: the net position in one base asset over every instrument of the gateway, every venue and every attached strategy, with the orders working at the venues. Not adjustable at run time.
| Key | Type | Required | Meaning |
|---|---|---|---|
max_net |
any | yes | fastmm-gateway: refuse an order that would take the account's net position in this base asset, working orders on the order's side included, further past this; base units, decimal |
[gateway.shared."<venue>:<symbol>"]¶
An instrument of [[instruments]] that several attached strategies may trade at once (Run behind a gateway). Without this table a second strategy claiming it is refused. An empty table shares it with no primary.
| Key | Type | Required | Meaning |
|---|---|---|---|
primary |
string | fastmm-gateway: [engine] name of the strategy that books this instrument's events naming no order (funding, liquidation fills, executions of orders placed elsewhere); default none: the account alone books them |
[accounting]¶
Converts totals to one reporting currency, so instruments that settle in different currencies (an inverse contract in its base coin, a linear one in its quote currency) can share a session or a gateway. fastmm-live, fastmm-gateway, the backtester and replay read this section.
[accounting]
reporting_currency = "USDT"
[accounting.fx]
BTC = "binance:BTCUSDT" # BTC in USDT: the mid of BTCUSDT
- Positions, PnL and fees stay in each instrument's settlement currency. The PnL totals,
[risk] max_loss,max_gross_notionalandmax_net_notional(and the[gateway]ones) are inreporting_currency, converted at the mid of each currency's source. - A source is
"venue:symbol", an instrument listed in[[instruments]](enabled = falseif it is not traded): its book is where the rate comes from, so the session must subscribe to it. It prices the currency inreporting_currencyeither way round:BTCUSDTgives BTC in USDT,USDTBTCis inverted. - Once the venues' reference data has loaded, every settlement currency of an enabled instrument (of every instrument, in
fastmm-gateway) must bereporting_currencyor have a source. Otherwisefastmm-liveandfastmm-gatewayexit with code 3 whilemax_lossor an exposure cap is set, and warn and convert nothing when none is. - A rate is unknown until its source's book is valid, and not current while that book is invalid or has not updated for
stale_fx_ms(default 60 s; 0: a valid book is enough). It is not[risk] stale_md_ms: a venue sends a book only when it changes, and a quiet FX pair can go seconds without one. With no current rate, an order that adds to exposure in that currency is refused (FxRateUnknown,GatewayFxRateUnknownin the gateway) whilemax_lossor an exposure cap is set; one that reduces a position passes. PnL already booked stays measured at the last rate; a currency whose rate was never known counts as zero, and nothing can be traded in it until it is. - Fees are booked in the settlement currency (a commission in the base asset is valued at the fill price); a commission in another asset is not booked, so it needs no source.
- Without this section nothing is converted.
| Key | Type | Required | Meaning |
|---|---|---|---|
reporting_currency |
string | currency the PnL totals, [risk] max_loss and the exposure caps (and fastmm-gateway's) are in when instruments settle in more than one; every other settlement currency needs a source in [accounting.fx] (default unset: no conversion) | |
fx |
table | [accounting.fx] table: one entry per other settlement currency, the instrument whose mid prices it in reporting_currency (BTC = "binance:BTCUSDT"; a pair quoted the other way round, USDTBTC, is inverted) | |
mark |
string | what a derivative's position is valued at for the unrealized PnL, max_loss and the exposure caps (fastmm-gateway's too): venue (its venue's mark price while it is fresh, else the book's mid) | mid (default venue) | |
stale_mark_ms |
integer | a venue's mark or index price older than this is stale: ctx.mark and ctx.index flag it and the position is valued at the mid, ms (default 15000) | |
stale_funding_ms |
integer | a venue's funding rate older than this is stale (ctx.funding flags it), ms (default 180000) | |
stale_fx_ms |
integer | an [accounting.fx] rate whose source's book has not updated for this long is not current: an order that adds exposure in that currency is refused; ms (default 60000; 0: a valid book is enough). Not [risk] stale_md_ms: a quiet FX book can go seconds without a change |
[logging]¶
| Key | Type | Required | Meaning |
|---|---|---|---|
level |
string | trace | debug | info | warn (or warning) | error | off (default info) | |
file |
string | log file; empty = stderr only | |
mirror_level |
string | records at or above this level are also written to stderr (default warn) | |
status_interval_s |
integer | seconds between the per-venue status lines, 0 = none (default 1 with a log file, 10 on stderr) |
[backtest]¶
Read by fastmm-backtest, fastmm-replay, the tests and the Python module (src/backtest/backtest_config.cpp); the section is free-form in the schema. The engine, risk and strategy settings come from the sections above; each instrument pays its own venue's fees table (or its own maker_bps / taker_bps), self-trade prevention follows [risk] stp, and in-place replace follows [engine] supports_replace, unless [backtest.venues.<name>] sets them for one venue.
| Key | Type | Default | Meaning |
|---|---|---|---|
source |
string | "" |
Market-data source: a name (synthetic, journal, csv, binance, tardis) or a whole spec with its options ("binance:BTCUSDT,2024-03-27"). Several specs separated by ; are merged by event time, ties to the earlier spec ("binance:BTCUSDT,2024-03-27,venue=0; csv:bybit.csv,venue=1"); a TOML list is refused. When empty, the format is inferred from path (Market-data sources) |
path |
string | "" |
Data file, the positional argument of a bare source name. .fmj is a journal, .csv is CSV; empty means synthetic data |
seed |
int | [sim] seed, else 1 |
Seed for the synthetic market and the simulated venue; the engine's random generator uses [engine] rng_seed |
duration_s |
int | [sim] duration_s, else 60 |
Simulated horizon for synthetic data, s; must be positive |
fill_model |
string | "matching" |
matching matches our orders against the simulated order flow. l2_queue estimates queue position on recorded L2 data, which has no counterparties; it checks post-only orders against the book the strategy saw, so it never produces the post-only rejects that stale market data causes under matching. Its results are optimistic |
queue_conservatism |
number | [engine] queue_conservatism, else 1.0 |
For l2_queue, from 0 to 1: at 0 cancellations ahead of us always move our order up the queue, at 1 they never do. The strategy's ctx.queue_ahead uses the same value in a backtest. fastmm-data fill-check compares values against a live session (Check the fill model), fastmm-data calibrate fits it (Calibrating) |
latency_fixed_us |
int | 200 |
Fixed latency for orders to the venue, and for acknowledgements back unless latency_ack_us is set, µs |
latency_jitter_us |
int | 50 |
Random jitter added to that latency, µs, seeded |
latency_ack_us |
int | latency_fixed_us |
Fixed latency for acknowledgements and fills back from the venue, µs |
latency_ack_jitter_us |
int | latency_jitter_us |
Random jitter added to that latency, µs, seeded |
latency_md_us |
int | 0 |
Fixed market-data latency, µs |
latency_md_jitter_us |
int | 0 |
Market-data latency jitter, µs |
latency_cancel_us |
int | latency_fixed_us |
Fixed latency for cancels to the venue, µs, when either cancel key is set; otherwise cancels take the order latency. A venue can take a cancel in faster than a new order (Binance Spot: about 0.4 ms less to its time on the ack) |
latency_cancel_jitter_us |
int | latency_jitter_us |
Random jitter added to that latency, µs, seeded |
order_service_us |
int | 0 |
The venue takes the order messages (new, cancel, replace) of one connection one after another, each no sooner than this after the one before: a burst of cancels reaches the book later and later, as on a live venue. 0 draws every message's latency on its own and may reorder them. fastmm-data calibrate fits it (Calibrating) |
md_arrival |
string | "venue" |
When recorded market data reaches the strategy: venue at its venue time plus latency_md_us, recorded at the recv_ts it was recorded with plus latency_md_us, which replays the feed lag of a live journal ([risk] max_feed_lag_ms reads it); a message keeps its place behind the one before it |
reorder_window_ms |
int | 1000 |
Recorded data reaches the simulated venues in venue-time order, assuming no event was received more than this long after its venue time; the strategy still gets it in recorded order. 0 keeps the recorded order (Backtesting) |
transfer_latency_ms |
int | 0 |
With a pool treasury ([venues.<primary>.treasury]): a simulated transfer between the pool's accounts is carried out this long after it is sent; 0 carries it out at once |
p_drop |
number | 0.0 |
Probability, below 1, that an outbound order message is lost |
orders_10s |
int | 0 |
The simulated venue's order count per 10 s (new orders and replaces, as Binance's ORDERS 10 s interval): one past it is refused with VenueRateLimit, and ctx.order_budget reports the count; 0 is unlimited and leaves the budget unknown |
orders_1d |
int | 0 |
The same per day |
equity_bar_s |
int | 1 |
Bar length for the equity curve and the Sharpe ratio, s. The annualised Sharpe ratio is reported only for runs of at least 1 day (86,400 s); shorter runs report n/a (NaN in Python, null in summary.json) |
initial_capital |
number | 0 |
Starting capital, quote currency. The drawdown percentage is the largest fall from peak equity divided by this value; with 0 it is not reported (NaN in Python, null in summary.json) |
markout_horizons_s |
string | "1,10,60" |
Post-fill markout horizons in seconds, comma separated ("0.5,5" is allowed); "" turns markouts off. The run stops the simulated clock at every fill time plus horizon to read the venue mid there, so the shortest horizon also bounds how often the run loop is entered (Backtesting) |
output_dir |
string | "runs/backtest" |
Where equity.csv, fills.csv, orders.csv and summary.json are written |
journal_out |
string | "" |
When set, the backtest session is also recorded as a .fmj journal |
own_orders_in_feed |
bool | true |
The simulated venues' market data shows the strategy's resting orders, as a live venue's does, and the engine takes them out where it needs to (ctx.own_qty, ctx.best_ex_self, ctx.queue_ahead); false forwards recorded data as recorded (Backtesting) |
initial_state |
string | "state_file" |
Where the strategy's starting state comes from: state_file reads [strategy] state_file when set; journal takes the data journal's: the state the recorded session restored at its start, else its first state snapshot, with the positions of that snapshot, or with the positions the session restored and reconciled before its first order (Backtesting); none starts the strategy fresh. journal needs a journal as the data source |
params_from_journal |
bool | false |
The data journal's parameter updates reach the strategy at the times the recorded session received them, their fields mapped by name and type onto the strategy's schema (as fastmm-replay maps them); the data source must be a journal |
balances_from_journal |
bool | false |
Each venue's account starts from the first balance snapshot the journal source recorded for it (every asset at its total; account-wide margin rows are left out); a venue without one keeps [backtest.balances]. The data source must be one journal |
Command-line flags of fastmm-backtest (--data, --strategy, --param key=value, --seed, --duration, --out, --journal-out) override these values (Command lines).
[backtest.venues.<name>]¶
One venue's own settings; <name> is a [venues.<name>] that an instrument trades on, or a member of such a venue's pool (pool_of). Every venue an instrument names is simulated separately, and so is each pool member: orders, acknowledgements, fills and market data of an instrument go through its venue's latency and its venue's two connections, so a slow venue never delays a fast one, and an order's events come back on the member it went to. A missing key takes the value above. An unknown name, a venue without instruments or an unknown key is an error.
| Key | Type | Default | Meaning |
|---|---|---|---|
latency_fixed_us |
int | [backtest] |
Fixed latency for orders to this venue and acknowledgements back, µs |
latency_jitter_us |
int | [backtest] |
Jitter added to that latency, µs |
latency_ack_us |
int | [backtest] latency_ack_us, else this venue's latency_fixed_us |
Fixed latency for acknowledgements and fills back from this venue, µs |
latency_ack_jitter_us |
int | [backtest] latency_ack_jitter_us, else this venue's latency_jitter_us |
Jitter added to that latency, µs |
latency_md_us |
int | [backtest] |
Fixed market-data latency, µs |
latency_md_jitter_us |
int | [backtest] |
Market-data latency jitter, µs |
latency_cancel_us |
int | [backtest], else this venue's order latency |
Fixed latency for cancels to this venue, µs; the cancel key it does not set comes from [backtest], else from this venue's order latency |
latency_cancel_jitter_us |
int | [backtest], else this venue's order jitter |
Jitter added to that latency, µs |
order_service_us |
int | [backtest] |
Time this venue takes per order message of one connection, µs; each pool member is a connection of its own |
md_arrival |
string | [backtest] |
venue or recorded, for this venue's market data |
p_drop |
number | [backtest] |
Probability that an outbound order message to this venue is lost |
supports_replace |
bool | [engine] supports_replace |
In-place replace on this venue: the engine replaces quotes on its instruments if [engine] supports_replace is true, cancel and new otherwise |
stp |
bool | [risk] stp |
Self-trade prevention (cancel the taker) on this venue |
orders_10s |
int | [backtest] |
This venue's order count per 10 s (0: unlimited); per pool member, so the engine's routing sees each account's window |
orders_1d |
int | [backtest] |
This venue's order count per day |
[backtest.balances]¶
The account the simulated venue keeps: <ASSET> = "<amount>", one line per asset (a decimal string, at least 0). Without the table, and without balances_from_journal, the venues take every order whatever it costs and send no balances. With it, every venue an instrument trades on starts with these amounts, and an asset its instruments use that is not listed starts at 0; [backtest.venues.<name>.balances] replaces the table for one venue.
The venue holds each order's amount and refuses an order its free balance cannot cover with InsufficientBalance (venue code -2010, as Binance), and reports its balances as a live venue does: a snapshot at the start and an update after every order event that moved one. The engine's balance table, [risk] check_balance, ctx.balance and the strategies read those reports as they read a live venue's (Backtesting).
[sim]¶
The synthetic market; free-form in the schema. Prices and sizes use the first instrument's tick and lot. fastmm-sim-exchange and fastmm-sim-itch read their own keys from [sim] too (Simulated exchange, fastmm-sim-itch).
| Key | Type | Default | Meaning |
|---|---|---|---|
seed |
int | 1 |
Used when [backtest] seed is not set |
duration_s |
int | 60 |
Used when [backtest] duration_s is not set, s |
start_mid |
decimal | "60000" |
Initial mid price |
seed_levels |
int | 20 |
Price levels populated before the run starts |
limit_rate_per_s |
number | 200 |
Limit-order arrivals per second |
cancel_rate_per_order_s |
number | 0.5 |
Cancellation hazard of each resting order, per second |
market_rate_per_s |
number | 10 |
Market-order arrivals per second |
mid_step_rate_per_s |
number | 2 |
Steps of one tick in the latent mid price, per second |
offset_p |
number | 0.35 |
In (0, 1]. Limit orders land k ticks from the touch with probability p(1-p)^k |
base_spread_ticks |
int | 1 |
Distance of the touch from the latent mid, ticks, at least 1 |
limit_qty_median_lots |
number | 200 |
Median limit-order size in lots (log-normal) |
market_qty_median_lots |
number | 100 |
Median market-order size in lots (log-normal) |
regimes |
bool | true |
Switch between a calm and a volatile regime |
volatile_mult |
number | 4.0 |
Multiplier on mid steps and market orders in the volatile regime |
depth_update_ms |
int | 100 |
Book changes are aggregated into one depth diff per interval, ms, like Binance @depth@100ms |
book_ticker |
bool | true |
Also publish top-of-book updates, at the same flush as the depth diff |
Example: configs/backtest-example.toml.