Фьючерсы против спота: заработок на ставке финансирования

Урок 14 курса по арбитражу. Что такое бессрочный фьючерс и его ставка финансирования, как держать монету на спотовом рынке и противоположную позицию во фьючерсе, и как комиссии, отрицательная ставка и ликвидация влияют на результат. Короткий код на Python с числами, которые можно проверить вручную.

Урок 14 курса "Собери своего арбитражного бота". До сих пор бот искал разрывы, которые закрываются за секунды. Сегодня у него появится вторая стратегия: она зарабатывает медленно и стабильно, но несет другой вид риска.

Два способа владеть одной монетой

Монету можно купить на спотовом рынке: вы платите деньги, получаете монету, она ваша.

Можно также торговать фьючерсом на эту монету. Фьючерс является контрактом, цена которого следует за монетой. Саму монету вы не получаете. Вы получаете позицию: если вы в лонге и цена растет на 10, вы зарабатываете 10 на монету, а если падает на 10, теряете 10. Если вы в шорте, все наоборот. Для фьючерса нужна только часть денег в виде залога, его называют маржой, поэтому в нем есть кредитное плечо.

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

Ставка финансирования

Ответом является платеж между трейдерами, который называют ставкой финансирования. Каждые 8 часов, на многих биржах, одна сторона платит другой.

  • Если бессрочный фьючерс торгуется выше спота, ставка положительна, лонги платят шортам.
  • Если он торгуется ниже спота, ставка отрицательна, шорты платят лонгам.

Платеж равен ставке, умноженной на размер позиции. Когда лонг слишком популярен, его становится дорого держать, часть трейдеров закрывает позиции, и цена возвращается к споту. В этом весь механизм.

Идея стратегии

Держите обе стороны одновременно: купите 1 монету на спотовом рынке и откройте шорт на 1 монету в бессрочном фьючерсе. Теперь цена монеты не важна. Если она растет на 10, монета приносит 10, а шорт теряет 10. Если падает, происходит обратное. Суммарный результат не меняется, трейдеры называют это дельта-нейтральной позицией.

Остается финансирование. Когда ставка положительна, вы держите шорт и получаете платеж каждые 8 часов. Вам платят за удержание позиции без ценового риска. Это называют кэш-энд-керри или арбитражем финансирования.

Прямой подход: посмотреть на годовое число

Предположим, ставка равна 0.01 процента за 8 часов. Это 3 платежа в день, поэтому 3 × 365 × 0.01 процента = 10.95 процента в год. Прямой вывод: вложить 100 и получать 10.95 в год без риска, значит, надо вложить все деньги.

Проверьте это циклом, который учитывает больше. Следующий код открывает пару по цене 100, проходит через 90 платежей финансирования, 30 дней, и добавляет комиссии. Комиссии такие: сделка на споте стоит 0.1 процента, сделка с бессрочным фьючерсом стоит 0.05 процента, обе комиссии платятся при открытии и при закрытии.

SPOT_FEE, PERP_FEE = 0.001, 0.0005          # taker fees, per trade
price = 100.0

# funding rate per 8 hours, 3 payments a day, 30 days = 90 payments
rates = [0.0001] * 45 + [0.00005] * 20 + [-0.0001] * 15 + [0.0001] * 10

def carry(rates, exit_when_negative=False):
    cost_in = price * SPOT_FEE + price * PERP_FEE        # buy spot, open short
    cost_out = price * SPOT_FEE + price * PERP_FEE       # sell spot, close short
    income = 0.0
    for r in rates:
        if exit_when_negative and r < 0:
            break
        income += price * r                              # the short side receives r * notional
    return income - cost_in - cost_out, income

for name, flag in [("hold all 30 days", False), ("leave at first negative", True)]:
    net, gross = carry(rates, flag)
    print(f"{name:24} funding {gross:6.3f}  fees {2*(price*SPOT_FEE+price*PERP_FEE):.3f}  net {net:+.3f}")

fees = 2 * (price * SPOT_FEE + price * PERP_FEE)
print("payments to cover fees", round(fees / (price * 0.0001)), "=", round(fees / (price * 0.0001) / 3, 1), "days")

Результат:

hold all 30 days         funding  0.500  fees  0.300  net +0.200
leave at first negative  funding  0.550  fees  0.300  net +0.250
payments to cover fees 30 = 10.0 days

Разберем его.

  • Ставки в тесте не постоянны: 45 платежей по 0.01 процента, затем 20 вдвое меньших, затем 15 отрицательных платежей, затем 10 снова по 0.01 процента. Это похоже на настоящий рынок, где ставка меняется вместе с настроением трейдеров.
  • Удержание все 30 дней: финансирование 0.45 + 0.10 − 0.15 + 0.10 = 0.50. Комиссии составляют 0.30, 0.15 при открытии и 0.15 при закрытии. Чистый результат равен +0.20 на 100, то есть 0.2 процента за месяц, около 2.4 процента в год, а не 10.95 процента из заголовка.
  • Выход сразу после того, как ставка станет отрицательной, приносит на 0.05 больше: 15 отрицательных платежей стоят 0.15, последние 10 хороших возвращают 0.10.
  • Только для безубыточности при полной ставке нужны 30 платежей, 10 дней. Трейдер, который открывает пару на 3 дня при ставке 0.01 процента, теряет деньги.

Итак, 10.95 процента является потолком, который существует, только если ставка остается 0.01 процента и вы долго держите позицию. Цикл показывает, почему первая идея слишком оптимистична: ставка меняет знак, а комиссии платятся с обеих сторон.

Что бот проверяет перед открытием пары

  • Текущая и ожидаемая ставка. Среднее за последние дни, а не только последнее значение.
  • Комиссии с обеих сторон. Учтите их в сроке безубыточности и сравните с тем, как долго ставка была стабильной.
  • Какая биржа платит. У одной и той же монеты ставка различается на разных биржах. Бот может использовать спотовый рынок одной биржи и бессрочный фьючерс другой, это также добавляет стоимость хранения денег в двух местах.
  • Правило выхода. Например, "выйти, когда средняя ставка станет отрицательной". Его нужно записать и проверить, как в цикле выше.

Настоящая опасность: шорт открыт на марже

Спотовая монета и шорт находятся на разных счетах. Шорт является позицией с плечом, и биржа принудительно закроет его, ликвидирует, когда убыток съест маржу. Грубая формула без учета небольшой поддерживающей маржи такова: шорт с плечом L ликвидируется, когда цена вырастет примерно на 100 / L процентов:

for L in (2, 5, 10):
    print(f"leverage {L:>2}: perp short liquidated near price {price*(1+1/L):6.1f} (+{100/L:.0f}%)")
leverage  2: perp short liquidated near price  150.0 (+50%)
leverage  5: perp short liquidated near price  120.0 (+20%)
leverage 10: perp short liquidated near price  110.0 (+10%)

Теперь картина меняется. Если цена за день вырастет на 20 процентов при плече 5, монета в портфеле принесет 20, но биржа уже закрыла шорт с убытком 20, и у вас остается открытая позиция в монете с ценовым риском, которого вы не планировали. На бумаге общий результат был нулевым, принудительное закрытие его нарушило. Решение скучное, но работает: используйте небольшое плечо, от 1 до 2, держите резерв денег, который бот сможет перевести на маржинальный счет при росте цены, и позвольте лимиту риска из урока 12 остановить все при низком уровне маржи.

Попросите ИИ-агента написать и проверить код

Расплывчатый запрос:

Создай арбитражного бота для ставки финансирования.

Запрос со встроенными проверками:

Напиши carry.py с функцией carry(rates, price, spot_fee, perp_fee, exit_when_negative), которая возвращает (net, gross). При price 100, комиссиях 0.001 и 0.0005 и списке ставок из урока 14, 45 раз 0.0001, 20 раз 0.00005, 15 раз -0.0001, 10 раз 0.0001, функция должна вернуть net 0.2 при удержании всего периода и net 0.25 с exit_when_negative. Добавь break_even_payments(rate, price, spot_fee, perp_fee), она должна вернуть 30 при rate 0.0001. Добавь liquidation_price(entry, leverage) = entry * (1 + 1 / leverage). Никаких сетевых вызовов, никаких реальных ордеров.

Проверьте три числа вручную до того, как доверять коду.

Ваше упражнение

  1. Запустите код и сравните каждое напечатанное число с уроком.
  2. Измените комиссию спота на 0.0002 и комиссию бессрочного фьючерса на 0.0002, это скидка для трейдера. Сколько дней полной ставки теперь нужно для безубыточности? Сначала посчитайте вручную: комиссии равны 2 × (0.02 + 0.02) = 0.08.
  3. Измените список ставок так, чтобы отрицательная часть длилась 40 платежей. Что теперь лучше: удерживать или выйти при первом отрицательном платеже? На сколько?
  4. При плече 5 цена за один день растет на 20 процентов. Напишите на бумаге, что происходит со спотовой стороной и шортом, и что бот должен был бы сделать до движения, чтобы остаться в безопасности.

Что дальше

Теперь у бота есть две стратегии: быстрый разрыв между биржами и медленный доход от финансирования. Урок 15 даст ему третью, на выбор: торговлю парой монет, которые движутся вместе, статистический арбитраж, или фильтр машинного обучения для уже имеющихся сигналов.