Это официальный шаблон FMZ для WebSocket, который можно скопировать и сохранить в качестве шаблона, который можно использовать при нажатии на этот шаблон в новой политике:https://www.fmz.com/strategy/470349
В настоящее время стратегия FMZ основана на традиционном REST API-оболочке, при котором на каждом этапе доступа к API устанавливается сетевое соединение для получения рыночных данных путем опроса.
Однако REST-протокол имеет проблемы с задержкой, которые усиливаются при необходимости использования нескольких торговых пар и стратегий для нескольких бирж. Хотя Go-функции платформы могут выполняться одновременно, проблемы с задержкой остаются, что затрудняет удовлетворение потребностей в стратегических сделках с относительно высокой частотой, а также слишком большое количество сделок, частота обращений слишком быстро и ограничение частоты доступа к торговым платформам.
В настоящее время серверные нагрузки на биржи также очень высоки, и все они предлагают исправные протоколы WebSocket и рекомендуют пользователям API использовать. По сравнению с протоколом REST, WebSocket предоставляет устойчивый двусторонний способ подключения, который позволяет бирже отправлять данные клиентам в режиме реального времени, избегая частых запросов и ответов, что значительно снижает задержку. В целом, если задержка доступа к REST API составляет около 20 мс, то задержка доступа к WebSocket составляет около 2 мс.
Квалификационная торговая платформа FMZ поддерживала протокол WebSocket с самого начала и была относительно удобной для вызова, но для новичков обрабатывать несколько подписок, подписки на несколько биржевых рынков и эффективно и легко встраиваться во все стратегические процессы было слишком сложно. Этот открытый шаблон WebSocket Real-time Market Data Acceleration решает эту проблему, очень простой в использовании, полностью совместим с текущими упакованными API-призывами, для большинства из первоначальных REST-политик, простой переход прямо в использование, чтобы ускорить вашу политику.
Основные особенности:
Обратите внимание, что эта стратегия использует TypeScript, что может показаться немного незнакомым, если вы просто знакомы с JavaScript. TypeScript внедряет типную систему и более богатые языковые функции на основе JavaScript. Использование TypeScript может уменьшить потенциальные ошибки, улучшить читаемость и устойчивость кода.
В дополнение к этому политика использует синхронный механизм платформы FMZ, который позволяет потоку передавать сообщения через функцию __threadPostMessage. Этот способ является синхронным и используется для уведомления потока об обновлении данных, созданных в потоке. Данные могут быть обменены между потоком и потоком с помощью функций __threadGetData и __threadSetData.
Основной принцип этой стратегии заключается в том, чтобы подключить основные цифровые валютные биржи через WebSocket и получить рыночные данные в режиме реального времени (например, глубинную информацию и информацию о сделках), чтобы обеспечить поддержку данных для количественных торговых решений.
1. Настройки подключения WebSocket
setupWebsocket
Функция используется для инициализации соединения WebSocket, для получения рыночных данных.main_exchanges
В этом случае, если вы хотите, чтобы ваш сайт был доступен для всех пользователей, вы можете использовать его.
MyDial
Функции: создает соединение WebSocket, записывает время соединения и выводит время закрытия при закрытии соединения.updateSymbols
Функции: регулярно проверяет, есть ли новые запросы на подписку, и обновляет список текущих сделок по мере необходимости.2. Обработка данных
supports
Объект определяет поддерживаемые биржи и их функции обработки (например,Binance
Процессорная функция каждой биржи отвечает за анализ полученных сообщений и извлечение соответствующих данных.
processMsg
Функции: обрабатывает сообщения с бирж, идентифицирует различные типы данных (например, глубокие обновления, сделки и т.д.) и форматирует их в единые объекты событий;3. Данные о подписке
При каждом подключении система будет переводить данные рынка по каналам, связанным с подпиской, в зависимости от текущей сделки.
getFunction
Функции: получение соответствующей обработки в соответствии с именем биржи.this.wssPublic
Функции: инициализировать соединение WebSocket и начать прием данных.4. Управление потоками
Запускается одна нить для каждой биржи, получается информация в режиме реального времени и обрабатывается с помощью функций обратного вызова.
threadMarket
Функции: получать данные в подпроводках, анализировать и хранить новейшую глубину и информацию о сделках.5. Перезапись методов получения данных
Для каждой биржи переписывается способ получения глубины и информации о сделках, приоритетом которого является возвращение обновленных данных в режиме реального времени.
$.setupWebsocket()
Инициализировать соединение WebSocket с целевой биржей.GetDepth()
иGetTrades()
Функция, автоматически использующая данные WebSocket в режиме реального времени для глубины рынка и возвращения записей сделок.Если в политику включить функцию EventLoop (), то она превратится в триггерный механизм, который автоматически сразу же будет доступен, когда обновляются данные wss, а не ждать, пока не будут обновлены данные. Это эквивалентно умной функции Sleep, но, конечно же, можно использовать ее напрямую.
function main() {
$.setupWebsocket()
while (true) {
exchanges.map(e=>{
Log(e.GetName(), e.GetDepth())
Log(e.GetName(), e.GetTrades())
})
EventLoop(100) // trigger by websocket
}
}
Ссылаясь на мой предыдущий материал о стратегии торговли в разных валютахhttps://www.fmz.com/digest-topic/10506Здесь вы можете очень легко изменить его для поддержки WebSocket:
function MakeOrder() {
for (let i in Info.trade_symbols) {
let symbol = Info.trade_symbols[i];
let buy_price = exchange.GetDepth(symbol + '_USDT').Asks[0].Price;
let buy_amount = 50 / buy_price;
if (Info.position[symbol].value < 2000){
Trade(symbol, "buy", buy_price, buy_amount, symbol);
}
}
}
function OnTick() {
try {
UpdatePosition();
MakeOrder();
UpdateStatus();
} catch (error) {
Log("循环出错: " + error);
}
}
function main() {
$.setupWebsocket()
InitInfo();
while (true) {
let loop_start_time = Date.now();
if (Date.now() - Info.time.last_loop_time > Info.interval * 1000) {
OnTick();
Info.time.last_loop_time = Date.now();
Info.time.loop_delay = Date.now() - loop_start_time;
}
Sleep(5);
}
}
Если вы будете следовать шаблону политики и копировать формат, указанный ниже, то сможете ознакомиться с API-документацией биржи:
supports["Binance"] = function (ctx:ICtx) {
let processMsg = function (obj) {
let event = {
ts: obj.E,
instId: obj.s,
depth: null,
trades: [],
}
if (obj.e == "depthUpdate") {
let depth = {
asks: [],
bids: []
}
obj.b.forEach(function (item) {
depth.bids.push({
price: Number(item[0]),
qty: Number(item[1])
})
})
obj.a.forEach(function (item) {
depth.asks.push({
price: Number(item[0]),
qty: Number(item[1])
})
})
event.depth = depth
} else if (obj.e == 'bookTicker') {
event.depth = {
asks: [{ price: Number(obj.a), qty: Number(obj.A) }],
bids: [{ price: Number(obj.b), qty: Number(obj.B) }]
}
} else if (obj.e == 'aggTrade') {
event.ts = obj.E
event.trades = [{
price: Number(obj.p),
qty: Number(obj.q),
ts: obj.T,
side: obj.m ? "sell" : "buy"
}]
} else if (typeof (obj.asks) !== 'undefined') {
event.ts = obj.E || new Date().getTime()
let depth = {
asks: [],
bids: []
}
obj.bids.forEach(function (item) {
depth.bids.push({
price: Number(item[0]),
qty: Number(item[1])
})
})
obj.asks.forEach(function (item) {
depth.asks.push({
price: Number(item[0]),
qty: Number(item[1])
})
})
event.depth = depth
} else {
return
}
return event
}
let channels = ["depth20@100ms", /*"bookTicker", */"aggTrade"]
let ws = null
let endPoint = "wss://stream.binance.com/stream"
if (ctx.name == "Futures_Binance") {
endPoint = "wss://fstream.binance.com/stream"
}
while (true) {
if (!ws) {
let subscribes = []
ctx.symbols.forEach(function (symbol) {
channels.forEach(function (channel) {
subscribes.push(symbol.toLowerCase() + "@" + channel)
})
})
ws = MyDial(endPoint + (subscribes.length > 0 ? ("?streams=" + subscribes.join("/")) : ""))
}
if (!ws) {
Sleep(1000)
continue
}
updateSymbols(ctx, function(symbol:string, method:string) {
ws.write(JSON.stringify({
"method": method.toUpperCase(),
"params": channels.map(c=>symbol.toLowerCase()+'@'+c),
"id": 2
}))
})
let msg = ws.read(1000)
if (!msg) {
if (msg == "") {
trace("websocket is closed")
ws.close()
ws = null
}
continue
}
if (msg == 'ping') {
ws.write('pong')
} else if (msg == 'pong') {
} else {
let obj = JSON.parse(msg)
if (obj.error) {
trace(obj.error.msg, "#ff0000")
continue
}
if (!obj.stream) {
continue
}
if (obj.stream.indexOf("depth") != -1) {
if (typeof(obj.data.s) !== 'string') {
// patch
obj.data.s = obj.stream.split('@')[0].toUpperCase()
}
}
let event = processMsg(obj.data)
if (event) {
ctx.callback(event)
}
}
}
}