
Đây là mẫu thị trường WebSocket do FMZ phát triển chính thức. Sao chép và lưu dưới dạng mẫu, sau đó chọn mẫu này trong chiến lược mới để sử dụng: https://www.fmz.com/strategy/470349
Hiện tại, chiến lược FMZ chủ yếu dựa trên việc đóng gói API REST truyền thống. Mỗi lần truy cập API đều yêu cầu thiết lập kết nối mạng và thu thập dữ liệu thị trường thông qua thăm dò. Phương pháp này đơn giản, dễ sử dụng và đáp ứng được hầu hết các nhu cầu.
Tuy nhiên, giao thức REST có vấn đề về độ trễ cố hữu, sẽ trở nên nghiêm trọng hơn khi cần nhiều cặp giao dịch và nhiều chiến lược trao đổi. Mặc dù các chức năng Go của nền tảng có thể được thực hiện đồng thời, nhưng vấn đề chậm trễ vẫn tồn tại, khiến việc đáp ứng nhu cầu giao dịch chiến lược tần suất tương đối cao trở nên khó khăn. Ngoài ra, nếu có quá nhiều cặp giao dịch và tần suất thăm dò quá nhanh, nó sẽ gặp phải giới hạn tần suất truy cập của nền tảng giao dịch.
Hiện tại, các máy chủ của các sàn giao dịch cũng đang chịu tải nặng. Tất cả đều cung cấp giao thức WebSocket hoàn chỉnh và giới thiệu cho người dùng API. So với giao thức REST, WebSocket cung cấp phương thức kết nối hai chiều liên tục, cho phép trao đổi dữ liệu đến máy khách theo thời gian thực, tránh các yêu cầu và phản hồi thường xuyên và giảm đáng kể độ trễ. Nói chung, nếu độ trễ truy cập REST API là khoảng 20ms thì độ trễ đẩy dữ liệu qua WebSocket là khoảng 2ms. Ngoài ra, giao thức WebSocket không bị giới hạn bởi tần suất truy cập của nền tảng và về cơ bản có thể đăng ký hàng chục cặp giao dịch cùng một lúc.
Nền tảng giao dịch định lượng FMZ đã hỗ trợ giao thức WebSocket trong một thời gian dài và tương đối thuận tiện để gọi, nhưng đối với người dùng mới, việc xử lý nhiều đăng ký, đăng ký nhiều báo giá trao đổi và nhúng chúng một cách hiệu quả và thuận tiện vào vẫn còn quá phức tạp. toàn bộ quá trình chiến lược. . Mẫu tăng tốc dữ liệu thị trường thời gian thực WebSocket công khai này giải quyết được điểm khó khăn này. Nó rất dễ sử dụng và hoàn toàn tương thích với các lệnh gọi API được đóng gói hiện tại. Đối với hầu hết các chiến lược REST ban đầu, bạn chỉ cần sửa đổi chúng và sử dụng chúng trực tiếp để tăng tốc chiến lược của bạn.
Các tính năng chính:
Lưu ý rằng chiến lược này sử dụng TypeScript, có thể trông hơi lạ nếu bạn chỉ quen với JavaScript. TypeScript giới thiệu một hệ thống kiểu và các tính năng ngôn ngữ phong phú hơn dựa trên JavaScript. Đối với các ứng dụng như giao dịch định lượng cần xử lý logic phức tạp, sử dụng TypeScript có thể giảm thiểu các lỗi tiềm ẩn và cải thiện khả năng đọc và bảo trì mã. Vì vậy, bạn nên học một cách đơn giản.
Ngoài ra, chiến lược sử dụng cơ chế không đồng bộ của nền tảng FMZ và cơ chế luồng phụ có thể đượcHàm threadPostMessage gửi một thông điệp đến luồng chính. Phương pháp này không đồng bộ và phù hợp để thông báo cho luồng chính về các cập nhật dữ liệu được tạo trong luồng con. Luồng chính và luồng con có thể được kết nối thông quathreadGetData và__Hàm threadSetData chia sẻ dữ liệu. Cách tiếp cận này cho phép các luồng truy cập và sửa đổi trạng thái được chia sẻ. Nếu bạn muốn tìm hiểu về đa luồng, chiến lược này cũng là một ví dụ học tập tốt kết hợp với tài liệu nền tảng.
Nguyên tắc chính của chiến lược này là kết nối với các sàn giao dịch tiền kỹ thuật số chính thống thông qua WebSocket và nhận dữ liệu thị trường (như thông tin chuyên sâu và thông tin giao dịch) theo thời gian thực để cung cấp hỗ trợ dữ liệu cho các quyết định giao dịch định lượng. Quy trình thực hiện cụ thể như sau:
1. Cài đặt kết nối WebSocket
setupWebsocket Hàm này được sử dụng để khởi tạo kết nối WebSocket và nhận dữ liệu thị trường. Nó nhận được một tham sốmain_exchanges, biểu thị cuộc trao đổi cần được kết nối.
MyDial chức năng: Tạo kết nối WebSocket, ghi lại thời gian kết nối và xuất ra thời gian đóng khi kết nối bị đóng.updateSymbols chức năng: Kiểm tra thường xuyên các yêu cầu đăng ký mới và cập nhật danh sách cặp giao dịch hiện tại khi cần thiết.2. Xử lý dữ liệu
supports Đối tượng xác định các trao đổi được hỗ trợ và các chức năng xử lý của chúng (chẳng hạn nhưBinance). Mỗi chức năng xử lý của sàn giao dịch có trách nhiệm phân tích các tin nhắn đã nhận và trích xuất dữ liệu có liên quan.
processMsg chức năng: Xử lý tin nhắn từ các cuộc trao đổi, xác định các loại dữ liệu khác nhau (như cập nhật độ sâu, giao dịch, v.v.) và định dạng chúng thành các đối tượng sự kiện thống nhất.3. Dữ liệu đăng ký
Tại mỗi kết nối, hệ thống sẽ đăng ký các kênh dữ liệu thị trường có liên quan dựa trên cặp giao dịch hiện tại.
getFunction chức năng: Lấy hàm xử lý tương ứng theo tên trao đổi.this.wssPublic chức năng: Khởi tạo kết nối WebSocket và bắt đầu nhận dữ liệu.4. Quản lý luồng
Bắt đầu một luồng cho mỗi trao đổi, nhận dữ liệu theo thời gian thực và xử lý dữ liệu thông qua các hàm gọi lại.
threadMarket chức năng: Nhận dữ liệu trong luồng con, phân tích và lưu trữ thông tin độ sâu và giao dịch mới nhất.5. Viết lại phương pháp thu thập dữ liệu
Viết lại phương pháp để thu thập thông tin chuyên sâu và giao dịch cho từng sàn giao dịch, ưu tiên trả về dữ liệu được cập nhật theo thời gian thực.
$.setupWebsocket() Khởi tạo kết nối WebSocket tới điểm trao đổi mục tiêu.GetDepth() Và GetTrades() Chức năng này tự động sử dụng dữ liệu thời gian thực của WebSocket để trả về thông tin chi tiết về thị trường và hồ sơ giao dịch.Nếu hàm EventLoop() được thêm vào chiến lược, nó sẽ được thay đổi thành cơ chế kích hoạt. Khi dữ liệu wss được cập nhật, nó sẽ tự động được lấy ngay lập tức và nếu không có dữ liệu mới nhất, nó sẽ chờ. Tương đương với chức năng Ngủ thông minh. Tất nhiên, bạn cũng có thể sử dụng Ngủ trực tiếp.
function main() {
$.setupWebsocket()
while (true) {
exchanges.map(e=>{
Log(e.GetName(), e.GetDepth())
Log(e.GetName(), e.GetTrades())
})
EventLoop(100) // trigger by websocket
}
}
Tham khảo hướng dẫn trước đây của tôi về các chiến lược giao dịch đa tiền tệ https://www.fmz.com/digest-topic/10506, tại đó bạn có thể dễ dàng sửa đổi để hỗ trợ 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);
}
}
Chỉ cần làm theo mẫu chiến lược, mô phỏng định dạng sau và tham khảo tài liệu API trao đổi:
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)
}
}
}
}