
Plataforma de negociación cuantitativa FMZSección de vigilancia de estrategiasEs común ver algunas estrategias multiproducto que monitorean simultáneamente las condiciones del mercado de docenas o incluso de todo el mercado de una bolsa. ¿Cómo se hace esto? ¿Y cómo debería diseñarse? Este artículo explorará cómo utilizar la interfaz de mercado agregado de intercambio para construir una estrategia multiproducto.
Tomemos como ejemplo a Binance y Huobi y revisemos sus documentos API. Descubrimos que ambos tienen interfaces de información de mercado agregada:
[
{
"symbol": "BTCUSDT", // 交易对
"bidPrice": "4.00000000", //最优买单价
"bidQty": "431.00000000", //挂单量
"askPrice": "4.00000200", //最优卖单价
"askQty": "9.00000000", //挂单量
"time": 1589437530011 // 撮合引擎时间
}
...
]
[
{
"open":0.044297, // 开盘价
"close":0.042178, // 收盘价
"low":0.040110, // 最低价
"high":0.045255, // 最高价
"amount":12880.8510,
"count":12838,
"vol":563.0388715740,
"symbol":"ethbtc",
"bid":0.007545,
"bidSize":0.008,
"ask":0.008088,
"askSize":0.009
},
...
]
Sin embargo, no es así. La estructura que realmente devuelve la interfaz de Huobi es:
{
"status": "ok",
"ts": 1616032188422,
"data": [{
"symbol": "hbcbtc",
"open": 0.00024813,
"high": 0.00024927,
"low": 0.00022871,
"close": 0.00023495,
"amount": 2124.32,
"vol": 0.517656218,
"count": 1715,
"bid": 0.00023427,
"bidSize": 2.3,
"ask": 0.00023665,
"askSize": 2.93
}, ...]
}
Tenga cuidado al procesar los datos devueltos por la interfaz.
¿Cómo encapsular estas dos interfaces en la estrategia y cómo procesar los datos? Vamos a echarle un vistazo juntos.
Primero escribamos un constructor para construir el objeto de control.
// 参数e用于传入exchange交易所对象,参数subscribeList是需要处理的交易对列表,例如["BTCUSDT", "ETHUSDT", "EOSUSDT", "LTCUSDT", "ETCUSDT", "XRPUSDT"]
function createManager(e, subscribeList) {
var self = {}
self.supportList = ["Futures_Binance", "Huobi"] // 支持的交易所的
// 对象属性
self.e = e
self.name = e.GetName()
self.type = self.name.includes("Futures_") ? "Futures" : "Spot"
self.label = e.GetLabel()
self.quoteCurrency = ""
self.subscribeList = subscribeList // subscribeList : [strSymbol1, strSymbol2, ...]
self.tickers = [] // 接口获取的所有行情数据,定义数据格式:{bid1: 123, ask1: 123, symbol: "xxx"}}
self.subscribeTickers = [] // 需要的行情数据,定义数据格式:{bid1: 123, ask1: 123, symbol: "xxx"}}
self.accData = null // 用于记录账户资产数据
// 初始化函数
self.init = function() {
// 判断是否支持该交易所
if (!_.contains(self.supportList, self.name)) {
throw "not support"
}
}
// 判断数据精度
self.judgePrecision = function (p) {
var arr = p.toString().split(".")
if (arr.length != 2) {
if (arr.length == 1) {
return 0
}
throw "judgePrecision error, p:" + String(p)
}
return arr[1].length
}
// 更新资产
self.updateAcc = function(callBackFuncGetAcc) {
var ret = callBackFuncGetAcc(self)
if (!ret) {
return false
}
self.accData = ret
return true
}
// 更新行情数据
self.updateTicker = function(url, callBackFuncGetArr, callBackFuncGetTicker) {
var tickers = []
var subscribeTickers = []
var ret = self.httpQuery(url)
if (!ret) {
return false
}
try {
_.each(callBackFuncGetArr(ret), function(ele) {
var ticker = callBackFuncGetTicker(ele)
tickers.push(ticker)
for (var i = 0 ; i < self.subscribeList.length ; i++) {
if (self.subscribeList[i] == ele.symbol) {
subscribeTickers.push(ticker)
}
}
})
} catch(err) {
Log("错误:", err)
return false
}
self.tickers = tickers
self.subscribeTickers = subscribeTickers
return true
}
self.httpQuery = function(url) {
var ret = null
try {
var retHttpQuery = HttpQuery(url)
ret = JSON.parse(retHttpQuery)
} catch (err) {
// Log("错误:", err)
ret = null
}
return ret
}
self.returnTickersTbl = function() {
var tickersTbl = {
type : "table",
title : "tickers",
cols : ["symbol", "ask1", "bid1"],
rows : []
}
_.each(self.subscribeTickers, function(ticker) {
tickersTbl.rows.push([ticker.symbol, ticker.ask1, ticker.bid1])
})
return tickersTbl
}
// 初始化
self.init()
return self
}
Uso de las funciones API de FMZHttpQueryLa función envía una solicitud para acceder a la interfaz de intercambio. usarHttpQuerySe requiere manejo de excepcionestry...catchManejar excepciones como fallas de retorno de interfaz.
Algunos estudiantes pueden preguntarse: “Las estructuras de datos que devuelven las interfaces de intercambio son diferentes. ¿Cómo debemos manejarlas? Definitivamente no es posible utilizar el mismo método de procesamiento”.
De hecho, no sólo es diferente la estructura de datos devuelta por la interfaz de intercambio, sino que incluso el nombre de los campos de datos devueltos es diferente. El mismo significado puede tener diferentes nombres. Por ejemplo, las interfaces que enumeramos anteriormente. La misma expresión significa el precio de compra, que se llama:bidPrice, que se llamabid。
Aquí utilizamos funciones de devolución de llamada para separar estas partes de procesamiento especiales.
Entonces, después de inicializar el objeto anterior, cuando se usa se vuelve así:
(El siguiente código omite el constructorcreateManager)
Monitorea estos contratos con Binance Futures:["BTCUSDT", "ETHUSDT", "EOSUSDT", "LTCUSDT", "ETCUSDT", "XRPUSDT"]
Huobi Spot monitorea estos pares comerciales de moneda a moneda:["btcusdt", "ethusdt", "eosusdt", "etcusdt", "ltcusdt", "xrpusdt"]Por ejemplo.
function main() {
var manager1 = createManager(exchanges[0], ["BTCUSDT", "ETHUSDT", "EOSUSDT", "LTCUSDT", "ETCUSDT", "XRPUSDT"])
var manager2 = createManager(exchanges[1], ["btcusdt", "ethusdt", "eosusdt", "etcusdt", "ltcusdt", "xrpusdt"])
while (true) {
// 更新行情数据
var ticker1GetSucc = manager1.updateTicker("https://fapi.binance.com/fapi/v1/ticker/bookTicker",
function(data) {return data},
function (ele) {return {bid1: ele.bidPrice, ask1: ele.askPrice, symbol: ele.symbol}})
var ticker2GetSucc = manager2.updateTicker("https://api.huobi.pro/market/tickers",
function(data) {return data.data},
function(ele) {return {bid1: ele.bid, ask1: ele.ask, symbol: ele.symbol}})
if (!ticker1GetSucc || !ticker2GetSucc) {
Sleep(1000)
continue
}
var tbl1 = {
type : "table",
title : "期货行情数据",
cols : ["期货合约", "期货买一", "期货卖一"],
rows : []
}
_.each(manager1.subscribeTickers, function(ticker) {
tbl1.rows.push([ticker.symbol, ticker.bid1, ticker.ask1])
})
var tbl2 = {
type : "table",
title : "现货行情数据",
cols : ["现货合约", "现货买一", "现货卖一"],
rows : []
}
_.each(manager2.subscribeTickers, function(ticker) {
tbl2.rows.push([ticker.symbol, ticker.bid1, ticker.ask1])
})
LogStatus(_D(), "\n`" + JSON.stringify(tbl1) + "`", "\n`" + JSON.stringify(tbl2) + "`")
Sleep(10000)
}
}
Ejecutar las pruebas:
El primer objeto de intercambio agrega futuros de Binance y el segundo objeto de intercambio agrega spot de Huobi.


Se puede ver que aquí, las operaciones tales como obtener los datos devueltos por la interfaz se procesan específicamente para diferentes intercambios utilizando funciones de devolución de llamada.
var ticker1GetSucc = manager1.updateTicker("https://fapi.binance.com/fapi/v1/ticker/bookTicker",
function(data) {return data},
function (ele) {return {bid1: ele.bidPrice, ask1: ele.askPrice, symbol: ele.symbol}})
var ticker2GetSucc = manager2.updateTicker("https://api.huobi.pro/market/tickers",
function(data) {return data.data},
function(ele) {return {bid1: ele.bid, ask1: ele.ask, symbol: ele.symbol}})
Después de formular la adquisición de información de mercado, podemos formular la adquisición de activos de cuenta. Debido a la estrategia multiproducto, los datos de activos de cuenta también deben ser múltiples. Afortunadamente, la interfaz de activos de la cuenta de intercambio generalmente devuelve todos los datos de los activos.
En el constructorcreateManagerAgregar un método para obtener activos
// 更新资产
self.updateAcc = function(callBackFuncGetAcc) {
var ret = callBackFuncGetAcc(self)
if (!ret) {
return false
}
self.accData = ret
return true
}
Además, debido a los diferentes formatos y nombres de campos devueltos por la interfaz de intercambio, también se requieren funciones de devolución de llamada para un procesamiento especial.
Tomando como ejemplos el spot de Huobi y los futuros de Binance, la función de devolución de llamada se puede escribir de la siguiente manera:
// 获取账户资产的回调函数
var callBackFuncGetHuobiAcc = function(self) {
var account = self.e.GetAccount()
var ret = []
if (!account) {
return false
}
// 构造资产的数组结构
var list = account.Info.data.list
_.each(self.subscribeList, function(symbol) {
var coinName = symbol.split("usdt")[0]
var acc = {symbol: symbol}
for (var i = 0 ; i < list.length ; i++) {
if (coinName == list[i].currency) {
if (list[i].type == "trade") {
acc.Stocks = parseFloat(list[i].balance)
} else if (list[i].type == "frozen") {
acc.FrozenStocks = parseFloat(list[i].balance)
}
} else if (list[i].currency == "usdt") {
if (list[i].type == "trade") {
acc.Balance = parseFloat(list[i].balance)
} else if (list[i].type == "frozen") {
acc.FrozenBalance = parseFloat(list[i].balance)
}
}
}
ret.push(acc)
})
return ret
}
var callBackFuncGetFutures_BinanceAcc = function(self) {
self.e.SetCurrency("BTC_USDT") // 设置为U本位合约的交易对
self.e.SetContractType("swap") // 合约都是永续合约
var account = self.e.GetAccount()
var ret = []
if (!account) {
return false
}
var balance = account.Balance
var frozenBalance = account.FrozenBalance
// 构造资产数据结构
_.each(self.subscribeList, function(symbol) {
var acc = {symbol: symbol}
acc.Balance = balance
acc.FrozenBalance = frozenBalance
ret.push(acc)
})
return ret
}
Citas:

activos:

Se puede observar que después de obtener los datos del mercado, los datos se pueden procesar para calcular la diferencia de precio de cada variedad y monitorear la diferencia de precio al contado y futuros de múltiples pares comerciales. Luego, se puede diseñar una estrategia de cobertura de futuros y spot de múltiples variedades.
De acuerdo con este método de diseño, también se pueden ampliar otros intercambios. Los estudiantes que estén interesados pueden probarlo.