Richte Deinen eigenen Telegram-Bot für den Handel mit der lemon.markets API ein (Teil 1 von 2)


blog photo
Joanne Snel22. September 2021
Insights


Hallo! Ich bin Joanne aus dem lemon.markets Team. Ich habe in den letzten Wochen an einem Use-Case für unser Produkt gearbeitet und freue mich sehr, das Ergebnis hier mit Euch zu teilen! Falls Du lemon.markets noch nicht kennst: Wir sind ein Start-up aus Berlin mit dem Entwickler:innen ihr eigenes Trading Erlebnis an der Börse bauen können. Es gibt hunderte Use-Cases für unser Produkt, von automatisierten Handelsstrategien bis hin zu Dashboards, die Dein Portfolio visualisieren. Heute zeige ich Dir, wie Du die lemon.markets API mit der Telegram API verbinden kannst. Warum? Damit Du einen personalisierten Butler - ähm, Bot - hast, der mit ein paar sehr einfachen Nachrichten Orders für dich platzieren kann.

In diesem Artikel zeige ich Dir wie Du Deinen Bot einrichtest, mit Ihm Kontakt aufnimmst und schließlich, wie Du Deinen Bot mit lemon.markets verbindest. Um es kurz zu halten, werden wir in diesem Artikel nur die Konto Authentifizierung einrichten und in einem zweiten Blogpost all die Funktionen zeigen, die Du in Deinem Bot implementieren kannst. Für diejenigen unter Euch, die visuell besser lernen: Ihr findet dieses Tutorial auch auf YouTube. Und wenn Du direkt loslegen möchtest, kannst Du auch auf das öffentlich zugängliche GitHub-Repository zugreifen. Du kannst auch mit unserem @LemonTraderBot auf Telegram chatten, um ein Gefühl dafür zu bekommen, was möglich ist. Los geht´s!

lemon.markets 🤝 Telegram

Warum solltest Du Dir die Mühe machen, Deinen eigenen Telegram-Bot zu bauen? Dafür gibt es mehrere Gründe: Zum einen macht es den Wertpapierhandel leichter zugänglich. Wenn Du Trades in derselben App abschließen kannst, über die Du auch Deinen Freunden schreibst, wird der Handel viel bequemer (oder Du hast zumindest einen Anreiz, Dein Portfolio regelmäßiger zu überprüfen). Darüber hinaus ist dies eine großartige Möglichkeit, Deine eigene Frontend-Trading-Experience zu erstellen wenn,

  1. Du keine Frontend-Erfahrung hast oder, 
  2. Du nicht die Zeit investieren willst, um Dein eigenes Frontend zu erstellen.

Und schließlich ist es wahrscheinlich intuitiver, Trades im Gespräch abzuschließen - mit häufigen Überprüfungen zwischendurch (zusammen mit Warnungen, wenn man sich den Kauf nicht leisten kann), wird man darauf aufmerksam gemacht, was man kauft. Das Konzept ähnelt dem von TaxFix, einem Berliner Start-up, das die Steuererklärung in Form eines Chatbots etwas einfacher gestaltet.

Der @LemonTraderBot ist nur ein Anfang - es gibt zusätzlich noch eine Menge von Sonderfällen (die Du implementieren kannst, wenn sie zu Deinem Use-Case passen). Außerdem gibt es eine Reihe von Funktionen, die wir noch nicht eingebunden haben (und das aus gutem Grund). Mit diesem Tutorial möchten wir unsere Nutzer:innen dazu ermutigen, eigene Produkte zu bauen, die ihren eigenen Bedürfnissen entsprechen.

Wenn Telegram nicht die App Deiner Wahl ist, kannst Du alternativ einen WhatsApp- oder Discord-Bot einrichten.

Eine beispielhafte Konversation, die Du mit dem @LemonTraderBot führen könntest

Deinen Telegram Bot einrichten🤖

Starten wir das Projekt! Nachdem Du Telegram auf Deinem Handy installiert und Dich mit einem Konto angemeldet hast, musst Du Deinen lemon.markets Telegram Bot konfigurieren. Glücklicherweise macht Telegram es Dir leicht: Alles, was Du tun musst, ist @BotFather auf Telegram zu schreiben. Wenn Du eine Step-by-step-Anleitung benötigst, kannst Du diese offiziellen Anleitung nutzen. Kurz gesagt, Du schickst @BotFather den /newbot Command, wählst den Namen und Benutzernamen Deines Bots und erhältst im Gegenzug ein Autorisierungs-Token für Deinen Bot. Dieser Token ähnelt Deinem lemon.markets API key. Bewahre ihn also gut auf (und speichere ihn in einer .env-Datei), sonst kann damit jeder, der Zugriff darauf hat, Deinen Bot steuern. Du wirst diese Datei nicht im GitHub-Repository finden, da wir sie zu unserer .gitignore-Datei hinzugefügt haben. Wenn Du mit dieser Vorgehensweise nicht vertraut bist, erfährst Du hier mehr dazu. Leider haben wir den Benutzernamen @LemonTraderBot bereits verwendet, aber wir sind uns sicher, dass Du einen ebenso passenden Namen finden wirst.

Du kannst mit Deinem Bot über die umfangreiche Telegram-API kommunizieren. Ich verwende dazu den Python-telegram-bot-Wrapper, der die Interaktion mit der API vereinfacht. Hier findest Du ein hilfreiches Tutorial, um Deinen ersten Bot zum Laufen zu bringen. 

Erde an Bot 🌍

Nachdem wir nun alles auf der Telegram Seite eingerichtet haben, wollen wir den ersten Kontakt mit unserem Bot herstellen. Wir verwenden für dieses Tutorial Python, aber Du kannst es auch an Deine bevorzugte Sprache anpassen.

Der Python-telegram-bot Wrapper funktioniert über die Klassen Updater und Dispatcher. Auf einer höheren Ebene ist die Logik wie folgt: Du erstellst ein Updater-Objekt, das auf Deinen Bot verweist (über seinen API-Token). Die Updater-Klasse holt kontinuierlich Updates von Telegram ein, und sobald sie etwas empfängt, gibt sie es an die Dispatcher-Klasse weiter. Jede Updater-Instanz ist mit einem Dispatcher-Objekt verbunden, das verschiedene Handler enthält. Je nachdem, welche Art von Input empfangen wird, gibt der Dispatcher diesen an einen bestimmten Handler weiter, der von Dir definiert wird und eine bestimmte Aktion ausführt. Zum Beispiel könnten (und werden) wir einen CommandHandler einrichten, der die Nachricht '/start' als Beginn einer Konversation interpretiert. Der Wrapper enthält verschiedene Arten von Handler-Unterklassen. Wir werden für dieses Tutorial den ConversationHandler, CommandHandler und MessageHandler verwenden. Wenn das verwirrend klingt, keine Sorge, sobald Du es angewendet siehst, wird es klarer.

Telegram Handlers ✋

Wir werden unseren Code mithilfe dieser Handler strukturieren. Das Herzstück unseres Projekts ist der ConversationHandler, denn schließlich führen wir eine Konversation mit unserem Bot. Ein ConversationHandler nimmt drei Collections als Input: entry_points, states und fallbacks. Die entry_points-Collection ist eine Liste von Befehlen, die die Konversation einleiten. Die zweite Collection, ein Dictionary namens states, enthält die verschiedenen Konversationsschritte und einen oder mehrere zugehörige Handler. Diese werden aufgerufen wenn der*die Nutzer:in eine Nachricht sendet, während sich die Konversation mit ihm*ihr gerade in diesem state befindet. Und die dritte Collection, eine Liste namens fallbacks, wird verwendet, wenn der*die Benutzer:in gerade in einer Konversation ist und eine unerwartete Antwort erhält. Unser ConversationHandler wird wie folgt aussehen:

1conv_handler = ConversationHandler(
2        # initiate the conversation
3        entry_points=[CommandHandler('start', TradingBot().start)],
4        # different conversation steps and handlers that should be used if user sends a
5        # message when conversation with them is currently in that state
6        states={
7            TradingBot.SPACE:[MessageHandler(Filters.text & ~Filters.regex('^/'),
8                                          TradingBot().get_space_id],
9            TradingBot.TYPE:[MessageHandler(Filters.text & ~Filters.regex('^/'),
10                                              TradingBot().get_type)],
11        },
12        # if user currently in conversation but state has no handler or 
13        # handle inappropriate for update
14        fallbacks=[CommandHandler('cancel', TradingBot().cancel)],
15    )

In diesem Code Snippet gibt es einiges, was wir noch nicht gesehen haben: eine TradingBot()-Klasse mit mehreren Funktionen, darunter start, get_space und cancel. Wir werden diese Funktionen gleich definieren, aber lasst uns erst über den Unterschied zwischen einem CommandHandler und einem MessageHandler sprechen: Telegram erkennt einen "Befehl", wenn die Nachricht mit einem Schrägstrich ("/") beginnt, zum Beispiel "/start" und eine "normale Nachricht" als jede weitere Texteingabe. Es ist üblich, einen Bot mit dem Befehl "/start" zu beginnen, also folgen wir dieser Vorgehensweise und verwenden einen CommandHandler. Wenn wir erwarten, dass der*die Nutzer:in einen Space auswählen, erwarten wir den User-Input in Form eines Strings.

Daher verwenden wir in diesem Fall den MessageHandler mit zwei Filtern: Einem, der sicherstellt, dass nur Texteingaben als angemessene Antwort des Nutzers oder der Nutzerin registriert werden (und nicht z. B. ein Bild) und einem der sicherstellt, dass Befehle nicht fälschlicherweise als Textantworten registriert werden. Im Klartext bedeuten die Zeilen 7-8: Wenn sich die Konversation im Zustand "SPACE" befindet und eine Texteingabe empfangen wird, die nicht mit "/" beginnt, führst Du die Funktion get_space für dieses TradingBot-Objekt aus. Die folgenden Zeilen können auf gleiche Weise interpretiert werden.

Funktionalität hinzufügen 👷‍♀

Da das Grundgerüst für unsere Konversation eingerichtet ist, lass uns die Funktionen der TradingBot-Klasse definieren, die wir oben erwähnt haben.

1from telegram import Update
2from telegram.ext import CallbackContext, ConversationHandler
3from models.Token import Token
4class TradingBot:
5    ID, SECRET = range(2)
6    def start(self, update: Update, context: CallbackContext) -> int:
7        """Initiates conversation and prompts user to fill in lemon.markets client ID."""
8        context.user_data.clear()
9        update.message.reply_text(
10            'Hi! I\'m the Lemon Trader Bot! I can place trades for you using the lemon.markets API. '
11            'Send /cancel to stop talking to me.\n\n'
12            'Please fill in your lemon.markets client ID.',
13        )
14        print("Conversation started.")
15        print(context.user_data)
16        return TradingBot.ID
17    def get_client_id(self, update: Update, context: CallbackContext) -> int:
18        """Prompts user to fill in lemon.markets client secret."""
19        context.user_data['client_id'] = update.message.text
20        update.message.reply_text('Please enter your lemon.markets client secret.')
21        print(context.user_data)
22        return TradingBot.SECRET
23    def get_client_secret(self, update: Update, context: CallbackContext) -> int:
24        """Authenticates user."""
25        context.user_data['client_secret'] = update.message.text
26        print(context.user_data)
27        try:
28            authentication = Token().authenticate(context.user_data['client_id'], context.user_data['client_secret'])
29            
30            context.user_data['access_token'] = authentication.get('access_token')
31            # if credentials not correct, prompt user to fill in client ID again
32            if 'access_token' not in authentication:
33                update.message.reply_text(
34                    'Authentication failed. Please fill in your client ID again.'
35                )
36                return TradingBot.ID
37        except Exception as e:
38            print(e)
39            update.message.reply_text(
40                "There was an error, ending conversation. If you'd like to try again, send /start")
41            return ConversationHandler.END
42        update.message.reply_text(
43            'Authentication successful.'
44        )
45        print(context.user_data)
46        return ConversationHandler.END
47    
48    def cancel(self, update: Update, context: CallbackContext) -> int:
49        """Cancels and ends the conversation."""
50        update.message.reply_text(
51            "Bye! Come back if you would like to make any other trades.", reply_markup=ReplyKeyboardRemove()
52        )
53        print(context.user_data)
54        return ConversationHandler.END

Auch hier stoßen wir wieder auf einige Funktionen und Klassen, die wir noch nicht definiert haben, wie die Klasse Space(), die in Zeile 4 importiert wird. Aber lass uns zuerst über das sprechen, was wir bereits kennen.

In Zeile 7 definieren wir unsere verschiedenen Conversation states als ganze Zahlen (beachte, dass der Bereich aktualisiert wird, sobald wir weitere Conversation States einführen). Jede Funktion gibt den Conversation state zurück, der sich logisch aus dem Gesprächsablauf ergibt. Die Startfunktion sendet beispielsweise eine Willkommensnachricht an den Chat und fordert den*die Nutzer:in auf, den Space seiner*ihrer Wahl einzugeben und wechselt in den ersten Conversation state: SPACE. Wie wir in ConversationHandler.py gesehen haben, wird die Funktion get_space aufgerufen, sobald sich die Unterhaltung in dem SPACE State befindet. In dieser Funktion wird die Benutzereingabe verwendet, um die entsprechende Space-ID abzurufen, und der*die Benutzer:in wird über seine*ihre Wahl informiert.

Was denkst Du, was die cancel Funktion bewirkt?

Einbindung von lemon.markets 🍋

Es ist an der Zeit, sich mit der Space()-Klasse zu befassen, die auf magische Weise die verfügbaren Spaces aus unserem lemon.markets Konto zu sammeln scheint. In Wahrheit bist Du der Zauberer und Deine Magie sieht wie folgt aus:

1import os
2import requests
3class Space():
4  
5    def __init__(self):
6        self.api_key: str = os.environ.get("API_KEY")
7        self.url_trading: str = os.environ.get("BASE_URL_TRADING")
8  
9    def get_spaces(self):
10        endpoint = f'spaces'
11        results = requests.post(self.url_trading + endpoint, 
12                                headers={"Authorization": f"Bearer {self.api_key}"}
13                               ).json()['results']
14        spaces: dict = {}
15          
16        for result in results:
17            spaces[result['name']] = result['id']
18            
19    return spaces

Die Space()-Klasse wird mit Deinem API Key und der lemon.markets (Paper) Trading URL initialisiert, die wir in einer .env-Datei definiert haben:

1API_KEY=<api key here>
2BASE_URL_TRADING=<trading URL here>
3BOT_TOKEN=<bot token here>

Beachte, dass wir in dieser .env-Datei auch den Bot-Token definiert haben, den wir später verwenden werden.

Unsere Funktion get_space() funktioniert indem sie ein Lexikon an Space Namen (keys) und IDs (values) ausgibt, wenn wir den /spaces Endpunkt abrufen. Lies unsere Dokumentation, um mehr zu erfahren.

Alles zusammenfügen 🛠

Der ConversationHandler, den wir zuvor definiert haben, muss noch in ein funktionierendes Skript eingefügt werden. Lass uns die Datei main.py wie folgt definieren:

1import logging
2import os
3from dotenv import load_dotenv
4from models.TradingBot import TradingBot
5from telegram.ext import (
6    Updater,
7    CommandHandler,
8    MessageHandler,
9    Filters,
10    ConversationHandler,
11)
12logging.basicConfig(format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
13                    level=logging.DEBUG)
14logger = logging.getLogger(__name__)
15def main() -> None:
16    load_dotenv()
17    """Start the bot."""
18    # Create the Updater and pass it to your bot's token.
19    updater = Updater(os.getenv('BOT_TOKEN'), use_context=True)
20    # Get the dispatcher to register handlers
21    dispatcher = updater.dispatcher
22   conv_handler = ConversationHandler(
23        # initiate the conversation
24        entry_points=[CommandHandler('start', TradingBot().start)],
25        # different conversation steps and handlers that should be used if user sends a
26        # message when conversation with them is currently in that state
27        states={
28            TradingBot.SPACE:[MessageHandler(Filters.text & ~Filters.regex('^/'),
29                                          TradingBot().get_space)],
30        },
31        # if user currently in conversation but state has no handler or 
32        # handle inappropriate for update
33        fallbacks=[CommandHandler('cancel', TradingBot().cancel)],
34    )
35    dispatcher.add_handler(conv_handler)
36    # Start the Bot
37    updater.start_polling()
38    # Run the Bot until you press Ctrl-C
39    updater.idle()
40if __name__ == '__main__':
41    main()

In den Zeilen 15-18 richten wir einen Logger ein, der den Bot-Status auf dem Terminal ausgibt und uns "sehen" lässt, was passiert (auch sehr nützlich für die Fehlersuche). In der Hauptmethode initialisieren wir ein Updater-Objekt, weisen ihm einen Dispatcher zu und fügen dem Dispatcher unseren Handler hinzu. Um den Bot zu starten, rufen wir die start_polling() Funktion auf dem Updater auf. Außerdem müssen wir die idle() Funktion aufrufen.

Und das sollte alles sein. Wenn Du dieses Skript ausführst, wird ein Bot erstellt, der Dich einen Space auswählen lässt. Natürlich ist das in Bezug auf die Funktionalität nicht sehr aufregend - was gibt es also noch?

What’s Next? 🤔

Wenn Du dich mit dem @LemonTraderBot bekannt gemacht hast, wirst Du feststellen, dass der Bot weit über das Auswählen eines Spaces hinaus. Und Dein Bot kann auf ähnliche Weise erweitert werden - Du kannst ihm erlauben, die lemon.markets-Datenbank zu durchsuchen, Kauf- oder Verkaufsorders zu erteilen, Dein Portfolio einzusehen oder einfache Metriken wie Deinen Kontostand anzuzeigen. All das und mehr werden wir im zweiten Teil dieser Blog-Post-Serie implementieren.

Wenn Du es nicht abwarten kannst, kannst Du Dir in der Zwischenzeit unser GitHub-Repository ansehen, das Dir einen Vorgeschmack darauf gibt, was Dich erwartet.

Wenn sich das für Dich nach einem coolen Projekt anhört und Du noch nicht Teil unserer Community bist, melde Dich hier bei lemon.markets an und tritt unserer lebhaften Slack-Community bei. Wir haben viele aktive Mitglieder, die sich über ihre Projekte und Visionen austauschen. Vielleicht möchtest Du ja auch etwas von Dir zeigen?!

Wenn Du weitere Fragen zu diesem Telegram-Bot hast, schicke uns gerne eine E-Mail. Und wenn Du interessante Funktionswünsche für diesen Bot hast, lass es uns wissen (oder erstelle eine Branch in unserem Repo!).

Bleib gespannt für Teil 2 und bis bald bei 🍋.markets,

Joanne

Das könnte Dich auch interessieren

blog photo

Using Time Series Forecasting to predict stock prices 🔮

In this article you will learn what time series forecasting is and how its application in finance looks like. Then you'll also dive into Facebook's Prophet Model for Time Series Forecasting and use it together with the lemon.markets Market Data API to forecast the development of the Tesla Stock.

blog photo

Dummies Guide to Trading with Machine Learning

Ever wonder how a trader with decades of experience on thousands of stocks and lightning fast reaction times might perform in the market? With some machine learning knowledge, you might be able to automate such a trader yourself! 💻 📈

blog photo

4 reasons why YOU should automate your trading strategy

In the current volatile market conditions, everyone is trying to find ways to minimise portfolio loss. In that context, have you ever thought about automating your trading strategy? In this article, we will dive into 4 reasons for doing so. Expect to learn how it can save you time, make your trading more efficient and lead to data-based decisions.

Tiefer eintauchen

Finde weitere Ressourcen für einen einfachen Einstieg

In unserer Dokumentation erfahrt Ihr mehr über unsere API-Struktur, die verschiedenen Endpunkte und spezifische Anwendungsfälle.

Austauschen

Tritt der lemon.markets Community bei

Tritt unserem Slack-Channel bei, um Dich aktiv an unserer Community zu beteiligen, Fragen an andere Nutzer:innen zu stellen und immer auf dem Laufenden zu bleiben.

Team unterstützen

Lust lemon.markets mit uns zu bauen?

Wir sind immer auf der Suche nach großartigen Ergänzungen für unser Team, die uns beim Aufbau einer Brokerage Infrastruktur für das 21. Jahrhundert helfen.

Brauchst Du Hilfe?
Stell Deine Fragen in unserer CommunityStell Deine Fragen in unserer CommunityLeg mit unserer API Dokumentation losLeg mit unserer API Dokumentation losLass Dich von unserem Blog inspirierenLass Dich von unserem Blog inspirieren
© lemon.markets 2022DatenschutzImpressum
Systems are down

Als vertraglich gebundener Vermittler gemäß § 3 Abs. 2 WpIG für Rechnung und unter der Haftung der DonauCapital Wertpapier GmbH, Passauer Str. 5, 94161 Ruderting (kurz: DonauCapital), bietet Dir die lemon.markets GmbH, die Anlagevermittlung (§ 2 Abs. 2 Nr. 3 WpIG) von Finanzinstrumenten im Sinne des § 3 Abs. 5 WpIG sowie die Vermittlung von Konten an.