Backtesting System
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.
Backtesting System Modes
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. CallGetDepthandGetTradesfunctions to obtain playback market data. At a certain market data moment on the timeline, callingGetTicker,GetTrades,GetDepth,GetRecordswill 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.
Impact of Backtest Data Granularity on Backtesting
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 multiple programming languages
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.
-
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.
-
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.
-
Backtesting of JavaScript strategies supports debugging in Chrome's DevTools; see the reference documentation for details.
-
Workflow strategies support backtesting, allowing you to visually view node execution status and the data flow process.
-
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.
Exchanges Supported by Backtesting System
-
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} }
Backtest System Parameter Optimization
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 forJavaScript,PINE, andMyLanguagestrategies, 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.
Save Backtest Settings
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?
-
- 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.
-
- 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?
-
- 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.
-
- If the current strategy code does not contain backtest configuration information recorded as a
backtestcomment (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.
- If the current strategy code does not contain backtest configuration information recorded as a
-
- 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
backtestsection in the strategy editing area.
- 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
Examples
-
When you click "Save Backtest Settings", the format in which
JavaScript/Python/C++/My Language/PINEstrategies 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"}] */
Custom Data Source
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:
| Parameter | Meaning | Description |
|---|---|---|
| symbol | Symbol name | Spot 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 |
| eid | Exchange | For example: OKX, Futures_OKX |
| round | Data precision | When 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 |
| period | K-line data period (milliseconds) | For example: 60000 represents a 1-minute period |
| depth | Order book depth levels | 1-20 |
| trades | Whether tick data is required | True (1) / False (0) |
| from | Start time | Unix timestamp |
| to | End time | Unix timestamp |
| detail | Request detailed information of the symbol | When 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
Data Format
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,askssorted in ascending order by price,bidssorted 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] ] }
| Field | Description |
|---|---|
| detail | Detailed information of the requested instrument, including quote currency name, base currency name, precision, minimum order quantity, etc. |
| schema | Specifies the column attributes in the data array, case-sensitive. Limited to time, open, high, low, close, vol, asks, bids, trades |
| data | Data recorded according to the column structure set by schema |
detail field
| Field | Description |
|---|---|
| eid | Exchange ID, note that spot and futures of the same exchange use different eids |
| symbol | Trading instrument code |
| alias | The corresponding symbol of the current trading instrument code on the exchange |
| baseCurrency | Base currency |
| quoteCurrency | Quote currency |
| marginCurrency | Margin currency |
| basePrecision | Base currency precision |
| quotePrecision | Quote currency precision |
| minQty | Minimum order quantity |
| maxQty | Maximum order quantity |
| minNotional | Minimum order amount |
| maxNotional | Maximum order amount |
| priceTick | Minimum price tick size |
| volumeTick | Minimum volume tick size |
| marginLevel | Futures leverage multiplier |
| contractType | For 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:
| Field | Description | Remarks |
|---|---|---|
| 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
Custom Data Source Example
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)
}
Local Backtesting Engine
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 -----------------------------------
Backtest Page Shortcuts
- Shortcut for switching between strategy editor and backtest page
UseCtrl + ,to switch between backtest page and strategy editor page. HoldCtrland press,. - Shortcut for saving strategy
UseCtrl + sto save strategy. - Shortcut for starting backtest
UseCtrl + bto start backtest.
Backtest Data Download
- 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.
Backtest System Sharpe Ratio Algorithm
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
}
}