
Al escribir estrategias de negociación programática, utilizando datos de la línea K, a menudo es necesario utilizar algunos datos de la línea K de período no estándar, como datos de la línea K de período de 12 minutos, datos de la línea K de período de 4 horas, generalmente Este tipo de período no estándar no se puede obtener directamente. ¿Cómo respondemos entonces a tales demandas? La respuesta es definitivamente que hay una manera. Los ciclos no estándar se pueden obtener fusionando y sintetizando datos de ciclos más pequeños. Puede imaginar que el precio más alto en varios ciclos se cuenta como el precio más alto después de la síntesis, y el precio más bajo se cuenta como el precio más bajo después de la síntesis. El precio no cambiará. Se utiliza el primer precio de apertura de los datos de materia prima de la línea K sintetizada, el precio de cierre corresponde al último precio de cierre de los datos de materia prima de la línea K sintetizada, el tiempo es el tiempo del precio de apertura y el volumen de negociación se calcula sumando los volúmenes de transacción de los datos de la materia prima. Como se muestra en la imagen:
Tomamos el mercado de activos blockchain BTC_USDT como ejemplo y sintetizamos 1 hora en 4 horas.




|Hora|Alto|Apertura|Bajo|Cierre| |- |- |- |- |-| |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|
Los datos de estos cuatro ciclos de 1 hora se combinan en un ciclo de 4 horas. El precio de apertura es el precio de apertura de las primeras 00:00: 11382,57 El precio de cierre es el último, es decir, el precio de cierre a las 03:00: 11384.71 El precio más alto es el precio más alto aquí: 11447.07 El precio más bajo está aquí: 11365.51 La hora de inicio del ciclo de 4 horas es 00:00, la hora de inicio de la línea K de 1 hora, es decir, 2019.8.12 00:00 El volumen de operaciones se puede sumar cada 1 hora (principalmente para observar cómo se sintetiza el precio, lo que no se muestra en los datos del volumen de operaciones). No entraré en detalles aquí.
La línea K de 4 horas sintetizada es: Alto: 11447.07 Abierto: 11382.57 Mínimo: 11365,51 Recibidos: 11384.71 Hora: 2019.8.12 00:00

Se puede ver que los datos son consistentes.
Después de verificar las ideas preliminares, puede comenzar a escribir código para implementar preliminarmente este requisito.
Libere el código directamente, el código es solo para referencia:
function GetNewCycleRecords (sourceRecords, targetCycle) { // K线合成函数
var ret = []
// 首先获取源K线数据的周期
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++) {
// 获取 时区偏移数值
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
}
// 测试
function main () {
while (true) {
var r = exchange.GetRecords() // 原始数据,作为合成K线的基础K线数据,例如要合成4小时K线,可以用1小时K线作为原始数据。
var r2 = GetNewCycleRecords(r, 1000 * 60 * 60 * 4) // 通过 GetNewCycleRecords 函数 传入 原始K线数据 r , 和目标周期, 1000 * 60 * 60 * 4 即 目标合成的周期 是4小时K线数据。
$.PlotRecords(r2, "r2") // 策略类库栏 可以勾选画线类库,调用 $.PlotRecords 画线类库 导出函数 画图。
Sleep(1000) // 每次循环间隔 1000 毫秒,防止访问K线接口获取数据过于频繁,导致交易所限制。
}
}
De hecho, para sintetizar la línea K se necesitan dos cosas: la primera son los datos de la materia prima, es decir, los datos de la línea K de un período pequeño.var r = exchange.GetRecords()
Datos de la línea K de período pequeño obtenidos. El segundo es definir claramente el período de síntesis, es decir, el período objetivo para la síntesis de datos de la línea K.
Luego, a través del algoritmo de la función GetNewCycleRecords, se pueden devolver finalmente los datos de una estructura de matriz de K líneas sintetizada.
Cabe señalar que:
00:00:00 ~ 00:12:00, el segundo ciclo es00:12:00 ~ 00:24:00, el tercer ciclo es00:24:00 ~ 00:36:00, el cuarto ciclo es00:36:00 ~ 00:48:00, el quinto ciclo es00:48:00 ~ 01:00:00 , lo que supone una hora completa.Si se trata de un ciclo de 13 minutos, es un ciclo abierto y los datos calculados en dicho ciclo no son únicos, porque los datos sintetizados diferirán dependiendo del punto de inicio de los datos sintetizados.
El disco real se ejecutó:

Comparar gráficos de cambio

Los miembros del grupo a menudo hacen preguntas: Quiero calcular el promedio móvil del precio más alto de cada línea K, ¿qué debo hacer?
Generalmente, calculamos la media móvil calculando el promedio de los precios de cierre para formar la media móvil, pero a veces es necesario calcular el precio más alto, el precio más bajo, el precio de apertura, etc.
En este momento, no puedes simplementeexchange.GetRecords() Los datos de la línea K devueltos por la función se pasan directamente a la función de cálculo del indicador.
Por ejemplo:
La función de cálculo del indicador de media móvil talib.MA tiene dos parámetros. El primer parámetro son los datos que se deben introducir y el segundo parámetro es el parámetro del período del indicador.
Por ejemplo, queremos calcular los siguientes indicadores

El ciclo de la línea K es de 4 horas.
En el gráfico de intercambio, se ha establecido una media móvil con un parámetro de período de media móvil de 9.
Y la fuente de datos para el cálculo se establece en el precio más alto de cada barra.
Es decir, esta media móvil es el promedio de los precios más altos de 9 barras de la línea K de 4 horas, que constituyen la media móvil del indicador.
Construyamos algunos datos nosotros mismos y veamos si son los mismos que los calculados por el gráfico del exchange.
var highs = []
for (var i = 0 ; i < r2.length ; i++) {
highs.push(r2[i].High)
}
Dado que necesitamos calcular el promedio de los precios más altos de cada barra para derivar el indicador de promedio móvil. Luego primero debes construir una matriz, en la que cada elemento de datos corresponde al precio más alto de cada barra. Puedes ver que la variable highs es inicialmente una matriz vacía, y luego recorremos la variable de datos de la vela r2 (¿no recuerdas r2? Mira el código en la función principal de síntesis de la vela de 4 horas anterior). Leer el precio más alto de cada barra de r2 (es decir, r2[i].High , i varía de 0 a r2.length - 1 ), y luego lo empuja hacia los máximos. De esta manera se construye una estructura de datos que se corresponde uno a uno con los datos de la línea K Bar.
En este punto, los máximos se pueden pasar a la función talib.MA para calcular el promedio móvil.
Ejemplo completo:
function main () {
while (true) {
var r = exchange.GetRecords()
var r2 = GetNewCycleRecords(r, 1000 * 60 * 60 * 4)
if (!r2) {
continue
}
$.PlotRecords(r2, "r2") // 画出K线
var highs = []
for (var i = 0 ; i < r2.length ; i++) {
highs.push(r2[i].High)
}
var ma = talib.MA(highs, 9) // 用均线指标函数 talib.MA 计算 均线指标
$.PlotLine("high_MA9", ma[ma.length - 2], r2[r2.length - 2].Time) // 使用画线类库把均线指标画在图表上
Sleep(1000)
}
}
Ejecución de backtest:

Puede ver que los valores del indicador de promedio móvil en la posición del mouse en la figura son11466.9289
El código anterior se puede copiar en la estrategia para ejecutar la prueba. ¡Recuerde marcar “Biblioteca de dibujo de líneas” y guardarlo!
La plataforma de comercio cuantitativo Inventor ya tiene una interfaz empaquetada, concretamente la función exchange.GetRecords, que puede obtener datos de la línea K. Lo siguiente se centra en el acceso directo a la interfaz de datos de la línea K de intercambio para obtener datos, porque a veces es necesario especificar parámetros para obtener más líneas K, interfaz GetRecords encapsulada Generalmente se devuelven 100. Si la estrategia requiere inicialmente más de 100 líneas K, es necesario recolectar y esperar. Para que la estrategia se ejecute lo más rápido posible, puede encapsular una función usted mismo, acceder directamente a la interfaz de intercambio de K-line y especificar parámetros para obtener más datos de K-line.
Tomando como ejemplo el par comercial BTC_USDT de Huobi, implementamos este requisito:
Encuentre el documento API del exchange y vea la descripción de la interfaz K-line:

https://api.huobi.pro/market/history/kline?period=1day&size=200&symbol=btcusdt
parámetro: |Nombre del parámetro|Tipo|Obligatorio|Descripción|Valor| |-|-|-|-|-| |símbolo|cadena|verdadero|Par comercial|btcusdt, ethbtc…| |period|string|true|Devuelve la granularidad del tiempo de los datos, es decir, el intervalo de tiempo de cada vela|1min, 5min, 15min, 30min, 60min, 1day, 1mon, 1week, 1year| |size|integer|false|Devuelve el número de datos de la línea K|[1, 2000]|
Código de prueba:
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")
}
Versión Python, ejemplo de acceso a la interfaz de intercambio de Huobi:
#!python3
import json
import urllib2
def GetRecords_Huobi(period, size, symbol):
headers = {'User-Agent':'Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US; rv:1.9.1.6) Gecko/20091201 Firefox/3.5.6'}
url = "https://api.huobi.pro/market/history/kline?" + "period=" + period + "&size=" + size + "&symbol=" + symbol
request = urllib2.Request(url)
request.add_header('User-Agent','Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US; rv:1.9.1.6) Gecko/20091201 Firefox/3.5.6')
opener = urllib2.build_opener()
f= opener.open(request)
ret = f.read().decode('utf-8')
try :
jsonData = json.loads(ret)
records = []
for i in range(len(jsonData["data"]) - 1, -1, -1):
records.append({
"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
except Exception as e:
Log(e)
def main():
r = GetRecords_Huobi("1day", "300", "btcusdt")
Log(len(r))
ext.PlotRecords(r, "K") # 需要引用Python画线类库
Versión Python, un ejemplo de acceso a la interfaz K-line de Binance Exchange:
#!python3
import json
import urllib2
def GetRecords_Huobi(period, size, symbol):
headers = {'User-Agent':'Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US; rv:1.9.1.6) Gecko/20091201 Firefox/3.5.6'}
url = "https://api.binance.com/api/v3/klines?symbol=" + symbol + "&interval=" + period
request = urllib2.Request(url)
request.add_header('User-Agent','Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US; rv:1.9.1.6) Gecko/20091201 Firefox/3.5.6')
opener = urllib2.build_opener()
f= opener.open(request)
ret = f.read().decode('utf-8')
try :
jsonData = json.loads(ret)
records = []
for i in range(len(jsonData)):
records.append({
"Time" : float(jsonData[i][0]),
"High" : float(jsonData[i][2]),
"Open" : float(jsonData[i][1]),
"Low" : float(jsonData[i][3]),
"Close" : float(jsonData[i][4]),
"Volume" : float(jsonData[i][5]),
})
return records
except Exception as e:
Log(e)
def main():
r = GetRecords_Huobi("1m", "300", "BTCUSDT")
Log(len(r))
ext.PlotRecords(r, "K") # 需要引用Python画线类库


Podemos ver en el registro que records.length es 300, lo que significa que hay 300 barras de datos de la línea K de registros.
