
Nền tảng giao dịch định lượng FMZMục Theo dõi Chiến lượcNgười ta thường thấy một số chiến lược đa sản phẩm đồng thời theo dõi tình hình thị trường của hàng chục hoặc thậm chí toàn bộ thị trường của một sàn giao dịch. Làm thế nào để thực hiện được điều này? Và nó nên được thiết kế như thế nào? Bài viết này sẽ khám phá cách sử dụng giao diện thị trường tổng hợp để xây dựng chiến lược đa sản phẩm.
Lấy Binance và Huobi làm ví dụ và kiểm tra tài liệu API của họ. Chúng tôi thấy rằng cả hai đều có giao diện thông tin thị trường tổng hợp:
[
{
"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
},
...
]
Tuy nhiên, thực tế không phải vậy. Cấu trúc thực sự được trả về bởi giao diện Huobi là:
{
"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
}, ...]
}
Hãy cẩn thận khi xử lý dữ liệu trả về từ giao diện.
Làm thế nào để đóng gói hai giao diện này trong chiến lược và xử lý dữ liệu như thế nào? Chúng ta hãy cùng xem xét nhé.
Trước tiên chúng ta hãy viết một hàm tạo để xây dựng đối tượng điều khiển.
// 参数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
}
Sử dụng các hàm API FMZHttpQueryHàm này gửi yêu cầu truy cập vào giao diện trao đổi. sử dụngHttpQueryXử lý ngoại lệ là bắt buộctry...catchXử lý các trường hợp ngoại lệ như lỗi trả về giao diện.
Một số sinh viên có thể hỏi: “Cấu trúc dữ liệu trả về từ giao diện trao đổi là khác nhau. Chúng ta nên xử lý chúng như thế nào? Chắc chắn không thể sử dụng cùng một phương pháp xử lý”.
Trên thực tế, không chỉ cấu trúc dữ liệu trả về từ giao diện trao đổi khác nhau mà ngay cả cách đặt tên cho các trường dữ liệu trả về cũng khác nhau. Cùng một ý nghĩa có thể được gọi theo nhiều cách khác nhau. Ví dụ, các giao diện chúng tôi đã liệt kê ở trên. Biểu thức tương tự có nghĩa là giá mua, được gọi là:bidPrice, được gọi làbid。
Chúng tôi sử dụng các hàm gọi lại ở đây để tách các phần xử lý đặc biệt này.
Vì vậy, sau khi đối tượng trên được khởi tạo, nó sẽ trở thành như thế này khi sử dụng:
(Đoạn mã sau bỏ qua hàm tạocreateManager)
Theo dõi các hợp đồng này với Binance Futures:["BTCUSDT", "ETHUSDT", "EOSUSDT", "LTCUSDT", "ETCUSDT", "XRPUSDT"]
Huobi Spot giám sát các cặp giao dịch tiền này:["btcusdt", "ethusdt", "eosusdt", "etcusdt", "ltcusdt", "xrpusdt"]Ví dụ.
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)
}
}
Chạy thử nghiệm:
Đối tượng trao đổi đầu tiên thêm Binance futures và đối tượng trao đổi thứ hai thêm Huobi spot


Có thể thấy rằng ở đây, các hoạt động như cách lấy dữ liệu trả về từ giao diện được xử lý cụ thể cho các trao đổi khác nhau bằng cách sử dụng các hàm gọi lại.
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}})
Sau khi xây dựng thông tin thị trường, chúng ta có thể xây dựng tiếp tài sản tài khoản. Do chiến lược đa sản phẩm, dữ liệu tài sản tài khoản cũng phải đa dạng. May mắn thay, giao diện tài sản của tài khoản trao đổi thường trả về tất cả dữ liệu tài sản.
Trong trình xây dựngcreateManagerThêm phương pháp để lấy tài sản
// 更新资产
self.updateAcc = function(callBackFuncGetAcc) {
var ret = callBackFuncGetAcc(self)
if (!ret) {
return false
}
self.accData = ret
return true
}
Ngoài ra, do các định dạng và tên trường khác nhau được trả về bởi giao diện trao đổi nên các hàm gọi lại cũng được yêu cầu để xử lý đặc biệt.
Lấy ví dụ về giao dịch giao ngay Huobi và giao dịch tương lai Binance, hàm gọi lại có thể được viết như thế này:
// 获取账户资产的回调函数
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
}
Trích dẫn:

tài sản:

Có thể thấy rằng sau khi có được dữ liệu thị trường, dữ liệu có thể được xử lý để tính toán chênh lệch giá của từng loại và theo dõi chênh lệch giá giao ngay-tương lai của nhiều cặp giao dịch. Sau đó, có thể thiết kế một chiến lược phòng ngừa rủi ro giao ngay và tương lai đa dạng.
Theo phương pháp thiết kế này, các trao đổi khác cũng có thể được mở rộng, sinh viên nào quan tâm có thể thử nghiệm.