July 16

Интересная ситуация со ставкой фондирования ByBit SUN Perpetual 14.07.2024

В минувшее воскресенье Никита Ануфриев пригласил меня на свой YouTube канал провести прямой эфир. Получилось очень даже ничего. Никита, спасибо!

Было достаточно много обратной связи. Всем, кто её оставил, огромное спасибо! Какой бы она не была :)

"Хейтеры, люблю вас!" (с)

Но отдельного внимания заслуживает комментарий Aleksandr Chernikov, потому что именно он вдохновил меня на написание этой статьи.

Комментарий Александра

И касается это одной из моих любимых тем — ставки фондирования.

Intro

Сегодняшняя наша статья будет касаться монетки SUN, а точнее бессрочного фьючерсного usd-margin контрактов на эту монетку на бирже ByBit.

Фьючерсный контракт ByBit SUN perpetual

Не будем углубляться в фундаментальную составляющую этого проекта. С ним вы можете ознакомиться на официальном сайте проекта.

Важно кое-что другое. Последнюю неделю монетка активно растёт: +15%, в то время как широкий рынок дал ~3%.

Фьючерсный контракт ByBit SUN perpetual

13.07.2024 в 17:39 UTC+3 в показателе Funding rate по бессрочного фьючерсного контракта ByBit SUN Perpetual начали происходить аномалии — он стал экстремально отрицательным.

Ставка фондирования по бессрочному фьючерсному контракту ByBit SUNUSDT Perpetual по данным Coinglass на момент 15.07.2024
Ставка фондирования по бессрочному фьючерсному контракту ByBit SUNUSDT Perpetual по данным биржи ByBit на момент 15.07.2024

На самом деле обычное дело, когда на экстремумах движений растаскивают базовый актив и производный инструмент без экспирации, чтобы длинные позиции по последнему были более привлекательными. А потом во всех закупившихся на хаях прокатывают на хуях... перераспределяют риски :)

Открытый интерес в ByBit SUNUSDT Perpetual по данным Coinglass

Считаем funding rate

Про funding rate или ставку фондирования у меня есть отдельная статья. Там вы можете ознакомиться с природой этого явления.

Но дабы соблюсти все формальности мы сходим на биржу ByBit и заберём все формулы.

Расчёт ставки фондирования на бирже ByBit на момент 15.07.2024

Ключевая составляющая в формировании ставки фондирования это премиум или Premium index, который по сути равен разнице между ценой маркировки дериватива, для которого производятся расчеты, и индексной ценой.

Цена маркировки — если очень упрощенно, средняя цена между лучшими ask и bid заявками.
Индексная цена — средневзвешенная по объёму цена базового актива.

Хорошо, давайте рассчитаем индексную цену для SUN. Тем более биржа ByBit очень любезно посчитала для нас все необходимые весовые коэффициенты.

Весовые коэффициенты для расчёта индексной цены для SUN по данным биржи ByBit
Корректнее будет сделать это с использованием взвешивания по объёму. Я сделаю в двух вариантах — с коэффициентами, которые отдает биржа и через объёмное взвешивание (с коэффициентами у меня, кстати, не получилось. Но исходный код индикатора я оставлю, может быть кто-то допилит).

Для расчётов будем использовать TradingView и язык pinescript.

Чтобы убедиться, что данные TradingView валидны и соответствуют биржевым, сходим на каждую биржу и сравним котировки.

Котировки базового спотового актива SUN на бирже Binance на момент 15.07.2024
Котировки базового спотового актива SUN на бирже Bitfinex на момент 15.07.2024
Что-то на финике совсем грустно...
Котировки базового спотового актива SUN на бирже Bitget на момент 15.07.2024
Котировки базового спотового актива SUN на бирже ByBit на момент 15.07.2024
Котировки базового спотового актива SUN на бирже Gate на момент 15.07.2024
Котировки базового спотового актива SUN на бирже Kucoin на момент 15.07.2024

Да, бывают расхождения. Но в целом мы можем доверять котировкам Tradingview.

Tradingview забирает котировки с бирж, но формальность, я считаю, нужно соблюсти.

Отлично. Приступаем к расчетам. Исходный код индикатора ниже, а также доступен по ссылке.

//@version=5
indicator(title = "Index price", shorttitle = "Index price", overlay = false)

// Inputs
var string displayModeInput = input.string(defval = "Default", title = "Display mode", options = ["Default", "Normalized"], group = "Display settings")
var string calculationModeInput = input.string(defval = "Volume", title = "Calculation mode", options = ["Const", "Volume"], group = "Calculations settings")

// Market data parsingvar string timeframe = syminfo.type == "futures" and timeframe.isintraday ? "1D" : timeframe.period

var string binaceSymbol = str.replace(source = "BINANCE:" + syminfo.ticker, target = ".P", replacement = "", occurrence = 0)
float binaceClose = request.security(symbol = binaceSymbol, timeframe = timeframe, expression = close , ignore_invalid_symbol = true)
float binaceVolume = request.security(symbol = binaceSymbol, timeframe = timeframe, expression = volume , ignore_invalid_symbol = true)
binaceClose := na(x = binaceClose) == true ? 0 : binaceClose
binaceVolume := na(x = binaceVolume) == true ? 0 : binaceVolume

var string bitfinexSymbol = str.replace(source = "BITFINEX:" + syminfo.ticker, target = ".P", replacement = "", occurrence = 0)
float bitfinexClose = request.security(symbol = bitfinexSymbol, timeframe = timeframe, expression = close, ignore_invalid_symbol = true)
float bitfinexVolume = request.security(symbol = bitfinexSymbol, timeframe = timeframe, expression = volume, ignore_invalid_symbol = true)
bitfinexClose := na(x = bitfinexClose) == true ? 0 : bitfinexClose
bitfinexVolume := na(x = bitfinexVolume) == true ? 0 : bitfinexVolume

var string bitgetSymbol = str.replace(source = "BITGET:" + syminfo.ticker, target = ".P", replacement = "", occurrence = 0)float bitgetClose = request.security(symbol = bitgetSymbol, timeframe = timeframe, expression = close , ignore_invalid_symbol = true)
float bitgetVolume = request.security(symbol = bitgetSymbol, timeframe = timeframe, expression = volume , ignore_invalid_symbol = true)
bitgetClose := na(x = bitgetClose) == true ? 0 : bitgetClose
bitgetVolume := na(x = bitgetVolume) == true ? 0 : bitgetVolume

var string bybitSymbol = str.replace(source = "BYBIT:" + syminfo.ticker, target = ".P", replacement = "", occurrence = 0)
float bybitClose = request.security(symbol = bybitSymbol, timeframe = timeframe, expression = close , ignore_invalid_symbol = true)
float bybitVolume = request.security(symbol = bybitSymbol, timeframe = timeframe, expression = volume , ignore_invalid_symbol = true)
bybitClose := na(x = bybitClose) == true ? 0 : bybitClose
bybitVolume := na(x = bybitVolume) == true ? 0 : bybitVolume

var string gateSymbol = str.replace(source = "GATE:" + syminfo.ticker, target = ".P", replacement = "", occurrence = 0)
float gateClose = request.security(symbol = gateSymbol, timeframe = timeframe, expression = close , ignore_invalid_symbol = true)
float gateVolume = request.security(symbol = gateSymbol, timeframe = timeframe, expression = volume , ignore_invalid_symbol = true)
gateClose := na(x = gateClose) == true ? 0 : gateClose
gateVolume := na(x = gateVolume) == true ? 0 : gateVolume

var string kucoinSymbol = str.replace(source = "KUCOIN:" + syminfo.ticker, target = ".P", replacement = "", occurrence = 0)
float kucoinClose = request.security(symbol = kucoinSymbol, timeframe = timeframe, expression = close , ignore_invalid_symbol = true)
float kucoinVolume = request.security(symbol = kucoinSymbol, timeframe = timeframe, expression = volume , ignore_invalid_symbol = true)
kucoinClose := na(x = kucoinClose) == true ? 0 : kucoinClose
kucoinVolume := na(x = kucoinVolume) == true ? 0 : kucoinVolume

// Calculations
float chartPrice = closefloat indexPrice = na
if calculationModeInput == "Volume"
    float totalVolume = binaceVolume + bitfinexVolume + bitgetVolume + bybitVolume + gateVolume + kucoinVolume    indexPrice := (binaceClose * binaceVolume + bitfinexClose * bitfinexVolume + bitgetClose * bitgetVolume + bybitClose * bybitVolume + gateClose * gateVolume + kucoinClose * kucoinVolume) / totalVolume
else
    indexPrice := binaceClose * 0.7869 + bitfinexClose * 0.0001 + bitgetClose * 0.0188 + bybitClose * 0.0673 + gateClose * 0.1042 + kucoinClose * 0.0223
if displayModeInput == "Normalized"
    chartPrice := close - indexPrice
    indexPrice := 0.0

// Plot
plot(series = chartPrice, title = "Close price", color = color.white, style = plot.style_stepline)
plot(series = indexPrice, title = "Close price", color = color.orange, style = plot.style_stepline)

Результаты расчетов

Ну что, а теперь самое интересное. Идём на биржу ByBit и смотрим, какую индексную цену посчитали они.

Индексная цена SUN по данным биржи ByBit на момент 15.07.2024

Давайте чуть крупнее сделаем, чтобы было лучше видно.

Индексная цена SUN по данным биржи ByBit на момент 15.07.2024

Как мы видим то, что биржа посчитала в качестве индексной цены, не соответствует действительности. А значит расчеты по ставке фондирования произведены неверно: ставка фондирования должна была быть экстремально положительной, а не экстремально отрицательной.

Чтобы ставка фондирования была экстремально положительной, цена маркировки должна быть выше индексной цены (то, что было по факту). Чтобы ставка фондирования была экстремально отрицательной, цена маркировки должна быть ниже индексной цены (то, что посчитал ByBit).

Outro

Интересно:

  • знают арбитражеры и шортисты, что у них откусили часть доходности?
  • знают ли об этом лонгисты, которые залетели в SUN Perpetual (возможно с целью заработать ту самую ставку фондирования)?
  • Или в какой-то момент времени ByBit стала заинтересована в том, чтобы производный инструмент начал сильнее отрываться от базового актива?
Или я опять что-то напутал. Никогда не исключаю :)

UPD: на втором прямом эфире Александр добавил важную деталь. На бирже OKX SUN стоил сильно дороже, чем на всех других биржах. Но как раз в этот момент монету делистнули (с чем, скорее всего, и связано расхождение), поэтому проверить не получится ¯\_(ツ)_/¯. Так что не катим бочку на ByBit :) Но всё равно было интересно.