При разработке стратегий тренда часто необходимо иметь достаточное количество K-линейных строк для расчета индикаторов.exchange.GetRecords()
В начале разработки API для криптовалютных бирж не было поддержки для pagination в интерфейсе K-line, и интерфейс K-line предоставлял только ограниченное количество данных. В результате некоторые разработчики не смогли удовлетворить требованиям для расчета показателей с большими значениями параметров.
В этой статье мы будем использовать интерфейс API Binance K-line в качестве примера, чтобы научить вас, как реализовать pagination и указать количество строк для извлечения с помощью библиотеки шаблонов платформы FMZ.
Данные K-линии
Время открытия каждой K-линииGET /dapi/v1/klines
конечная точка может рассматриваться как уникальный идентификатор.
Вес запроса зависит от значения параметра
Параметры:
Во-первых, нам нужно обратиться к документации API обмена, чтобы понять конкретные параметры интерфейса K-линии. Мы можем видеть, что при вызове этой конечной точки K-линии нам нужно указать тип, период K-линии, диапазон данных (время начала и окончания) и количество страниц и т. Д.
Поскольку наше требование к дизайну заключается в запросе определенного количества данных K-линии, например, запрос 1-часовой K-линии, 5000 баров 1-часовой K-линии данных от текущего момента в прошлое, очевидно, что выполнение одного вызова API на обмен не будет извлекать желаемые данные.
Для достижения этого мы можем реализовать pagination и разделить запрос на сегменты от текущего момента к конкретному историческому моменту. Поскольку мы знаем желаемый период K-линейных данных, мы можем легко рассчитать время начала и окончания каждого сегмента. Затем мы можем запросить каждый сегмент в последовательности к историческому моменту, пока не получим достаточное количество строк. Подход звучит просто, так что давайте продолжим и реализуем его!
Функция интерфейса для шаблонов проектирования:$.GetRecordsByLength(e, period, length)
.
/**
* desc: $.GetRecordsByLength is the interface function of this template library, this function is used to get the K-line data of the specified K-line length
* @param {Object} e - exchange object
* @param {Int} period - K-line period, in seconds
* @param {Int} length - Specify the length of the acquired K-line data, which is related to the exchange interface limits
* @returns {Array<Object>} - K-line data
*/
Проектировать функцию$.GetRecordsByLength
, который обычно используется на начальном этапе выполнения стратегии для расчета индикаторов на основе длительного периода данных K-линии. После выполнения этой функции и получения достаточных данных необходимо обновлять только новые данные K-линии. Не нужно вызывать эту функцию снова для получения чрезмерно длинных данных K-линии, так как это приведет к ненужным вызовам API.
Поэтому также необходимо разработать интерфейс для последующих обновлений данных:$.UpdataRecords(e, records, period)
.
/**
* desc: $.UpdataRecords is the interface function of this template library, this function is used to update the K-line data.
* @param {Object} e - exchange object
* @param {Array<Object>} records - K-line data sources that need to be updated
* @param {Int} period - K-line period, needs to be the same as the K-line data period passed in the records parameter
* @returns {Bool} - Whether the update was successful
*/
Следующим шагом является реализация этих функций интерфейса.
/**
* desc: $.GetRecordsByLength is the interface function of this template library, this function is used to get the K-line data of the specified K-line length
* @param {Object} e - exchange object
* @param {Int} period - K-line period, in seconds
* @param {Int} length - Specify the length of the acquired K-line data, which is related to the exchange interface limits
* @returns {Array<Object>} - K-line data
*/
$.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, the specific implementation of the function to get K-line data for Binance Futures Exchange
* @param {Object} e - exchange object
* @param {Int} period - K-line period, in seconds
* @param {Int} length - Specify the length of the acquired K-line data, which is related to the exchange interface limits
* @returns {Array<Object>} - K-line data
*/
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 -> minute; h -> hour; d -> day; w -> week; M -> month
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 maximum value: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
// The difference between startTime and endTime can be up to 200 days.
// limit maximum value: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) {
// Calculate 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
}
// When the data cannot be searched because it is beyond the searchable range of the exchange
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 is the interface function of this template library, this function is used to update the K-line data.
* @param {Object} e - exchange object
* @param {Array<Object>} records - K-line data sources that need to be updated
* @param {Int} period - K-line period, needs to be the same as the K-line data period passed in the records parameter
* @returns {Bool} - Whether the update was successful
*/
$.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) {
// Add a new Bar
records.push(r[i])
// Update the previous 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) {
// Update Bar
records[records.length - 1] = r[i]
}
}
return true
}
В шаблоне мы реализовали только поддержку интерфейса K-линии фьючерсного контракта Binance, т.е.getRecordsForFuturesBinance
Он также может быть расширен для поддержки K-линейных интерфейсов других криптовалютных бирж.
Как вы можете видеть, код для реализации этих функций в шаблоне не обширен, в общей сложности менее 200 строк. После написания кода шаблона тестирование имеет решающее значение и не должно быть проигнорировано. Кроме того, для восстановления данных, подобных этому, важно провести тщательное тестирование.
Чтобы протестировать его, вам нужно скопировать как
Используется
function main() {
LogReset(1)
var testPeriod = PERIOD_M5
Log("Current exchanges tested:", exchange.GetName())
// If futures, you need to set up a contract
exchange.SetContractType("swap")
// Get K-line data of specified length using $.GetRecordsByLength
var r = $.GetRecordsByLength(exchange, testPeriod, 8000)
Log(r)
// Use the Plot test for easy observation
$.PlotRecords(r, "k")
// Test data
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++) {
// Check the repeat bar
if (i != j && r[i].Time == r[j].Time) {
Log(r[i].Time, i, r[j].Time, j)
throw "With duplicate Bar"
}
}
// Check Bar continuity
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 discontinuity"
}
}
}
Log("Test passed")
Log("The length of the data returned by the $.GetRecordsByLength function:", r.length)
// Update data
while (true) {
$.UpdataRecords(exchange, r, testPeriod)
LogStatus(_D(), "r.length:", r.length)
$.PlotRecords(r, "k")
Sleep(5000)
}
}
Здесь мы используем линиюvar testPeriod = PERIOD_M5
Затем мы можем выполнить тест на графике на длинные данные K-линии, возвращенныеvar r = $.GetRecordsByLength(exchange, testPeriod, 8000)
interface.
// Use the plot test for easy observation
$.PlotRecords(r, "k")
Следующее испытание для данных длинной линии K:
// Test data
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++) {
// Check the repeat Bar
if (i != j && r[i].Time == r[j].Time) {
Log(r[i].Time, i, r[j].Time, j)
throw "With duplicate Bar"
}
}
// Check Bar continuity
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 discontinuity"
}
}
}
Log("Test passed")
После прохождения этих проверок проверьте, используется ли интерфейс для обновления данных K-линии,$.UpdateRecords(exchange, r, testPeriod)
, работает нормально.
// Update data
while (true) {
$.UpdataRecords(exchange, r, testPeriod)
LogStatus(_D(), "r.length:", r.length)
$.PlotRecords(r, "k")
Sleep(5000)
}
Этот код будет непрерывно выводить данные K-линии на графике стратегии во время торговли в режиме реального времени, что позволит нам проверить, правильно ли работают обновления и дополнения данных K-линии.
Используя ежедневные данные K-линии, мы установили его на получение 8000 бар (зная, что нет доступных рыночных данных за 8000 дней назад).
Учитывая, что существует только 1309 ежедневных K-линий, сравните данные на графиках обмена:
Вы можете видеть, что данные также совпадают.
Адрес формы:
Приведенный выше шаблон и код стратегии предназначены только для обучения и обучения, пожалуйста, оптимизируйте и модифицируйте в соответствии с конкретными потребностями торговли в режиме реального времени.