Type/to search
Welcome to FMZ Quant Trading Platform
Programming Languages
JavaScript
TypeScript
Python
Rust
C++
MyLanguage
PINE Language
Blockly Visual Programming
Workflow
Key Security
Live Trading
Strategy Library
Docker
Deploy Docker
One-Click Docker Rental
Manual Deployment of Bot
Docker Operation Precautions
Global IP Address Specification
Command Line Parameters for Bot Program
Live Trading Data Migration
Docker Monitor
Exchange
Strategy Editor
Backtesting System
Strategy Entry Functions
Strategy Framework and API Functions
Template Library
Strategy Parameters
Interactive Controls
Options Trading
Rust Strategy Development Guide
C++ Strategy Writing Guide
JavaScript Strategy Writing Guide
Web3
Built-in Libraries
Extended API Interface
MCP Service
Trading Terminal
Data Explorer
Alpha Factor Analysis Tool
General Protocol
Debugging Tool
Remote Editing
Import and Export of Complete Strategies
Multi-language Support
Live Trading and Strategy Grouping
Live Trading Display
Strategy Sharing and Renting
Live Trading Message Push
Common Causes of Live Trading Errors and Abnormal Exits
Exchange-Specific Notes

After completing the design of your quantitative trading strategy, how do you verify key metrics such as the strategy's logical correctness and expected returns? Obviously, you cannot test directly in the market with real funds. The correct approach is to backtest the strategy using historical data, evaluating its profitability and risk characteristics by analyzing the strategy's performance in historical market conditions.

The FMZ Quant Trading Platform divides backtesting modes into Real-tick Level backtesting and Simulated-tick Level backtesting. Real-tick Level backtesting is completely based on complete historical data for backtesting; Simulated-tick Level backtesting generates tick data based on real candlestick data for backtesting. Both are based on real historical data for backtesting, but Real-tick Level backtesting has more accurate data and more reliable results. It should be noted that backtesting only reflects the performance of strategies under historical data, and historical data cannot fully represent future market conditions, so backtesting results should be treated with a rational and objective attitude.

Simulated-tick Level backtesting generates simulated tick data based on the underlying candlestick period, generating up to 12 backtesting time points on each underlying candlestick period. Real-tick Level backtesting uses real collected second-by-second tick data, which has a large amount of data and slower backtesting speed, so it is not suitable for backtesting particularly long time ranges. FMZ Quant's backtesting mechanism allows strategies to trade multiple times on a single candlestick, avoiding the limitation of only being able to trade at the closing price, ensuring accuracy while balancing backtesting speed.

Backtesting System Mechanism Description

  • Simulated-tick Level
    Simulated-tick Level backtesting generates tick data for backtesting based on the underlying candlestick data of the backtesting system, simulating tick data within the price framework composed of the high, low, open, and close prices of the given underlying candlestick bar according to specific algorithms, serving as real-time tick data on the backtesting time series, which is returned when the strategy program calls the interface. For details, please refer to: Backtesting System Simulated Level Mechanism Description.

  • Real-tick Level
    Real-tick level backtesting uses real tick-level data in the bar time series. For strategies based on tick-level data, using real-tick level backtesting is closer to actual conditions. The ticks in real-tick level backtesting are real recorded data, not simulated. It supports depth data, market trade record data playback, supports custom depth, and supports trade-by-trade data. Real-tick level backtesting data supports a maximum of 50MB, with no limit on the backtesting time range within the data limit. To maximize the backtesting time range as much as possible, you can reduce the depth level value settings and not use trade-by-trade data to extend the backtesting time range. Call GetDepth and GetTrades functions to obtain playback market data. At a certain market data moment on the timeline, calling GetTicker, GetTrades, GetDepth, GetRecords will not move time forward multiple times on the backtesting timeline (will not trigger jumping to the next market data moment). Repeated calls to any of the above functions will push the backtesting time forward on the backtesting timeline (jump to the next market data moment). When using real-tick level backtesting, it is not advisable to select too early a time, as earlier time periods may not have real-tick level data.

Real-tick Level and Simulated-tick Level mode backtesting system order matching mechanism: Order matching is executed based on price-taking and full-fill execution. Therefore, partial fill scenarios cannot be tested in the backtesting system.

The following test code will exhibit different performance for different data granularities (A. Live trading level backtest, B. Simulation level backtest (smaller underlying K-line period), C. Simulation level backtest (larger underlying K-line period), etc.). Both the number of trades and profit/loss results will vary. When backtesting, the smallest possible data granularity should be maintained. While backtesting may be faster with larger data granularity, the results obtained may lack objectivity.

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 rises above threshold -> Go short exchange.Sell(ticker.Last, lotSize) Log("Short @", ticker.Last) direction = "short" } else if ((!direction || direction == "short") && diff <= -delta) { // Price falls below threshold -> Go long exchange.Buy(ticker.Last, lotSize) Log("Long @", ticker.Last) direction = "long" } // Should be as short as possible in Tick mode, no impact in K-line backtest Sleep(100) } }

The backtesting system supports backtesting strategies written in the following languages: JavaScript, TypeScript, Python, Rust, C++, PINE, My Language, Blockly visual programming, and the Workflow workflow.

  1. Backtesting of JavaScript and C++ strategies is performed in the browser, and these strategies require no additional software, libraries, or modules to be installed when running in either live trading or backtesting.

  2. Backtesting of Python strategies is performed on the docker, and can be run either on FMZ Quant's public servers or on the user's own docker. Both live trading and backtesting depend on the Python environment installed on the system where the docker resides; if you need to use certain libraries, please install them yourself, as FMZ Quant's public servers only support commonly used Python libraries.

  3. Backtesting of JavaScript strategies supports debugging in Chrome's DevTools; see the reference documentation for details.

  4. Workflow strategies support backtesting, allowing you to visually view node execution status and the data flow process.

  5. Rust strategies are compiled by the platform server during backtesting, and the compiled module runs in the browser-based backtesting system; third-party crate dependencies declared via frontmatter in the strategy are automatically fetched at compile time, with no need to install any toolchain locally.

  • Cryptocurrency
    Supports mainstream cryptocurrency spot and futures exchanges, covering all trading pairs data from exchanges.

  • Futu Securities
    Supports multiple markets including Hong Kong stocks and US stocks.

    Backtesting Notes: The backtesting system currently only supports Futu daily level data:

    javascript
    /*backtest start: 2024-05-01 00:00:00 end: 2025-02-17 00:00:00 period: 1d basePeriod: 1d exchanges: [{"eid":"Futures_Futu","currency":"STOCK","fee":[0.03,0.03]}] */ function main() { let info = exchange.SetContractType("TLSA.US") // Set stock symbol: Tesla Log("info:", info) // info: {"InstrumentID":"TLSA.US","LotTick":1,"PriceTick":0.01,"VolumeMultiple":1} Log(exchange.GetTicker()) // {"Time":1714482000000,"Symbol":"TLSA.US","Open":0.62,"High":0.63,"Low":0.61,"Sell":0.63,"Buy":0.61,"Last":0.62,"Volume":0,"OpenInterest":0} }

The FMZ Quant Trading Platform backtest system parameter optimization feature allows you to set parameter combinations based on optimization options for each parameter during backtesting. On the "Simulation Backtest" page in the strategy parameters section, check the Optimization option on the right side of the strategy parameter to display optimization settings.

  • Minimum Value: Set the starting value of the parameter.
  • Maximum Value: Set the maximum value after the parameter increments.
  • Step Size: The increment amount for parameter changes.
  • Concurrent Threads:
    When optimizing parameters, set the number of threads for concurrent execution of each backtest parameter combination. This option only supports parameter optimization for JavaScript, PINE, and MyLanguage strategies, and does not support template parameter optimization.

The system generates parameter combinations based on Minimum Value, Maximum Value, and Step Size settings, and iterates through these parameter combinations for backtesting (i.e., executes one backtest for each parameter combination). Only strategy parameters of type number can be configured for parameter optimization in the backtest system.

In the "Simulated Backtest" tab (i.e., the backtesting system) of the Strategy Editing Page, you can configure options such as the backtest configuration and strategy parameters to run a strategy backtest. The backtest configuration is used to set conditions such as the backtest time range, exchanges, trading slippage, and fees; the strategy parameters are used to set the strategy's parameter options.

Once you have configured these parameters, you can run the strategy backtest according to your settings. So, how do you save these configured settings?

    1. You can use the "Save Backtest Settings" button on the Strategy Editing Page to record all backtest configuration information (including backtest settings and strategy parameter settings) as code within the strategy source code.
    1. When you click the "Save Strategy" button on the strategy editing page to save the strategy, the platform will automatically record the current backtest settings, strategy parameter configuration, and other information.

How does the backtesting system load the backtest configuration?

    1. When you refresh or reopen the strategy editing page, the system will preferentially and automatically load the backtest configuration recorded by the "Save Backtest Settings" button.
    1. If the current strategy code does not contain backtest configuration information recorded as a backtest comment (i.e., it was not saved in the strategy code via the "Save Backtest Settings" button), the backtesting system will automatically set the backtest configuration to the backtest settings from the last time the "Save Strategy" button was clicked for the current strategy.
    1. If you modify the backtest configuration information recorded as a comment at the beginning of the strategy code on the strategy editing page and need to sync the updated backtest configuration to the options in the strategy backtest interface, you can click the "Backtest Settings" button above the backtest section in the strategy editing area.

Examples

  • When you click "Save Backtest Settings", the format in which JavaScript/Python/C++/My Language/PINE strategies save the backtest settings to the strategy code differs slightly:

    javascript
    /*backtest start: 2021-06-26 00:00:00 end: 2021-09-23 00:00:00 period: 1d basePeriod: 1h exchanges: [{"eid":"Binance","currency":"BTC_USDT"}] */
    python
    '''backtest start: 2021-06-26 00:00:00 end: 2021-09-23 00:00:00 period: 1d basePeriod: 1h exchanges: [{"eid":"Binance","currency":"BTC_USDT"}] '''
    rust
    /*backtest start: 2021-06-26 00:00:00 end: 2021-09-23 00:00:00 period: 1d basePeriod: 1h exchanges: [{"eid":"Binance","currency":"BTC_USDT"}] */
    c++
    /*backtest start: 2021-06-26 00:00:00 end: 2021-09-23 00:00:00 period: 1d basePeriod: 1h exchanges: [{"eid":"Binance","currency":"BTC_USDT"}] */
  • My Language:

    mylang
    (*backtest start: 2021-06-26 00:00:00 end: 2021-09-23 00:00:00 period: 1d basePeriod: 1h exchanges: [{"eid":"Binance","currency":"BTC_USDT"}] *)
  • PINE Language:

    pine
    /*backtest start: 2021-06-26 00:00:00 end: 2021-09-23 00:00:00 period: 1d basePeriod: 1h exchanges: [{"eid":"Binance","currency":"BTC_USDT"}] */

The FMZ Quant Trading Platform's backtesting system supports custom data sources. The backtesting system uses the GET method to request custom URLs (publicly accessible addresses) to obtain external data sources for backtesting. The additional request parameters are as follows:

ParameterMeaningDescription
symbolSymbol nameSpot market data example: BTC_USDT, Futures market data example: BTC_USDT.swap, Perpetual futures funding rate data example: BTC_USDT.funding, Perpetual futures price index data example: BTC_USDT.index
eidExchangeFor example: OKX, Futures_OKX
roundData precisionWhen true, indicates that the data returned by the custom data source defines specific precision. The request sent by the FMZ Quant Trading Platform backtesting system to the custom data source is fixed as: round=true
periodK-line data period (milliseconds)For example: 60000 represents a 1-minute period
depthOrder book depth levels1-20
tradesWhether tick data is requiredTrue (1) / False (0)
fromStart timeUnix timestamp
toEnd timeUnix timestamp
detailRequest detailed information of the symbolWhen true, indicates that it needs to be provided by the custom data source. The request sent by the FMZ Quant Trading Platform backtesting system to the custom data source is fixed as: detail=true
custom--This parameter can be ignored

When the data source of spot exchange or futures exchange objects is set to custom data source (feeder), examples of requests sent by the backtesting system to the custom data source service:

url
http://customserver:9090/data?custom=0&depth=20&detail=true&eid=Bitget&from=1351641600&period=86400000&round=true&symbol=BTC_USDT&to=1611244800&trades=1 http://customserver:9090/data?custom=0&depth=20&detail=true&eid=Futures_OKX&from=1351641600&period=86400000&round=true&symbol=BTC_USDT.swap&to=1611244800&trades=1

The returned format must be one of the following two formats (automatically recognized by the system):

  • Simulated-level Tick, here is a JSON data example:
    json
    { "detail": { "eid": "Binance", "symbol": "BTC_USDT", "alias": "BTCUSDT", "baseCurrency": "BTC", "quoteCurrency": "USDT", "marginCurrency": "USDT", "basePrecision": 5, "quotePrecision": 2, "minQty": 0.00001, "maxQty": 9000, "minNotional": 5, "maxNotional": 9000000, "priceTick": 0.01, "volumeTick": 0.00001, "marginLevel": 10 }, "schema":["time", "open", "high", "low", "close", "vol"], "data":[ [1564315200000, 9531300, 9531300, 9497060, 9497060, 787], [1564316100000, 9495160, 9495160, 9474260, 9489460, 338] ] }
  • Live-level Tick, here is a JSON data example:
    Tick-level backtest data (includes order book depth information, depth format is an array of [price, quantity]. Can include multiple levels of depth, asks sorted in ascending order by price, bids sorted in descending order by price).
    json
    { "detail": { "eid": "Binance", "symbol": "BTC_USDT", "alias": "BTCUSDT", "baseCurrency": "BTC", "quoteCurrency": "USDT", "marginCurrency": "USDT", "basePrecision": 5, "quotePrecision": 2, "minQty": 0.00001, "maxQty": 9000, "minNotional": 5, "maxNotional": 9000000, "priceTick": 0.01, "volumeTick": 0.00001, "marginLevel": 10 }, "schema":["time", "asks", "bids", "trades", "close", "vol"], "data":[ [1564315200000, [[9531300, 10]], [[9531300, 10]], [[1564315200000, 0, 9531300, 10]], 9497060, 787], [1564316100000, [[9531300, 10]], [[9531300, 10]], [[1564316100000, 0, 9531300, 10]], 9497060, 787] ] }
FieldDescription
detailDetailed information of the requested instrument, including quote currency name, base currency name, precision, minimum order quantity, etc.
schemaSpecifies the column attributes in the data array, case-sensitive. Limited to time, open, high, low, close, vol, asks, bids, trades
dataData recorded according to the column structure set by schema

detail field

FieldDescription
eidExchange ID, note that spot and futures of the same exchange use different eids
symbolTrading instrument code
aliasThe corresponding symbol of the current trading instrument code on the exchange
baseCurrencyBase currency
quoteCurrencyQuote currency
marginCurrencyMargin currency
basePrecisionBase currency precision
quotePrecisionQuote currency precision
minQtyMinimum order quantity
maxQtyMaximum order quantity
minNotionalMinimum order amount
maxNotionalMaximum order amount
priceTickMinimum price tick size
volumeTickMinimum volume tick size
marginLevelFutures leverage multiplier
contractTypeFor perpetual contracts set to: swap, the backtest system will continue to send funding rate and price index requests

Special column attributes asks, bids, trades description:

FieldDescriptionRemarks
asks / bids[[price, quantity], ...]For example, data in the Live-level Tick data example: [[9531300, 10]]
trades[[time, direction(0:buy,1:sell), price, quantity], ...]For example, data in the Live-level Tick data example: [[1564315200000, 0, 9531300, 10]]

When backtesting perpetual contracts on futures exchanges, custom data sources also need to provide additional funding rate data and price index data. Only when the requested market data is returned and the detail field in the return structure contains the "contractType": "swap" key-value pair, will the backtest system continue to send funding rate requests.
After the backtest system receives the funding rate data, it will continue to send price index data requests.

Funding rate data structure is as follows:

json
{ "detail": { "eid": "Futures_Binance", "symbol": "BTC_USDT.funding", "alias": "BTC_USDT.funding", "baseCurrency": "BTC", "quoteCurrency": "USDT", "marginCurrency": "", "basePrecision": 8, "quotePrecision": 8, "minQty": 1, "maxQty": 10000, "minNotional": 1, "maxNotional": 100000000, "priceTick": 1e-8, "volumeTick": 1e-8, "marginLevel": 10 }, "schema": [ "time", "open", "high", "low", "close", "vol" ], "data": [ [ 1584921600000, -16795, -16795, -16795, -16795, 0 ], [ 1584950400000, -16294, -16294, -16294, -16294, 0 ] // ... ] }
  • Adjacent period interval is 8 hours
  • For example, Binance funding rate updates every 8 hours, why is the funding rate data -16795?
    This is because, like K-line data, to avoid floating-point precision loss during network transmission, data is represented as integers; funding rate data can also be negative.

Example of funding rate data request sent by the backtest system:

url
http://customserver:9090/data?custom=0&depth=20&detail=true&eid=Futures_Binance&from=1351641600&period=86400000&round=true&symbol=BTC_USDT.funding&to=1611244800&trades=0

Price index data structure is as follows:

json
{ "detail": { "eid": "Futures_Binance", "symbol": "BTC_USDT.index", "alias": "BTCUSDT", "baseCurrency": "BTC", "quoteCurrency": "USDT", "contractType": "index", "marginCurrency": "USDT", "basePrecision": 3, "quotePrecision": 1, "minQty": 0.001, "maxQty": 1000, "minNotional": 0, "maxNotional": 1.7976931348623157e+308, "priceTick": 0.1, "volumeTick": 0.001, "marginLevel": 10, "volumeMultiple": 1 }, "schema": [ "time", "open", "high", "low", "close", "vol" ], "data": [ [1584921600000, 58172, 59167, 56902, 58962, 0], [1584922500000, 58975, 59428, 58581, 59154, 0], // ... ] }

Example of price index data request sent by the backtest system:

url
http://customserver:9090/data?custom=0&depth=20&detail=true&eid=Futures_Binance&from=1351641600&period=86400000&round=true&symbol=BTC_USDT.index&to=1611244800&trades=0

Specify the data source address, for example: http://120.24.2.20:9090/data. The custom data source service program is written in Golang:

golang
package main import ( "fmt" "net/http" "encoding/json" ) func Handle (w http.ResponseWriter, r *http.Request) { // e.g. set on backtest DataSourse: http://xxx.xx.x.xx:9090/data // request: GET http://xxx.xx.x.xx:9090/data?custom=0&depth=20&detail=true&eid=OKX&from=1584921600&period=86400000&round=true&symbol=BTC_USDT&to=1611244800&trades=1 // http://xxx.xx.x.xx:9090/data?custom=0&depth=20&detail=true&eid=Futures_Binance&from=1599958800&period=3600000&round=true&symbol=BTC_USDT.swap&to=1611244800&trades=0 fmt.Println("request:", r) // response defer func() { // response data /* e.g. data { "detail": { "eid": "Binance", "symbol": "BTC_USDT", "alias": "BTCUSDT", "baseCurrency": "BTC", "quoteCurrency": "USDT", "marginCurrency": "USDT", "basePrecision": 5, "quotePrecision": 2, "minQty": 0.00001, "maxQty": 9000, "minNotional": 5, "maxNotional": 9000000, "priceTick": 0.01, "volumeTick": 0.00001, "marginLevel": 10 }, "schema": [ "time", "open", "high", "low", "close", "vol" ], "data": [ [1610755200000, 3673743, 3795000, 3535780, 3599498, 8634843151], [1610841600000, 3599498, 3685250, 3385000, 3582861, 8015772738], [1610928000000, 3582499, 3746983, 3480000, 3663127, 7069811875], [1611014400000, 3662246, 3785000, 3584406, 3589149, 7961130777], [1611100800000, 3590194, 3641531, 3340000, 3546823, 8936842292], [1611187200000, 3546823, 3560000, 3007100, 3085013, 13500407666], [1611273600000, 3085199, 3382653, 2885000, 3294517, 14297168405], [1611360000000, 3295000, 3345600, 3139016, 3207800, 6459528768], [1611446400000, 3207800, 3307100, 3090000, 3225990, 5797803797], [1611532800000, 3225945, 3487500, 3191000, 3225420, 8849922692] ] } */ // /* Simulation-level Tick ret := map[string]interface{}{ "detail": map[string]interface{}{ "eid": "Binance", "symbol": "BTC_USDT", "alias": "BTCUSDT", "baseCurrency": "BTC", "quoteCurrency": "USDT", "marginCurrency": "USDT", "basePrecision": 5, "quotePrecision": 2, "minQty": 0.00001, "maxQty": 9000, "minNotional": 5, "maxNotional": 9000000, "priceTick": 0.01, "volumeTick": 0.00001, "marginLevel": 10, }, "schema": []string{"time","open","high","low","close","vol"}, "data": []interface{}{ []int64{1610755200000, 3673743, 3795000, 3535780, 3599498, 8634843151}, // 1610755200000 : 2021-01-16 08:00:00 []int64{1610841600000, 3599498, 3685250, 3385000, 3582861, 8015772738}, // 1610841600000 : 2021-01-17 08:00:00 []int64{1610928000000, 3582499, 3746983, 3480000, 3663127, 7069811875}, []int64{1611014400000, 3662246, 3785000, 3584406, 3589149, 7961130777}, []int64{1611100800000, 3590194, 3641531, 3340000, 3546823, 8936842292}, []int64{1611187200000, 3546823, 3560000, 3007100, 3085013, 13500407666}, []int64{1611273600000, 3085199, 3382653, 2885000, 3294517, 14297168405}, []int64{1611360000000, 3295000, 3345600, 3139016, 3207800, 6459528768}, []int64{1611446400000, 3207800, 3307100, 3090000, 3225990, 5797803797}, []int64{1611532800000, 3225945, 3487500, 3191000, 3225420, 8849922692}, }, } // */ /* Live trading-level Tick ret := map[string]interface{}{ "detail": map[string]interface{}{ "eid": "Binance", "symbol": "BTC_USDT", "alias": "BTCUSDT", "baseCurrency": "BTC", "quoteCurrency": "USDT", "marginCurrency": "USDT", "basePrecision": 5, "quotePrecision": 2, "minQty": 0.00001, "maxQty": 9000, "minNotional": 5, "maxNotional": 9000000, "priceTick": 0.01, "volumeTick": 0.00001, "marginLevel": 10, }, "schema": []string{"time", "asks", "bids", "trades", "close", "vol"}, "data": []interface{}{ []interface{}{1610755200000, []interface{}{[]int64{9531300, 10}}, []interface{}{[]int64{9531300, 10}}, []interface{}{[]int64{1610755200000, 0, 9531300, 10}}, 9497060, 787}, []interface{}{1610841600000, []interface{}{[]int64{9531300, 15}}, []interface{}{[]int64{9531300, 15}}, []interface{}{[]int64{1610841600000, 0, 9531300, 11}}, 9497061, 789}, }, } */ b, _ := json.Marshal(ret) w.Write(b) }() } func main () { fmt.Println("listen http://localhost:9090") http.HandleFunc("/data", Handle) http.ListenAndServe(":9090", nil) }

Test strategy, JavaScript example:

javascript
/*backtest start: 2021-01-16 08:00:00 end: 2021-01-22 00:00:00 period: 1d basePeriod: 1d exchanges: [{"eid":"OKX","currency":"BTC_USDT","feeder":"http://120.24.2.20:9090/data"}] args: [["number",2]] */ function main() { var ticker = exchange.GetTicker() var records = exchange.GetRecords() Log(exchange.GetName(), exchange.GetCurrency()) Log(ticker) Log(records) }

FMZ Quant Trading Platform has open-sourced local backtesting engines for JavaScript and Python languages, supporting custom underlying K-line periods during backtesting.

Using Python as an example, here's a brief guide on how to use the local backtesting engine:

python
'''backtest start: 2022-02-19 00:00:00 end: 2022-03-22 12:00:00 period: 15m exchanges: [{"eid":"Binance","currency":"BTC_USDT","balance":10000,"stocks":0}] ''' # Part 1 ----------------------------------- # Initialize the backtesting engine, backtest contains the engine configuration # which is consistent with the FMZ platform's online backtesting system configuration # Read the configuration string above via __doc__ and initialize the backtesting environment from fmz import * task = VCtx(__doc__) # initialize backtest engine from __doc__ # End ----------------------------------- # Part 2 ----------------------------------- # Below is an example of strategy code to be tested (complete strategy code can be copied from FMZ platform) # Note: When copying strategy code only, it does not include parameter design, interaction design, and other configurations def onTick(): ticker = _C(exchange.GetTicker) LogStatus(_D(), ticker.Last) def main(): exchange.SetCurrency("ETH_USDT") # exchange.SetContractType("swap") # If testing futures exchange objects, you need to set the contract, for example, set to perpetual contract here Log(exchange.GetAccount()) while True: onTick() Sleep(1000) # End ----------------------------------- # Part 3 ----------------------------------- # Execute backtesting and catch the termination signal, EOF exception will be triggered when backtesting ends # After catching the exception, you can output backtesting result data or display backtesting charts try: main() except: print("Strategy testing completed.") print(task.Join(False)) # print backtest result # task.Show() # or show backtest chart # End -----------------------------------

  • Shortcut for switching between strategy editor and backtest page
    Use Ctrl + , to switch between backtest page and strategy editor page. Hold Ctrl and press ,.
  • Shortcut for saving strategy
    Use Ctrl + s to save strategy.
  • Shortcut for starting backtest
    Use Ctrl + b to start backtest.

  • Backtest System Log Data Download
    Open the specific strategy and switch to the "Backtest Page" to run strategy backtesting. After the backtest is completed, there is a "Download Table" button in the upper right corner of the displayed "Status Information" bar. Click it to download the CSV format file of the status bar data at the end of the backtest.
  • Backtest System Status Bar Data Download
    Open the specific strategy and switch to the "Backtest Page" to run strategy backtesting. After the backtest is completed, there is a "Download Table" button in the upper right corner of the displayed "Log Information" bar. Click it to download the CSV format file of the backtest log data.

Backtesting system Sharpe ratio algorithm source code:

javascript
function returnAnalyze(totalAssets, profits, ts, te, period, yearDays) { // force by days period = 86400000 if (profits.length == 0) { return null } var freeProfit = 0.03 // 0.04 var yearRange = yearDays * 86400000 var totalReturns = profits[profits.length - 1][1] / totalAssets var annualizedReturns = (totalReturns * yearRange) / (te - ts) // MaxDrawDown var maxDrawdown = 0 var maxAssets = totalAssets var maxAssetsTime = 0 var maxDrawdownTime = 0 var maxDrawdownStartTime = 0 var winningRate = 0 var winningResult = 0 for (var i = 0; i < profits.length; i++) { if (i == 0) { if (profits[i][1] > 0) { winningResult++ } } else { if (profits[i][1] > profits[i - 1][1]) { winningResult++ } } if ((profits[i][1] + totalAssets) > maxAssets) { maxAssets = profits[i][1] + totalAssets maxAssetsTime = profits[i][0] } if (maxAssets > 0) { var drawDown = 1 - (profits[i][1] + totalAssets) / maxAssets if (drawDown > maxDrawdown) { maxDrawdown = drawDown maxDrawdownTime = profits[i][0] maxDrawdownStartTime = maxAssetsTime } } } if (profits.length > 0) { winningRate = winningResult / profits.length } // trim profits var i = 0 var datas = [] var sum = 0 var preProfit = 0 var perRatio = 0 var rangeEnd = te if ((te - ts) % period > 0) { rangeEnd = (parseInt(te / period) + 1) * period } for (var n = ts; n < rangeEnd; n += period) { var dayProfit = 0.0 var cut = n + period while (i < profits.length && profits[i][0] < cut) { dayProfit += (profits[i][1] - preProfit) preProfit = profits[i][1] i++ } perRatio = ((dayProfit / totalAssets) * yearRange) / period sum += perRatio datas.push(perRatio) } var sharpeRatio = 0 var volatility = 0 if (datas.length > 0) { var avg = sum / datas.length; var std = 0; for (i = 0; i < datas.length; i++) { std += Math.pow(datas[i] - avg, 2); } volatility = Math.sqrt(std / datas.length); if (volatility !== 0) { sharpeRatio = (annualizedReturns - freeProfit) / volatility } } return { totalAssets: totalAssets, yearDays: yearDays, totalReturns: totalReturns, annualizedReturns: annualizedReturns, sharpeRatio: sharpeRatio, volatility: volatility, maxDrawdown: maxDrawdown, maxDrawdownTime: maxDrawdownTime, maxAssetsTime: maxAssetsTime, maxDrawdownStartTime: maxDrawdownStartTime, winningRate: winningRate } }