The penniless Investor: Lesson3 -Basics of a trading bot, a Discord example. (Part 1)

Franklin Schram
17 min readMay 11, 2023

--

“Welcome to ‘The Penniless Investor’ — a series aimed at helping you make smarter investment choices when money is tight. Through tech-savvy tricks, free data sources, and sample investment strategies, we’ll equip you with actionable tips to make the most of your limited funds and get you started on your investing journey.” — This article is part of a series -> Start Here! <-

He is green, he is greedy, AND HE’LL TAKE ALL OF YOUR MONEY.

In this iteration of the penniless investor we will:
1. Introduce Discord and how to set up a bot
2. Connect the bot to a server
3. Show you how to control the bot
4. Show you how to add functional modules
5. Faff about data streams and data structures
6. Showcase an example with Bitcoin data streamed from coinbase.com
7. Collapse after reading “The Penniless Investor: Lesson 3”.

Do you guys spend much time watching Youtube? Do you like video game streamers? Basically these people make a living by playing video games and showcasing themselves playing the game online. Believe it or not, some of these folks make a decent living in advertising revenues, sponsored contents and even… “donations”. Surfing on the wave of community marketing they usually end up creating “hubs” where the faithful congregate to discuss their shared interests. Where about do these things happen? Well on Twitch and… on Discord.

Now some of you might remember a time in the 90s when the cool kids where hanging out on mIRC. Back in those days, the less commendable individuals would host mIRC channels full of “bot farms”, aka. programs that would enable “pirates” (#shiver_me_timbers) to coordinate armies of unsuspecting infected computers to perform various kind of attacks: Yes… back in those days you did really feel the power of the keybsword.

mIRC — The place where you could find dodgy stuff… The back alley of the internet really, reminds me of my beloved North End Road @ W!4 .

I have expressed my passion for Command Line applications in my previous posts, but there is something clunky about it, a “clunkiness” the hackers of the past understood as well. Why? Well, at some point you just need to mandate intermediaries to do the dirty work while you orchestrate the “grand plan.” This is where the bots come in and in our case this is where Discord comes in.

1. Meet Discord: The chat app you never suspected to be your next investment platform.

Who would have guessed really? I was just goofing arround their services until I ended up on… THE DEVELOPER PORTAL, OMG! These guys encourage you to make bots, they WANT YOU to make bots, there is even a Python library to HELP YOU make bots. Yes… discord.py; I don’t really like python but there comes a time when a penniless guy needs to swallow his pride, especially when he comes across something THAT GOOD.

Oh… Did I forgot to mention everything on there is asynchronous and event driven? A different programming paradigm… perfect when you’re expecting… the unexpected, hmm… isn’t that what the markets are all about?

First we have to create an account and set up a Discord server. That’s pretty easy and you can do that -> here <-.

Discord: Raw, bare and naked — Kind a like penniless guy.

So how do we go about building our trading bot? First we need to go on the Discord developer portal and create a bot — we’ll call it Azrael.

The Discord bot creation portal — Go to applications then click “New Application”

We then need to configure a few things… under the Bot menu we first:

  • reset the token and record the TOKEN CODE which we will need to use to connect the bot to Discord.
  • using the slider PUBLIC BOT we set it to -> FALSE
  • using the slider OAUTH2we set it to -> FALSE
  • using the sliders PRESENCE INTENT, SERVER MEMBER INTENT , MESSAGE CONTENT INTENT and we set them to -> TRUE
Setting the bot intents — Enabling the bot to access specific data on discord.
  • Under the OAuth2 section we select URL generator and assign the scopes bot and applications.commands. We then give permissions to the bot (to make it easy you could give it administrator permission, in the video I have been a bit picky… Always be careful with permissions! That's how unwanted guests… invite themselves.)
  • Scrolling down to the bottom of the page we locate the invite URL, copy the URL and paste it in our web browser to invite the bot into our discord channel.
Clickability — the basis of bot making, setting the bot scope and permissions
Looks like Azrael joined the game…

2. Booting up our server and setting up skeleton code

To have Azrael go online, we need to host it on a server. Using WSL (<- click if you need help setting it up) we boot a Ubuntu distro which will act as a server for our Discord bot. We run the usual stuff…

## Updating our distro and installing packages
sudo apt-get update && sudo apt-get dist-upgrade
sudo apt-get install python3 python3-virtualenv

## Creating a clean python virtual environement and activating it
python3 -m venv ~/virtualenvs/discord_bot
source ~/virtualenvs/discord_bot/bin/activate

## Installing python libraries
pip3 install nextcord aiohttp numpy

Note: I’m installing the nextcord library (alternatives/complements would be discord.py or py-cord) so I can have methods to interact with the Discord API. On the server we create a bot directory in which we will host Azrael.

Inside the azrael directory we create the structure of our bot.

  • we create a main.py file: This will host the main code block of our bot.
  • we create a config.py file : This file will host our keys. (Note we could have loaded the dotenv library instead but to “keep it simple” we just dumped our keys and tokens in config.py)
  • we create a cogs directory: This will host our bot’s modules.
Sorry for the TYPO it should be config.py and not confi.py — I won’t take another screenshot, FULL STOP.

The power of discord bots is their modularity: YOU CAN LOAD AND UNLOAD MODULES AS YOU SEE FIT. Lets start with a simple example without any modules loaded.

#!/home/test/virtualenv/discord_bot/bin/python3
###############################################
# ____ ____ /\/| /\/| /\/| #
# | __ ) _____ _____| _ \ |/\/ |/\/ |/\/ #
# | _ \|_____|_____| | | | #
# | |_) |_____|_____| |_| | #
# |____/ |____/ #
# #
###############################################
# BASIC SKELETON FOR A DISCORD BOT @ main.py #
###############################################

# 1. IMPORTING LIBRARIES ##################################################
# nextcord -> methods to interact with Discord API
# config -> hosting api keys
import nextcord
from nextcord import Interaction, SlashOption # Used for UI slashcommands
from nextcord.ext import commands # Used for bot interactions
from config import * # hosting our various keys

# 2. LOADING BOT KEYS #####################################################
discord_key = api_key_discord

# 3. PREFIX COMMANDS ######################################################
# Preffix commands are used directly in the chat bar to ask
# the bot to perform certain actions in this case -> ! <- will
# be picked up by the bot which will run the code associated with a
# defined prefix.
client = commands.Bot(command_prefix="!", intents=nextcord.Intents.all())

# 4. BOT CONNECTION STATUS ################################################
# ON SUCCESSFULL CONNECTION - THESE COMMANDS ARE PERFORMED BY THE BOT
@client.event
async def on_ready():
#Prints to linux console
print("Success: Bot is connected to Discord")
#Displays status on discord channel
activity = nextcord.Activity(type= 1, name="a dangerous game")
await client.change_presence(status=nextcord.Status.online, activity=activity)

#5. SAMPLE PREFIX COMMANDS ################################################
# These basically involve using the prefix defined in 3. and passing a
# function to it as defined below.

## 5.1 Say hello
### On passing !hello to the chat, the bot will return a string.
@client.command()
async def hello(ctx):
await ctx.send("Hello I'm Azrael... What do you want?!")

## 5.2 Connection latency in milli seconds
### On passing !ping to the chat, the bot will return the RTR in milli seconds.
@client.command()
async def ping(ctx):
latency = round(client.latency * 1000) # Convert to milliseconds
await ctx.send(f"Bot latency: {latency} ms")

# 6 Running the event loop ###############################################
client.run(discord_key)
#config.py
###############################################
# ____ ____ /\/| /\/| /\/| #
# | __ ) _____ _____| _ \ |/\/ |/\/ |/\/ #
# | _ \|_____|_____| | | | #
# | |_) |_____|_____| |_| | #
# |____/ |____/ #
# ┌П┐(▀̿Ĺ̯▀̿) #
###############################################
# API keys @ config.py #
###############################################

api_key_weather = 'insert_your_apikey'
api_key_discord = 'insert_your_bot_token'
api_key_alphavantage = 'insert_your_apikey'

So what does all that stuff do? If you look at section 5 of the code, I have written two simple functions:

  • Function 5.1 will return the simple string Hello I'm Azrael what do you want when we pass the command !hello in the Discord chat bar.
  • Function 5.2 will return the latency between our server and the Discord server whenever we type in !ping

We’re now ready to test our code:

Is that stuff EVEN WORKING? Bah… who cares.

3. Making user-friendly interactions through slash commands.

While prefix type commands are fine to test a few simple bits of code, if we aim to build a proper investment platform we need commands that take multiple arguments, are easy to pass to our bots and benefit from an built-in help system. Lets take an example:

  • To activate the trading bot we could pass the command attack, to stop it we could issue a retreat command.
  • To specify the target we could pass a ticker.
  • To specify the type of attack we could choose a specific algorithm.

In the chat box we would then type something akin to/attack USDEUR algo.1 which would indicate to our bot to instantiate the trading strategy on a preestablished “stream of data” we’ll call that stream a “ring buffer or a time loop”.

But for now let’s keep it simple and illustrate the power of slash commands by simplifying our code (and keeping config.py untouched). The below code will create a simple slash command that will be launched by typing /hello in the chat bar. The command will ask for:

  • User name
  • User age
  • Location

Upon which the bot will return a customized “welcome prompt”.

#!/home/test/virtualenv/discord_bot/bin/python3
###############################################
# ____ ____ /\/| /\/| /\/| #
# | __ ) _____ _____| _ \ |/\/ |/\/ |/\/ #
# | _ \|_____|_____| | | | #
# | |_) |_____|_____| |_| | #
# |____/ |____/ #
# #
###############################################
# BASIC SKELETON FOR A DISCORD BOT @ main.py #
###############################################

# 1. IMPORTING LIBRARIES ##################################################
# nextcord -> methods to interact with Discord API
# config -> hosting api keys
import nextcord
from nextcord import Interaction, SlashOption # Used for UI slashcommands
from nextcord.ext import commands # Used for bot interactions
from config import * # hosting our various keys

# 2. LOADING BOT KEYS #####################################################
discord_key = api_key_discord

# 3. PREFIX COMMANDS ######################################################
# Preffix commands are used directly in the chat bar to ask
# the bot to perform certain actions in this case -> ! <- will
# be picked up by the bot which will run the code associated with a
# defined prefix.
client = commands.Bot(command_prefix="!", intents=nextcord.Intents.all())

# 4. BOT CONNECTION STATUS ################################################
# ON SUCCESSFULL CONNECTION - THESE COMMANDS ARE PERFORMED BY THE BOT
@client.event
async def on_ready():
#Prints to linux console
print("Success: Bot is connected to Discord")
#Displays status on discord channel
activity = nextcord.Activity(type= 1, name="a dangerous game")
await client.change_presence(status=nextcord.Status.online, activity=activity)

# 5. SHOWCASING SLASHCOMMANDS #############################################
# The code below will leverage the slashcommands and interaction classes of
# the nextcord library to build an interactive command launcher. For proof
# of concept we will prompt the user to enter his/her name, age and location.

cities = ["New York", "London", "Paris"]

@client.slash_command(guild_ids=[<server_id>]) # <- !Replace with your server id!
async def hello(interaction: Interaction, name: str, age: int, location: str = SlashOption(name="cities", choices=cities, description="A customized welcome message")):
await interaction.response.send_message(f"Hello {name}, you are {age} years old and live in {location}!")

# 6 Running the event loop ################################################
client.run(discord_key)

In the above code don’t forget to include your server id so the bots slash commands can be used on your server. How do you get that? Right click on your server icon and select copy server ID.

Where to copy your server ID — You’ll need that to have your bots receive slash commands
Slash commands in action — Easy and readable

The power of slash commands are linked to their user friendliness and strong data validation potential.

4. Adding cogs (modules) to our bot

To prevent our main file from becoming clogged up with functions, Discord Bots have this neat functionality to allow the loading of “modules”. This is great since it enables us to specialise our code in separate files; easier to troubleshoot, easier to switch things on and off. For instance I could have module that handles fetching data, another one which deals with number crunching or another one which pumps out data to a charting service.

To illustrate that, we will introduce APIcalls: If we’re building a trading bot we’ll have to make calls to an API to query financial data or ingest timestamps from a Websocket (more on that in Section 5 of this article). As a rule of thumb we always start simple so we can build more complex features later on, so…

Lets say I want Azrael to tell me about the weather. To do so I will:

  • Use my basic skeleton in my main.py to connect the bot to Discord and load acog1.py module which will host specific code related to issuing the weather slash command from the chat bar.
  • Craft specific code that connects to WeatherAPI, processes the returned JSON object and returns the data to the Discord UI. All this, incog1.py

Our main.py is now simplified and most of the magic happens in cog1.py

#!/home/test/virtualenv/discord_bot/bin/python3
###############################################
# ____ ____ /\/| /\/| /\/| #
# | __ ) _____ _____| _ \ |/\/ |/\/ |/\/ #
# | _ \|_____|_____| | | | #
# | |_) |_____|_____| |_| | #
# |____/ |____/ #
# #
###############################################
# BASIC SKELETON FOR A DISCORD BOT @ main.py #
###############################################

# 1. IMPORTING LIBRARIES ##################################################
# nextcord -> methods to interact with Discord API
# config -> hosting api keys
import nextcord
from nextcord.ext import commands # Used for bot interactions
from config import * # hosting our various keys

# 2. LOADING BOT KEYS #####################################################
discord_key = api_key_discord

# 3. PREFIX COMMANDS ######################################################
# Preffix commands are used directly in the chat bar to ask
# the bot to perform certain actions in this case -> ! <- will
# be picked up by the bot which will run the code associated with a
# defined prefix.
client = commands.Bot(command_prefix="!", intents=nextcord.Intents.all())

# 4. BOT CONNECTION STATUS ################################################
# ON SUCCESSFULL CONNECTION - THESE COMMANDS ARE PERFORMED BY THE BOT
@client.event
async def on_ready():
#Prints to linux console
print("Success: Bot is connected to Discord")
#Displays status on discord channel
activity = nextcord.Activity(type= 1, name="a dangerous game")
await client.change_presence(status=nextcord.Status.online, activity=activity)

# 5. Running the event loop ################################################
async def main():
client.load_extension('cogs.cog1') # <- loading cog1 module
await client.start(discord_key)

asyncio.run(main())
###############################################
# ____ ____ /\/| /\/| /\/| #
# | __ ) _____ _____| _ \ |/\/ |/\/ |/\/ #
# | _ \|_____|_____| | | | _ #
# | |_) |_____|_____| |_| | V_T F_F #
# |____/ |____/ #
# B==D ~ (_!_) #
###############################################
# Weather data from WeatherAPI.com @ cog1.py #
###############################################

import aiohttp
from datetime import datetime, timedelta
import nextcord
from nextcord import Interaction, SlashOption # Used for UI slashcommands
from nextcord.ext import commands
from config import *

# 0.Importing keys
api_key = api_key_weather

# 1. Creating lists and dictionaries for dropdown menus
## 1.1 Specifying a list of Cities this will be used by the slash command
## to pass to the user.
cities = ['Paris', 'London', 'New York']

# 1.2 Creating a dictionary of dates for the next 5 days:
# This will be used by our slash commands to offer the user
# a selection of dates
today = datetime.today().date()
next_7_days = {
'now': today.strftime('%Y-%m-%d'),
'tomorrow': (today + timedelta(days=1)).strftime('%Y-%m-%d'),
'in 2 days': (today + timedelta(days=2)).strftime('%Y-%m-%d'),
'in 3 days': (today+ timedelta(days=3)).strftime('%Y-%m-%d'),
'in 4 days': (today+ timedelta(days=4)).strftime('%Y-%m-%d'),
'in 5 days': (today+ timedelta(days=5)).strftime('%Y-%m-%d')
}

#2. Creating our weather class
class Weather(commands.Cog):
## 2.1 Constructor
def __init__(self, client):
self.client = client

## 2.2 Salshcommand decorator
@nextcord.slash_command(guild_ids=[<slap_your_server_id_here>]) # <-!!!

async def my_weather(self,
interaction: Interaction,
my_date: str = SlashOption(name="date", choices=next_7_days.keys()),
city: str = SlashOption(name="cities", choices=cities)):

async with aiohttp.ClientSession() as session:

##### 1. Passing end point URL
url = 'https://api.weatherapi.com/v1/forecast.json'

##### 2. Specifying Parameters to API
params = {'key': api_key, 'dt' : next_7_days[my_date],'q': city}

##### 3. Querry API and Returning data
async with session.get(url, params=params) as response:
data = await response.json() # Parse the JSON response into a Python dictionary
max_temp = data['forecast']['forecastday'][0]['day']['maxtemp_c']
min_temp = data['forecast']['forecastday'][0]['day']['mintemp_c']
avg_temp = data['forecast']['forecastday'][0]['day']['avgtemp_c']
max_wind = data['forecast']['forecastday'][0]['day']['maxwind_kph']
rain_chance = data['forecast']['forecastday'][0]['day']['daily_chance_of_rain']

# C. Creating a Discord Embed to pass fields to.
embed = nextcord.Embed(
title=f"Weather for the {next_7_days[my_date]} in {city}",
color=nextcord.Color.green()
)

# D. set the fields in the Embed object
embed.add_field(name="Average Temperature", value=f"{avg_temp}°C", inline=True)
embed.add_field(name="Max temperature", value=f"{max_temp}°C", inline=True)
embed.add_field(name="Min temperature", value=f"{min_temp}°C", inline=True)
embed.add_field(name="Max wind speed", value=f"{max_wind}°km/h", inline=True)
embed.add_field(name="Rain chance", value=f"{rain_chance}%", inline=True)

# E. send the Embed object as a response
await interaction.response.send_message(embed=embed)

def setup(client):
client.add_cog(Weather(client))
Interactive Slash Command linked to an API query — You should be amazed, if you’re not its okay… BECAUSE I AM.

If you go through the amended code in main.py you will see we have changed the event loop by adding functions that allow us to load our module. The real game happens in cog1.py though, this is where we have:

  • Created a dictionary of dates which (for the drop down menu)
  • A list of cities (for the drop down menu)
  • Added code that connects to WeatherAPI and retrieves a range of data from the returned json object (based on options passed to the drop down menu)
  • Formatted all of this into a slash command which returns a Discord “Embed” with the data requested.

Now I understand that all that weather stuff might not fit your interests, yet… Perhaps you could change the WeatherAPI end point to a financial data vendor endpoint maybe? And just maybe… have an R script somewhere else read by the bot? Bah… I’m sure you’ll figure it out.

5. From talking about weather snapshots to talking about financial time series

Okay… So you clicked on that article because you wanted to make a trading bot and not a weather bot. We just need to introduce a few more concepts:

a. Webhooks / WebSockets

In my previous article I talked about REST APIs and how these can be used to get data from vendors. The issue with REST API is that you have to make “a call” to get the data. How does that work? Imagine you are on a deserted street and you need to get information from someone. You pick a house then a door (we will call that the “endpoint”), you then knock on the door, the person opens up the door and you can finally ask your question; you get a response back and the door shuts straight after. Now you if you want to ask a another question you need to knock on the door again, wait for the person to open it and get your response (assuming you’re still using the same endpoint for the type of information you want). What does that all boil down to?

  • Computationally inefficient -> some part of your code, hence some part of your hardware (or your cloud provider) is dedicated to making calls to the API, which will cost you dollar BUCKS.
  • Timestamp “risk” -> you take charge of assessing data continuity as part of your query process.

In case of a Websocket, the data provider manages the data buffer and you ingest data at your own pace (think of it like spoon feeding), which reduces timestamps continuity risk if your data ingestion rate is fast enough. Think of it as knocking on the door and keeping the door open, communication flows easily and you can get all the info you want without having to knock again.

b. Frames

Okay, so you have the door open, communication is flowing freely and then… what’s next? Well we will need to parse the data and one way to do this is via frames. Frames are a concept that has been arround for ages, it is used in digital audio and digital video. How? I want you to make a mental image of a gun, then think about the clip and the bullets loaded inside, now replace the bullets with timestamps of data (lets say prices): There! You know what a frame is. Lets take another example: Most movies when you go see them in theatres have got a frame rate of 24 images / seconds, which means that assuming a frame size of 1 seconds, 24 “data points” will be in there.

We can define frames in two ways:

  • How many sample do they contain (how many bullets are in the clip)? For sound applications we call sampling rate, measured in kilo Hertz. Note that the Nyquist–Shannon sampling theorem states that perfect reconstruction of a signal is possible when the sampling frequency is greater than twice the maximum frequency of the signal being sampled. So if you wanted to sample EURUSD at 24 timestamps per seconds in practice to “reconstruct the signal” and avoid any aliasing issues, you’d need more than 48 timestamps.
  • What is the frame size? For sound applications that is measured in bytes or bits and is directly linked to the precision of each data point. By precision we mean the discrete levels an integer can take within the sampled data. For financial time series like FX, floating point precision would determine the overall size of the sample (i.e. a number like 101.3435 takes less memory than 101.3435786948).

When the websockets spoon feeds you data, you capture it in set frame upon which you will do calculations. In our case we need a special kind of frame… We need a RINGBUFFER.

c. Ring buffers

A ring buffer is essentially about folding the frame back on itself. Pretty much like the barrel of revolver (sorry for all the guns analogy), we snatch one bullet or one new data point (we pop 1 in), process the buffer, and release one (oldest) data point out (we pop 1 out), then we repeat.

Source Wikimedia commons: https://commons.wikimedia.org/wiki/File:Ring_buffer.svg

Using numpy we can craft a simple ringbuffer, and on each pass we run our algorithm. For this we need to delve into the intricacies of signal processing, another story, for another day…

6. Using Coinbase.com as proof of concept.

Right, so I wanted to do serious stuff, like trade Rates or FX but you know what? All those data providers expected me to give them some ACTUAL MONEY to get the chance to lap up their data… Who do they think I am?! A professional investor?! So, out of desperation I asked my great friend chat GPT for advice.

I went for coinbase — Why? I don’t know… Should have checked the commissions first.

And so… as I was typing the prompt to my beloved ChatGPT, I thought to myself: “HOW DID YOU END UP HERE FRANKLIN”, doing Bitcoin stuff on Discord. That’s not serious stuff is it? HOW ARE YOU GONNA BUILD UP STREET CRED BY DOING BITCOIN STUFF ON DISCORD. Bah… apparently coinbase supports OAuth2 app integration do you know what means?

  • I can place trades from within discord with my discord bot :) !
  • I can get portfolio performance, aum and even conduct risk analysis and stream all that back to my bot, perform calculations on the server and link it to a dashboard (grafana anyone?)

I thought I would put all that in this article but it is already getting quite lenghty so you know what? I THINK WE NEED… A PART 2.

Cheerio

Franklin

--

--