Автоматизированная торговля криптовалютой с Python и Binance
Введение в python-binance
С появлением биткоина 15 лет назад появился новый класс активов: криптовалюта. Хотя каждый должен учитывать свою собственную толерантность к риску, нельзя отрицать, что криптоинвестиции пережили невероятные взлеты и впечатляющие доходы, а также разрушительные минимумы и значительные потери.
Одной из определяющих черт криптовалютного рынка является его (почти) полная децентрализация, отмеченная отсутствием регулирующего надзора и регулирующих органов — к лучшему или к худшему. Это также означает, что криптоактивами можно торговать 24/7, в отличие от традиционных акций, которые ограничены рыночными часами. Такая непрерывная доступность торгов делает криптовалюту особенно привлекательной для автоматической торговли: с помощью простых скриптов мы можем следить за рынком и совершать сделки быстрее, чем любой человек, готовый использовать возможности днем и ночью.
В этой статье мы дадим введение в эту тему и, в частности, представим API Python , который позволяет нам программно получить доступ к популярной платформе для торговли криптовалютой Binance. Мы рассмотрим все основные функции, такие как получение текущих и исторических цен, покупка и продажа криптовалюты, а также перевод ее на другие адреса. Все примеры кода, представленные здесь, также будут доступны на GitHub. В следующих постах мы углубимся в торговые стратегии и разработаем полностью автоматизированных торговых ботов.python-binance
Binance и python-binance
Binance — крупнейшая в мире криптовалютная биржа, известная удобным интерфейсом и поддержкой автоматизации, а также относительно низкими торговыми комиссиями (0,1%).
Мы будем использовать библиотеку, удобную оболочку Python вокруг REST API Binance. Эта библиотека стала победителем конкурса, запущенного Binance для определения лучших библиотек API на разных языках программирования. Однако имейте в виду, что это не единственный вариант — вы также можете напрямую взаимодействовать с REST API Binance, если предпочитаете больше контроля или хотите использовать другой язык или инструмент.python-binance
Настройка
Для установки просто выполните:
pip install python-binance
Тем не менее, на момент написания статьи и с Python 3.10 это дало мне некоторые неправильные зависимости для вызовов websocket (я укажу на это позже в соответствующих разделах, когда это будет необходимо) — это означает, что мне дополнительно пришлось исправить следующие версии:
pip install websockets==10.4pip install aiohttp==3.8.5
Далее нам нужно создать ключ API. Для этого войдите в Binance, нажмите на иконку своего профиля и затем выберите «Управление API» / «Создать API», и ответьте на контрольные вопросы. Вам будет показан ключ API и секретный ключ. Обратите на них внимание, они больше не будут видны после того, как вы покинете этот сайт.
Для некоторых вещей, показанных в этом посте (а именно для торговли и перевода криптовалют), нам нужно будет поставить галочки в полях «Включить спотовую и маржинальную торговлю» и «Включить вывод средств». Итак, нажмите «Редактировать ограничения» и включите соответствующее поле. Как вы увидите, для этого необходимо ввести некоторые доверенные IP-адреса, с которых используется ключ. Для этого перейдите по ссылке, например, https://whatismyipaddress.com/ и скопируйте свой IP-адрес. Обратите внимание, что ваш интернет-провайдер будет регулярно предоставлять вам новый, а это означает, что вам придется обновлять соответствующий ключ всякий раз, когда это происходит.
Вот и все! Теперь перейдем к сохранению и использованию ключа. Никогда не стоит хранить такие секреты в коде — таким образом, одной из альтернатив является хранение ключей где-то в вашей системе и их загрузка оттуда в коде. Одним из таких вариантов являются переменные среды. Чтобы использовать их, в Linux выполните:
echo 'export binance_api={API Key}' >> ~/.bashrcecho 'export binance_secret={Secret Key}' >> ~/.bashrc
Это запишет соответствующие ключи в ваш bashrc. После перезагрузки терминала вы можете запросить их в Python через:
api_key = os.environ.get('binance_api')api_secret = os.environ.get('binance_secret')
Затем мы можем создать экземпляр клиента Binance:
from binance.client import Clientclient = Client(api_key, api_secret)
Теперь, когда все настроено, давайте перейдем к собственно использованию API.
Запрос данных учетной записи
Мы начинаем с запроса некоторой простой информации о нашем аккаунте / кошельке.
client.get_account()
Распечатывает всю информацию о нашем счете / остатки.
Чтобы запросить баланс определенной валюты, выполните:
client.get_asset_balance(asset='BTC')['free']
Получить актуальную информацию о ценах
Далее давайте перейдем к получению текущей ценовой информации. Для этого есть две возможности. Один из них — это простой и прямой вызов API, например:
client.get_symbol_ticker(symbol='BTCUSDT')['price']
Это даст нам текущую цену пары активов BTC-USDT.
В качестве альтернативы мы можем использовать вебсокеты для периодических запросов: после запуска вебсокета и регистрации обратного вызова он вызывается каждый раз, когда появляется новая информация — например, обновленная цена актива. Этот подход больше подходит для регулярных и частых звонков, например, бота, который совершает автоматические сделки на основе последних движений цен.
Чтобы использовать веб-сокеты, нам сначала понадобится менеджер вебсокетов:
twm = ThreadedWebsocketManager()twm.start()
Далее мы определяем коллбэк и запускаем определенный поток — в нашем примере живой тикер по паре BTC-USDT:
def handle_socket_message(msg: str) -> None: print(f"message type: {msg['e']}") print(msg)btc_ticker = twm.start_symbol_ticker_socket( callback=handle_socket_message, symbol="BTCUSDT")
Запуск менеджера сокетов запускает фоновый процесс, то есть интерпретатор Python продолжит движение по этой строке до следующей, но, дойдя до конца файла, не завершит работу. Таким образом, мы добавляем следующий код:
sleep(4)twm.stop()
Т.е. мы спим на несколько секунд, чтобы сообщения не поступили, а затем завершаем работу менеджера сокетов (и программы в целом).
И для этой строки (остановка менеджера сокетов) мне нужно было исправить вышеупомянутые версии библиотеки:
pip install websockets==10.4pip install aiohttp==3.8.5
Без них я получал ошибку примерно следующего содержания:
Task exception was never retrievedAttributeError: 'ClientConnection' object has no attribute 'fail_connection'
И я никогда не смог бы закончить программу, не убив ее силой. Так что следите за этим.
В общей сложности полный код выглядит следующим образом:
import osfrom time import sleepfrom binance import ThreadedWebsocketManagerfrom binance.client import Clientapi_key = os.environ.get("binance_api")api_secret = os.environ.get("binance_secret")client = Client(api_key, api_secret)twm = ThreadedWebsocketManager()twm.start()def handle_socket_message(msg: str) -> None: print(f"message type: {msg['e']}") print(msg)btc_ticker = twm.start_symbol_ticker_socket( callback=handle_socket_message, symbol="BTCUSDT")sleep(4)twm.stop()
Теперь давайте проанализируем, что находится внутри сообщений, которые мы получаем от тикера. Наш обратный вызов выводит что-то вроде:
message type: 24hrTicker{'e': '24hrTicker', 'E': 1731260966852, 's': 'BTCUSDT', 'p': '4176.51000000', 'P': '5.491', 'w': '78679.91492173', 'x': '76065.49000000', 'c': '80242.00000000', 'Q': '0.01631000', 'b': '80242.00000000', 'B': '1.32691000', 'a': '80242.01000000', 'A': '6.20730000', 'o': '76065.49000000', 'h': '80349.00000000', 'l': '76065.49000000', 'v': '46358.04002900', 'q': '3647446645.41970462', 'O': 1731174566851, 'C': 1731260966851, 'F': 4028251704, 'L': 4033956844, 'n': 5705141}
Чтобы расшифровать значение этого (то же самое для любых будущих сообщений, которые вам могут понадобиться), всегда можно обратиться к официальной документации Binance API. Там мы находим следующую информацию:
И благодаря этому у нас есть вся необходимая информация. Например, теперь мы знаем, что запись "c" содержит текущую / последнюю цену, т.е. количество, которое мы искали.
Получение исторической информации о ценах
Зачастую, нас дополнительно интересуют исторические цены на активы. Многие торговые стратегии используют ту или иную форму паттернов для определения хороших временных точек для покупки и продажи активов, и многие из них используют историческую информацию, такую как Золотой крест. Он сравнивает историческое среднее значение актива с его текущей ценой и подает сигнал о покупке всякий раз, когда текущая цена «прорывает» линию исторического среднего значения.
Для получения исторических цен, как и раньше, мы можем использовать прямые вызовы API или вебсокеты — только вывод немного другой.
Давайте запросим цену ETH за последние семь дней и рассмотрим возвращенное значение:
klines = client.get_historical_klines( "ETHUSDT", Client.KLINE_INTERVAL_1DAY, "7 days ago UTC")
Мы запросили исторические цены ETH за последние семь дней с интервалом в один день — то есть возврат содержит список длиной семь. Каждая из них теперь содержит K-линию или свечу, которая является распространенным представлением в сфере торговли. Свечи, которые мы получаем, представляют собой списки длины 12, первые семь значений которых обозначают:
- Открытое время
- Открыть цену
- Самая высокая цена
- самая низкая цена
- Цена закрытия
- Объем торгов
- время замыкания
К формату этих запросов нужно некоторое время, чтобы привыкнуть на мой взгляд — нужные интервалы и длительность можно выразить разными способами — я бы рекомендовал немного почитать документацию и привести примеры.
Для подхода с использованием веб-сокетов выполните следующий код:
import osfrom time import sleepfrom binance import ThreadedWebsocketManagerfrom binance.client import Clientapi_key = os.environ.get("binance_api")api_secret = os.environ.get("binance_secret")client = Client(api_key, api_secret)twm = ThreadedWebsocketManager()twm.start()def handle_socket_message(msg: str) -> None: print(f"message type: {msg['e']}") print(msg)twm.start_kline_socket(callback=handle_socket_message, symbol="ETHUSDT")sleep(4)twm.stop()
Не должно быть никаких сюрпризов после того, как вы увидите первое приложение вебсокета выше. Чтобы понять возвращенные сообщения, вот полное описание.
Покупка и продажа криптовалюты
Далее давайте перейдем к (возможно?) самой ожидаемой функции: автоматической покупке и продаже криптомонет (надеемся, с солидной прибылью).
Мы начинаем с рыночных ордеров, а затем объясняем лимитные ордера.
Рыночные ордера
С рыночными ордерами, возможно, немного проще: они просто покупают/продают выбранную монету по текущей рыночной цене.
buy_order = client.order_market_buy(symbol="BNBUSDT", quantity=0.01)
И наоборот, мы можем продать 0,01 BNB, которые мы приобрели выше, через:
sell_order = client.order_market_sell(symbol="BNBUSDT", quantity=0.01)
Переменные и в конечном итоге содержат сводки ордеров — обычно указывающие на немедленное исполнение сделок (обозначается статусом «Исполнено»). Чтобы узнать о различных исходах, следите за обновлениями до следующего раздела.buy_ordersell_order
Но есть один момент, который следует учитывать: мы не можем торговать произвольными величинами. Существует несколько фильтров, которые определяют диапазоны и минимальные размеры шага между величинами. Мы можем запросить их через:
client.get_symbol_info(asset_pair)
Я не буду перечислять все фильтры, возвращаемые этой функцией, а опишу только те, с которыми я чаще всего сталкивался (и которые, вероятно, должны охватывать 99% ваших вариантов использования) — а затем перечисляю две приятные удобные функции, превращающие произвольную сумму покупки/продажи в действующую. Наиболее важными фильтрами являются:
- Точность: Обозначает количество десятичных цифр, используемых в монете.
- Lot Size: минимальный и максимальный размер лота (количество на покупку/продажу), а также размер шага между допустимыми величинами.
- Номинальная стоимость: минимальные и максимальные значения, которые могут торговаться в базовой валюте. Рассмотрим торговую пару BNB-USDT с минимальным значением понятия 5. Это означает, что можно торговать только BNB со стоимостью выше 5 USDT.
Сначала мы определяем функцию, уменьшающую огромное количество информации о символах, которую мы получаем, с помощью фильтров, перечисленных выше:
import mathfrom typing import Anyfrom binance.client import Clientdef get_symbol_info(client: Client, asset_pair: str) -> dict[str, Any]: full_symbol_info = client.get_symbol_info(asset_pair) if not full_symbol_info: raise ValueError(f"Did not find pair {asset_pair}") symbol_info = {"precision": full_symbol_info["quotePrecision"]} filters = {f["filterType"]: f for f in full_symbol_info["filters"]} symbol_info["lot_size"] = { "min": float(filters["LOT_SIZE"]["minQty"]), "max": float(filters["LOT_SIZE"]["maxQty"]), "step": float(filters["LOT_SIZE"]["stepSize"]), } symbol_info["notional"] = { "min": float(filters["NOTIONAL"]["minNotional"]), "max": float(filters["NOTIONAL"]["maxNotional"]), } return symbol_info
Затем следующая функция соответствует всем описанным фильтрам и выдает следующее лучшее количество, которое мы можем купить при желаемом количестве покупки:
def get_valid_buy_quantity( client: Client, trading_pair: str, desired_value_quote: float) -> float: """Converts the desired buy value to an allowed one respecting all filters. Args: client: Binance client trading_pair: trading pair to buy (e.g. BNBUSDT) desired_value_base: amount to buy in QUOTE value (e.g. USDT) Returns: fixed buy value as base quantity (e.g. BNB) """ symbol_info = get_symbol_info(client, trading_pair) current_price = float(client.get_symbol_ticker(symbol=trading_pair)["price"]) buy_quantity = desired_value_quote / current_price # Clip to [min, max] notional value buy_quantity = max(buy_quantity, symbol_info["notional"]["min"] / current_price) buy_quantity = min(buy_quantity, symbol_info["notional"]["max"] / current_price) # Round to precision buy_quantity = round(buy_quantity, symbol_info["precision"]) # Clip to [min, max] lot size, round to step size buy_quantity = round( buy_quantity, int(math.log10(1 / symbol_info["lot_size"]["step"])) ) buy_quantity = max(buy_quantity, symbol_info["lot_size"]["min"]) buy_quantity = min(buy_quantity, symbol_info["lot_size"]["max"]) print( f"Requested buying {desired_value_quote} QUOTE of {trading_pair}. Adhering to filters gave a post-processed value of {buy_quantity} BASE, equal to approximately {buy_quantity * current_price} QUOTE" ) return buy_quantity
Внимание: это может изменить ваш ордер на покупку (значительно, в зависимости от монеты) — будьте осторожны при исполнении!
Давайте обсудим это немного подробнее. Здесь мы снова используем понятие торговой пары, также упомянутое ранее. Это, например, BNB-USDT. Первая запись называется BASE, вторая — QUOTE. Наша функция покупки ожидает значение в КОТИРОВКАХ, например: «купи мне BNB стоимостью 10 USDT». Сначала мы получаем соответствующую информацию о фильтре, как описано выше, а затем определяем количество, которое пользователь намерен купить ( / ). Далее мы обрезаем количество, которое должно находиться в допустимых условных диапазонах. Затем округляем с точностью — и также обрезаем количество, чтобы придерживаться минимального и максимального размера лота, и округляем до соответствующего размера шага.buy_quantitycurrent_price
В конце концов, мы выводим полученное количество и его отклонение от того, что запрашивал пользователь. Это может выглядеть следующим образом:
Requested buying 1 QUOTE of BNBUSDT. Adhering to filters gave a post-processed value of 0.008 BASE, equal to approximately 5.13024 QUOTE
Опять же, внимание — возможно, будет полезно добавить подсказку пользователя, подтверждающую или отклоняющую запрашиваемую сделку.
Наша функция для продажи выглядит очень похоже (и на самом деле, мы могли бы / должны повторно использовать некоторые части кода):
def get_valid_sell_quantity( client: Client, trading_pair: str, desired_sell_quantiy: float) -> float: """Converts the requested sell quantity into an allowed one respecting all filters. Args: client : Binance client trading_pair: trading pair to sell (e.g. BNBUSDT) desired_sell_quantiy: desired amount to sell in BASE Returns: fixed amount to sell in BASE """ symbol_info = get_symbol_info(client, trading_pair) current_price = float(client.get_symbol_ticker(symbol=trading_pair)["price"]) # Clip to [min, max] notional value sell_quantity = max( desired_sell_quantiy, symbol_info["notional"]["min"] / current_price ) sell_quantity = min(sell_quantity, symbol_info["notional"]["max"] / current_price) # Round to precision sell_quantity = round(sell_quantity, symbol_info["precision"]) # Clip to [min, max] lot size, round to step size sell_quantity = round( sell_quantity, int(math.log10(1 / symbol_info["lot_size"]["step"])) ) sell_quantity = max(sell_quantity, symbol_info["lot_size"]["min"]) sell_quantity = min(sell_quantity, symbol_info["lot_size"]["max"]) print( f"Requested selling {desired_sell_quantiy} {trading_pair}. Adhering to filters gave a post-processed value of {sell_quantity}, equal to approximately {sell_quantity * current_price} BASE" ) return sell_quantity
Для этой функции мы указываем желаемую сумму продажи в BASE, т.е. «продам 10 BNB» — а остальное работает аналогично.
В следующем коде показано, как эти функции могут быть использованы вместе:
import osfrom binance.client import Clientfrom utils import get_valid_buy_quantity, get_valid_sell_quantityapi_key = os.environ.get("binance_api")api_secret = os.environ.get("binance_secret")client = Client(api_key, api_secret)buy_qty = get_valid_buy_quantity(client, "BNBUSDT", 1)buy_order = client.order_market_buy(symbol="BNBUSDT", quantity=buy_qty)sell_qty = get_valid_sell_quantity(client, "BNBUSDT", buy_qty)sell_order = client.order_market_sell(symbol="BNBUSDT", quantity=sell_qty)
Лимитные ордера
Далее мы переходим к лимитным ордерам. Это запланированные ордера, которые исполняются, как только рынок достигает желаемой цены (для покупки: исполняйте сделку, когда цена упадет достаточно низко, для продажи: продавайте, когда цена достигает желаемого порога).
Чтобы создать лимитный ордер на покупку 0,1 BNB при достижении ценой 500 USDT или ниже, выполните:
buy_order = client.order_limit_buy( symbol='BNBUSDT', quantity=0.1, price=500)
Обратите внимание, что указанная цена не может быть слишком сильно ниже текущей цены (то же самое при продаже, только в обратном направлении) — чтобы избежать манипуляций рынком и повысить стабильность.
Теперь при осмотре мы видим статус «Новый», а также пустое поле, указывающее на то, что заказ еще не был выполнен. Обратите внимание, что заказ действителен до отмены, если вы не установили срок действия.buy_orderfills
Чтобы запросить все активные заказы, выполните:
client.get_open_orders()
А чтобы отменить заказ, выполните:
client.cancel_order(symbol=buy_order["symbol"], orderId=buy_order["orderId"])
Для продажи мы можем аналогично запустить:
sell_order = client.order_limit_sell( symbol='BNBUSDT', quantity=0.1, price=800)
Это позволит продать 0,1 BNB в случае, если цена достигнет 800 USDT или выше.
Перевод криптовалюты
Наконец, давайте перейдем к отправке криптовалюты на другой адрес (также известной как вывод средств — вывод монет со своего счета и добавление их на другой).
Для этого мы можем просто запустить:
result = client.withdraw( coin="{COIN}", address="{ADDRESS}", amount={AMOUNT}, network="{NETWORK}")
Конкретно, допустим, например, вы хотите купить мне (настоящий) кофе с помощью BNB, вы можете выполнить следующий код:
result = client.withdraw( coin="BNB", address="0x73c3F0A96094E31fC3168f915e4Deb9D8Ff5239F", amount=0.01, network="BSC")
Мы выбрали BNB и сеть BSC, адрес моего кошелька и сумму 0,01 BNB. Опять же, осторожность! При этом указанные средства будут переведены с вашего счета. Также следите за текущей ценой BNB — я (по-прежнему) плачу евро за свой кофе — и кто знает, насколько высоко мы поднимемся :)
Заключение
Это подводит нас к концу этого поста, где мы подробно рассказали о самой популярной оболочке Python для программного доступа к Binance, крупнейшей в мире криптовалютной бирже.python-binance
Мы рассмотрели настройку среды Python и настройку аккаунта Binance для доступа к API. Мы продемонстрировали, как запрашивать данные счета, такие как доступные балансы, и получать как текущие, так и исторические цены на криптовалюту. Далее мы рассмотрели автоматизацию сделок с криптовалютой, начав с рыночных ордеров, а затем перейдя к лимитным ордерам. Попутно мы объяснили торговые фильтры Binance, которые ограничивают количество и цены, и показали, как правильно их рассчитывать. Наконец, мы подробно рассказали, как отправить криптовалюту на другие счета.
Все примеры кода доступны на GitHub.
Хотите поддержать нас?
Просто подпишитесь на наш канал ТГ и получайте эксклюзивную информацию о нодах, ИТ решениях в криптомире и технологиях web3 и не только, раньше всех! Вокруг Крипты и Youtube👍