저는 2020년에 고주파 거래 전략을 소개하는 기사를 썼습니다 (https://www.fmz.com/digest-topic/6228) 비록 약간의 관심을 받았지만, 그것은 매우 깊이 있지 않았다. 그 이후로 2 년 반이 흘렀고, 시장은 변했습니다. 그 기사를 발표 한 후, 내 고주파 전략은 오랫동안 안정적인 이익을 얻을 수 있었지만, 이익은 점차 감소하고 심지어 한 지점에서 멈췄습니다. 최근 몇 달 동안, 나는 그것을 리모델링하는 데 시간을 보냈고, 여전히 약간의 이익을 얻을 수 있습니다. 이 기사는 토론과 피드백의 출발점으로 봉사하는 내 고주파 거래 전략과 간단한 코드에 대한 보다 자세한 소개를 제공합니다.
위원회의 환불계산
예를 들어 바이낸스를 사용하여 현재 거래된 100,000 유닛마다 0.05%의 메이커 할인을 제공합니다. 일일 거래량이 1억 유닛이라면 할인액은 5,000 유닛입니다. 물론, 수령 수수료는 여전히 VIP 비율에 달려 있습니다. 따라서 전략이 주문을 받아 들일 필요가 없다면 VIP 수준은 고주파 전략에 거의 영향을 미치지 않습니다. 다양한 거래소에서 다양한 수준의 수수료 할인금이 제공됩니다. 높은 거래량을 필요로합니다. 초기에는 여전히 할인 없이도 수익을 얻을 수 있었지만 경쟁이 심화됨에 따라 할인료는 수익의 더 큰 비율을 차지했으며 고주파 거래자는 최고 비율을 추구했습니다.
속도
높은 주파수 거래는 빠른 속도 때문에 그렇게 불린다. 거래 거래소의 콜로케이션 서버에 가입하고 가장 낮은 지연 시간 및 가장 안정적인 연결을 얻는 것이 경쟁 조건 중 하나가되었습니다. 전략의 내부 처리 시간 또한 가능한 한 낮아야합니다. 이 기사는 동시 실행을 사용하는 내가 사용한 WebSocket 프레임워크를 소개합니다.
적절한 시장
높은 주파수 거래는 양적 거래의 보석으로 간주되며, 많은 알고리즘 트레이더들이 시도했다고 믿습니다. 그러나 대부분의 사람들은 돈을 벌 수 없었고 개선할 방법을 찾지 못했기 때문에 중단해야했습니다. 주된 이유는 아마도 잘못된 거래 시장을 선택했기 때문입니다. 전략의 초기 단계에서 상대적으로 쉬운 시장은 수익을 창출하고 개선에 대한 피드백을 받기 위해 거래를 목표로 삼아야하며, 이는 전략의 발전에 유리한 것입니다. 가장 치열한 경쟁 시장에서 시작하여 많은 경쟁자와 경쟁하는 경우, 아무리 노력해도 돈을 잃게 될 것이며, 빠르게 포기 할 것입니다. 나는 새로 출시 된 영구 계약 거래 쌍으로 시작하는 것이 좋습니다. 경쟁자가 적고, 특히 상대적으로 큰 거래량이있는 거래가 더 쉬워지기 때문입니다. BTC와 ETH는 거래량이 가장 높고 가장 활발하지만 생존 가능성이 가장 어렵습니다.
경쟁을 직시하라
어떤 거래의 시장도 끊임없이 변화하고 있으며, 어떤 거래 전략도 일회성 솔루션이 될 수 없습니다. 이것은 시장에 진입하는 것이 가장 똑똑하고 가장 열심히 거래하는 상인과 직접 경쟁하는 것을 의미하는 고주파 거래에서 더욱 분명합니다. 제로섬 게임 시장에서, 당신이 더 많이 벌면 다른 사람들이 덜 벌습니다. 당신이 늦게 들어가면 더 어려워지고, 이미 시장에있는 사람들은 지속적으로 개선해야하며 언제든지 제거 될 수 있습니다. 3 년 또는 4 년 전은 아마도 가장 좋은 기회였습니다. 그러나 최근 디지털 통화 시장의 전체 활동 감소로 인해 초보자가 고주파 거래를 시작하는 것은 매우 어려워졌습니다.
이 또는 다른 거래소를 통해 중재 기회를 찾는 것과 다른 거래소를 통해 주문을 먹어치우며 속도 이점으로 수익을 창출하는 기회를 잡는 것과 같은 고주파 거래 전략이 여러 가지가 있습니다. 단기 트렌드에서 이익을 얻는 고주파 트렌드 거래 및 매출 거래의 양쪽에서 주문을 배치하고, 포지션을 잘 제어하고 수수료 할인으로 수익을 창출하는 마켓 메이킹과 같은 마켓 메이킹 전략이 있습니다. 내 전략은 트렌드와 시장 메이킹을 결합하여 먼저 트렌드를 식별하고 그 다음 주문을 배치하여 실행 후 즉시 판매하고 재고 포지션을 보유하지 않습니다. 아래는 전략 코드에 대한 소개입니다.
다음 코드는 바이낸스 영구 계약의 기본 아키텍처에 기반하고 주로 웹소켓 깊이 주문 흐름 거래 및 위치 정보를 구독합니다. 시장 데이터와 계정 정보가 별도로 구독되기 때문에 최신 정보가 얻었는지 여부를 결정하기 위해 읽기 (-1) 를 지속적으로 사용해야합니다. 여기서 EventLoop (1000) 은 직접적인 죽은 루프를 피하고 시스템 부하를 줄이기 위해 사용됩니다. EventLoop (1000) 는 1000ms의 타임아웃으로 wss 또는 동시 작업 반환이있을 때까지 블록합니다.
var datastream = null
var tickerstream = null
var update_listenKey_time = 0
function ConncetWss(){
if (Date.now() - update_listenKey_time < 50*60*1000) {
return
}
if(datastream || tickerstream){
datastream.close()
tickerstream.close()
}
// need APIKEY
let req = HttpQuery(Base+'/fapi/v1/listenKey', {method: 'POST',data: ''}, null, 'X-MBX-APIKEY:' + APIKEY)
let listenKey = JSON.parse(req).listenKey
datastream = Dial("wss://fstream.binance.com/ws/" + listenKey + '|reconnect=true', 60)
// Symbols is the pair of symbol
let trade_symbols_string = Symbols.toLowerCase().split(',')
let wss_url = "wss://fstream.binance.com/stream?streams="+trade_symbols_string.join(Quote.toLowerCase()+"@aggTrade/")+Quote.toLowerCase()+"@aggTrade/"+trade_symbols_string.join(Quote.toLowerCase()+"@depth20@100ms/")+Quote.toLowerCase()+"@depth20@100ms"
tickerstream = Dial(wss_url+"|reconnect=true", 60)
update_listenKey_time = Date.now()
}
function ReadWss(){
let data = datastream.read(-1)
let ticker = tickerstream.read(-1)
while(data){
data = JSON.parse(data)
if (data.e == 'ACCOUNT_UPDATE') {
updateWsPosition(data)
}
if (data.e == 'ORDER_TRADE_UPDATE'){
updateWsOrder(data)
}
data = datastream.read(-1)
}
while(ticker){
ticker = JSON.parse(ticker).data
if(ticker.e == 'aggTrade'){
updateWsTrades(ticker)
}
if(ticker.e == 'depthUpdate'){
updateWsDepth(ticker)
}
ticker = tickerstream.read(-1)
}
makerOrder()
}
function main() {
while(true){
ConncetWss()
ReadWss()
worker()
updateStatus()
EventLoop(1000)
}
}
앞서 언급했듯이, 내 고주파 전략은 먼저 구매 및 판매 거래를 실행하기 전에 트렌드를 식별해야합니다. 단기 트렌드를 판단하는 것은 주로 거래 데이터, 즉 방향, 가격, 양 및 거래 시간을 포함하는 aggTrade 가입을 기반으로합니다. 구매 및 판매 트레이드는 주로 깊이와 거래량을 참조합니다. 다음은 고려해야 할 자세한 지표입니다. 대부분은 구매 및 판매를 위해 두 그룹으로 나뉘어져 있으며 특정 시간 창 내에서 동적으로 계산됩니다. 내 전략의 시간 창은 10 초 이내에 있습니다.
let bull = last_sell_price > avg_sell_price && last_buy_price > avg_buy_price &&
avg_buy_amount / avg_buy_time > avg_sell_amount / avg_sell_time;
let bear = last_sell_price < avg_sell_price && last_buy_price < avg_buy_price &&
avg_buy_amount / avg_buy_time < avg_sell_amount / avg_sell_time;
마지막 사격 가격이 평균 사격 가격보다 높고 마지막 사격 가격이 평균 사격 가격보다 높고 구매 주문의 값이 일정한 간격에서 판매 주문의 값보다 높으면 단기 상승 시장이라고 판단됩니다. 하향 시장의 경우는 그 반대의 경우입니다.
function updatePrice(depth, bid_amount, ask_amount) {
let buy_price = 0
let sell_price = 0
let acc_bid_amount = 0
let acc_ask_amount = 0
for (let i = 0; i < Math.min(depth.asks.length, depth.bids.length); i++) {
acc_bid_amount += parseFloat(depth.bids[i][1])
acc_ask_amount += parseFloat(depth.asks[i][1])
if (acc_bid_amount > bid_amount && buy_price == 0) {
buy_price = parseFloat(depth.bids[i][0]) + tick_size
}
if (acc_ask_amount > ask_amount && sell_price == 0) {
sell_price = parseFloat(depth.asks[i][0]) - tick_size
}
if (buy_price > 0 && sell_price > 0) {
break
}
}
return [buy_price, sell_price]
}
여기, 필요한 양에 깊이 반복의 오래된 방법은 여전히 사용 됩니다. 1 초 이내에 10 동전 실행 될 수 있는 구매 주문을 가정하고 새로운 주문의 상황을 고려하지 않고, 판매 가격은 구매 주문이 위치 설정됩니다
10개의 동전을 넣을 수 있습니다. 특정 시간 창 크기를 스스로 설정해야 합니다.
let buy_amount = Ratio * avg_sell_amount / avg_sell_time
let sell_amount = Ratio * avg_buy_amount / avg_buy_time
이 비율은 마지막 판매 주문 양의 고정된 비율을 나타내고, 구매 주문 양은 최신 판매 주문 양의 고정된 비율로 나타냅니다. 이것은 전략이 현재 구매 및 판매 활동에 따라 주문 크기를 조정 할 수 있습니다.
if(bull && (sell_price-buy_price) > N * avg_diff) {
trade('buy', buy_price, buy_amount)
}else if(position.amount < 0){
trade('buy', buy_price, -position.amount)
}
if(bear && (sell_price-buy_price) > N * avg_diff) {
trade('sell', sell_price, sell_amount)
}else if(position.amount > 0){
trade('sell', sell_price, position.amount)
}
그 중 avg_diff는 스프레드의 평균 차이이며, 주문을 하는 경우 구매 및 판매 차이가 이 값의 특정 배수보다 크고 시장이 상승할 때만 구매 주문이 배치됩니다. 짧은 포지션을 보유하면 포지션을 오랫동안 보유하지 않도록 포지션도 닫을 것입니다. 주문이 채워지는지 확인하기 위해 오직 메이커 주문이 배치 될 수 있으며 주문 반환을 기다리는 것을 피하기 위해 사용자 정의 주문 ID를 사용할 수 있습니다.
var tasks = []
var jobs = []
function worker(){
let new_jobs = []
for(let i=0; i<tasks.length; i++){
let task = tasks[i]
jobs.push(exchange.Go.apply(this, task.param))
}
_.each(jobs, function(t){
let ret = t.wait(-1)
if(ret === undefined){
new_jobs.push(t)//未返回的任务下次继续等待
}
})
jobs = new_jobs
tasks = []
}
/*
tasks.push({'type':'order','param': ["IO", "api", "POST","/fapi/v1/order",
"symbol="+symbol+Quote+"&side="+side+"&type=LIMIT&timeInForce=GTX&quantity="+
amount+"&price="+price+"&newClientOrderId=" + UUID() +"×tamp="+Date.now()]})
*/