
Lors de la conception de certaines stratégies de tendance, le calcul des indicateurs nécessite souvent un nombre suffisant de barres K-line. Dépend de l’API de la plateforme FMZ :exchange.GetRecords()La quantité de données fournie par la fonction, etexchange.GetRecords()Il s’agit de l’encapsulation de l’interface d’échange K-line. Dans la conception initiale des interfaces API des échanges de crypto-monnaie, il n’y avait pas de requête de pagination et l’interface K-line de l’échange ne fournissait qu’une quantité limitée de données, de sorte que les besoins de certains développeurs en matière de calculs d’indicateurs avec des paramètres plus importants ne pouvaient pas être satisfaits.
L’interface K-line de l’API de contrat Binance prend en charge les requêtes paginées. Cet article prend l’interface API K-line de Binance comme exemple pour vous apprendre à implémenter une requête paginée et à spécifier la bibliothèque de modèles de la plateforme FMZ pour obtenir le nombre de barres.

Tout d’abord, vous devez lire la documentation de l’API Exchange pour voir les paramètres spécifiques de l’interface. Nous pouvons voir que lors de l’appel de cette interface K-line, il est nécessaire de spécifier le produit, la période K-line, la plage de données (heure de début et de fin), le nombre de pages, etc.
Étant donné que notre exigence de conception est d’interroger un nombre spécifié de données de ligne K, par exemple, pour interroger la ligne K d’une heure, passez de l’heure actuelle à l’heure passée, le nombre est de 5 000 barres. De cette façon, vous ne pouvez évidemment pas obtenir les données souhaitées en appelant une seule fois la requête de l’interface API d’échange.
Nous effectuerons ensuite des requêtes par pages, en traitant par segments depuis le moment actuel jusqu’à un certain moment de l’histoire. Tant que nous connaissons la période des données de la ligne K requises, il est facile de calculer l’heure de début et de fin de chaque segment. Recherchez simplement les moments historiques dans l’ordre jusqu’à ce que vous trouviez suffisamment de barres. L’idée semble simple, n’est-ce pas ? Mettons-la en pratique !
Fonction d’interface de modèle de conception :$.GetRecordsByLength(e, period, length)。
/**
* desc: $.GetRecordsByLength 是该模板类库的接口函数,该函数用于获取指定K线长度的K线数据
* @param {Object} e - 交易所对象
* @param {Int} period - K线周期,秒数为单位
* @param {Int} length - 指定获取的K线数据的长度,具体和交易所接口限制有关
* @returns {Array<Object>} - K线数据
*/
conception$.GetRecordsByLengthCette fonction est généralement utilisée au début du fonctionnement de la stratégie lorsqu’une longue ligne K est nécessaire pour calculer l’indicateur. Une fois cette fonction exécutée, elle obtient des données suffisamment longues et il ne lui reste plus qu’à mettre à jour les nouvelles données de la ligne K. Il n’est pas nécessaire d’appeler cette fonction pour obtenir des données de ligne K très longues, ce qui entraînerait des appels d’interface inutiles.
Il est donc également nécessaire de concevoir une interface pour les mises à jour ultérieures des données :$.UpdataRecords(e, records, period)。
/**
* desc: $.UpdataRecords 是该模板类库的接口函数,该函数用于更新K线数据
* @param {Object} e - 交易所对象
* @param {Array<Object>} records - 需要更新的K线数据源
* @param {Int} period - K线周期,需要和records参数传入的K线数据周期一致
* @returns {Bool} - 是否更新成功
*/
L’étape suivante consiste à implémenter ces fonctions d’interface.
/**
* 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
}
Dans le modèle, nous implémentons uniquement la prise en charge de l’interface K-line du contrat Binance, c’est-à-diregetRecordsForFuturesBinanceFonction, il peut également être étendu pour prendre en charge les interfaces K-line d’autres échanges de crypto-monnaie.
Vous pouvez voir qu’il n’y a pas beaucoup de code dans le modèle pour implémenter ces fonctions, probablement moins de 200 lignes. Une fois le code du modèle écrit, les tests sont absolument nécessaires. Et pour une telle acquisition de données, nous devons également les tester le plus rigoureusement possible.
Le test nécessite de copier cette « version JavaScript du modèle de données historiques K-line de requête de page » et le modèle « bibliothèque de dessins de lignes » dans votre propre bibliothèque de stratégies (enPlace de la stratégiepeut être recherché dans ). Ensuite, nous créons une nouvelle stratégie et vérifions ces deux modèles :



La « bibliothèque de dessins de lignes » est utilisée car nous devons dessiner les données de ligne K obtenues pour l’observation.
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)
}
}
Ici nous utilisonsvar testPeriod = PERIOD_M5Cette phrase définit la période de la ligne K à 5 minutes et spécifie d’obtenir 8 000 barres. Alors pourvar r = $.GetRecordsByLength(exchange, testPeriod, 8000)L’interface renvoie une longue ligne de données K pour le test de dessin :
// 使用画图测试,方便观察
$.PlotRecords(r, "k")
Ensuite, nous allons tester ces très longues données de ligne 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("检测通过")
Une fois ces vérifications effectuées, vérifiez l’interface utilisée pour mettre à jour la ligne K$.UpdataRecords(exchange, r, testPeriod)Normale:
// 更新数据
while (true) {
$.UpdataRecords(exchange, r, testPeriod)
LogStatus(_D(), "r.length:", r.length)
$.PlotRecords(r, "k")
Sleep(5000)
}
Ce code affichera en continu la ligne K sur le graphique de stratégie lorsqu’il s’exécute dans le cadre d’un trading réel, afin que nous puissions vérifier si les données de la barre de ligne K sont mises à jour et ajoutées normalement.


Utilisez la fonction d’acquisition quotidienne de la ligne K et définissez l’acquisition sur 8 000 (sachant qu’il n’y a pas de données de marché avant 8 000 jours), et effectuez un test de force brute comme celui-ci :

Il n’y a que 1309 lignes quotidiennes, comparées aux données du graphique d’échange :


Vous pouvez voir que les données sont cohérentes.
Adresse du modèle :« Version JavaScript du modèle de données historiques K-line de requête paginée » Adresse du modèle :Bibliothèque de dessins au trait
Les modèles et codes de stratégie ci-dessus sont uniquement destinés à des fins d’enseignement et d’apprentissage. Veuillez les optimiser et les modifier en fonction de vos besoins réels.