Type/to search
Getting Started
Welcome to FMZ Quant Trading Platform
Quick Start
Key Security
Platform Basics
Account and Billing
Live Robot Billing and Top-up
Sub-accounts
Exchange
General Protocol
Local Credential Files
Exchange-Specific Notes
Securities and Futures
Crypto
Docker
Strategy Library
Live Trading
Writing Strategies
Development Tools
Backtesting System
Advanced Topics
Data and Research
Integrations

There are two backtest modes, simulated tick and real tick. Both are based on real historical data: simulated tick generates ticks from K-lines, real tick replays recorded ticks. The latter is more accurate and slower.

Simulated tick

Within the price frame of each base K-line (open, high, low, close), the engine generates 2-14 simulated ticks along the path open → low/high → close and spreads the bar's volume over them; market calls return the data of the current simulated tick. Each base K-line therefore has several backtest time points, and a strategy can trade several times inside one bar instead of only at the close. The smaller the base K-line period, the closer the ticks follow the real price path, and the slower the backtest. Details: Simulated-tick mechanism, Backtesting mechanism.

Simulated order book: best ask = tick close + one tick + slippage, best bid = close − one tick − slippage (slippage counted in ticks); GetDepth() returns several simulated levels spaced by that amount, each with the configured "amount per level".

Real tick

Uses per-second ticks recorded by the platform, including order-book depth (configurable, up to 20 levels) and, optionally, replayed trade prints; GetDepth() and GetTrades() return the replayed real data. Because the data is large and the backtest slow, one backtest may use at most 50 MB of data, which limits the time range; to cover a longer range, lower the depth levels and do not use trade prints. Early periods may have no real-tick data, so do not choose a start time that is too early.

At a given market moment, calling each of GetTicker(), GetDepth(), GetTrades() and GetRecords() once does not move the backtest time; calling the same function again jumps to the next market moment. In real-tick mode keep the Sleep() in the strategy loop short (e.g. 100 ms).

Order matching

Both modes use the same matching rules:

  • Orders fill when the price is touched, and always in full; there are no partial fills in a backtest.
  • A market order fills on the current tick at the best ask/bid; the amount of a spot market buy order is in the quote currency.
  • A limit buy fills when its price is at or above the best ask, a limit sell when its price is at or below the best bid; this is checked on every tick after the order is placed. An order that fills immediately when placed fills at the market price and pays the taker fee; an order that rests in the book and is touched later fills at its own price and pays the maker fee.
  • In real-tick mode an order resting exactly at the best bid/ask fills only after the volume queued ahead of it has been consumed.
  • Futures freeze margin of notional value ÷ leverage; when the market data includes funding rates, perpetual contracts are charged funding.

Effect of data granularity

The same strategy produces different trade counts and P&L at different data granularities (real tick, simulated tick with a small base period, simulated tick with a large base period, ...). Coarse data backtests faster but may give misleading results, so use fine granularity where possible. The following strategy can be backtested at several granularities for comparison:

javascript
/*backtest start: 2025-04-01 08:00:00 end: 2025-04-18 00:00:00 period: 1m exchanges: [{"eid":"Binance","currency":"BTC_USDT","balance":1000000}] mode: 1 */ var delta = 50 var lotSize = 0.001 var lastPrice = null var direction = null function main() { while (true) { var ticker = _C(exchange.GetTicker) if (!lastPrice) { lastPrice = ticker.Last } var diff = ticker.Last - lastPrice if ((!direction || direction == "long") && diff >= delta) { // Price rose above the threshold -> go short exchange.Sell(ticker.Last, lotSize) Log("Short @", ticker.Last) direction = "short" } else if ((!direction || direction == "short") && diff <= -delta) { // Price fell below the threshold -> go long exchange.Buy(ticker.Last, lotSize) Log("Long @", ticker.Last) direction = "long" } // Keep it short in tick mode; it has no effect in K-line mode Sleep(100) } }