Type/to search
8
Follow
1364
Followers
教你设计获取指定长度K线数据的模板类库
Original
Created 2023-06-27 13:37:01  Updated 2023-09-18 19:34:23
 0
 1604

img

教你设计获取指定长度K线数据的模板类库

在设计一些趋势策略时,计算指标往往需要足够数量的K线Bar。依赖于FMZ平台API:exchange.GetRecords()函数给出的数据量,而exchange.GetRecords()就是对交易所K线接口的封装。在早期的加密货币交易所API接口设计中并没有分页查询,交易所的K线接口都只提供给有限的数据量,所以一些开发者对于参数较大的指标计算需求无法满足。

Binance合约API的K线接口支持分页查询,那么本篇就以Binance K线API接口为例,教你实现一个分页查询,并且可以指定获取Bar数量的FMZ平台模板类库。

Binance的K线接口

img

首先要看交易所API文档,看接口的具体参数。我们可以看到这个K线接口调用时需要指定品种、K线周期、数据范围(起始、终止时间)、分页数量等。

由于我们设计需求是查询一个指定数量的K线数据,例如要查询1小时K线,从当前时刻向过去时刻的方向推,数量5000根Bar。这样你只调用一次交易所API接口查询显然是拿不到你想要的数据的。

那我们就分页查询,从当前时刻向历史某个时刻分段处理。我们已知需要的K线数据的周期就很好计算出每次分段的起始、终止时间。只用依次分段向历史时刻方向查询,直到查询到足够的Bar数量即可。思路是不是听着很简单,那就动手实现吧!

设计「JavaScript版分页查询K线历史数据模板」

设计模板的接口函数:$.GetRecordsByLength(e, period, length)

/** * desc: $.GetRecordsByLength 是该模板类库的接口函数,该函数用于获取指定K线长度的K线数据 * @param {Object} e - 交易所对象 * @param {Int} period - K线周期,秒数为单位 * @param {Int} length - 指定获取的K线数据的长度,具体和交易所接口限制有关 * @returns {Array<Object>} - K线数据 */

设计$.GetRecordsByLength这个函数,通常的使用场景为策略运行初期需要很长的K线才能进行计算指标。这个函数执行后拿到了足够长的数据,之后就只需要更新新K线数据了。没有必要再调用这个函数获取超长的K线数据,这样就会造成不必要的接口调用。

所以还需要设计一个用于后续数据更新的接口:$.UpdataRecords(e, records, period)

/** * desc: $.UpdataRecords 是该模板类库的接口函数,该函数用于更新K线数据 * @param {Object} e - 交易所对象 * @param {Array<Object>} records - 需要更新的K线数据源 * @param {Int} period - K线周期,需要和records参数传入的K线数据周期一致 * @returns {Bool} - 是否更新成功 */

接下来就是实现这些接口函数了。

/** * desc: $.GetRecordsByLength 是该模板类库的接口函数,该函数用于获取指定K线长度的K线数据 * @param {Object} e - 交易所对象 * @param {Int} period - K线周期,秒数为单位 * @param {Int} length - 指定获取的K线数据的长度,具体和交易所接口限制有关 * @returns {Array<Object>} - K线数据 */ $.GetRecordsByLength = function(e, period, length) { if (!Number.isInteger(period) || !Number.isInteger(length)) { throw "params error!" } var exchangeName = e.GetName() if (exchangeName == "Futures_Binance") { return getRecordsForFuturesBinance(e, period, length) } else { throw "not support!" } } /** * desc: getRecordsForFuturesBinance 币安期货交易所获取K线数据函数的具体实现 * @param {Object} e - 交易所对象 * @param {Int} period - K线周期,秒数为单位 * @param {Int} length - 指定获取的K线数据的长度,具体和交易所接口限制有关 * @returns {Array<Object>} - K线数据 */ function getRecordsForFuturesBinance(e, period, length) { var contractType = e.GetContractType() var currency = e.GetCurrency() var strPeriod = String(period) var symbols = currency.split("_") var baseCurrency = "" var quoteCurrency = "" if (symbols.length == 2) { baseCurrency = symbols[0] quoteCurrency = symbols[1] } else { throw "currency error!" } var realCt = e.SetContractType(contractType)["instrument"] if (!realCt) { throw "realCt error" } // m -> 分钟; h -> 小时; d -> 天; w -> 周; M -> 月 var periodMap = {} periodMap[(60).toString()] = "1m" periodMap[(60 * 3).toString()] = "3m" periodMap[(60 * 5).toString()] = "5m" periodMap[(60 * 15).toString()] = "15m" periodMap[(60 * 30).toString()] = "30m" periodMap[(60 * 60).toString()] = "1h" periodMap[(60 * 60 * 2).toString()] = "2h" periodMap[(60 * 60 * 4).toString()] = "4h" periodMap[(60 * 60 * 6).toString()] = "6h" periodMap[(60 * 60 * 8).toString()] = "8h" periodMap[(60 * 60 * 12).toString()] = "12h" periodMap[(60 * 60 * 24).toString()] = "1d" periodMap[(60 * 60 * 24 * 3).toString()] = "3d" periodMap[(60 * 60 * 24 * 7).toString()] = "1w" periodMap[(60 * 60 * 24 * 30).toString()] = "1M" var records = [] var url = "" if (quoteCurrency == "USDT") { // GET https://fapi.binance.com /fapi/v1/klines symbol , interval , startTime , endTime , limit // limit 最大值:1500 url = "https://fapi.binance.com/fapi/v1/klines" } else if (quoteCurrency == "USD") { // GET https://dapi.binance.com /dapi/v1/klines symbol , interval , startTime , endTime , limit // startTime 与 endTime 之间最多只可以相差200天 // limit 最大值:1500 url = "https://dapi.binance.com/dapi/v1/klines" } else { throw "not support!" } var maxLimit = 1500 var interval = periodMap[strPeriod] if (typeof(interval) !== "string") { throw "period error!" } var symbol = realCt var currentTS = new Date().getTime() while (true) { // 计算limit var limit = Math.min(maxLimit, length - records.length) var barPeriodMillis = period * 1000 var rangeMillis = barPeriodMillis * limit var twoHundredDaysMillis = 200 * 60 * 60 * 24 * 1000 if (rangeMillis > twoHundredDaysMillis) { limit = Math.floor(twoHundredDaysMillis / barPeriodMillis) rangeMillis = barPeriodMillis * limit } var query = `symbol=${symbol}&interval=${interval}&endTime=${currentTS}&limit=${limit}` var retHttpQuery = HttpQuery(url + "?" + query) var ret = null try { ret = JSON.parse(retHttpQuery) } catch(e) { Log(e) } if (!ret || !Array.isArray(ret)) { return null } // 超出交易所可查询范围,查询不到数据时 if (ret.length == 0 || currentTS <= 0) { break } for (var i = ret.length - 1; i >= 0; i--) { var ele = ret[i] var bar = { Time : parseInt(ele[0]), Open : parseFloat(ele[1]), High : parseFloat(ele[2]), Low : parseFloat(ele[3]), Close : parseFloat(ele[4]), Volume : parseFloat(ele[5]) } records.unshift(bar) } if (records.length >= length) { break } currentTS -= rangeMillis Sleep(1000) } return records } /** * desc: $.UpdataRecords 是该模板类库的接口函数,该函数用于更新K线数据 * @param {Object} e - 交易所对象 * @param {Array<Object>} records - 需要更新的K线数据源 * @param {Int} period - K线周期,需要和records参数传入的K线数据周期一致 * @returns {Bool} - 是否更新成功 */ $.UpdataRecords = function(e, records, period) { var r = e.GetRecords(period) if (!r) { return false } for (var i = 0; i < r.length; i++) { if (r[i].Time > records[records.length - 1].Time) { // 添加新Bar records.push(r[i]) // 更新上一个Bar if (records.length - 2 >= 0 && i - 1 >= 0 && records[records.length - 2].Time == r[i - 1].Time) { records[records.length - 2] = r[i - 1] } } else if (r[i].Time == records[records.length - 1].Time) { // 更新Bar records[records.length - 1] = r[i] } } return true }

模板中我们只实现了Binance合约K线接口的支持,即getRecordsForFuturesBinance函数,也可以扩展支持其它加密货币交易所的K线接口。

测试环节

可以看到模板中实现这些功能的代码并不多,大概也就不到200行。模板代码编写完毕之后,测试是绝对不能少的。并且对于这样的数据获取,我们还需要尽可能的严格测试。

测试需要把这个「JavaScript版分页查询K线历史数据模板」和「画线类库」模板都复制到自己的策略库(在策略广场中可以搜索到)。然后我们新建一个策略,勾选这两个模板:

img

img

img

使用「画线类库」是因为我们需要把获取的K线数据画出来观察。

function main() { LogReset(1) var testPeriod = PERIOD_M5 Log("当前测试的交易所:", exchange.GetName()) // 如果是期货则需要设置合约 exchange.SetContractType("swap") // 使用$.GetRecordsByLength获取指定长度的K线数据 var r = $.GetRecordsByLength(exchange, testPeriod, 8000) Log(r) // 使用画图测试,方便观察 $.PlotRecords(r, "k") // 检测数据 var diffTime = r[1].Time - r[0].Time Log("diffTime:", diffTime, " ms") for (var i = 0; i < r.length; i++) { for (var j = 0; j < r.length; j++) { // 检查重复Bar if (i != j && r[i].Time == r[j].Time) { Log(r[i].Time, i, r[j].Time, j) throw "有重复Bar" } } // 检查Bar连续性 if (i < r.length - 1) { if (r[i + 1].Time - r[i].Time != diffTime) { Log("i:", i, ", diff:", r[i + 1].Time - r[i].Time, ", r[i].Time:", r[i].Time, ", r[i + 1].Time:", r[i + 1].Time) throw "Bar不连续" } } } Log("检测通过") Log("$.GetRecordsByLength函数返回的数据长度:", r.length) // 更新数据 while (true) { $.UpdataRecords(exchange, r, testPeriod) LogStatus(_D(), "r.length:", r.length) $.PlotRecords(r, "k") Sleep(5000) } }

这里我们使用var testPeriod = PERIOD_M5这句,设置5分钟K线周期,指定获取8000根Bar。然后对于var r = $.GetRecordsByLength(exchange, testPeriod, 8000)接口返回的很长的K线数据进行画图测试:

// 使用画图测试,方便观察 $.PlotRecords(r, "k")

接下来对于这个很长的K线数据进行检测:

// 检测数据 var diffTime = r[1].Time - r[0].Time Log("diffTime:", diffTime, " ms") for (var i = 0; i < r.length; i++) { for (var j = 0; j < r.length; j++) { // 检查重复Bar if (i != j && r[i].Time == r[j].Time) { Log(r[i].Time, i, r[j].Time, j) throw "有重复Bar" } } // 检查Bar连续性 if (i < r.length - 1) { if (r[i + 1].Time - r[i].Time != diffTime) { Log("i:", i, ", diff:", r[i + 1].Time - r[i].Time, ", r[i].Time:", r[i].Time, ", r[i + 1].Time:", r[i + 1].Time) throw "Bar不连续" } } } Log("检测通过")

1、检查K线Bar中是否有重复的。
2、检查K线Bar的连贯性(相邻Bar的时间戳差值是否相等)

这些检查通过之后,检查用于更新K线的接口$.UpdataRecords(exchange, r, testPeriod)是否正常:

// 更新数据 while (true) { $.UpdataRecords(exchange, r, testPeriod) LogStatus(_D(), "r.length:", r.length) $.PlotRecords(r, "k") Sleep(5000) }

这段代码会在实盘运行时,策略图表上持续输出K线,借此我们检查K线Bar数据更新、添加是否正常。

img

img

使用获取日K线,设置获取8000根(明知道8000天之前,没有行情数据),这样暴力测试:

img

看到日线只有1309根,对比交易所图表上的数据:

img

img

可以看到数据也是吻合的。

END

模板地址:「JavaScript版分页查询K线历史数据模板」
模板地址:「画线类库」

以上模板、策略代码仅用于教学、学习使用,实盘请根据需求具体优化、修改。

Comment
All comments (0)
No data
No data
  • 1
iPhone Download
Forums
PINE Language
© 2015 - ∞ INVENTOR PTE LTD (SG)