Coding a Binance crypto trading bot that trades based on daily news sentiment

0 42

News are the talk of the day, or more specifically building a Binance trading bot that buys or sells based on news sentiment. The cryptocurrency market is still quite young compared to the stock market, and therefore news are especially influential in the crypto world.

I’m not sure about you, but I panic sold a few times, as well as hype-bought – a strategy also known as Buy High Sell Low. Me building and testing crypto bots is a rational response to my emotional trading tactics. I have made good manual trades though believe it or not, and this blog is about taking the best strategies that I have tested, read or thought about and turn them into efficient crypto trading bots. 

In the previous blog, we covered how to analyse the daily crypto news sentiment by surfing the web in search of articles that match our keywords, and today we’re going to use that strategy in order to create a fully functional Python crypto trading bot for Binance. 

Just a note here though, during the building and testing of this bot, I realised that the two APIs used in the last article don’t really fit so well in the scope of this bot so I have opted for a slightly different, more robust solution, and I ended up rewriting most of the code from the previous blog post.

Parameters and prerequisites of our Binance trading bot

Let’s clearly define what the bot will do and what you’re going to need to complete this project. 

The bot will:

  • pull and analyse the last headline from the top 100 crypto news sites

  • provide an overview on the most mentioned coin across all the headlines

  • analyse the sentiment of each headline and categorise the output by coin

  • Place a Buy order if the compound sentiment is positive and there are at least 10 articles mentioning a particular coin

This is just a general overview, I will go through each of the points in more detail as we make our way through the code

You will need:

  • a Binance account

  • a GitHub account

  • Binance Testnet API keys

  • a few python libraries

  • some knowledge of Python

Generating Binance Testnet keys

With all of that out of the way, the first thing you’ll want to do is generate an API key and and API secret for the Binance Testnet. The Testnet allows you to test your bot in a safe environment and without having to use real cryptocurrency. Later on if you’re happy with your testing and would like to use it one live account you will simply replace the testnet keys with the mainnet ones.

If  you don’t have a Binance account go ahead and create one. After you have created your Binance account you will need to go over to testnet.binance.vision in order to create a testnet account.

 

 

Click on Log In with GitHub and authenticate with your GitHub account, or start by creating a GitHub account if you don’t have one. After logging in, click on Generate HMAC_SHA256 Key.

 

Give your key a description and click Generate.

 

 

Now it’s important that you SAVE your API Key and Secret displayed as for security reasons, this is the only time you will be able to see them. Simply regenerate the keys if you haven’t saved them.

The code

Initial set up

If you don’t already have Python installed on your machine, head over to python.org and select a version compatible with your operating system. After Python has been installed on your computer, open up your cmd or terminal to install Pypi – this will allow you to download the Python modules that we need in order to set your Binance bot up.

Install Pip on Windows

Simply run the following command in Windows to install the pip installer

python get-pip.py

Install Pip on Mac

To install the pip installer on mac you’ll need to run both commands below. Make sure to replace python3.x with the version of Python that you have installed.

curl https://bootstrap.pypa.io/get-pip.py -o get-pip.pypython3.x get-pip.py

Install Pip on Linux (debian/ubuntu)

apt install python3-pip

Install modules with Pip

That’s it, we’re now only one step away from pure Python joy, the last set-up step is to install the required modules using the pip installer. So, in your cmd or terminal, run the following commands. On Mac, you may need to replace pip with pip3like so: pip3 install requests.

pip install requests

pip install numpy

pip install python-binance

pip install nltk

Importing modules

A few modules are needed in order to achieve the desired functionality. I have included comments above each import so you can understand what they do and why they’re needed, but for now simply copy this into your Python compiler. If you don’t have a compiler, I recommend Atom.

# import for environment variables and waiting

import os, time

# used to parse XML feeds

import xml.etree.ElementTree as ET

# get the XML feed and pass it to Element tree

import requests

# date modules that we'll most likely need

from datetime import date, datetime, timedelta

# used to grab the XML url list from a CSV file

import csv

# numpy for sums and means

import numpy as np

# nlp library to analyse sentiment

import nltk

from nltk.sentiment import SentimentIntensityAnalyzer

# needed for the binance API

from binance.client import Client

from binance.enums import *

from binance.exceptions import BinanceAPIException, BinanceOrderException

# used for binance websocket

from binance.websockets import BinanceSocketManager

from twisted.internet import reactor

# used for executing the code

from itertools import count

Connecting to the Binance Testnet

It’s time to use our API key and secret to connect to the Binance Testnet, so that we can place trades and view the trade history. In order to keep my API keys private, I have created environment variables in my OS and I’m calling them below using the os.getenv() function. If you’re not planning to share the code  and anonymize your keys, simply input your keys as a string: api_key = ‘YOUR_API_KEY’

# get binance key and secret from environment variables

api_key = os.getenv('binance_api_stalkbot_testnet')

api_secret = os.getenv('binance_secret_stalkbot_testnet')

# Authenticate with the client

client = Client(api_key, api_secret)

# The API URL is manually changed in the library to work on the testnet

client.API_URL = 'https://testnet.binance.vision/api'

Creating user input variables

With the connection to the Binance testnet, it’s time to define user-configurable variables for you Binance trading bot. These can be edited without too much technical skills and pose little risk of breaking the code. 

############################################

# USER INPUT VARIABLES LIVE BELOW #

# You may edit those to configure your bot #

############################################

# select what coins to look for as keywords in articles headlines

# The key of each dict MUST be the symbol used for that coin on Binance

# Use each list to define keywords separated by commas: 'XRP': ['ripple', 'xrp']

# keywords are case sensitive

keywords = {

'XRP': ['ripple', 'xrp', 'XRP', 'Ripple', 'RIPPLE'],

'BTC': ['BTC', 'bitcoin', 'Bitcoin', 'BITCOIN'],

'XLM': ['Stellar Lumens', 'XLM'],

'BCH': ['Bitcoin Cash', 'BCH'],

'ETH': ['ETH', 'Ethereum'],

'BNB' : ['BNB', 'Binance Coin'],

'LTC': ['LTC', 'Litecoin']

}

# The Buy amount in the PAIRING symbol, by default USDT

# 100 will for example buy the equivalent of 100 USDT in Bitcoin.

QUANTITY = 100

# define what to pair each coin to

# AVOID PAIRING WITH ONE OF THE COINS USED IN KEYWORDS

PAIRING = 'USDT'

# define how positive the news should be in order to place a trade

# the number is a compound of neg, neu and pos values from the nltk analysis

# input a number between -1 and 1

SENTIMENT_THRESHOLD = 0.5

# define the minimum number of articles that need to be analysed in order

# for the sentiment analysis to qualify for a trade signal

# avoid using 1 as that's not representative of the overall sentiment

MINUMUM_ARTICLES = 10

# define how often to run the code (check for new + try to place trades)

# in minutes

REPEAT_EVERY = 60

############################################

# END OF USER INPUT VARIABLES #

# Edit with care #

############################################

  • keywords represent the coins you want the Bot to look for in each article headline

  • QUANTITY represents the amount to buy, by default in USDT

  • PAIRING allows you to change the pairing for example from BTCUSD to BTCBNB – If you do choose BNB as the paired coin, make sure to adjust the Quantity as 100 BNB is a lot more than 100 USDT.

  • SENTIMENT_THRESHOLD represents a compound value of positive, negative and neutral sentiments for each headline. Adjust this depending on how you want the bot to trade. A smaller value means that the average headline sentiment doesn’t have to be overwhelmingly positive in order to place a trade.

  • MINUMUM_ARTICLES determines the minimum amount of mentions for a coin across all headlines in order to trigger a trade. This is to ensure we get an accurate sentiment reading by analysing multiple headlines.

  • REPEAT_EVERY tells your Binance bot how often to run, check for news and decide whether to trade or not.

Connecting to a Binance websocket

In order to keep track of the current price, we need to get this from Binance. It can be done with the REST api as a request, or preferably through a websocket. Websockets stream live data to your code, so you don’t have to call them every time you need to get the live price for a coin. 

# current price of CRYPTO pulled through the websocketCURRENT_PRICE = {} def # current price of CRYPTO pulled through the websocket

CURRENT_PRICE = {}

def ticker_socket(msg):

'''Open a stream for financial information for CRYPTO'''

if msg['e'] != 'error':

global CURRENT_PRICE

CURRENT_PRICE['{0}'.format(msg['s'])] = msg['c']

else:

print('error')

# connect to the websocket client and start the socket

bsm = BinanceSocketManager(client)

for coin in keywords:

conn_key = bsm.start_symbol_ticker_socket(coin+PAIRING, ticker_socket)

bsm.start()

The live price for each of our coins defined in keywords will live in CURRENT_PRICE under their own dictionary. So if you want to access the current price of bitcoin you would use CURRENT_PRICE[‘BTCUSDT’].

Calculate trading volume

By default, Binance will expect you to input the volume of your trade in the coin that you’re trading on. So for example, if you’re trading BTCUSDT, the Binance API expects the volume to be in BTC. This is tricky when you work with multiple coins and can lead to uncontrolled spend. The next function will help you to convert from your QUANTITY variable USDT to BTC. 

def calculate_volume():

'''Calculate the amount of CRYPTO to trade in USDT'''

while CURRENT_PRICE == {}:

print('Connecting to the socket...')

time.sleep(3)

else:

volume = {}

for coin in CURRENT_PRICE:

volume[coin] = float(QUANTITY / float(CURRENT_PRICE[coin]))

volume[coin] = float('{:.6f}'.format(volume[coin]))

return volume

Import a list of crypto sites

I have used Feedspot’s top 100 Cryptocurrency blogs as news sources to be scraped – or so I thought. I actually ended up using Feedspot’s top 100 Bitcoin RSS Feeds, so right now there is some bias towards bitcoin. Luckily it’s easy to add more sources to the list as they are saved in a CSV file. 

# load the csv file containg top 100 crypto feeds

# want to scan other websites?

# Simply add the RSS Feed url to the Crypto feeds.csv file

with open('Crypto feeds.csv') as csv_file:

# open the file

csv_reader = csv.reader(csv_file)

# remove any headers

next(csv_reader, None)

# create empty list

feeds = []

# add each row cotaining RSS url to feeds list

for row in csv_reader:

feeds.append(row[0])

Returning the last headline for each news source

The next function will return the latest headline in a dictionary format. It’s currently iterating through every rss feed one by one in a for loop. The operation takes about 2 minutes to complete. This can be improved by enabling multiprocessing, so I will probably update this in a future version.

# TO DO - run this in multiple processes to speed up the scraping

def get_headlines():

'''Returns the last headline for each link in the CSV file'''

# add headers to the request for ElementTree. Parsing issues occur without headers

headers = {

'User-Agent': 'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:87.0) Gecko/20100101 Firefox/87.0'

}

headlines = {'source': [], 'title': [], 'pubDate' : [] }

for feed in feeds:

try:

# grab the XML for each RSS feed

r = requests.get(feed, headers=headers, timeout=7)

# define the root for our parsing

root = ET.fromstring(r.text)

# identify the last Headline

channel = root.find('channel/item/title').text

pubDate = root.find('channel/item/pubDate').text

# append the source and the title

headlines['source'].append(feed)

# append the publication date

headlines['pubDate'].append(pubDate)

# some jank to ensure no alien characters are being passed

headlines['title'].append(channel.encode('UTF-8').decode('UTF-8'))

print(channel)

except:

print(f'Could not parse {feed}')

return headlines

Categorising the headlines

Now that all the headlines are returned, we need to organise them in their own dictionary, based on the keywords found. So every time a headline matches one of the keywords in our keywords variable, that headline will be appended to a dictionary that looks like this: categorised_headlines[BTC] = [‘Headline 1’, ‘headline 2’, ‘headline 3’].

def categorise_headlines():

'''arrange all headlines scaped in a dictionary matching the coin's name'''

# get the headlines

headlines = get_headlines()

categorised_headlines = {}

# this loop will create a dictionary for each keyword defined

for keyword in keywords:

categorised_headlines['{0}'.format(keyword)] = []

# keyword needs to be a loop in order to be able to append headline to the correct dictionary

for keyword in keywords:

# looping through each headline is required as well

for headline in headlines['title']:

# appends the headline containing the keyword to the correct dictionary

if any(key in headline for key in keywords[keyword]):

categorised_headlines[keyword].append(headline)

return categorised_headlines

Determining the sentiment

The first function will analyse the sentiment of each headline, while the second one will categorise the results in a dictionary for each coin that was mentioned at least once in the headlines. The sentiment is analysed by using the Sentiment Intensity Analyser in the nltk module – a great Python library for neuro-linguistic analysis. You may have to run nltk.download() in your Python compiler once, otherwise you might get an error.

def analyse_headlines():

'''Analyse categorised headlines and return NLP scores'''

sia = SentimentIntensityAnalyzer()

categorised_headlines = categorise_headlines()

sentiment = {}

for coin in categorised_headlines:

if len(categorised_headlines[coin]) > 0:

# create dict for each coin

sentiment['{0}'.format(coin)] = []

# append sentiment to dict

for title in categorised_headlines[coin]:

sentiment[coin].append(sia.polarity_scores(title))

return sentiment

def compile_sentiment():

'''Arranges every compound value into a list for each coin'''

sentiment = analyse_headlines()

compiled_sentiment = {}

for coin in sentiment:

compiled_sentiment[coin] = []

for item in sentiment[coin]:

# append each compound value to each coin's dict

compiled_sentiment[coin].append(sentiment[coin][sentiment[coin].index(item)]['compound'])

return compiled_sentiment

Calculating the average compound sentiment

Now that you’ve returned and compiled each result based on the coin keyword found, it’s time we crunch the numbers for each coin. We’re going to use numpy in order to automatically calculate the mean from all the values in our lists. So instead of having an array with multiple compound values, for each coin we will only have an average of all these values. We’re also going to return the number of headlines analysed so that our Binance bot knows how many headlines were taken into the calculation of the average. 

def compound_average():

'''Calculates and returns the average compoud sentiment for each coin'''

compiled_sentiment = compile_sentiment()

headlines_analysed = {}

for coin in compiled_sentiment:

headlines_analysed[coin] = len(compiled_sentiment[coin])

# calculate the average using numpy if there is more than 1 element in list

compiled_sentiment[coin] = np.array(compiled_sentiment[coin])

# get the mean

compiled_sentiment[coin] = np.mean(compiled_sentiment[coin])

# convert to scalar

compiled_sentiment[coin] = compiled_sentiment[coin].item()

return compiled_sentiment, headlines_analysed

Buy logic and request

This is it, we now have the headlines, the sentiment, the price and the coins we’re looking for. All we need to do now is check that the compound_sentiment is over the threshold that we have defined, and that the number of articles is higher than 10. If those two conditions match, we create and send a trading request to the Binance API.

If an order has been successful, the Binance bot will confirm in a message that says: “Order 1234 has been placed on BTCUSDT with 0.0012500 at 17/04/2021 and bought at 62,500”. There is currently no logging apart from what is stored on Binance, in the future all the information could be stored in a CSV file.

def buy():

'''Check if the sentiment is positive and keyword is found for each handle'''

compiled_sentiment, headlines_analysed = compound_average()

volume = calculate_volume()

for coin in compiled_sentiment:

# check if the sentiment and number of articles are over the given threshold

if compiled_sentiment[coin] > SENTIMENT_THRESHOLD and headlines_analysed[coin] > MINUMUM_ARTICLES:

# check the volume looks correct

print(f'preparing to buy {coin} with {volume} USDT at {CURRENT_PRICE[coin+PAIRING]}')

# create test order before pushing an actual order

test_order = client.create_test_order(symbol=coin+PAIRING, side='BUY', type='MARKET', quantity=volume[coin+PAIRING])

# try to create a real order if the test orders did not raise an exception

try:

buy_limit = client.create_order(

symbol=coin+PAIRING,

side='BUY',

type='MARKET',

quantity=volume[coin+PAIRING]

)

#error handling here in case position cannot be placed

except BinanceAPIException as e:

print(e)

except BinanceOrderException as e:

print(e)

# run the else block if the position has been placed and return some info

else:

# retrieve the last order

order = client.get_all_orders(symbol=coin+PAIRING, limit=1)

# convert order timsestamp into UTC format

time = order[0]['time'] / 1000

utc_time = datetime.fromtimestamp(time)

# grab the price of CRYPTO the order was placed at for reporting

bought_at = CURRENT_PRICE[coin+PAIRING]

# print order condirmation to the console

print(f"order {order[0]['orderId']} has been placed on {coin} with {order[0]['origQty']} at {utc_time} and bought at {bought_at}")

return bought_at, order

else:

print(f'Sentiment not positive enough for {coin}, or not enough headlines analysed: {compiled_sentiment[coin]}, {headlines_analysed[coin]}')

Putting it all together

You did it. You followed along through over 300 lines of code and now you have a working Binance trading bot that places Buy positions based on analysed news sentiment. All you need to do now, is ensure the code will execute at a pre-defined interval. By default the script will run once every hour, you can change the frequency in the REPEAT_EVERY variable. If you’re using Atom to compile your code you can Run it by pressing Ctrl+Shift+B, other compilers will have different shortcuts.

if name == 'main':

print('Press Ctrl-Q to stop the script')

for i in count():

buy()

print(f'Iteration {i}')

time.sleep(60 * REPEAT_EVERY)

Considerations

  • The list of articles can be improved to make it less biased

  • Multiprocessing would help improve the performance

  • The default compound threshold seems to be quite high in order to place any trades, experiment with the value of the SENTIMENT_THRESHOLD

Please see the GitHub Repo here for the source code!

Thank you for taking the time to go though this guide, and I hope that it was useful. If you liked this guide, please don't forget to like this article. If you want to see more guides like this, tip me to enable me to write more content like this!

0
$ 0.00
Sponsors of CyberPunkMetalHead
empty
empty
empty

Comments