Это шаблон рынка WebSocket, официально разработанный FMZ. Скопируйте и сохраните его как шаблон, затем выберите этот шаблон в новой стратегии, чтобы использовать его: https://www.fmz.com/strategy/470349
В настоящее время стратегия FMZ в основном основана на традиционной инкапсуляции REST API. Каждый доступ к API требует установления сетевого соединения и получения рыночных данных посредством опроса. Этот метод прост и удобен в использовании и достаточен для большинства нужд.
Однако протокол REST имеет присущие ему проблемы с задержкой, которые будут усугубляться, когда требуется несколько торговых пар и несколько стратегий обмена. Хотя функции Go платформы могут выполняться одновременно, проблема задержки все еще существует, что затрудняет удовлетворение потребностей относительно высокочастотной стратегии торговли. Кроме того, если торговых пар слишком много, а частота опроса слишком быстро, он столкнется с ограничением частоты доступа торговой платформы.
В настоящее время серверы бирж также испытывают большую нагрузку. Все они предоставляют полноценный протокол WebSocket и рекомендуют его пользователям API. По сравнению с протоколом REST, WebSocket обеспечивает постоянный двусторонний метод соединения, который позволяет бирже передавать данные клиенту в режиме реального времени, избегая частых запросов и ответов и значительно сокращая задержку. В общем случае, если задержка доступа к REST API составляет около 20 мс, задержка передачи данных через WebSocket составляет около 2 мс. Кроме того, протокол WebSocket не ограничен частотой доступа к платформе, и в принципе можно подписаться на десятки торговых пар одновременно.
Количественная торговая платформа FMZ уже давно поддерживает протокол WebSocket, и он относительно удобен в использовании, но для начинающих пользователей все еще слишком сложно обрабатывать несколько подписок, подписываться на несколько биржевых котировок и эффективно и удобно встраивать их в весь процесс стратегии. Этот публичный шаблон ускорения рыночных данных в реальном времени WebSocket решает эту проблему. Он очень прост в использовании и полностью совместим с текущими инкапсулированными вызовами API. Для большинства исходных стратегий REST вы можете просто изменить их и использовать их напрямую для ускорения ваша стратегия.
Основные характеристики:
Обратите внимание, что эта стратегия использует TypeScript, что может показаться немного незнакомым, если вы знакомы только с JavaScript. TypeScript представляет систему типов и более богатые языковые возможности на основе JavaScript. Для таких приложений, как количественная торговля, которым необходимо обрабатывать сложную логику, использование TypeScript может уменьшить потенциальные ошибки и улучшить читаемость и удобство обслуживания кода. Поэтому рекомендуется выучить его просто.
Кроме того, стратегия использует асинхронный механизм платформы FMZ, а механизм sub-thread может бытьФункция 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. Конечно, вы также можете использовать 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)
}
}
}
}