
이것은 FMZ에서 공식적으로 개발한 WebSocket 마켓 템플릿입니다. 복사하여 템플릿으로 저장한 다음 새 전략에서 이 템플릿을 선택하여 사용하세요: https://www.fmz.com/strategy/470349
현재 FMZ 전략은 주로 기존 REST API 캡슐화에 기반을 두고 있습니다. 각 API 액세스에는 네트워크 연결을 설정하고 폴링을 통해 시장 데이터를 얻어야 합니다. 이 방법은 간단하고 사용하기 쉬우며, 대부분의 요구 사항을 충족시키기에 충분합니다.
그러나 REST 프로토콜은 본질적으로 지연 문제가 있으며, 여러 거래 쌍과 여러 거래 전략이 필요한 경우 이 문제가 더욱 심화됩니다. 플랫폼의 Go 기능은 동시에 실행될 수 있지만 지연 문제가 여전히 존재하여 비교적 고빈도 전략 거래의 요구를 충족하기 어렵습니다. 또한 거래 쌍이 너무 많고 폴링 빈도가 너무 높으면 빠르면 거래 플랫폼의 접속 빈도 제한에 부딪히게 됩니다.
현재 거래소의 서버도 엄청난 부담을 받고 있습니다. 그들은 모두 완전한 WebSocket 프로토콜을 제공하고 API 사용자에게 권장합니다. REST 프로토콜과 비교해 WebSocket은 지속적인 양방향 연결 방법을 제공하므로 거래소가 클라이언트에 실시간으로 데이터를 푸시할 수 있으며, 빈번한 요청과 응답을 피하고 지연 시간을 대폭 줄일 수 있습니다. 일반적으로 REST API에 접근하는 데 걸리는 지연 시간이 약 20ms인 경우 WebSocket을 통해 데이터를 푸시하는 데 걸리는 지연 시간은 약 2ms입니다. 또한, WebSocket 프로토콜은 플랫폼의 접속 빈도에 제한을 받지 않으며, 기본적으로 한 번에 수십 개의 거래 쌍을 구독하는 것이 가능합니다.
FMZ 양적 거래 플랫폼은 오랫동안 WebSocket 프로토콜을 지원해 왔고 호출이 비교적 쉽지만 초보 사용자에게는 여러 구독을 처리하고 여러 거래소 호가를 구독하고 효율적이고 편리하게 임베드하는 것이 여전히 너무 복잡합니다. 전체 전략 프로세스. 이 공개 WebSocket 실시간 시장 데이터 가속 템플릿은 이러한 문제점을 해결합니다. 사용하기 매우 쉽고 현재 캡슐화된 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 기능과 동일합니다. 물론 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)
}
}
}
}