В 2020 году я написал статью, в которой представил высокочастотные торговые стратегии (https://www.fmz.com/digest-topic/6228Несмотря на то, что он получил некоторое внимание, он не был очень глубоким. С тех пор прошло два с половиной года, и рынок изменился. После того, как я опубликовал эту статью, моя высокочастотная стратегия смогла добиться стабильной прибыли в течение длительного времени, но прибыль постепенно снижалась и даже остановилась в определенный момент. В последние месяцы я потратил время на ее обновление, и она все еще может приносить небольшие прибыли. Эта статья предоставит более подробное введение в мою высокочастотную торговую стратегию и некоторый упрощенный код, служащий отправной точкой для обсуждения и обратной связи.
Счета скидок Комиссии
используя Binance в качестве примера, в настоящее время предлагают скидку производителя в размере 0,05% за каждые 100 000 торговых единиц. Если ежедневный объем торговли составляет 100 миллионов единиц, скидка составляет 5000 единиц. Конечно, комиссионный сбор по-прежнему зависит от ставки VIP, поэтому, если стратегия не нуждается в приеме заказов, уровень VIP мало влияет на стратегию высокой частоты. Различные уровни скидок комиссионных доступны на разных биржах, что требует высокого объема торговли. В первые дни, еще можно было получать прибыль без скидок, но по мере усиления конкуренции, скидки составляли большую часть прибыли, и высокочастотные трейдеры преследовали самые высокие ставки.
Скорость
Высокочастотная торговля называется так из-за своей высокой скорости. Присоединение к коллокационному серверу торговой биржи и получение наименьшей задержки и наиболее стабильного соединения стало одним из условий конкуренции. Время внутренней обработки стратегии также должно быть как можно меньше. В этой статье будет представлена используемая мной структура WebSocket, которая использует параллельное исполнение.
Подходящий рынок
Высокочастотная торговля считается жемчужиной в короне количественной торговли, и я считаю, что многие алгоритмические трейдеры пробовали это, но большинство людей должны были остановиться, потому что они не могли заработать деньги и не могли найти способ улучшить. Основная причина, вероятно, в том, что они выбрали неправильный торговый рынок. На начальном этапе стратегии относительно простые рынки должны быть нацелены на торговлю, чтобы заработать прибыль и получить обратную связь для улучшения, что способствует прогрессу стратегии. Если вы начинаете на самом жестком конкурентном рынке и конкурируете со многими оппонентами, вы потеряете деньги, как бы вы ни старались, и вы быстро сдадитесь.
Встретимся лицом к лицу с конкурентами
Рынок для любой торговли постоянно меняется, и ни одна торговая стратегия не может быть разовым решением. Это еще более очевидно в высокочастотной торговле, где вход на рынок означает непосредственную конкуренцию с самыми умными и самыми прилежными трейдерами. На рынке игры с нулевой суммой, чем больше вы зарабатываете, тем меньше зарабатывают другие. Чем позже вы входите, тем сложнее становится, и те, кто уже на рынке, должны постоянно улучшаться и могут быть устранены в любое время. Три или четыре года назад, вероятно, была лучшая возможность, но с недавним общим снижением активности на рынке цифровой валюты для начинающих стало очень сложно начать делать высокочастотную торговлю.
Существует несколько высокочастотных торговых стратегий, таких как высокочастотный арбитраж, который включает в себя поиск возможностей арбитража через эту или другие биржи, использование возможности поглощения заказов раньше других и получения прибыли с преимуществом скорости; высокочастотный трендный трейдинг, который включает в себя получение прибыли от краткосрочных тенденций; и рыночное формирование, которое включает в себя размещение заказов по обе стороны торговли покупкой и продажей, хорошее управление позициями и получение прибыли через скидки на комиссию. Моя стратегия сочетает тренд и рыночное формирование, сначала выявление тенденций, а затем размещение заказов, продажа сразу после исполнения и недержание запасных позиций. Ниже приведено введение в код стратегии.
Следующий код основан на базовой архитектуре бессрочного контракта Binance и в основном подписывается на глубину веб-сокета. Поскольку данные рынка и информация о счете подписываются отдельно, необходимо непрерывно использовать чтение (-1), чтобы определить, была ли получена последняя информация. Здесь EventLoop (1000) используется для избежания прямых мертвых петлей и снижения нагрузки системы. EventLoop (1000) блокирует до тех пор, пока не будет wss или одновременный возврат задачи, с тайм-аутом 1000 мс.
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]
}
Здесь все еще используется старый метод итерации глубины к требуемому количеству. Предположим, что ордер на покупку, который может быть выполнен за 10 монет в течение 1 секунды и без учета ситуации с новыми ордерами, цена продажи устанавливается на позицию, где ордер на покупку
Размер конкретного времени окна должен быть установлен самим.
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 - это средняя разница в спреде, и только тогда, когда разница между покупкой и продажей при размещении ордеров больше определенного кратного этого значения, и рынок быстрый, будет размещен ордер на покупку. Если удерживать короткую позицию, позиция также будет закрыта, чтобы избежать длительного удержания позиции.
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()]})
*/