Python Binance Trading Robot
***GitHub***
- Introduction
- Importing Libraries and Keys
- Current Price
- Account Balance
- Latest Transaction
- Submit Order
- Trading Loop
- Bash Script
Introduction
This tutorial will show the reader how to set up a Binance Trading Robot. The robot is written in Python and uses the Binance REST API to communicate with Binance's trading platform. REpresentational State Transfer (REST) is a standardized architecture for systems to communicate with one another. An Application Programming Interface (API) is a method for systems to communicate with one another. Therefore, a REST API is an API that adheres to the REST architecture. We will use the Python library requests to communicate with the Binance REST API. This will allow us (clients) to send 'get' and 'post' requests to Binance (servers) which will send back responses that contain account information and trade confirmations.
This Binance Trading Robot runs continuously in a while True:
loop. The trading robot has four functions that are called upon in the while loop: current_price()
(checks current price for selected symbol pair), account_balance()
(checks account balance for selected symbol pair), latest_transaction()
(checks latest transaction for selected symbol pair), and submit_order()
(submits order for selected symbol pair). The robot submits market orders and its logic can be seen below.
The function submit_order()
can be commented out in the while loop to avoid submitting an order to understand the logic of the bot.
- Checks account balance
account_balance()
to see how much of the symbol pair is free to trade - Checks latest transaction
latest_transaction()
for the symbol pair to see the price it was traded at - Calculates buy/sell price for the next trade based on the latest transaction price
- Submits order
submit_order()
to trade all available free asset if buy/sell price is met based on current price of symbol pair - Wait for a designated amount of time and go back to Step 1.
Importing Libraries and Keys
The first thing that needs to be done is to import the necessary libraries. The numpy, time, and datetime libraries are imported for maths and creating some verification timestamps for the Binance Rest API. The requests library is used to communicate with the Binance Rest API while the hmac and hashlib libraries are used to hash the keys for authentication with the Binance Rest API. The yaml library safely imports the configuration file config.yml (which contains the keys to access the account) using the function yaml.safe_load()
instead of a .py file since we don't want potential malicious code to run within the .py file. The json library prints the response data from the Binance Rest API request in a more human readable format in order to better understand the format of the returned data. A link to the Binance Rest API is included along with the USE AT YOUR OWN RISK Disclaimer that is also located in the Introduction section.
The API and Secret Keys are then loaded from the config.yml file. DO NOT SHARE THESE WITH ANYBODY!!! This is how access to the account is granted. The Binance tutorial on how to create a Binance Rest API key pair can be seen here. The config.yml file should have the information and format below. Select the desired symbol_pair
to trade and make sure symbol_first
and symbol_second
match accordingly.
apikey: 'API_KEY_HERE' secretkey: 'SECRET_KEY_HERE'binance_bot.py file:
import numpy as np import time from datetime import datetime, timezone, timedelta import requests import hmac import hashlib import yaml import json # Binance Rest API documentation # https://github.com/binance-us/binance-official-api-docs/blob/master/rest-api.md # Each url gets you slightly different information and has slightly different input params # api/v3/exchangeInfo, api/v3/depth, api/v3/trades, etc... # Disclaimer: # I do not provide personal investment advice and I am not a qualified licensed investment advisor. # The information provided may include errors or inaccuracies. Conduct your own due diligence, or consult # a licensed financial advisor or broker before making any and all investment decisions. Any investments, # trades, speculations, or decisions made on the basis of any information found on this site and/or script, # expressed or implied herein, are committed at your own risk, financial or otherwise. No representations # or warranties are made with respect to the accuracy or completeness of the content of this entire site # and/or script, including any links to other sites and/or scripts. The content of this site and/or script # is for informational purposes only and is of general nature. You bear all risks associated with the use # of the site and/or script and content, including without limitation, any reliance on the accuracy, # completeness or usefulness of any content available on the site and/or script. Use at your own risk. config = yaml.safe_load(open('config.yml')) # Load yaml file that has api and secret key apikey=config['apikey'] # Api key from yaml file secretkey=config['secretkey'] # Secret key from yaml file symbol_pair='BNBBUSD' # Pair to trade symbol_first='BNB' # Make sure this matches above symbol_second='BUSD' # Make sure this matches above
Current Price
The first of the four functions discussed in the Introduction section is the current_price()
function. This function calls the Binance Rest API ticker/price endpoint by passing in the endpoint url urlcp
and parameters paramscp
to the requests.get()
function (which is from the requests library) to 'get' the ticker price. A response of <Response [200]> signifies a successful request. We convert the response from the Binance Rest API to the json format to extract the necessary data as a Python dictionary.The current_price()
function returns the current price cp
of the symbol_pair
that will be used to calculate the buy/sell price to decide if the trade should be executed. The pretty print version of the response in json format can be seen below. This is useful as it allows the user to understand the format of the data in order to extract the necessary information.
def current_price(): ################################# ##### Ticker price endpoint ##### ################################# urlcp='https://api.binance.us/api/v3/ticker/price' paramscp={'symbol':symbol_pair} # Ticker wanted response_cp = requests.get(urlcp, params=paramscp) # Sending GET request for ticker information # print(response_cp) # Returns HTTP Status, a value of 200 (OK) means no error pair_info = response_cp.json() # Convert response to JSON object for data extraction # print(json.dumps(pair_info, indent=4)) # Pretty print to understand structure of data cp = pair_info['price'] # Current price of symbol print('--------------------Current Price--------------------') print(f'{symbol_pair} Current Price: {cp}') print('\n') return cp
>>> print(response_cp) <Response [200]> >>> print(json.dumps(pair_info, indent=4)) { "symbol": "BNBBUSD", "price": "411.13100000" }
Account Balance
The next function is the account_balance()
function which calls the Binance Rest API account endpoint to acquire the symbol_pair
account balance. The endpoint url urlcp
and parameters paramscp
are passed to the requests.get()
function in a slightly different manner since we are now using the generated keys to 'get' specific account information. Again: DO NOT SHARE THESE WITH ANYBODY!!! This is how access to the account is granted.
A timestamp in milliseconds is created for this request since the Binance Rest API has timing security to ensure communication can only happen within a small window of time. The hmac (Hashing for Message Authentication) library hashes the secret key secretkey
with Secure Hash Algorithm 256-bit (SHA-256) using the hash library to create an authentication signature
for the API. The url
, queryString
(which contains the parameters), signature
, and api key apikey
are all passed to the requests.get()
function to 'get' the account balance.
If we take a look at the pretty print version of the response in json format below, we can start extracting some key information now that we know how the response data looks like. We want to extract how much of each asset in the symbol_pair
we have free to trade. The balances key in the response dictionary has a list of assets and how much of each are free to trade or locked. We want to extract the symbol_pair
information but we can't get the value of the desired asset using the key:pair method since the value of the key balances is a list of dictionaries and not a dictionary itself so we cycle through the list until we get the index of the desired assets. We print some information regarding the account balance for the symbol_pair
and return the assetfree
and assetfree2
variables. These variables contain the total amount of free assets in the symbol_pair
that we can use to trade later on in the script.
def account_balance(): print('###############################################################') print('########################Account Balance########################') print('###############################################################') print('\n\n') ######################################## ##### Account information endpoint ##### ######################################## url = "https://api.binance.us/api/v3/account" # Creating datetime variable required for account connection now = datetime.now(timezone.utc) # current date epoch = datetime(1970, 1, 1, tzinfo=timezone.utc) # use POSIX epoch posix_timestamp_micros = (now - epoch) // timedelta(microseconds=1) posix_timestamp_millis = posix_timestamp_micros // 1000 # or `/ 1e3` for float # Input variables queryString = "timestamp=" + str(posix_timestamp_millis) # Creating hash for authentication signature = hmac.new(secretkey.encode(), queryString.encode(), hashlib.sha256).hexdigest() # Combining account information endpoint url with input variables and authentication hash url = url + f"?{queryString}&signature={signature}" # Sending GET request for account information response_ai = requests.get(url, headers={'X-MBX-APIKEY': apikey}) # Convert response to JSON object for data extraction account_info=response_ai.json() # print(json.dumps(account_info, indent=4)) # Pretty print to understand structure of data # Can't call out the symbols directly because 'balances' is a list of dictionaries and not a dictionary # Cycle through the list of dictionaries containing the different assets for i, balance in enumerate(account_info['balances']): if balance['asset']==symbol_first: # Finding first symbol in symbol pair ifirst=i if balance['asset']==symbol_second: # Finding second symbol in symbol pair isecond=i # First asset information asset=account_info['balances'][ifirst]['asset'] # Asset name assetfree=account_info['balances'][ifirst]['free'] # Asset amount that is free to trade assetlocked=account_info['balances'][ifirst]['locked'] # Asset amount that is locked and can't be traded assetfreecp=float(assetfree)*float(cp) # Current price of asset that is free assetlockedcp=float(assetlocked)*float(cp) # Current price of asset that is locked # Printing information print(f'Asset: {asset}') print(f'Free: {assetfree} at {cp} = ${assetfreecp}' ) print(f'Locked: {assetlocked} at {cp} = ${assetlockedcp}') print(f'Subtotal: $ {assetfreecp+assetlockedcp}') print(f'Subtotal: $ {assetfreecp+assetlockedcp}') print('\n') print('+') print('\n') # Second asset information asset2=account_info['balances'][isecond]['asset'] # Asset name assetfree2=account_info['balances'][isecond]['free'] # Asset amount that is free to trade assetlocked2=account_info['balances'][isecond]['locked'] # Asset amount that is locked and can't be traded # Printing information print(f'Asset: {asset2}') print(f'Free: {assetfree2}') print(f'Locked: {assetlocked2}') print(f'Subtotal: {float(assetfree2)+float(assetlocked2)}') print('\n') # Total net worth in account print('----------Total----------') print('$',assetfreecp+assetlockedcp+float(assetfree2)+float(assetlocked2)) print('\n\n') print('###############################################################') print('########################Account Balance########################') print('###############################################################') print('\n') return assetfree, assetfree2
>>> print(json.dumps(account_info, indent=4)) { "makerCommission": 10, "takerCommission": 10, "buyerCommission": 0, "sellerCommission": 0, "canTrade": true, "canWithdraw": true, "canDeposit": true, "updateTime": ############, "accountType": "SPOT", "balances": [ { "asset": "BTC", "free": "0.00000000", "locked": "0.00000000" }, { "asset": "ETH", "free": "0.00000000", "locked": "0.00000000" }, ..., { "asset": "BNB", "free": "0.00000000", "locked": "0.00000000" }, ..., { "asset": "POLY", "free": "0.00000000", "locked": "0.00000000" } ], "permissions": [ "SPOT" ] }
Latest Transaction
The third function we have is the latest_transaction()
function which calls the myTrades endpoint to see the user's latest transactions. Just like the account information endpoint in the last section, we pass in all the relevant parameters and key information to get access to the user's trade history. The response of the 'get' request returns a list of dictionaries containing the latest 500 symbol_pair
trades. However, we only want the latest trade latest_transaction=trades[-1]
since this is what we base our trading criteria on. The function returns the price tp_price
at which this latest transaction occurred and whether it was a buy or sell order isBuyer
. The value of isBuyer
refers to the first asset in the symbol_pair
.
def latest_transaction(): ################################### ##### Account trades endpoint ##### ################################### url = "https://api.binance.us/api/v3/myTrades" # Creating datetime variable required for account connection now = datetime.now(timezone.utc) epoch = datetime(1970, 1, 1, tzinfo=timezone.utc) # use POSIX epoch posix_timestamp_micros = (now - epoch) // timedelta(microseconds=1) posix_timestamp_millis = posix_timestamp_micros // 1000 # or `/ 1e3` for float # Input variables queryString = "symbol=" + symbol_pair + "×tamp=" + str(posix_timestamp_millis) # Creating hash for authentication signature = hmac.new(secretkey.encode(), queryString.encode(), hashlib.sha256).hexdigest() # Combining account information endpoint url with input variables and authentication hash url = url + f"?{queryString}&signature={signature}" # Sending GET request for account trades response_trades = requests.get(url, headers={'X-MBX-APIKEY': apikey}) # Convert response to JSON object for data extraction trades=response_trades.json() # print(json.dumps(trades, indent=4)) # Pretty print to understand structure of data latest_transaction=trades[-1] # Latest transaction print('--------------------Latest Transaction--------------------') print('\n') tp=latest_transaction['symbol'] # Trading pair tp_price=latest_transaction['price'] # Trading pair price # Printing information print(f'Trading Pair: {tp}') print(f'Price: {tp_price}') print('\n') # Buy and Sell is for the first symbol of the trading pair # True is buy first symbol # False is sell first symbol isBuyer= latest_transaction['isBuyer'] symbol_first_qty = latest_transaction['qty'] # First Symbol quantity symbol_second_qty = latest_transaction['quoteQty'] # Second Symbol quantity # Buy first symbol if isBuyer == True: print('BUY') print(f'{symbol_first} BOUGHT: {symbol_first_qty}') print(f'{symbol_second} SOLD: {symbol_second_qty}') # Sell first symbol elif isBuyer == False: print('SELL') print(f'{symbol_first} SOLD: {symbol_first_qty}') print(f'{symbol_second} BOUGHT: {symbol_second_qty}') print('\n') return tp_price, isBuyer
>>> print(json.dumps(trades, indent=4)) [ { "symbol": "BNBBUSD", "id": ######, "orderId": ######, "orderListId": -1, "price": "######", "qty": "######", "quoteQty": "######", "commission": "######", "commissionAsset": "BUSD", "time": ######, "isBuyer": false, "isMaker": false, "isBestMatch": true }, ..., { "symbol": "BNBBUSD", "id": ######, "orderId": ######, "orderListId": -1, "price": "######", "qty": "######", "quoteQty": "######", "commission": "######", "commissionAsset": "BNB", "time": ######, "isBuyer": true, "isMaker": false, "isBestMatch": true } ]
Submit Order
The final function submit_order()
uses the order endpoint to submit a buy or sell market order based on the isBuyer
variable. This variable also determines the parameters we pass in to the queryString
. Note that Binance also has a dummy order endpoint to practice submitting an order which will return a response of <Response [200]> if successful. The type
variable is set to 'MARKET' as this script will submit a market order but other options are available. The submit_order()
function will buy or sell the maximum amount of free assets available (assetfree
and assetfree2
) acquired from the account_balance()
function. Note that this time we are using requests.post()
rather than requests.get()
(like all the previous functions) since we want to 'post' some data to the Binance Rest API in order to submit the order. The other functions were just 'getting' data that was already there to post process but this time we want to 'post' some data. An example of the pretty print response after submitting a successful order can be seen in the order endpoint documentation linked at the beginning of this section.
The function submit_order()
can be commented out in the while loop to avoid submitting an order to understand the logic of the bot.
def submit_order(): ########################## ##### Order endpoint ##### ########################## url = "https://api.binance.us/api/v3/order" # url = "https://api.binance.us/api/v3/order/test" # Test url for dummy trades # Creating datetime variable required for account connection now = datetime.now(timezone.utc) epoch = datetime(1970, 1, 1, tzinfo=timezone.utc) # use POSIX epoch posix_timestamp_micros = (now - epoch) // timedelta(microseconds=1) posix_timestamp_millis = posix_timestamp_micros // 1000 # or `/ 1e3` for float # Input variables type='MARKET' # Order type if isBuyer == False: side='BUY' # Want to buy quoteOrderQty=str(symbol_second_avail) # How much second symbol you want to use to buy first symbol queryString = "symbol=" + symbol_pair + "&side=" + side + "&type=" + type + ""eOrderQty=" + quoteOrderQty + "×tamp=" + str(posix_timestamp_millis) elif isBuyer == True: side='SELL' # Want to sell quantity=str(symbol_first_avail) #how much first symbol you want to sell to buy second symbol queryString = "symbol=" + symbol_pair + "&side=" + side + "&type=" + type + "&quantity=" + quantity + "×tamp=" + str(posix_timestamp_millis) # Creating hash for authentication signature = hmac.new(secretkey.encode(), queryString.encode(), hashlib.sha256).hexdigest() # Combining account information endpoint url with input variables and authentication hash url = url + f"?{queryString}&signature={signature}" # Sending POST request for order response_order = requests.post(url, headers={'X-MBX-APIKEY': apikey}) # Convert response to JSON object for data extraction order=response_order.json() # print(json.dumps(order, indent=4)) # Pretty print to understand structure of data symbol_first_transaction=order['executedQty'] # How much of first symbol was bought/sold symbol_second_transaction=order['cummulativeQuoteQty'] # How much of second symbol was sold/bought fills=order['fills'] # Order fill information # Printing information if isBuyer == False: print('########################BOUGHT!########################') print(f'{symbol_first} Bought: {symbol_first_transaction}') print(f'{symbol_second} Sold: {symbol_second_transaction}') print(f'Fills: {fills}') print('########################BOUGHT!########################') elif isBuyer == True: print('########################SOLD!########################') print(f'{symbol_first} Sold: {symbol_first_transaction}') print(f'{symbol_second} Bought: {symbol_second_transaction}') print(f'Fills: {fills}') print('########################SOLD!########################') print('\n\n') time.sleep(5) # In seconds
Trading Loop
Now that all the essential functions for this Binance Trading Robot have been defined, they can be called in the continuous while loop that will automatically trade the selected symbol_pair
. This while loop continuously runs without end since it will always be true while True:
. The logic of the loop can be seen below.
- We first call the
current_price()
function to acquire the current pricecp
of thesymbol_pair
. - We then call the
account_balance()
function to calculate the user'ssymbol_pair
account balance usingcp
and return how much of each asset is free (assetfree
andassetfree2
) to determine how much we can trade later on. - The
latest_transaction()
function is called to grab the latest transaction pricetp_price
at which thesymbol_pair
was traded at and to determine whether the first symbol was bought or soldisBuyer
. - The next section of code determines what the buy/sell percentage
bsp
criteria is to submit an order to the Binance Rest API. The variablebsp
is used to calculate buy/sell pricedelta
needed to submit the order based off of the latest transaction pricetp_price
. - The script then goes into various if statements depending on the value of
isBuyer
.- If
isBuyer
is equal to False, then the latest transaction was a SELL meaning that we want to buy the first symbol. The buy pricebuyprice
is then calculated to bebuyprice = tp_price - delta
since we want to buy at a lesser price than what we sold it at. The buy pricebuyprice
is compared to the current pricecp
to see if the buy order should be submittedif cp < buyprice
; else it doesn't do anything. - If
isBuyer
is equal to True, then the latest transaction was a BUY meaning that we want to sell the first symbol. The sell pricesellprice
is then calculated to besellprice = tp_price + delta
since we want to sell at a greater price than what we bought it at. The sell pricesellprice
is compared to the current pricecp
to see if the sell order should be submittedif cp > sellprice
; else it doesn't do anything.
- If
- Once the if statement is exited, the script waits an hour
time.sleep(60*60)
before it goes to the next loop in the continous while loop.
submit_order()
is commented out by default for buying and selling orders in the actual script on GitHub. ⚠⚠⚠
while True: # Continuously Run print('--------------------------------------------------New Check-------------------------------------------------') print('----------------------------------------',datetime.now(),'----------------------------------------') print('\n') ################# # Current Price # ################# cp = current_price() # Current price of symbol ################### # Account Balance # ################### assetfree, assetfree2 = account_balance() time.sleep(5) ###################### # Latest Transaction # ###################### tp_price, isBuyer = latest_transaction() ############ # BUY/SELL # ############ print('--------------------BUY/SELL Criteria--------------------') print('\n') # Trading fee is .075% if using bnb for fees https://www.binance.com/en/fee/schedule # Buy Sell Percentage bsp below must be greater than trading fee # But also needs to include a bit more margin to account for slippage since this script does market orders bsp=0.5 #buy/sell percent criteria in % print('Buy/Sell Percent Criteria: ', bsp, '%') delta=bsp/100*float(tp_price) # Delta of price calculated from buy/sell percent criteria print('Buy/Sell Criteria Delta (Previous Price * Percent): $', delta) print('\n') ################# # Current Price # ################# # Do this again to get the latest and greatest price since prices change so quickly cp = current_price() # Current price of symbol ################## ##### Buying ##### ################## if isBuyer == False: # If latest transaction was a SELL buyprice=float(tp_price) - delta # Price to buy at print('--------------------Checking to see if time to BUY--------------------') print(f'Needed Price to Buy (Previous Price - Delta): ${buyprice}') print('\n') ################### ##### Yes Buy ##### ################### if float(cp) < buyprice: print('YES!') print(f'Current Price: {cp} < BUY Price: {buyprice}') print('\n') symbol_second_avail=np.floor(float(assetfree2)) # Round down to nearest whole number for second asset print(f'{symbol_second} Available: {symbol_second_avail}') print('\n') ################ # Submit Order # ################ submit_order() ################### # Account Balance # ################### #Do this again to get the latest and greatest account info after the new trade assetfree, assetfree2 = account_balance() ################## ##### No Buy ##### ################## elif float(cp) > buyprice: print('NO! PRICE IS HIGH!') print(f'Current Price: {cp} > BUY Price: {buyprice}') ################## ##### No Buy ##### ################## elif float(cp) == buyprice: print('NO! PRICE IS SAME!') print(f'Current Price: {cp} = BUY Price: {buyprice}') ################### ##### Selling ##### ################### elif isBuyer == True: # If latest transaction was a BUY sellprice=float(tp_price) + delta # Price to sell at print('--------------------Checking to see if time to SELL--------------------') print(f'Needed Price to Sell (Previous Price + Delta): ${sellprice}') print('\n') #################### ##### Yes Sell ##### #################### if float(cp) > sellprice: print('YES!') print(f'Current Price: {cp} > SELL Price: {sellprice}') # Round down to nearest hundredths place 0.01 for first asset n_decimals=2 a=float(assetfree) symbol_first_avail=((a*10**n_decimals)//1)/(10**n_decimals) print(f'{symbol_first} Available: {symbol_first_avail}') ################ # Submit Order # ################ submit_order() ################### # Account Balance # ################### #Do this again to get the latest and greatest account info after the new trade assetfree, assetfree2 = account_balance() ################### ##### No Sell ##### ################### elif float(cp) < sellprice: print('NO! PRICE IS LOW!') print(f'Current Price: {cp} < SELL Price: {sellprice}') ################### ##### No Sell ##### ################### elif float(cp) == sellprice: print('NO! PRICE IS SAME!') print(f'Current Price: {cp} = SELL Price: {sellprice}') print('\n\n\n\n') time.sleep(60*60) # In seconds
Bash Script
The b_rerun.sh bash script below can be used on a server/Raspberry Pi/etc... to run the binance_bot.py script and continuously check if it needs to be ran again just in case binance_bot.py crashes. The command ps aux | grep '[p]ython binance_bot.py'
looks for a process running with the text '[p]ython binance_bot.py' to see if the binance_bot.py script is running. If it finds a matching process, it continues, does nothing, and then waits for 30 minutes before continuing to the next loop in the continuous while loop. If it doesn't find a matching process, it calls on python to run the script, outputs the script's print to b_output.txt, and then waits for 30 minutes before continuing to the next loop in the continuous while loop. The b_rerun.sh bash script can be ran with sh b_rerun.sh &> b_rerunoutput.txt &
where the echo (print) is outputted to b_rerunoutput.txt.
If the process ID (PID) for either the b_rerun.sh or binance_bot.py script needs to be found in order to terminate the process, one can use ps aux | grep '[b]_rerun.sh'
and ps aux | grep '[b]inance_bot.py'
, respectively. Once the PIDs are found, one can terminate the scripts using kill PID
. Note that b_rerun.sh needs to be killed as well or else it will start binance_bot.py up again after 30 minutes. If the square brackets [] are not included in these commands, the search itself will be returned as well. This is why the square brackets [] are used in the b_rerun.sh bash script since it will see the search itself and think the binance_bot.py script is still running when it is not.
>>> ps aux | grep '[b]_rerun.sh' pi 618 0.0 0.0 1940 420 pts/1 S 22:53 0:00 sh b_rerun.sh >>> ps aux | grep 'b_rerun.sh' pi 618 0.0 0.0 1940 420 pts/1 S 22:53 0:00 sh b_rerun.sh pi 769 0.0 0.0 7348 492 pts/1 S+ 23:00 0:00 grep --color=auto b_rerun.sh >>> kill 618b_rerun.sh file:
while : do if ps aux | grep '[p]ython binance_bot.py'; then echo $(date) echo "Script is still running" else echo $(date) echo "Script stopped running, time to rerun" python binance_bot.py &> b_output.txt & fi sleep 30m done