This notebook shows how to develop a Discord bot that will publish signals generated by a strategy to a Discord channel. This notebook assumes you already have created a token that allows the bot to publish messages to the channel.
The library used to integrate with Discord is JDA
. It is a feature rich library and this notebook only shows the basics of what is possible. For more info visit https://github.com/DV8FromTheWorld/JDA.
WARNING: a bot can quickly generate many messages and flood a Discord channel. So be very careful when experimenting with this notebook using existing message channels.
%use roboquant(version=2.1.0, modules=crypto)
// Load the JDA library and import the required classes
@file:DependsOn("net.dv8tion:JDA:5.0.0-beta.13")
import net.dv8tion.jda.api.*
import net.dv8tion.jda.api.entities.MessageEmbed
import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel
import net.dv8tion.jda.api.utils.messages.MessageCreateData
Welcome()
This is the implementation of the bot itself that publishes a signal to the Discord channel.
When creating an instance of DiscordBot
, it will authenticate itself using an API token. Please note although it is possible to include the token in the code, it is better practice to define the token outside the code so it doesn't leak to the public by mistake. See also the roboquant documentation how to define these properties.
After being authenticated, it will get the channel based on the provided channel name. If there are more channels wit the same name, it will used the first one.
class DiscordBot(val channelName:String = "general") {
private val jda: JDA
private val channel: MessageChannel
init {
// val token = "my_secret_discord_token"
val token = Config.getProperty("discord.token")!!
jda = JDABuilder
.createDefault(token)
.build()
.awaitReady()
channel = jda.getTextChannelsByName(channelName, true).first()
}
fun publish(signal: Signal, time: Instant) {
val fields = listOf(
MessageEmbed.Field("asset", signal.asset.symbol, true),
MessageEmbed.Field("rating", signal.rating.toString(), true),
MessageEmbed.Field("time", time.toString(), true)
)
val embed = EmbedBuilder()
.setTitle("Signal Detector")
.setAuthor("roboquant", "http://roboquant.org", "http://roboquant.org/img/avatar.png")
.setFooter("generated by roboquant","http://roboquant.org/img/avatar.png")
embed.fields.addAll(fields)
val msg = MessageCreateData.fromEmbeds(embed.build())
channel.sendMessage(msg).queue()
}
}
Normally a policy in roboquant receives signals generated by a strategy and converts them to orders that are then send to a broker.
But in this scenario, we just publish the received signals to a Discord channel and don't generate any orders.
class DiscordPolicy(private val bot: DiscordBot) : FlexPolicy() {
override fun act(signals: List<Signal>, account: Account, event: Event): List<Order> {
for (signal in signals) bot.publish(signal, event.time)
// if no orders or trading are required
return emptyList()
// if orders or trading are required
//return super.act(signals, account, event)
}
}
For this notebook we use live market data from Binance. We subscribe to ETH/BUSD and BTC/BUSD candlesticks (default is 1-minute intervals)
val feed = BinanceLiveFeed()
feed.subscribePriceBar("ETHBUSD", "BTCBUSD")
Now we create an instance of Roboquant with the Strategy we wan't to use to generate the signals and we run it for a period of time.
So every time the strategy has generated a signal during this time, a new message will be publised to the Discord channel.
val bot = DiscordBot("bot-insights")
val policy = DiscordPolicy(bot)
val strategy = EMAStrategy()
val rq = Roboquant(strategy, policy = policy)
rq.run(feed, Timeframe.next(4.hours))