当您完成了一个量化交易策略的设计工作后,怎么才能知道您这个策略的逻辑、策略收益方向等基本情况?当然我们不能直接拿真金白银去交易市场上跑策略,我们可以用历史数据来测试您的策略。看看您的策略在历史数据中盈利如何。
发明者量化交易平台将回测模式分为实盘级 Tick回测和模拟级 Tick回测。实盘级 Tick回测完全按照完整的历史数据回测;模拟级 Tick回测则根据真实K线数据生成tick数据来进行回测。两者都是根据真实历史数据回测的,但实盘级 Tick回测的数据更精准,结果更加可信。但是回测仅仅是策略在历史数据下的表现,历史数据并不能完全代表将来的行情,所以对待回测结果要理性、客观。
模拟级 Tick回测根据底层K线周期生成模拟的tick数据,每个底层K线周期上最多将生成12个回测时间点。而实盘级 Tick回测使用的是真实收集的逐秒tick数据,数据量很大,回测速度慢。因此不能回测特别长的时间范围,FMZ量化的回测机制可以使策略在一根K线上交易多次,避免了只能收盘价成交的情况,更加精准又兼顾了回测速度。
模拟级 Tick 模拟级 Tick回测是根据回测系统的底层K线数据,按照一定算法在给定的底层K线Bar的最高价、最低价、开盘价、收盘价的数值构成的价格框架内模拟出tick数据进行回测,作为回测时间序列上的实时tick数据,在策略程序调用接口时返回。具体可以参考:回测系统模拟级别机制说明。
实盘级 Tick
实盘级别回测是真实的tick级别数据在Bar的时间序列中。对于基于tick级别数据的策略来说,使用实盘级别回测更贴近真实。实盘级别回测tick是真实记录的数据,并非模拟生成。支持深度数据、市场成交记录数据回放,支持自定义深度,支持分笔数据。实盘级别回测数据最大支持50MB,在数据上限内不限制回测时间范围,如果需要尽可能增大回测时间范围,可以降低深度档位数值设置,不使用分笔数据以增加回测时间范围。调用GetDepth
、GetTrades
函数获取回放行情数据。在时间轴上某个行情数据时刻,调用GetTicker
,GetTrades
,GetDepth
,GetRecords
,不会多次推动时间在回测时间轴上移动(不会触发跳到下一个行情数据时刻)。对于以上某个函数重复调用,将推动回测时间在回测时间轴上移动(跳到下一个行情数据时刻)。回测时使用实盘级别回测不宜选择过早时间,可能过早时间段没有实盘级别数据。
实盘级Tick和模拟级Tick模式,回测系统成交撮合机制:订单成交撮合是按照见价成交,全量成交进行。因此回测系统中无法测试出部分成交的场景。
回测系统支持:JavaScript
、TypeScript
、Python
、C++
、PINE
、My语言
、Blockly
可视化编写设计的策略进行回测测试。
JavaScript 和 C++ 策略回测是在浏览器端进行,JavaScript 和 C++ 语言的策略在实盘、回测运行时不用安装任何其它软件、库或模块。
Python 语言的策略回测是在托管者上进行,可以在FMZ量化的公共服务器上回测,也可以在用户自己的托管者上回测。实盘和回测都依赖托管者所在系统上安装的Python环境,如果需要使用一些库,需要自行安装,FMZ量化的公共服务器上只支持常用的 Python 库。
支持 JavaScript 语言的策略回测时在Chrome浏览器DevTools中调试,参考说明。
发明者量化交易平台回测系统参数调优功能是在回测时根据各个参数的调优选项设置参数组合,在「模拟回测」页面中策略参数部分,勾选策略参数右侧的调优选项即可出现调优设置。
JavaScript
、PINE
、My语言
的策略参数调优,不支持模板上的参数调优。根据最小值
、最大值
、步长
设置,生成参数组合。回测系统遍历这些参数组合进行回测(即每种参数组合都回测一遍)。策略参数只有类型为数字型(number)的参数才可以在回测系统中进行参数调优设置。
在策略编辑页面,「模拟回测」分页中(即:回测系统)可以设置回测配置、策略参数等选项进行策略回测。回测配置是用来设置回测时间范围、回测的交易所、交易滑点、手续费等条件;策略参数则是设置策略的参数选项。
当设置好这些参数配置时便可按照设定回测策略,那么如何保存这些设置好的配置信息呢? - 1、可以使用策略编辑页面的「保存回测设置」按钮将所有回测配置信息(包含回测设置、策略参数设置)以代码形式记录在策略源码中。 - 2、在策略编辑页面点击「保存策略」按钮保存策略时,平台会自动记录当前的回测设置、策略参数配置等信息。
回测系统如何载入回测配置呢?
- 1、刷新策略编辑页面或者重新打开这个策略编辑页面时优先自动载入「保存回测设置」按钮所记录的回测配置信息。
- 2、如果当前策略代码中没有以注释形式backtest
记录的回测配置信息(通过「保存回测设置」按钮保存在策略代码),回测系统自动配置回测设置为当前策略最后一次点击「保存策略」按钮时的回测配置信息。
- 3、如果在策略编辑页面中修改了策略代码开头部分以注释形式记录的回测配置信息,需要把当前更新后的回测配置信息同步到策略回测界面的选项,可以点击策略编辑区域backtest
上方的「回测设置」按钮。
/*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"}]
*/
'''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"}]
'''
/*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"}]
*/
点击「保存回测设置」,JavaScript
/Python
/C++
/My语言
/PINE
语言的策略保存回测设置到策略代码时,格式略有差别:
My语言:
(*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语言:
/*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"}]
*/
发明者量化交易平台的回测系统支持自定义数据源,回测系统使用GET
方法请求自定义的URL(可公开可访问的网址)来获取外部数据源进行回测,附加的请求参数如下:
参数 | 意义 | 说明 |
---|---|---|
symbol | 品种名 | 现货行情数据例如:BTC_USDT ,期货行情数据例如:BTC_USDT.swap ,期货永续合约资金费率数据例如:BTC_USDT.funding ,期货永续合约价格指数数据例如:BTC_USDT.index |
eid | 交易所 | 例如:OKX、Futures_OKX |
round | 数据精度 | 为true,表示由自定义数据源反馈的数据中定义具体精度。发明者量化交易平台回测系统向自定义数据源发送的请求固定为:round=true |
period | K线数据的周期(毫秒) | 例如:60000 为1分钟周期 |
depth | 深度档数 | 1-20 |
trades | 是否需要分笔数据 | 真(1)/假(0) |
from | 开始时间 | unix时间戳 |
to | 结束时间 | unix时间戳 |
detail | 请求数据的品种详细信息 | 为true,表示需要由自定义数据源提供。发明者量化交易平台回测系统向自定义数据源发送的请求固定为:detail=true |
custom | – | 可以忽略该参数 |
现货交易所、期货交易所对象的数据源设置为自定义数据源(feeder)时回测系统向自定义数据源服务发送请求的例子:
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
返回的格式必须为以下两种格式其中之一(系统自动识别):
{
"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]
]
}
[价格, 量]
的数组。可有多级深度,asks
为价格升序,bids
为价格倒序)。 {
"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]
]
}
字段 | 说明 |
---|---|
detail | 请求数据的品种详细信息,包含计价币名称、交易币名称,精度,最小下单量等 |
schema | 指定data数组中列的属性,区分大小写。仅限于 time, open, high, low, close, vol, asks, bids, trades |
data | 按照schema设置的列结构,记录的数据。 |
detail字段
字段 | 说明 |
---|---|
eid | 交易所Id,注意某个交易所现货与期货是不同的eid |
symbol | 交易品种代码 |
alias | 当前交易品种代码对应的交易所中的symbol |
baseCurrency | 交易币种 |
quoteCurrency | 计价币种 |
marginCurrency | 保证金币种 |
basePrecision | 交易币种精度 |
quotePrecision | 计价币种精度 |
minQty | 最小下单量 |
maxQty | 最大下单量 |
minNotional | 最小下单金额 |
maxNotional | 最大下单金额 |
priceTick | 价格一跳 |
volumeTick | 下单量最小变动数值(下单量一跳) |
marginLevel | 期货杠杆值 |
contractType | 对于永续合约设置为:swap ,回测系统会继续发送资金费率、价格指数请求 |
特殊的列属性asks
、bids
、trades
:
字段 | 说明 | 备注 |
---|---|---|
asks / bids | [[价格, 数量], …] | 例如实盘级 Tick 数据范例中的数据:[[9531300, 10]] |
trades | [[时间, 方向(0:买,1:卖), 价格, 数量], …] | 例如实盘级 Tick 数据范例中的数据:[[1564315200000, 0, 9531300, 10]] |
期货交易所的永续合约回测时,自定义数据源还需要额外的资金费率数据、价格指数数据。只有当请求的行情数据返回时,返回的结构中detail字段包含"contractType": "swap"
键值对,回测系统才会继续发送对于资金费率的请求。
当回测系统收到资金费率数据时,才会继续发送对于价格指数数据的请求。
资金费率数据结构如下:
{
"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
]
// ...
]
}
回测系统发出的资金费率数据请求,举例为:
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
价格指数数据结构如下:
{
"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],
// ...
]
}
回测系统发出的价格指数数据请求,举例为:
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
指定数据源地址,例如:http://120.24.2.20:9090/data
。自定义数据源服务程序使用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]
]
}
*/
// /* 模拟级 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},
},
}
// */
/* 实盘级 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)
}
测试策略,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)
}
发明者量化交易平台开源了JavaScript
语言和Python
语言的本地回测引擎,支持回测时设置底层K线周期。
策略编辑页面和策略回测页面切换的快捷键
使用Ctrl + ,
键,切换回测页面和策略编辑页面,按住Ctrl
键后,单按,
键。
策略保存的快捷键
使用Ctrl + s
键,保存策略。
启动回测的快捷键
使用Ctrl + b
键,启动回测。
回测系统夏普算法源码:
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
}
}
策略编辑器
策略入口函数