Возможности кросс-биржевого арбитража

Знакомство

Кросс-биржевой арбитраж был очень прибыльным в первые дни торговли криптовалютой. Простые модели, написанные на Python, позволили опытным трейдерам извлечь выгоду из расхождения цен между криптобиржами. Но по мере того, как рынок созревает, а модели становятся более продвинутыми, возникает вопрос: остались ли какие-либо возможности для кросс-биржевого арбитража?

Давайте сосредоточимся на Binance и Kraken, двух крупнейших и самых популярных биржах. Мы получим реальные рыночные данные из обоих соответствующих API. Мы проанализируем цены Bitcoin ($BTC) в долларах США с 20 апреля 2023 года с 00:00:00 до 23:45:00 с интервалом в 15 минут.

В этой статье мы продемонстрируем, как можно использовать возможности Python для анализа данных в реальном времени и определения жизнеспособности арбитражных стратегий в современном криптоландшафте. Хотя мы будем использовать реальные рыночные данные, имейте в виду, что эта статья является учебным пособием, а не финансовым советом, и направлена на улучшение наших навыков работы с Python. Присоединяйтесь к нам, когда мы углубимся в увлекательную сферу кросс-биржевого арбитража с использованием Python!

Часть 1: Импорт библиотек

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import requests
from datetime import datetime, timedelta

Часть 2: Определение функций для получения данных из API Binance и Kraken

# This function fetches data from Binance API
def get_binance_data(symbol, interval, start_time, end_time):
url = f"https://api.binance.com/api/v3/klines?symbol={symbol}&interval={interval}&startTime={start_time}&endTime={end_time}"
response = requests.get(url)
data = response.json()

df = pd.DataFrame(data, columns=['Open time', 'Open', 'High', 'Low', 'Close', 'Volume', 'Close time', 'Quote asset volume', 'Number of trades', 'Taker buy base asset volume', 'Taker buy quote asset volume', 'Ignore'])
df['Open time'] = pd.to_datetime(df['Open time'], unit='ms')
df['Close time'] = pd.to_datetime(df['Close time'], unit='ms')
return df[['Open time', 'Close time', 'Open', 'High', 'Low', 'Close', 'Volume']]
# This function fetches data from Kraken API
def get_kraken_data(pair, interval, since):
url = f"https://api.kraken.com/0/public/OHLC?pair={pair}&interval={interval}&since={since}"
response = requests.get(url)
data = response.json()['result'][pair]

df = pd.DataFrame(data, columns=['Open time', 'Open', 'High', 'Low', 'Close', 'Volume', 'Weighted average price', 'Number of trades'])
df['Open time'] = pd.to_datetime(df['Open time'], unit='s')
df['Close time'] = df['Open time'] + pd.to_timedelta(interval, unit='m')
return df[['Open time', 'Close time', 'Open', 'High', 'Low', 'Close', 'Volume']]

Параметры запроса для получения данных из API не обязательно совпадают. Для Binance параметрами запроса являются «символ», «интервал», «start_time», «end_time», а для Kraken — «пара», «интервал», «поскольку». Цель здесь состоит в том, чтобы создать функции, которые будут возвращать DataFrame с одинаковыми нужными столбцами.

Часть 3: Получение и объединение данных из обоих API

# Let's first define a function to convert a datetime object to timestamp in milliseconds (trading data usually is in milliseconds)
def datetime_to_timestamp(dt):
return int(dt.timestamp() * 1000)

# Setting the date range for fetching our data
start_date = datetime(2023, 4, 19)
end_date = datetime(2023, 4, 22)

# Using our datetime object to timestamp function
start_timestamp = datetime_to_timestamp(start_date)
end_timestamp = datetime_to_timestamp(end_date)

# Fetching Bitcoin data from Binance and Kraken APIs
binance_data = get_binance_data('BTCUSDT', '15m', start_timestamp, end_timestamp)
kraken_data = get_kraken_data('XXBTZUSD', 15, start_timestamp)

# Using the "Open time" column as the key for merging both Binance and Kraken data
merged_data = pd.merge(binance_data, kraken_data, on='Open time', suffixes=('_binance', '_kraken'))

# Price discrepancies: We finish by calculating the price difference between the closing prices from Binance and Kraken
merged_data['Price difference'] = merged_data['Close_binance'].astype(float) - merged_data['Close_kraken'].astype(float)

# We then set the 20th of April from the DataFrame merged_data
filter_start = datetime(2023, 4, 20, 0, 0, 0)
filter_end = datetime(2023, 4, 20, 23, 45, 0)

# We filter the merged_data DataFrame
filtered_data = merged_data[(merged_data['Open time'] >= filter_start) & (merged_data['Open time'] <= filter_end)]
filtered_data

Теперь у нас есть данные за 20 апреля, а также мы рассчитали разницу в цене между ценами закрытия Binance и Kraken. Давайте построим их на графике.

Часть 4: Постройте график 15-минутных расхождений в ценах

fig, ax = plt.subplots(figsize=(12, 6), dpi=200)

ax.plot(filtered_data['Open time'], filtered_data['Close_binance'].astype(float), label='Binance', linewidth=2)
ax.plot(filtered_data['Open time'], filtered_data['Close_kraken'].astype(float), label='Kraken', linewidth=2)

ax.set_title('BTC 15-Minute Prices on Binance and Kraken (April 20, 2023)')
ax.set_xlabel('Time')
ax.set_ylabel('Price (USD)')
ax.legend()

plt.show()

На приведенном ниже графике показаны цены закрытия $BTC от Binance и Kraken на 20 апреля 2023 года. Ценовые тренды Binance и Kraken очень похожи, но иногда расходятся. Это несоответствие, называемое спредом, представляет собой возможность кросс-биржевого арбитража, на которой трейдеры извлекают выгоду: покупают BTC по более низкой цене на одной бирже и продают по более высокой цене на другой, тем самым получая прибыль.

Затем мы могли бы напрямую построить график вновь созданной переменной «Разница цен», но тогда спред будет представлен как отрицательное и положительное значение. Однако нас интересует не направление, а величина разницы между ценами. Поэтому мы создадим линейный график, представляющий абсолютную разницу между ценами закрытия двух бирж. Давайте построим график этих ценовых разниц, давайте визуализируем спред!

fig, ax = plt.subplots(figsize=(12, 6), dpi=200)

# We calculate the absolute difference between Binance and Kraken closing prices with the abs() function
plt.plot(filtered_data['Open time'], abs(filtered_data['Close_binance'].astype(float) - filtered_data['Close_kraken'].astype(float)), label='Spread Binance vs Kraken', color='green')

plt.title('BTC Price Spread: Binance vs Kraken (April 20, 2023) - Absolute difference')
plt.legend()

Часть 5: Расчет общего спреда за день

В идеальной теоретической торговой среде, где можно было бы извлечь выгоду из каждого спреда, можно было бы получить прибыль от расхождения цен между Binance и Kraken. Вычислим сумму всех спредов. Общий спред за день составляет 922 USD. Эта цифра теоретическая.

# We calculate the total spread for the day in an theoretical environment
total_spread = abs(filtered_data['Close_binance'].astype(float) - filtered_data['Close_kraken'].astype(float)).sum()

print(f"Total Spread for the day: {total_spread} USD")

Часть 6: Введение торговых комиссий и Co

В реальных условиях следует учитывать несколько факторов, таких как комиссии, проскальзывание и другие переменные, такие как скорость исполнения сделок.

Такие биржи, как Binance и Kraken, обычно взимают комиссию с мейкеров и тейкеров. Когда вы выполняете арбитражную стратегию, очень важно включить эти сборы в расчеты прибыли.

Комиссия мейкера: Мейкер — это тот, кто размещает заказ, который не сразу совпадает с существующим заказом в книге заказов. Вместо этого он «создает» ликвидность, добавляя в пул открытые ордера, ожидая сопоставления. Поскольку мейкеры вносят свой вклад в общую ликвидность рынка, их комиссии, как правило, ниже, чем комиссии тейкеров.

Комиссия тейкера: Тейкер — это тот, кто размещает заказ, который немедленно исполняется, сопоставляя его с существующим заказом в книге заказов. Тейкеры «забирают» ликвидность с рынка, уменьшая количество открытых ордеров. В результате комиссия тейкера обычно выше, чем комиссия мейкера.

Предположим, вы покупаете 1 BTC на Binance по более низкой цене и продаете его на Kraken по более высокой цене. Размещение лимитного ордера на покупку 1 BTC на Binance классифицирует вас как мейкера, и вам придется заплатить комиссию мейкера при исполнении ордера. Затем, разместив рыночный ордер на этот 1 BTC на Kraken, вы будете классифицированы как тейкер, и вам придется заплатить комиссию тейкера. Пожалуйста, обратитесь к таблице ниже, чтобы увидеть различные структуры комиссий на биржах.

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

Изучив структуру комиссий как Binance, так и Kraken, мы решили применить среднюю торговую комиссию в размере 0,2%.

# We add and calculate the absolute difference and fees columns in our filtered_data DataFrame
filtered_data['Absolute Price Diff'] = abs(filtered_data['Close_binance'].astype(float) - filtered_data['Close_kraken'].astype(float))
filtered_data['Fees'] = 0.002 * (filtered_data['Close_binance'].astype(float) + filtered_data['Close_kraken'].astype(float))

# We create a new DataFrame with the desired columns using dictionary comprehension
arbitrage_df = pd.DataFrame({column: filtered_data[column] for column in ['Open time', 'Close_binance', 'Close_kraken', 'Absolute Price Diff', 'Fees']})

# We then rename the columns in the DataFrame 'Close_binance' and 'Close_kraken'
arbitrage_df.columns = ['Open time', 'Binance BTC', 'Kraken BTC', 'Absolute Price Diff', 'Fees']
arbitrage_df

Часть 7: На какой бирже покупать? Продавать?

Теперь давайте сгенерируем сигналы на покупку и продажу.

Мы вернем «0», когда цена Binance BTC превысит цену Kraken BTC (купить на Kraken и продать на Binance)

Мы вернем «1», когда цена Kraken BTC превысит цену Binance BTC (купить на Binance и продать на Kraken)

# np.where(condition, x, y) returns an array with the same shape as the condition
# If the condition is True, it returns the corresponding element from x, otherwise from y
signals_data = np.where(arbitrage_df['Binance BTC'] > arbitrage_df['Kraken BTC'], 0, 1)

# We create a new DataFrame to store the signals
# Set the index of signals_df to match the index of arbitrage_df
signals_df = pd.DataFrame(signals_data, columns=['BuyKrakenSellBinance'], index=arbitrage_df.index)

# We then add the signals to the arbitrage_df DataFrame
arbitrage_df = pd.concat([arbitrage_df, signals_df], axis=1)
arbitrage_df

Часть 8: Совершение прибыльной сделки

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

Если абсолютная разница в цене больше, чем сборы, установите значение «0» (выполнить сделку). Это выгодная сделка!

Если абсолютная разница в цене меньше или равна комиссиям, установите значение «1» (не выполняйте сделку)

# Create a new column 'Execute Trade' in the arbitrage_df DataFrame
arbitrage_df['Execute Trade'] = (arbitrage_df['Absolute Price Diff'] <= arbitrage_df['Fees']).astype(int)
arbitrage_df
# Let's then count the number of trade profitable (marked with "0")
number_of_profitable_trades = (arbitrage_df['Execute Trade'] == 0).sum()
print(f"Number of profitable arbitrage trades: {number_of_profitable_trades}")

К сожалению, 20 апреля у нас нет прибыльных сделок. Но ничто не мешает нам запускать этот скрипт Python каждый день!

Мы также могли бы использовать этот скрипт Python на менее ликвидных активах, таких как альткоины. Или получить данные с других небольших бирж, где разница в ценах может быть важна… Возможно, наконец-то мы поймаем отличную возможность для кросс-биржевого арбитража.

Спасибо, что прочитали, и следите за обновлениями, чтобы узнать больше статей из этой серии Python!

Источники:

Huobi, «Возможности криптоарбитража: все о кросс-активном и биржевом арбитраже!», средний, 23 августа 2022 г.

Спенсер Пао, «Квантовая стратегия: алгоритм арбитражной торговли (кросс-биржа)», YouTube, 23 ноября 2021 г.

Источник