在编写量化交易策略时,使用K线数据,经常会出现需要非标准周期K线数据的情况。例如,需要12分钟周期K线数据和4小时K线周期数据。通常这种非标准的 Cycles 不是直接可用的。那么我们如何应对这样的需求呢?
非标准周期K线数据可以通过组合较小周期的数据得到。想象一下,多周期的最高价算作多周期K线合成后的最高价,最低价算作合成后的最低价,开盘价不变。合成K线原材料数据的第一开盘价。收盘价对应K线最后原材料数据的收盘价。时间采用开盘价k线的时间。交易量使用汇总和计算的原材料数据。
如图所示:
我们以区块链资产 BTC_USDT 为例,将 1 小时合成为 4 小时。
时间 | 最高 | 打开 | 最低 | 关闭 |
---|---|---|---|---|
2019.8.12 00:00 | 11447.07 | 11382.57 | 11367.2 | 11406.92 |
2019.8.12 01:00 | 11420 | 11405.65 | 11366.6 | 11373.83 |
2019.8.12 02:00 | 11419.24 | 11374.68 | 11365.51 | 11398.19 |
2019.8.12 03:00 | 11407.88 | 11398.59 | 11369.7 | 11384.71 |
四个 1 小时周期的数据合并为一个 4 小时周期数据。
开盘价为00:00第一条K线开盘价:11382.57
收盘价为03:00最后一条K线收盘价:11384.71
最高价为求其中最高价:11447.07
最低价为求其中最低价:11365.51
注:中国商品期货市场于正常交易日下午 3:00 收市
4小时周期开始时间是第一个1小时K线的开始时间,即2019.8.12 00:00
所有 1 小时 k 线的成交量之和作为该 4 小时 k 线成交量。
4小时K线合成:
High: 11447.07
Open: 11382.57
Low: 11365.51
Close: 11384.71
Time: 209.8.12 00:00
可以看到数据是一致的。
了解初步思路后,可以手动编写代码实现需求。
这些代码仅供参考:
function GetNewCycleRecords (sourceRecords, targetCycle) { // K line synthesis function
var ret = []
// First get the source K line data cycle
if (!sourceRecords || sourceRecords.length < 2) {
Return null
}
var sourceLen = sourceRecords.length
var sourceCycle = sourceRecords[sourceLen - 1].Time - sourceRecords[sourceLen - 2].Time
if (targetCycle % sourceCycle != 0) {
Log(\"targetCycle:\", targetCycle)
Log(\"sourceCycle:\", sourceCycle)
throw \"targetCycle is not an integral multiple of sourceCycle.\"
}
if ((1000 * 60 * 60) % targetCycle != 0 && (1000 * 60 * 60 * 24) % targetCycle != 0) {
Log(\"targetCycle:\", targetCycle)
Log(\"sourceCycle:\", sourceCycle)
Log((1000 * 60 * 60) % targetCycle, (1000 * 60 * 60 * 24) % targetCycle)
throw \"targetCycle cannot complete the cycle.\"
}
var multiple = targetCycle / sourceCycle
var isBegin = false
var count = 0
var high = 0
var low = 0
var open = 0
var close = 0
var time = 0
var vol = 0
for (var i = 0 ; i < sourceLen ; i++) {
// Get the time zone offset value
var d = new Date()
var n = d.getTimezoneOffset()
if ((1000 * 60 * 60 * 24) - sourceRecords[i].Time % (1000 * 60 * 60 * 24) + (n * 1000 * 60)) % targetCycle == 0) {
isBegin = true
}
if (isBegin) {
if (count == 0) {
High = sourceRecords[i].High
Low = sourceRecords[i].Low
Open = sourceRecords[i].Open
Close = sourceRecords[i].Close
Time = sourceRecords[i].Time
Vol = sourceRecords[i].Volume
count++
} else if (count < multiple) {
High = Math.max(high, sourceRecords[i].High)
Low = Math.min(low, sourceRecords[i].Low)
Close = sourceRecords[i].Close
Vol += sourceRecords[i].Volume
count++
}
if (count == multiple || i == sourceLen - 1) {
Ret.push({
High : high,
Low : low,
Open : open,
Close : close,
Time : time,
Volume : vol,
})
count = 0
}
}
}
Return ret
}
// test
function main () {
while (true) {
var r = exchange.GetRecords() // Raw data, as the basic K-line data of the synthesize K line. for example, to synthesize a 4-hour K-line, you can use the 1-hour K-line as the raw data.
var r2 = GetNewCycleRecords(r, 1000 * 60 * 60 * 4) // Pass the original K-line data r through the GetNewCycleRecords function, and the target cycles, 1000 * 60 * 60 * 4, ie the target synthesis cycle is 4 hours K-line data .
$.PlotRecords(r2, \"r2\") // The strategy class library bar can be selected by check the line class library, and calling the $.PlotRecords line drawing class library to export the function drawing.
Sleep(1000) // Each cycle is separated by 1000 milliseconds, preventing access to the K-line interface too much, resulting in transaction restrictions.
}
}
实际上,要合成 K 线,您需要两件事。首先是原材料数据,即较小周期的K线数据。在这个例子中,var r = exchange.GetRecords()
获取较小的周期K线数据。
二是算出合成周期的大小,我们使用GetNewCycleRecords函数算法来做这个,最后就可以返回一个合成的K线数组结构的数据了。
请注意:
例如:
12分钟周期的K线从每小时0:0开始,第一个周期为00:00:00~00:12:00,第二个周期为00:12:00~00:24:00 ,第三个周期为00:24:00~00:36:00,第四个周期为00:36:00~00:48:00,第五个周期为00:48:00~01:00:00,即正好是一个完整的一小时。
如果是13分钟的循环,则为未关闭的循环。通过这种循环计算的数据不是唯一的,因为合成数据根据合成数据的起点而不同。
在真实市场中运行它:
对比交换图
我想计算所有 K 线的最高价格的移动平均线。我该怎么办?
通常我们使用收盘价的平均值来计算移动平均线,但有时也有使用最高价、最低价、开盘价等的需求。
对于这些额外的需求,exchange.GetRecords()函数返回的K线数据不能直接传递给指标计算函数。
eg:
均线talib.MA
指标计算函数有两个参数,第一个是需要传入的数据,第二个是指标周期参数。
例如,我们需要计算如下所示的指标。
K线周期为4小时。
在交易所市场报价图表上,已经设置了一条平均线,周期参数为 9。
计算的数据源使用每根柱的最高价格。
也就是说,这条移动平均线是由九个 4 小时周期 K 线 Bar 的最高均价的平均值组成。
我们自己建一个数据,看看和交易所的数据是不是一样。
var highs = []
for (var i = 0 ; i < r2.length ; i++) {
highs.push(r2[i].High)
}
由于我们需要计算每个 Bar 的最高价格来获得移动平均线指标的值,因此我们需要构造一个数组,其中每个数据元素都有每个 Bar 的最高价格。
可以看到highs
变量最初是一个空数组,然后我们遍历r2 k线数据变量(不记得r2了?看上面合成4小时k线的main函数中的代码)。
读取 r2 的每个 Bar 的最高价(即 r2[i].High,i 范围从 0 到 r2.length – 1),然后推入highs
。这样我们只是构造了一个与K线数据Bar一一对应的数据结构。
这时,highs
可以通过talib.MA
函数来计算移动平均线。
完整示例:
function main () {
while (true) {
var r = exchange.GetRecords()
var r2 = GetNewCycleRecords(r, 1000 * 60 * 60 * 4)
if (!r2) {
Continue
}
$.PlotRecords(r2, \"r2\") // Draw the K line
var highs = []
for (var i = 0 ; i < r2.length ; i++) {
Highs.push(r2[i].High)
}
var ma = talib.MA(highs, 9) // use the moving average function \"talib.MA\" to calculate the moving average indicator
$.PlotLine(\"high_MA9\", ma[ma.length - 2], r2[r2.length - 2].Time) // Use the line drawing library to draw the moving average indicator on the chart
Sleep(1000)
}
}
回测:
可以看到图中鼠标点位置的平均指标值为11466.9289
以上代码可以复制到策略运行测试,记得勾选“画线库”并保存!
FMZ量化平台已经有了一个封装好的接口,即
exchange.GetRecords
函数,获取K线数据。
下面重点介绍直接访问交易所的K线数据接口获取数据,因为有时需要指定参数才能获取更多的K线,封装GetRecords
接口一般返回100k线。如果遇到最初需要100多条K线的策略,需要等待采集过程。
为了让策略尽快生效,可以封装一个函数,直接访问交易所的K线接口,指定参数获取更多的K线数据。
以火必交易所的 BTC_USDT 交易对为例,我们实现这个需求:
找到交易所的API文档,查看K线接口说明:
https://huobiapi.github.io/docs/spot/v1/en/#get-klines-candles
参数:
名称 | 类型 | 有必要吗 | 描述 | 价值 |
---|---|---|---|---|
象征 | 细绳 | 真的 | 交易对 | btcusdt, ethbtc… |
时期 | 细绳 | 真的 | 返回数据的时间粒度,即每k行的时间间隔 | 1 分钟、5 分钟、15 分钟、30 分钟、60 分钟、1 天、1 个月、1 周、1 年 |
尺寸 | 整数 | 错误的 | 返回K行数据的数量 | [1, 2000] |
测试代码:
function GetRecords_Huobi (period, size, symbol) {
var url = \"https://api.huobi.pro/market/history/kline?\" + \"period=\" + period + \"&size=\" + size + \"&symbol=\" + symbol
var ret = HttpQuery(url)
try {
var jsonData = JSON.parse(ret)
var records = []
for (var i = jsonData.data.length - 1; i >= 0 ; i--) {
records.push({
Time : jsonData.data[i].id * 1000,
High : jsonData.data[i].high,
Open : jsonData.data[i].open,
Low : jsonData.data[i].low,
Close : jsonData.data[i].close,
Volume : jsonData.data[i].vol,
})
}
return records
} catch (e) {
Log(e)
}
}
function main() {
var records = GetRecords_Huobi(\"1day\", \"300\", \"btcusdt\")
Log(records.length)
$.PlotRecords(records, \"K\")
}
可以看到在log上,printrecords.length
是300,也就是records
K线数据条的个数是300。
买好币上币库:https://www.kucoin.com/r/1f7w3