Introduction to python-binance

With the introduction of Bitcoin 15 years ago, a new asset class emerged: cryptocurrency. While everyone must consider their own risk tolerance, there’s no denying that crypto investments have experienced incredible highs and impressive returns, as well as devastating lows and significant losses.

One defining feature of the crypto market is its (near) complete decentralization, marked by the absence of regulatory oversight and governing entities — for better or worse. This also means that crypto assets can be traded 24/7, unlike traditional stocks, which are limited to market hours. This continuous trading availability makes crypto particularly appealing for automated trading: with simple scripts, we can monitor the market and execute trades faster than any human, ready to seize opportunities day or night.

Photo by Kanchanara on Unsplash

In this post, we’ll provide an introduction to this topic and, more specifically, introduce the Python API python-binance, which allows us to programmatically access the popular crypto trading platform, Binance. We’ll cover all the essential functionalities, such as retrieving current and historical prices, buying and selling crypto, and transferring it to other addresses. All the code examples shared here will also be available on GitHub. In follow-up posts, we’ll dive into trading strategies and develop fully automated trading bots.

Binance and python-binance

Binance is the world’s largest cryptocurrency exchange, known for its user-friendly interface and support for automation, along with relatively low trading fees (0.1%). If you’re new to Binance, feel free to sign up using my referral link to enjoy benefits for both of us, such as a $100 USD trading fee voucher.

In this post, we’ll use the python-binance library, a convenient Python wrapper around Binance’s REST API. This library was the winner of a competition launched by Binance to identify the best API libraries across different programming languages. However, keep in mind that it’s not your only option—you can also directly interact with Binance’s REST API if you prefer more control or want to use a different language or tool.

Setup

To install, simply run:

pip install python-binance

However, at the time of writing and with Python 3.10 this gave me some wrong dependencies for websocket calls (I will point this out later in the respective sections again, when necessary) — meaning I additionally had to fix the following versions:

pip install websockets==10.4
pip install aiohttp==3.8.5

Next, we need to create an API key. For this, login to Binance, click on your profile icon and then select “API Management” / “Create API”, and answer the security questions. You will be shown an API Key and Secret Key. Take note of these, they will not be visible anymore once navigating away from this site.

For some of the things shown in this post (namely trading and transferring cryptocurrencies), we’ll need to tick the box “Enable Spot & Margin Trading” and “Enable Withdrawals”. Thus, hit “Edit Restrictions” and enable the corresponding box. As you will see, this requires entering some trusted IP addresses from which the key is used. For that, head to e.g. https://whatismyipaddress.com/ and copy your IP address. Note that your ISP will regularly give you a new one, meaning you’ll have to update the corresponding key whenever this happens.

With that, you’re done! Now on to saving and using the key. It’s never a good idea to store such secrets in code — thus one alternative is storing the keys somewhere on your system, and loading them from there in code. One such option are environment variables. To use these, on Linux, execute:

echo ‘export binance_api={API Key}’ >> ~/.bashrc
echo ‘export binance_secret={Secret Key}’ >> ~/.bashrc

This will write the corresponding keys to your bashrc. Upon re-loading the terminal, you can query them in Python via:

api_key = os.environ.get(‘binance_api’)
api_secret = os.environ.get(‘binance_secret’)

Then, we can instantiate a Binance client:

from binance.client import Client
client = Client(api_key, api_secret)

Now we’re all setup let’s move on to actually using the API.

Querying Account Details

We begin by querying some simple information about our account / wallet.

The line:

client.get_account()

prints all our account information / balances.

To query the balance of a specific currency, run:

client.get_asset_balance(asset=’BTC’)[‘free’]

Retrieve Latest Price Information

Next, let’s come to retrieving current price information. For this, there are two possibilities. One is a simple and direct API call, such as:

client.get_symbol_ticker(symbol=’BTCUSDT’)[‘price’]

This will give us the current price of the asset pair BTC-USDT.

Alternatively, we can use websockets for periodic queries: after starting a websocket and registering a callback, this is called everytime new information is available — such as an updated price of an asset. This approach is more suited for regular and frequent calls, think of for example a bot doing automated trades based on recent price movements.

To make use of the websockets, we first need a websocket manager:

twm = ThreadedWebsocketManager()
twm.start()

Next, we define a callback, and start a specific stream — in our example a live ticker for the BTC-USDT pair:

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”
)

Starting the socket manager starts a background process, meaning the Python interpreter will continue over this line on to the next, but upon reaching the end of the file, will not terminate. We thus add the following code:

sleep(4)

twm.stop()

I.e., we sleep for a few seconds to let the messages come in, and then terminate the socket manager (and the program overall).

And for this line (stopping the socket manager), I needed to fix the aforementioned library versions:

pip install websockets==10.4
pip install aiohttp==3.8.5

Without these, I was getting an error along the lines of:

Task exception was never retrieved
AttributeError: ‘ClientConnection’ object has no attribute ‘fail_connection’

And I could never finish the program without force-killing it. Thus, look out for this.

In total, the full code looks as follows:

import os
from time import sleep

from binance import ThreadedWebsocketManager
from binance.client import Client

api_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()

Now let’s analyze what’s inside the messages we get from the ticker. Our callback prints something along the lines of:

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}

To decode the meaning of this (same for any future messages you might need), one can always look at the official Binance API docs. There, we find the following information:

https://binance-docs.github.io/apidocs/spot/en/#all-market-mini-tickers-stream

And with that, we have all the information we need. For example, we know now that the entry “c” contains the current / last price, i.e. the quantity we were looking for.

Retrieving Historic Price Information

Often, we are further interested in historic prices of assets. A lot of trading strategies use some form of patterns in order to detect good timepoints to buy and sell assets, and many of these use historic information — such as the Golden cross. This compares the historic average of an asset vs. its current price, and signals a buy indication whenever the current price “breaks” the line of the historic average.

To obtain historic prices, similar as before we can use direct API calls or websockets — just the output is a bit different.

Let’s query the price of ETH over the last seven days and examine the returned value:

klines = client.get_historical_klines(
“ETHUSDT”, Client.KLINE_INTERVAL_1DAY, “7 days ago UTC”
)

We asked for historic ETH prices for the last seven days, with an interval of one day — i.e., the return contains a list of length seven. Each of these now contains a K-line or candlestick, which is a common representation in the field of trading. The candlesticks we get are lists of length 12, with the first seven values denoting:

open timeopen pricehighest pricelowest priceclosing pricetrading volumeclosing time

The format for these queries needs some time getting used to in my opinion — you can express desired intervals and duration in many ways — I would recommend reading the docs and provided examples for a bit.

For the websocket approach, execute the following code:

import os
from time import sleep

from binance import ThreadedWebsocketManager
from binance.client import Client

api_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()

There should not be any surprises after seeing the first websocket application above. To understand the returned messages, here’s the full description.

Buying and Selling Crypto

Next, let’s come to (possibly?) the most awaited feature: automatically buying and selling crypto coins (hopefully with a hefty gain).

We begin with market orders, and then explain limit orders.

Market Orders

Market orders maybe are slightly easier: they simply buy / sell the selected coin to the current market price.

This can be as simple as this:

buy_order = client.order_market_buy(symbol=”BNBUSDT”, quantity=0.01)

Conversely, we can sell the 0.01 BNB we acquired above via:

sell_order = client.order_market_sell(symbol=”BNBUSDT”, quantity=0.01)

The variables buy_order and sell_order eventually contain order summaries — usually indicating the trades were immediately executed (indicated by the status “Filled”). For different outcomes, stay tuned till the next section.

There is one thing to consider though: we cannot trade arbitrary quantities. There are several filters which define ranges and minimal step sizes between quantities. We can query these via:

client.get_symbol_info(asset_pair)

I will not go over all filters returned by this function, but only describe those which I mostly came across (and which should probably cover 99% of your use cases) — and then list two nice convenience function, turning an arbitrary buy / sell amount into a valid one. The most important filters are:

precision: this denotes the number of decimal digits a coin is using.lot size: there is a minimal and maximal lot size (a buy / sell quantity), and a step size between allowed quantities.notional value: minimal and maximal values which can be traded in the base currency. Consider the trading pair BNB-USDT with a minimal notion value of 5. This expresses, that one can trade only BNB with a value above 5 USDT.

We first define a function reducing the vast amount of symbol information we get into just the filters listed above:

import math
from typing import Any

from binance.client import Client

def 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

Then, the following function adheres to all described filters and gives the next-best quantity we can buy given a desired buy quantity:

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

Attention: this might alter your buy order (significantly, depending on the coin) — be careful when executing this!

Let’s discuss this a bit more. We here again use the notion of a trading pair, also mentioned before. This is for example BNB-USDT. The first entry is called BASE, the second QUOTE. Our buy function expects a value in QUOTE, such as: “buy me BNB worth 10 USDT”. We first get the relevant filter information as described above, and then determine the quantity the user intends to buy (buy_quantity / current_price). Next, we clip the quantity to be inside the allowed notional ranges. Then, we round to precision — and also clip the quantity to adhere to the minimal and maximal lot size, and round to the corresponding step size.

Eventually, we print the resulting quantity and it’s deviation from what was asked for by the user. This could look like this:

Requested buying 1 QUOTE of BNBUSDT. Adhering to filters gave a post-processed value of 0.008 BASE, equal to approximately 5.13024 QUOTE

Again, attention — it might be useful to add a user prompt confirming or rejecting the requested trade.

Our function for selling looks very similar (and in fact, we could / should re-use some of the code parts):

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

For this function, we specify the desired sell amount in BASE, i.e. “sell 10 BNB” — and the rest works analogously.

The following code shows how these functions could be used together:

import os

from binance.client import Client

from utils import get_valid_buy_quantity, get_valid_sell_quantity

api_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)

Limit Orders

Next, we come to limit orders. These are planned orders, which are executed once the market reaches a desired price (for buying: execute the trade when the price falls low enough, for selling: sell when the price reaches the desired threshold).

To create a limit order buying 0.1 BNB when the price reaches 500 USDT or below, run:

buy_order = client.order_limit_buy(
symbol=’BNBUSDT’,
quantity=0.1,
price=500)

Note that the specified price cannot be too far below the current price (same for selling, just in the other direction) — to avoid market manipulation and improve stability.

Now, when inspecting buy_order, we see the status “New” — as well as an empty field fills — indicating the order was not yet executed. Note the order is valid till cancellation, if you did not set an expiration date.

To query all active orders, run:

client.get_open_orders()

And to cancel the order, execute:

client.cancel_order(symbol=buy_order[“symbol”], orderId=buy_order[“orderId”])

For selling, we can analogously run:

sell_order = client.order_limit_sell(
symbol=’BNBUSDT’,
quantity=0.1,
price=800)

This will sell 0.1 BNB in case the price reaches 800 USDT or above.

Transfering Crypto

Lastly, let’s come to sending crypto to another address (also known as withdrawing — your withdrawing coins from your account and adding it to another).

To do this, we can simply run:

result = client.withdraw(
coin=”{COIN}”, address=”{ADDRESS}”, amount={AMOUNT}, network=”{NETWORK}”
)

Concretely, let’s say for example, you want to buy me a (real) coffee using BNB, you can execute the following code:

result = client.withdraw(
coin=”BNB”, address=”0x73c3F0A96094E31fC3168f915e4Deb9D8Ff5239F”, amount=0.01, network=”BSC”
)

We have selected BNB and the BSC network, my wallet address and an amount of 0.01 BNB. Again, caution! This will transfer the specified funds from your account. Also watch the current BNB price — I’m (still) paying Euros for my coffee — and who knows how high we’ll get 🙂

Conclusion

This brings us to the end of this post, where we provided a comprehensive introduction to python-binance, the most popular Python wrapper for programmatically accessing Binance, the world’s largest cryptocurrency exchange.

We covered setting up your Python environment and configuring your Binance account for API access. We demonstrated how to query account details, like available balances, and retrieve both current and historical crypto prices. Next, we walked through automating crypto trades, starting with market orders and then moving on to limit orders. Along the way, we explained Binance’s trading filters that restrict quantities and prices and showed how to calculate these appropriately. Finally, we detailed how to send crypto to other accounts.

All the code examples are available on GitHub. Thank you very much for reading! If you enjoyed this content, please consider leaving some claps and following me to stay updated on future posts. And if you’d like to support my work, sending a virtual crypto coffee (details above) would be greatly appreciated — it helps me grow this channel and continue contributing to the community.

See you next time!

Automated Crypto Trading with Python and Binance was originally published in Coinmonks on Medium, where people are continuing the conversation by highlighting and responding to this story.

By

Leave a Reply

Your email address will not be published. Required fields are marked *