Cuando se diseñan estrategias de tendencia, a menudo es necesario disponer de un número suficiente de barras de la línea K para el cálculo de los indicadores.exchange.GetRecords()
En el diseño inicial de las API de intercambio de criptomonedas, no había soporte para la paginación en la interfaz de línea K, y la interfaz de línea K de la bolsa solo proporcionó una cantidad limitada de datos.
La interfaz de línea K de la API de contratos de Binance
Datos de la línea K
La hora de apertura de cada línea K en elGET /dapi/v1/klines
el punto final puede considerarse como un identificador único.
El peso de la solicitud depende del valor del parámetro
Parámetros:
En primer lugar, necesitamos consultar la documentación de la API del exchange
Dado que nuestro requisito de diseño es consultar un número específico de datos de línea K, por ejemplo, consultar la línea K de 1 hora, 5000 bares de datos de línea K de 1 hora desde el momento actual hacia el pasado, es evidente que hacer una sola llamada de API al intercambio no recuperará los datos deseados.
Para lograr esto, podemos implementar la paginación y dividir la consulta en segmentos desde el momento actual hacia un momento histórico específico. Dado que conocemos el período de datos de la línea K deseado, podemos calcular fácilmente el tiempo de inicio y final para cada segmento. Luego podemos consultar cada segmento en secuencia hacia el momento histórico hasta que recuperemos suficientes barras.
Función de interfaz para plantillas de diseño:$.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
*/
Diseñar la función$.GetRecordsByLength
, que se utiliza típicamente en la etapa inicial de la ejecución de la estrategia para calcular indicadores basados en un largo período de datos de línea K. Una vez que esta función se ejecuta y se obtienen datos suficientes, solo se necesitan actualizar nuevos datos de línea K. No es necesario llamar a esta función nuevamente para recuperar datos de línea K excesivamente largos, ya que daría lugar a llamadas innecesarias de API.
Por lo tanto, también es necesario diseñar una interfaz para las actualizaciones de datos posteriores:$.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
*/
El siguiente paso es implementar estas funciones de interfaz.
/**
* 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
}
En la plantilla, sólo hemos implementado soporte para el contrato de futuros Binance interfaz K-line, es decir, elgetRecordsForFuturesBinance
También se puede extender para soportar interfaces K-line de otros intercambios de criptomonedas.
Como puede ver, el código para implementar estas funcionalidades en la plantilla no es extenso, con un total de menos de 200 líneas. Después de escribir el código de la plantilla, la prueba es crucial y no debe pasarse por alto. Además, para la recuperación de datos como esta, es importante realizar pruebas exhaustivas.
Para probarlo, debe copiar tanto la
La
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)
}
}
Aquí, usamos la líneavar testPeriod = PERIOD_M5
Entonces podemos realizar una prueba de gráficos en los datos de línea K largos devueltos por elvar r = $.GetRecordsByLength(exchange, testPeriod, 8000)
interface.
// Use the plot test for easy observation
$.PlotRecords(r, "k")
El siguiente ensayo para los datos de la línea K larga es:
// 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")
Después de pasar estos controles, comprobar si la interfaz utilizada para actualizar los datos de la línea K,$.UpdateRecords(exchange, r, testPeriod)
, funciona correctamente.
// Update data
while (true) {
$.UpdataRecords(exchange, r, testPeriod)
LogStatus(_D(), "r.length:", r.length)
$.PlotRecords(r, "k")
Sleep(5000)
}
Este código emitirá continuamente datos de línea K en el gráfico de estrategia durante el comercio en vivo, lo que nos permite verificar si las actualizaciones y adiciones de datos de línea K funcionan correctamente.
Utilizando los datos diarios de la línea K, lo configuramos para recuperar 8000 barras (sabiendo que no hay datos de mercado disponibles para 8000 días atrás).
Visto que sólo hay 1309 líneas K diarias, comparar los datos en los gráficos de cambio:
Se puede ver que los datos también coinciden.
Dirección del modelo:
La plantilla y el código de estrategia anteriores son solo para uso de enseñanza y aprendizaje, por favor optimice y modifique de acuerdo con las necesidades específicas del comercio en vivo.