Blog Post

Read more about Whale's Secret.

In the previous post, we implemented a market-watching bot that continuously monitors the BTC/USDT market on Binance exchange and provides…

Trading Bot in C# — Part 2— Notifications

In the previous post, we implemented a market-watching bot that continuously monitors the BTC/USDT market on Binance exchange and provides updates on the relative-strength indicator (RSI) values.

With this system in place, we may wish to be notified when noteworthy trading opportunities arise. This blog post will delve into that topic.

Get Notified!

A simple approach would be to send an email whenever RSI crosses a significant threshold. While this approach is valid, we believe that messaging apps could be more advantageous for our intended purpose. Messaging apps typically offer various notification settings, and the ease of joining or leaving a group chat. Although email can provide similar functionalities, it tends to be more complicated, and carries the risk of being perceived as spam.

Nowadays, messaging apps are ubiquitous. It’s quite possible that at least one message from such a service just buzzed in your pocket a moment ago. So why not to send a message to a chat group where your friends or customers can act on your trading tips! We chose Telegram because we are the most familiar with that particular service. The concept is straightforward: create a Telegram bot and utilize it to send messages to a public Telegram group. Let’s dive in!

Create a New Bot

Follow these steps to create a new Telegram bot:

  1. Open your Telegram app on your phone, or on your laptop.
  2. Send the message /newbot to @BotFather in your Telegram app.
  3. Specify a name of the bot. For example, MyTradingBot.
  4. Specify a bot’s username: <YOUR_BOT_NAME>_bot. Note that the name has to end with _bot or Bot.
  5. Now a summary is displayed:
    Done! Congratulations on your new bot. You will find it at t.me/<YOUR_BOT_NAME>_bot. You can now add a description, about section and profile picture for your bot, see /help for a list of commands. By the way, when you’ve finished creating your cool bot, ping our Bot Support if you want a better username for it. Just make sure the bot is fully operational before you do this.
    Use this token to access the HTTP API:
    XXXXXXXXXX:XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
    Keep your [API] token secure and store it safely, it can be used by anyone to control your bot.

    For a description of the Bot API, see this page:
    https://core.telegram.org/bots/api
  6. Search for <YOUR_BOT_NAME>_bot user in your Telegram app and press Start button.
  7. Create a new public Telegram group <YOUR_GROUP_NAME> — i.e. set public link to be: https://t.me/<YOUR_GROUP_NAME>. An example of the group name is trading_signal_group_123456. It’s certainly possible to send messages to private Telegram groups, it’s just harder to get its group ID.

Send a Message

Once your Telegram bot is set up, sending a message using Telegram API is just a matter of using its REST API:

using System.Diagnostics;
using System.Web;

// Telegram API token to authorize message sending.
string apiToken = "XXXXXXXXXX:XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX";

// Telegram group where to send a message to.
string groupId = "@your_group";

// Messages can be formatted using HTML syntax (e.g. <b>bold</b>, <i>italics</i>, etc.).
await SendTelegramMessageAsync("First test message from <b>the robot</b>!");

async Task SendTelegramMessageAsync(string message, bool htmlSyntax = true)
{
    string chatId = HttpUtility.UrlEncode(groupId);
    message = HttpUtility.UrlEncode(message);

    using HttpClient client = new();
    string uri = htmlSyntax
        ? $"https://api.telegram.org/bot{apiToken}/sendMessage?chat_id={chatId}&parse_mode=html&text={message}"
        : $"https://api.telegram.org/bot{apiToken}/sendMessage?chat_id={chatId}&text={message}";

    using HttpRequestMessage request = new(HttpMethod.Get, uri);
    HttpResponseMessage response = await client.SendAsync(request).ConfigureAwait(false);

    string content = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
    Debug.Assert(response.IsSuccessStatusCode, content);
}

Voilà! Now we know how to send a nicely formatted message to a Telegram group.

Bot Sending Notifications

Unsurprisingly, we might want to enhance our market-watching bot to send a notification whenever the RSI indicator signals that the market is oversold or overbought.

RSI can be computed with respect to various time frames (we call them candle widths)— 1 day, 6 hours, 2 hours, 5 minutes, and many more. We chose the 1-minute interval. Nobody wants to sit idle for such a long time, so we will also print last ticker prices as we get them. The following sample shows the main parts of our bot:

// [...]

// The Telegram API token should be in form "XXXXXXXXXX:YYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY".
string? apiToken = null;

if (apiToken is null)
    throw new SanityCheckException("Please provide your Telegram API token.");

// The Telegram group identifier is in form "@my_group".
string? groupId = null;

if (groupId is null)
    throw new SanityCheckException("Please provide a Telegram group identifier.");

await using ScriptApi scriptApi = await ScriptApi.CreateAsync();

// [...]

await using ICandlestickSubscription candlestickSubscription = await tradeClient.CreateCandlestickSubscriptionAsync(SymbolPair.BTC_USDT);
await using ITickerSubscription tickerSubscription = await tradeClient.CreateTickerSubscriptionAsync(SymbolPair.BTC_USDT);

// Get historical candlestick data for the last three days for 1-minute candles.
DateTime endTime = DateTime.Now;
DateTime startTime = endTime.AddDays(-3);

CandlestickData candlestickData = await tradeClient.GetCandlesticksAsync(SymbolPair.BTC_USDT, CandleWidth.Minute1, startTime, endTime);

// Compute RSI using Skender.Stock.Indicators.
// Add the package to the project using "dotnet add package Skender.Stock.Indicators".
// Add "using Skender.Stock.Indicators;" to the source file.
List<Quote> quotes = new();
foreach (Candle candle in candlestickData.Candles)
    quotes.Add(QuoteFromCandle(candle));

Task<Candle>? lastClosedCandleTask = null;

// Loop until the program is terminated using CTRL+C.
while (true)
{
    // Wait for a new ticker.
    Ticker lastTicker = await tickerSubscription.GetNewerTickerAsync();

    // If there is no task to get the latest closed candle, create one.
    lastClosedCandleTask ??= candlestickSubscription.WaitNextClosedCandlestickAsync(CandleWidth.Minute1);

    string priceStr = ToInvariantString(Math.Round(lastTicker.LastPrice!.Value, 2));
    Console.WriteLine($"The latest price is: {priceStr} USDT.");

    if (lastClosedCandleTask.IsCompleted)
    {
        Candle lastClosedCandle = await lastClosedCandleTask;
        quotes.Add(QuoteFromCandle(lastClosedCandle));
        lastClosedCandleTask = null;

        // Compute RSI.
        RsiResult lastRsi = quotes.GetRsi().Last();
        double rsiValue = Math.Round(lastRsi.Rsi!.Value, 2);

        string interpretation = rsiValue switch
        {
            < 30 => " (oversold!)",
            > 70 => " (overbought!)",
            _ => string.Empty
        };

        string rsiStr = ToInvariantString(rsiValue);

        Console.WriteLine();
        Console.WriteLine($"RSI was recomputed: {rsiStr}{interpretation}");
        Console.WriteLine();

        string message = interpretation != string.Empty
            ? $"Bot says: RSI signals <b>{rsiStr}{interpretation}</b> for BTC/USDT on Binance! Current price is {priceStr} USDT."
            : $"Bot says: RSI does <b>not</b> provide a clear signal for BTC/USDT on Binance! Current price is {priceStr} USDT. RSI value is {rsiStr}.";

        await SendTelegramMessageAsync(message);
    }
}

// In certain cultures, the decimal separator can be comma. We prefer full stops.
static string ToInvariantString(object o)
    => string.Format(CultureInfo.InvariantCulture, $"{o}");

// [...]

The code snippet synchronously waits for a new ticker and prints the current price as the new ticker is received. New closed 1-minute candles are awaited in the background and whenever a new candle arrives, RSI is re-computed. Based on the RSI value, we send a message using the Telegram API.

In practice, it would be preferable to notify only when RSI gives an interesting signal but for demonstration purposes we send a Telegram message every minute.

Traders often watch charts closely. For those passionate out there, we show a bot’s run with the corresponding chart:

Conclusion

In this blog post, we enhanced the market-watching bot to communicate with the outside world to share its trading signals with others using Telegram API. Next time, we will discuss how to trade based on signals received from the RSI indicator, as well as signals in general.

Disclaimer: The information provided in this blog post should not be considered financial advice. Always conduct your own research and consider your personal circumstances before acting on any financial information.